Add filters.IS_BOT — no filter exposes User.is_bot (needed for multi-bot group chats)

Author: johnnynunezCreated Jul 31, 2026Updated Sep 1, 2026

Feature request

Add a filters.IS_BOT (and its negation) so handlers can filter on whether the message author is a bot, without hand-rolling a MessageFilter subclass.

Rationale

User.is_bot has been part of the User model for as long as the Bot API has had it, and it is the natural discriminator for multi-bot group chats. But filters.py never exposes it:

bash
$ python -c "
import re
src = open('telegram/ext/filters.py').read()
print('is_bot occurrences in filters.py:', src.count('is_bot'))
m = re.search(r'__all__\s*=\s*\((.*?)\)', src, re.S)
print('total filters exported:', len([x for x in m.group(1).split(',') if x.strip()]))
"
is_bot occurrences in filters.py: 0
total filters exported: 66

66 exported filters, including several in exactly this neighbourhood — ViaBot, SenderChat, ChatType, IS_AUTOMATIC_FORWARD, IS_TOPIC_MESSAGE, IS_FROM_OFFLINE — and none of them answers "did a bot write this?".

The closest existing filters are not substitutes:

  • ViaBot matches inline messages sent via a bot (message.via_bot). A bot posting normally in a group has via_bot is None, so ViaBot misses it.
  • SenderChat covers channel/anonymous-admin senders, not bot users.
  • IS_AUTOMATIC_FORWARD is about channel-to-discussion-group forwards.

So today the only way is a custom filter:

python
class _IsBot(filters.MessageFilter):
    def filter(self, message):
        return bool(message.from_user and message.from_user.is_bot)

IS_BOT = _IsBot(name="filters.IS_BOT")

That is ~5 lines, but it is 5 lines every project rewrites, it is easy to get subtly wrong (from_user is None for channel posts — a naive message.from_user.is_bot raises AttributeError), and it is undiscoverable: a user scanning the filters list reasonably concludes PTB cannot do it.

Use case

I run a fleet of machines (2x DGX Spark, 2x Jetson Thor, AGX Orin, Orin Nano) each hosting an agent that joins one shared Telegram group. Agents need to treat bot-authored messages differently from human ones — typically: read them for context, but only act when a human addressed them, so two bots cannot ping-pong into a loop.

That is a routing decision on is_bot, made at handler-registration time, which is precisely what filters is for:

python
# react to humans
app.add_handler(MessageHandler(filters.TEXT & ~filters.IS_BOT, on_human))
# observe peers without replying
app.add_handler(MessageHandler(filters.TEXT & filters.IS_BOT, on_peer))

versus today, where the bot/human split has to happen inside the callback, after the handler already matched.

Multi-bot groups are increasingly common (agent fleets, bridges, relays, notification bots coexisting), and every one of them needs this discriminator.

Proposed change

Add to telegram/ext/filters.py, following the existing IS_AUTOMATIC_FORWARD / IS_TOPIC_MESSAGE pattern:

python
class _IsBot(MessageFilter):
    __slots__ = ()

    def filter(self, message: Message) -> bool:
        return bool(message.from_user and message.from_user.is_bot)


IS_BOT = _IsBot(name="filters.IS_BOT")
"""Messages whose sender is a bot (:attr:`telegram.User.is_bot`).

Note:
    Channel posts and anonymous-admin messages have no :attr:`Message.from_user`
    and therefore do **not** match; see :attr:`filters.SenderChat` for those.
"""

Then export IS_BOT in __all__. Negation already works via ~filters.IS_BOT, so no separate IS_NOT_BOT is needed.

The from_user is None guard is the part worth having in the library rather than in every downstream copy.

Caveat worth documenting either way

Independent of this filter: Telegram's privacy mode is ON by default, so a bot in a group only receives messages that mention it or reply to it — including messages from other bots. Bot.can_read_all_group_messages reports the current setting.

Whether or not IS_BOT lands, a sentence in the group-chat docs noting that can_read_all_group_messages must be True (BotFather → /setprivacy → Disable) for a bot to observe other bots would save people a long debugging session — the failure is completely silent: correct code, no updates delivered, no error.

I'm happy to open a PR with the filter, tests, and that docs note if the direction is acceptable.

Environment

python-telegram-bot 22.6, Python 3.11.

Source: python-telegram-bot/python-telegram-bot