inline mode
let users type @your_bot query in any chat and pick a result generated by your bot.
answer inline queries
inline query handlers must answer with a list of results. keep the response fast, cache where
possible, and use is_personal when results depend on the current user. ctx.answer() takes the results array positionally, plus an options object for cache_time/is_personal/next_offset.
import { createBot } from "yaebal";
const search = async (query: string) => [{ id: "1", title: "result", text: "text" }];
const bot = createBot(process.env.BOT_TOKEN!);
bot.on("inline_query", async (ctx) => {
const results = await search(ctx.query);
await ctx.answer(
results.map((item) => ({
type: "article" as const,
id: item.id,
title: item.title,
input_message_content: { message_text: item.text },
})),
{ cache_time: 10, is_personal: true },
);
});build results without hand-rolled objects
@yaebal/inline-results ships one typed factory per InlineQueryResult/InputMessageContent variant — required fields are
positional, everything else is a trailing options object, so a result reads as data instead of
an object literal that can silently drift from the schema.
import { createBot } from "yaebal";
import { InlineQueryResult, InputMessageContent } from "@yaebal/inline-results";
const search = async (query: string) => [{ id: "1", title: "result", url: "https://example.com" }];
const bot = createBot(process.env.BOT_TOKEN!);
bot.on("inline_query", async (ctx) => {
const items = await search(ctx.query);
await ctx.answer(
items.map((item) =>
InlineQueryResult.article(item.id, item.title, InputMessageContent.text(item.url)),
),
{ cache_time: 10 },
);
});track chosen results
if enabled in botfather, telegram sends chosen_inline_result after a user picks one of
your results. use it for analytics, not for critical business logic.
import { createBot } from "yaebal";
declare const analytics: { track(event: string, props: Record<string, unknown>): Promise<void> };
const bot = createBot(process.env.BOT_TOKEN!);
bot.on("chosen_inline_result", async (ctx) => {
await analytics.track("inline_chosen", {
resultId: ctx.result_id,
userId: ctx.from.id,
query: ctx.query,
});
});switch inline buttons
inline keyboard buttons can open the inline picker with a prefilled query. this is useful for sharing products, documents, search results, or mini app content.
import { InlineKeyboard } from "yaebal";
new InlineKeyboard()
.switchInline("share", "product:42")
.row()
.switchInlineCurrentChat("search here", "cats")
.build();rules of thumb
- return stable result ids so analytics and caches stay meaningful.
- keep result payloads small; the user is waiting inside telegram's inline picker.
- use
cache_timeaggressively for public search results. - set
is_personalfor private/user-specific results. - add
inline_queryandchosen_inline_resulttoallowedUpdateswhen polling.