IntegrationsIntermediate

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.

8 minIntermediate

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.

buttons.mjs
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" }],
      ],
    },
  });
});
Telegram - inline keyboard
Pick a size:
----------------------------------
[ Small ] [ Medium ] [ Large ]
[ Cancel ]
Buttons render under the message text.

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.

buttons.mjs (continued)
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);
});
Always answer the query
If you skip answerCallbackQuery, the user sees a spinning clock on the button for up to 30 seconds. Call it even when you have nothing to display.
Keep callback_data tiny
Telegram caps 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

Tags
#telegram#buttons#inline-keyboard#bot#callback