Building an Inline Mode Telegram Bot
Let users call your Telegram bot from any chat with inline queries — enabling inline mode, answering fast, caching, pagination, feedback and privacy considerations.
On this page
Inline mode lets people use your bot from any chat — type @yourbot something in a conversation and a list of results pops up above the keyboard. Pick one, and it’s sent into that chat. GIF search, translation, stickers, quick lookups and polls all work this way. This article shows how to build an inline bot that feels fast.
Enable inline mode
Inline mode is off by default. In @BotFather, choose your bot, then Bot Settings → Inline Mode → Turn on, or send /setinline. You’ll set placeholder text shown in the input field, such as “Search docs…”.
Optionally:
/setinlinefeedbackenableschosen_inline_resultupdates, telling you which result a user picked. Useful for analytics; not needed otherwise./setinlinegeolets the bot request the user’s location with inline queries — only if your results genuinely depend on location.
How inline queries work
When a user types after your bot’s username, Telegram sends an inline_query update containing the text, the user and an offset for pagination. Your bot responds with answerInlineQuery, passing up to 50 results. Telegram shows them; when the user taps one, the chosen content is sent as a message “via @yourbot”.
Queries arrive as the user types, so the same user may send several in quick succession. Speed matters more here than anywhere else in bot development: slow answers feel broken.
A minimal inline bot with aiogram 3
import hashlib
from aiogram import Router
from aiogram.types import InlineQuery, InlineQueryResultArticle, InputTextMessageContent
router = Router()
DOCS = {
"install": "Install with: pip install aiogram",
"polling": "Start polling with: await dp.start_polling(bot)",
"webhook": "Register a webhook with: await bot.set_webhook(url)",
}
@router.inline_query()
async def inline_search(query: InlineQuery):
text = query.query.strip().lower()
matches = [(k, v) for k, v in DOCS.items() if text in k or text in v.lower()] if text else list(DOCS.items())
results = [
InlineQueryResultArticle(
id=hashlib.md5(key.encode()).hexdigest(),
title=key.title(),
description=value[:60],
input_message_content=InputTextMessageContent(message_text=value),
)
for key, value in matches[:50]
]
await query.answer(results, cache_time=300, is_personal=False)
Each result needs a unique id (up to 64 bytes) — a hash of a stable key works well. InputTextMessageContent is what gets sent when the user picks the result. There are result types for photos, GIFs, videos, audio, documents, locations, contacts and more; many can reference files by file_id so nothing needs uploading.
In grammY, the same handler is bot.on('inline_query', ctx => ctx.answerInlineQuery(results, { cache_time: 300 })).
Make it fast
Let Telegram cache results
cache_time (seconds, default 300) tells Telegram it may reuse your answer for identical queries. For results that don’t depend on who’s asking, a long cache time means many queries never reach your bot at all. Set is_personal=True when results differ per user — such as “my saved notes” — so one user’s results aren’t shown to another.
Cache on your side too
If your results come from an external API or database, cache recent queries in memory or Redis for a short time. The user typing “cat”, “cats”, “cats g” produces three queries; the first two are often wasted work. A small cache keyed on the normalised query text removes most of the cost.
Keep answers lightweight
Return a handful of well-chosen results quickly rather than 50 after a slow search. Prefer thumbnails that are already small, and reference existing file_ids instead of URLs Telegram has to fetch.
Host close to Telegram
Every inline answer is a round trip to Telegram’s servers, while the user watches the result list. Kerit Cloud’s Telegram hosting runs under 40 ms from the Telegram API at the Virginia edge, which keeps the list appearing as fast as people type.
Pagination with offset
For searches with many results, return up to 50 at a time and set next_offset. When the user scrolls to the end, Telegram sends the same query again with that offset:
PAGE = 20
@router.inline_query()
async def paged(query: InlineQuery):
offset = int(query.offset or 0)
items = await search(query.query, limit=PAGE, skip=offset)
results = [to_result(i) for i in items]
next_offset = str(offset + PAGE) if len(items) == PAGE else ""
await query.answer(results, cache_time=60, next_offset=next_offset)
An empty next_offset tells Telegram there are no more results.
Empty queries and helpful defaults
When the user has typed only @yourbot, the query text is empty. Don’t return nothing — show popular items, recent picks or a short guide. It’s the first impression of your inline feature.
You can also add a button above the results (the button parameter in current Bot API versions) that opens a private chat with your bot — handy for “Sign in to see your items” or settings.
Buttons on inline messages
Results can carry inline keyboards. When a user presses a button on a message sent via inline mode, your bot receives a callback query with an inline_message_id instead of a chat and message ID. Edit such messages with editMessageText(inline_message_id=...). Remember that your bot usually isn’t a member of the chat where the message was sent, so it can’t read the rest of that conversation.
Privacy and responsibility
- Inline queries include the user’s ID and name, and location if you requested it. Only collect what your results need.
- Anything you return can end up in any chat, including groups with many people. Filter results for content that’s appropriate everywhere.
- Treat query text as untrusted input — escape it if you echo it back with a parse mode.
Troubleshooting
Nothing appears when typing @yourbot. Inline mode isn’t enabled in BotFather, or your handler isn’t registered for inline queries.
Results appear, then vanish or never show. The answer took too long, or it failed validation — check your logs for answerInlineQuery errors such as duplicate result IDs or an invalid URL.
Stale results. Telegram cached your previous answer. Lower cache_time while developing.
Users see each other’s personal results. Set is_personal=True.
Summary
Inline mode puts your bot in every chat. Enable it in BotFather, answer inline_query updates quickly with up to 50 results, give each a stable unique ID, and lean on caching — Telegram’s cache_time, plus your own short-lived cache for expensive lookups. Paginate with next_offset, show something useful for empty queries, and mark per-user results as personal.