The short answer
- 1Inline buttons sit under a message and signal the bot; reply keyboards send their text as a message.
- 2callback_data is at most 64 bytes; always call answerCallbackQuery.
- 3Turn on resize_keyboard; contacts and locations need the user's confirmation.
In this article
Two kinds of buttons
| Kind | Where it appears | What a tap does |
|---|---|---|
Inline keyboard (InlineKeyboardMarkup) | Attached right under a message | Sends nothing to the chat; the bot gets a signal, or a link or mini app opens |
Reply keyboard (ReplyKeyboardMarkup) | Replaces the user's keyboard under the message field | The button's text is sent to the chat as the user's message |
Use inline buttons for menus and settings, which should not clutter the chat. Use a reply keyboard when the user's answer belongs in the conversation, like "Yes" or "No".
Inline buttons under a message
Put rows of buttons (an array of arrays) in the reply_markup of sendMessage:
{"inline_keyboard": [
[{"text": "Notifications on", "callback_data": "notify_on"},
{"text": "Notifications off", "callback_data": "notify_off"}],
[{"text": "Read the help", "url": "https://example.com/help"}]
]}
| Field | What the button does |
|---|---|
callback_data | Sends the bot a signal (a callback_query); 1 to 64 bytes |
url | Opens a link |
web_app | Opens a mini app |
switch_inline_query | Lets the user pick a chat and calls the bot there in inline mode; see inline bots |
copy_text | Copies a preset text to the clipboard |
login_url / pay | Logs in to a website / pays; see Log in with Telegram |
Keep callback_data short, and always answer
The 64 limit is in bytes, not characters, and non-Latin text or emoji use several bytes each. Store a short code such as notify_on, and keep longer data on your server with just an ID in the button.
When a user taps the button, a loading indicator spins on their screen. Your bot should always call answerCallbackQuery to stop it, optionally with a short notice or an alert (show_alert). To show the result, editing the same message (editMessageText, editMessageReplyMarkup) keeps the chat tidier than sending a new one.
Reply keyboards that replace the keyboard
{"keyboard": [
[{"text": "Yes"}, {"text": "No"}],
[{"text": "Share my contact", "request_contact": true}]
],
"resize_keyboard": true,
"one_time_keyboard": true,
"input_field_placeholder": "Choose an answer"}
| Field | Meaning |
|---|---|
resize_keyboard | Shrinks the keyboard to fit the buttons; turn it on almost always |
one_time_keyboard | Hides the keyboard after one tap |
is_persistent | Keeps it available even after the user switches back to the normal keyboard |
input_field_placeholder | Grey hint text in the message field, up to 64 characters |
selective | In groups, shows the keyboard only to specific users |
Buttons with request_contact, request_location, request_users, request_chat or request_poll open Telegram's own picker. A contact or a location is only sent after the user confirms it, so the bot never gets them silently. To remove the keyboard, send {"remove_keyboard": true}.
For request URLs and reading responses, see your first Bot API requests; for receiving the tap signals, see webhooks vs long polling.
FAQ
What is the difference between an inline keyboard and a reply keyboard?
Inline buttons are attached to a message and send the bot a signal. Reply keyboard buttons replace the user's keyboard and send their text as a message.
Why does my Telegram bot button keep loading?
The bot did not call answerCallbackQuery after receiving the callback. Answer every callback, even with no text.
How long can callback_data be?
1 to 64 bytes. Use a short code and keep longer data on your server.
Sources
At a glance
- 1Inline
Menus and settings without cluttering the chat.
- 2Reply
Answers that should stay in the conversation.
- 3Safety
Treat callback_data as user input and re-check rights.
Updated