Commands & deep links

Commands /start, /help

Learn to handle /start and /help commands in PHP. Analysis of bot_command structure, registration via setMyCommands, and argument parsing in group chats.

Commands are a special format of text messages that begin with the slash character / (for example, /start or /help). Telegram processes them at the application interface level: it highlights them in blue, makes them clickable, and displays hints in a special menu.

For any quality bot, two commands are a mandatory standard:

  • /start — primary launch. The user sees a "Start" button when opening the dialog for the first time. Pressing it sends this command. It is also used to pass referral parameters.
  • /help — call help. A brief guide, list of available functions, and support contacts.

How Telegram Sends Commands to the API

When a user sends a command, Telegram servers package it into an object Update. Inside object Message, besides the raw text in field text, there is an array entities. In it, Telegram marks the data type — bot_command.

Here is an example of the incoming Update structure when the user sends command /start test_payload:

{
"ok": true,
"result": [
{
"update_id": 987654321,
"message": {
"message_id": 105,
"from": {
"id": 123456789,
"is_bot": false,
"first_name": "Alex",
"username": "alex_dev"
},
"chat": {
"id": 123456789,
"first_name": "Alex",
"username": "alex_dev",
"type": "private"
},
"date": 1710000000,
"text": "/start test_payload",
"entities": [
{
"offset": 0,
"length": 6,
"type": "bot_command"
}
]
}
}
]
}

Let's examine the key fields of this JSON:

  • text — contains the full line sent by the user, including the command itself and any parameters after the space.
  • entities — an array of markup objects. Field offset: 0 indicates that the command starts with the very first character of the line. Field length: 6 states that the command length is 6 characters (including the slash, i.e., /start).
  • type: "bot_command" — explicit marker indicating that Telegram has recognized this text segment as a command.

Registering Commands via setMyCommands

To have a "Menu" button with a list of commands appear in the left corner of the input field for the user, these must be registered on Telegram's servers. This can be done manually through the bot @BotFather, but it is much more convenient and flexible to manage this process dynamically via the API method setMyCommands.

We use function telegramApi(), which we created in chapter Первый запрос: getMe. Here we only call it — we do not copy the implementation.

<?php

// Подключаем файл с функцией telegramApi(), созданной в предыдущих главах
require_once 'telegram_api.php';

// Список команд, которые мы хотим зарегистрировать в интерфейсе
$commands = [
[
'command' => 'start',
'description' => 'Запустить бота и получить приветствие'
],
[
'command' => 'help',
'description' => 'Показать справку по командам'
],
[
'command' => 'catalog',
'description' => 'Открыть каталог товаров'
]
];

try {
$response = telegramApi('setMyCommands', [
'commands' => $commands
]);

echo "Команды успешно зарегистрированы!\n";
var_dump($response);
} catch (Exception $e) {
echo "Ошибка при регистрации команд: " . $e->getMessage() . "\n";
}

Upon successful request execution, Telegram returns a standard JSON response:

{
"ok": true,
"result": true
}

If there was an error in the command array (for example, if the command name contains uppercase letters or special characters other than underscores), field ok will return false, and field description will contain the reason for the error (for example, Bad Request: invalid command name).

Parsing Commands and Handling Arguments in PHP

When a bot works in groups, users may call commands specifying the bot's username, for example: /start@MyAwesomeBot payload. This is done so that multiple bots in one chat do not respond to the same command. Your parser should be able to correctly separate the bot's username and the command from the passed arguments.

We use function telegramApi(), which we created in chapter sendMessage. Here we only call it — we do not copy the implementation.

<?php

require_once 'telegram_api.php';

/**
* Разбирает входящий текст на команду и аргументы.
* Учитывает обращение к конкретному боту в группах (/help@MyBot).
*/
function parseCommand(string $text, string $botUsername = ''): ?array
{
if ($text === '' || $text[0] !== '/') {
return null;
}

// Работаем только с первой строкой (игнорируем переносы строк)
$firstLine = strtok($text, "\n") ?: '';
$parts = explode(' ', $firstLine, 2);
$rawCommand = $parts[0];
$argument = trim($parts[1] ?? '');

// Проверяем, есть ли в команде юзернейм бота (например, /start@MyBot)
if (($pos = strpos($rawCommand, '@')) !== false) {
$commandName = substr($rawCommand, 0, $pos);
$targetBot = substr($rawCommand, $pos + 1);

// Если команда адресована другому боту — игнорируем её
if ($botUsername !== '' && strcasecmp($targetBot, $botUsername) !== 0) {
return null;
}
} else {
$commandName = $rawCommand;
}

return [
'name' => strtolower($commandName), // приводим к нижнему регистру
'argument' => $argument
];
}

// Получаем входящее обновление от Telegram
$rawInput = file_get_contents('php://input');
$update = json_decode($rawInput, true);

if (isset($update['message'])) {
$message = $update['message'];
$text = $message['text'] ?? '';
$chatId = $message['chat']['id'];

// Проверяем, является ли сообщение командой по разметке entities
$isCommand = false;
if (isset($message['entities'])) {
foreach ($message['entities'] as $entity) {
if (($entity['type'] ?? '') === 'bot_command' && ($entity['offset'] ?? -1) === 0) {
$isCommand = true;
break;
}
}
}

if ($isCommand) {
// Укажите юзернейм вашего бота без знака @ для фильтрации групповых команд
$parsed = parseCommand($text, 'MyAwesomeBot');

if ($parsed !== null) {
switch ($parsed['name']) {
case '/start':
$payload = $parsed['argument'];
if ($payload !== '') {
$reply = "Привет! Вы запустили бота с параметром: <code>" . htmlspecialchars($payload) . "</code>";
} else {
$reply = "Привет! Добро пожаловать. Напишите /help для получения списка команд.";
}
break;

case '/help':
$reply = "<b>Доступные команды:</b>\n\n" .
"/start — Начать работу\n" .
"/help — Показать это меню\n" .
"/catalog — Перейти в каталог";
break;

default:
$reply = "Извините, команда <code>" . htmlspecialchars($parsed['name']) . "</code> не поддерживается.";
break;
}

// Отправляем ответ пользователю
telegramApi('sendMessage', [
'chat_id' => $chatId,
'text' => $reply,
'parse_mode' => 'HTML'
]);
}
}
}

Debugging Incoming Commands

In order to see exactly how your script parses incoming data, write the raw updates to a log file. This will save you from having to guess why the command did not work.

<?php
// Быстрое логирование входящего запроса для анализа структуры
file_put_contents(
'bot_debug.log',
file_get_contents('php://input') . "\n",
FILE_APPEND
);

To track requests in real-time on your server, run the command in the terminal:

tail -f bot_debug.log | grep --line-buffered "" | jq .

This command outputs new lines from the log as they appear, and utility jq nicely formats the JSON structure directly in the console, highlighting the syntax.

Continue: Deep links

"}