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 request
  • query — 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 user
  • next_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 result
  • from — the user
  • query — original query
  • inline_message_id — id of the sent message (for editing via editMessageText with inline_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_offset and 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: Безопасность

"}