inlineQuery
The inlineQuery method in GramIO allows your bot to respond to inline queries sent by users. An inline query is a special type of message where users can search for content from your bot by typing in the message input field without directly interacting with the bot.
WARNING
You should enable this options via @BotFather. Send the /setinline command, select the bot and provide the placeholder text that the user will see in the input field after typing your bot's name.
Basic Usage
Responding to an Inline Query with a Regular Expression
You can make your bot listen for specific inline queries that match a regular expression and respond with results. Here's an example:
bot.inlineQuery(/find (.*)/i, async (context) => {
if (context.args) {
await context.answer([
InlineQueryResult.article(
"id-1",
`Result for ${context.args.at(1)}`,
InputMessageContent.text(
`This is a message result for ${context.args.at(1)} query`
)
),
]);
}
});In this example:
- The bot listens for inline queries that match the pattern
search (.*). - If a match is found, the bot extracts the search term and returns a list of inline query results.
TIP
context.answer() accepts answerInlineQuery parameters as a second argument. The most notable is cache_time — how long Telegram caches results on its servers (default 300 s). Pass cache_time: 0 for dynamic results that change with every keystroke — and especially during development. See context.answer() options below for all the useful ones.
Thumbnail constraints
thumbnail_url on any InlineQueryResult* variant (article, photo, video, document, gif, etc.) must be a JPEG URL — PNG or WebP silently fails in some Telegram clients and the preview simply disappears instead of falling back to a default. Keep the image ≤320×320 px and ≤200 KB; oversized JPEGs fail the same way. If previews don't render and the URL is otherwise reachable, check the format and dimensions first.
Parameters
trigger: The condition that the inline query must meet. This can be a regular expression, a string, or a custom function.- Regular Expression: Matches queries that fit a specific pattern.
- String: Matches an exact query string.
- Function: Evaluates the query based on custom logic. Should return true to match.
handler: The function that processes the inline query. It receives thecontextobject, which includes details about the query and provides methods to respond.options: Additional options for handling the result selection.onResult: A function that is triggered when the user selects an inline query result. It can be used to modify or update the message after selection. Use ChosenInlineResult under the hood.
IMPORTANT
You can modify only messages which contains InlineKeyboard. More about ChosenInlineResult.
How inlineQuery Works
Query Matching: When a user types an inline query, the
inlineQuerymethod checks if the query matches the providedtrigger. This can be a direct match (string), a pattern match (regular expression), or a condition check (function).Handling the Query: If the query matches, the
handlerfunction is called. Inside this function, you can access the query parameters, generate results, and send them back to the user usingcontext.answer().Responding to Result Selection: If the
onResultoption is provided, the bot listens for when a user selects one of the inline query results. The provided function can then modify the message, for example, by editing the text.
Example: Custom Inline Query Handler
Here's a more detailed example that demonstrates using both the inline query trigger and handling result selection:
bot.inlineQuery(
/find (.*)/i,
async (context) => {
if (context.args) {
await context.answer(
[
InlineQueryResult.article(
"result-1",
`Result for ${context.args[1]}`,
InputMessageContent.text(
`You searched for: ${context.args[1]}`
),
{
reply_markup: new InlineKeyboard().text(
"Get Details",
"details-callback"
),
}
),
],
{
cache_time: 0,
}
);
}
},
{
onResult: (context) => context.editText("You selected a result!"),
}
);In this example:
- The bot listens for inline queries that start with
findfollowed by a search term. - The bot responds with an inline query result that includes a button labeled "Get Details."
- When the user selects this result, the bot edits the message to indicate that a selection was made.
Pair with switchTo* buttons — the best UX for inline mode
Most users will never type @YourBot <query> on their own — it's friction they don't know exists. The fix is to put the inline query behind an inline button. The three switchTo* buttons teleport the user directly into inline mode with a pre-filled query, and your inlineQuery handler serves a curated picker. The user taps once, picks a result, Telegram posts it — no typing, no commands.
switchToCurrentChat— opens inline mode in this chat. Perfect for "pick one of these for me" inside your bot's private UI.switchToChat— asks the user to pick any chat and share into it. Canonical "Send to friend" / "Share this card" flow.switchToChosenChat— same, but scoped to chat types (private / group / channel / bot).
Share flow example — /share replies with a picker button; the inline handler returns a specific card by id:
bot.command("share", (ctx) =>
ctx.send("Share this card:", {
reply_markup: new InlineKeyboard().switchToChosenChat("📤 Send to…", {
query: "card-42",
allow_user_chats: true,
allow_group_chats: true,
}),
}),
);
bot.inlineQuery(/^card-(\d+)$/, async (ctx) => {
const card = await loadCard(ctx.args![1]);
return ctx.answer(
[
InlineQueryResult.article(
card.id,
card.title,
InputMessageContent.text(card.body),
),
],
{ cache_time: 0, is_personal: true },
);
});Why the pairing is so strong:
- Zero typing. The
queryis pre-filled — the user only picks a result. - Native picker UI. Users already know the inline-results dialog from
@gif,@stickers,@vote— familiarity is free. - Stays in flow.
switchToCurrentChatnever leaves the chat;switchToChatturns sharing into a single tap. - No webapp, no deep link, no backend redirect. The whole round-trip is native Telegram.
Any time a feature would otherwise be "type a command, then reply with an option" — voting, GIF/sticker pick, language or timezone select, quote/card sharing, contact picker — a switchTo* button + an inline handler is almost always the better UX.
context.answer() options
The second argument to context.answer() accepts all the regular answerInlineQuery params. The four you will reach for most often:
button — prompt the user to log in, onboard, or open a Mini App
Inline mode runs inside any chat — it can't show your bot's private UI, accept file uploads, or do multi-step flows. When serving results requires something inline mode can't do (authentication, connecting a third-party account, accepting terms, configuring settings), return an empty results array plus a top button that sends the user somewhere your bot can run those flows.
InlineQueryResultsButton is a discriminated union — provide exactly one of:
start_parameter— opens a private chat and fires/start <parameter>, which reaches yourbot.command("start")handler asctx.args. 1-64 characters, onlyA-Z,a-z,0-9,_,-.web_app— launches a Mini App. See TMA guide.
bot.inlineQuery(async (ctx) => {
if (!(await isAuthenticated(ctx.from.id))) {
return ctx.answer([], {
cache_time: 0, // don't cache the login prompt
is_personal: true, // it's per-user
button: {
text: "Log in to continue",
start_parameter: "login-inline",
},
});
}
// ...normal results once authenticated
});
// In your /start command — receive the deep-link payload
bot.command("start", (ctx) => {
if (ctx.args === "login-inline") {
return ctx.send("Let's get you logged in…");
}
});This is the canonical "user is not authorized → redirect to PM" pattern for inline bots — there is no way for the bot to push the user to a private chat from inline mode on its own. The button is the escape hatch.
is_personal — per-user results (disables shared cache)
Pass is_personal: true whenever results depend on the current user (their subscriptions, favorites, permissions). Without it, one user's personalized results can leak to another user running the same query, because Telegram shares the cache across users by default.
next_offset — pagination
Pass a short token (max 64 bytes) to signal "more results available". When the user scrolls to the end of your response, Telegram reissues the query and the token arrives as ctx.inlineQuery.offset. Pass an empty string to signal "no more".
bot.inlineQuery(async (ctx) => {
const page = Number(ctx.inlineQuery.offset) || 0;
const { items, hasMore } = await fetchPage(page);
return ctx.answer(
items.map((i) =>
InlineQueryResult.article(
i.id,
i.title,
InputMessageContent.text(i.body)
)
),
{ next_offset: hasMore ? String(page + 1) : "" }
);
});cache_time — shared-cache duration
Telegram caches your response for identical queries across all users for this many seconds (default 300). Set 0 during development and for dynamic content. Combine with is_personal: true when per-user results should still be briefly cached for the same user.