How to add inline buttons and handle taps in a Telegram bot
Attach clickable inline keyboards to messages and respond to button taps with callback queries.
Inline buttons turn a bot from a text wall into a tappable interface. They sit under a message, and when a user taps one, Telegram sends your bot a callback query with the button's data. This guide builds a message with buttons and handles the taps.
What you need
- A bot token and a running bot
- A chat to test in
- node-telegram-bot-api installed
Step 1: Send a message with buttons
Attach an inline_keyboard to the message. Each button has a label and a small callback_data string that comes back to you when tapped. Buttons are arranged in rows.
import TelegramBot from "node-telegram-bot-api";
const bot = new TelegramBot(process.env.TELEGRAM_BOT_TOKEN, {
polling: true,
});
bot.onText(/^\/order/, (msg) => {
bot.sendMessage(msg.chat.id, "Pick a size:", {
reply_markup: {
inline_keyboard: [
[
{ text: "Small", callback_data: "size:s" },
{ text: "Medium", callback_data: "size:m" },
{ text: "Large", callback_data: "size:l" },
],
[{ text: "Cancel", callback_data: "cancel" }],
],
},
});
});Step 2: Handle the tap
Listen for callback_query. Read the data, act on it, and edit the original message so the choice is reflected. Always answer the query, or the button shows a spinner forever.
bot.on("callback_query", async (query) => {
const data = query.data;
const chatId = query.message.chat.id;
const msgId = query.message.message_id;
if (data === "cancel") {
await bot.editMessageText("Order cancelled.", {
chat_id: chatId,
message_id: msgId,
});
} else if (data.startsWith("size:")) {
const size = data.split(":")[1].toUpperCase();
await bot.editMessageText(`You chose size ${size}. Thanks!`, {
chat_id: chatId,
message_id: msgId,
});
}
// Stop the loading spinner on the tapped button.
await bot.answerCallbackQuery(query.id);
});answerCallbackQuery, the user sees a spinning clock on the button for up to 30 seconds. Call it even when you have nothing to display.callback_data at 64 bytes. Store short codes like size:m and keep the real state in your own database keyed by user or message id.Result
Typing /order shows a row of size buttons, and tapping one edits the message to confirm the choice with no spinner left hanging. You now have the building block for menus, wizards, and confirmation flows.
Watch related tutorials
1:42:18
28:14
41:09
9:47
8:23
52:31