Advanced
Inline mode
Inline mode allows users to call the bot via @username in any chat. We cover enabling it through BotFather, handling inline_query, result types (article, photo, video), pagination via next_offset, and chosen_inline_result.
Inline mode allows users to call the bot in any chat via @username запрос. The bot receives inline_query, creates a list of results, and returns them via answerInlineQuery. The user selects a result — it is inserted into the input field as a message from their name.
Enabling Inline mode
In BotFather select the bot → Bot Settings → Inline Mode → Turn on. You can set a placeholder (input hint). After enabling, the bot will start receiving updates such as inline_query.
Structure of inline_query
The update contains:
id— unique request identifier (string, up to 64 bytes)from— the user who made the requestquery— the request text (may be empty)offset— pagination offset (string)chat_type— chat type from which the request was made (optional)location— user location (optional, if requested)
Answer: answerInlineQuery
The method accepts an array results (up to 50 elements) and optional parameters:
cache_time— caching the result on the client side (seconds, default 300)is_personal— results are personal to the usernext_offset— token for the next page (pagination)switch_pm_text/switch_pm_parameter— "Go to bot" button
The helper telegramApi() is described in chapter Первый запрос: getMe.
<?php
function answerInlineQuery(string $inlineQueryId, array $results, array $options = []): array {
$payload = array_merge([
'inline_query_id' => $inlineQueryId,
'results' => json_encode($results),
], $options);
return telegramApi('answerInlineQuery', $payload);
}
Types of results (InlineQueryResult)
Each result is an object with required type and id (unique within the response). Main types:
- article — a text message with title, description,
input_message_content(InputTextMessageContent) - photo — a photo by URL (
photo_url,thumb_url) - video — video (
video_url,mime_type,thumb_url) - document — a file (
document_url,mime_type) - audio, voice, location, venue, contact, game, sticker
For article and file-type results, input_message_content is used — an object with fields message_text, parse_mode, entities, link_preview_options.
Example: searching articles
Suppose we have a local array of articles. We process inline_query inside processUpdate() (chapter Long polling).
<?php
function handleInlineQuery(array $update): void {
$iq = $update['inline_query'];
$query = mb_strtolower(trim($iq['query'] ?? ''));
$offset = (int)($iq['offset'] ?? 0);
$limit = 20;
// Пример данных — в продакшене это БД/поисковый движок
$articles = [
['id' => '1', 'title' => 'PHP 8.3: новинки', 'text' => 'JIT, typed class constants, json_validate...'],
['id' => '2', 'title' => 'Telegram Bot API 7.0', 'text' => 'Stars, Mini Apps, reply parameters...'],
['id' => '3', 'title' => 'PSR-18 HTTP Client', 'text' => 'Стандарт интерфейсов для HTTP-клиентов.'],
];
$filtered = array_filter($articles, fn($a) => $query === '' ||
mb_stripos($a['title'], $query) !== false ||
mb_stripos($a['text'], $query) !== false);
$filtered = array_values($filtered);
$page = array_slice($filtered, $offset, $limit);
$results = array_map(function ($art) {
return [
'type' => 'article',
'id' => $art['id'],
'title' => $art['title'],
'description' => mb_substr($art['text'], 0, 100),
'input_message_content' => [
'message_text' => "<b>{$art['title']}</b>\n{$art['text']}",
'parse_mode' => 'HTML',
],
];
}, $page);
$nextOffset = (count($filtered) > $offset + $limit) ? (string)($offset + $limit) : '';
answerInlineQuery($iq['id'], $results, [
'cache_time' => 60,
'next_offset' => $nextOffset,
'is_personal' => true,
]);
}
Processing the selected result
When the user selects a result, an update chosen_inline_result arrives with the following fields:
result_id— id of the selected resultfrom— the userquery— original queryinline_message_id— id of the sent message (for editing viaeditMessageTextwithinline_message_id)
This is useful for analytics and for subsequent editing of the inserted message.
<?php
function handleChosenInlineResult(array $update): void {
$cir = $update['chosen_inline_result'];
// Логируем выбор, можно обновить счётчик популярности
error_log("Chosen: {$cir['result_id']} by user {$cir['from']['id']} query: {$cir['query']}");
// Если нужно отредактировать вставленное сообщение позже:
// $inlineMessageId = $cir['inline_message_id'];
// telegramApi('editMessageText', [
// 'inline_message_id' => $inlineMessageId,
// 'text' => 'Обновлённый текст',
// 'parse_mode' => 'HTML',
// ]);
}
Integration in the update process
Add a branch in processUpdate():
<?php
function processUpdate(array $update): void {
if (isset($update['message'])) {
handleMessage($update['message']);
} elseif (isset($update['callback_query'])) {
handleCallbackQuery($update['callback_query']);
} elseif (isset($update['inline_query'])) {
handleInlineQuery($update);
} elseif (isset($update['chosen_inline_result'])) {
handleChosenInlineResult($update);
}
}
Important nuances
- Results must be returned quickly — response timeout ~1-2 seconds. For heavy queries use
next_offsetand background workers. cache_time> 0 allows the client not to trigger the bot when re-entering the same query.is_personal: true— results are not globally cached, only for a specific user.- Maximum 50 results per response. For large datasets — pagination via
next_offset. - Inline mode works in groups, channels (if the bot is an admin) and private chats.
Next: Безопасность
"}