Telegram InlineKeyboardButton: Complete Developer Guide 2026
Telegram InlineKeyboardButton: Complete Developer Guide 2026
Inline keyboards are one of the most powerful features of the Telegram Bot API. Instead of asking users to type commands, you present tappable buttons directly beneath messages. The InlineKeyboardButton is the individual button object — and mastering its options unlocks everything from simple confirmations to payment flows and Mini Apps.
This guide covers every InlineKeyboardButton type with working code examples in Python (using python-telegram-bot) and JavaScript (using grammy).
What Is InlineKeyboardButton?
An InlineKeyboardButton is a button object that appears as part of an InlineKeyboardMarkup — a grid of buttons attached to a specific message. Unlike ReplyKeyboardMarkup buttons (which replace the keyboard), inline buttons appear inline with the message content and don't interfere with the chat input.
The full button object structure (from the Bot API docs):
{
"text": "Button Label",
"callback_data": "...", // OR
"url": "...", // OR
"web_app": {...}, // OR
"switch_inline_query": "...", // OR
"switch_inline_query_current_chat": "...", // OR
"callback_game": {...}, // OR
"pay": true // Only one field per button
}
Each button must have exactly one functional field (in addition to text). Let's go through each type.
1. callback_data Buttons
The most common type. When pressed, the button sends a CallbackQuery to your bot with the callback_data string. You handle it, do something, and call answerCallbackQuery to dismiss the loading indicator.
Python example:
from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Update
from telegram.ext import Application, CallbackQueryHandler, CommandHandler, ContextTypes
async def start(update: Update, context: ContextTypes.DEFAULT_TYPE):
keyboard = [
[
InlineKeyboardButton("Option A", callback_data="option_a"),
InlineKeyboardButton("Option B", callback_data="option_b"),
],
[InlineKeyboardButton("Cancel", callback_data="cancel")],
]
reply_markup = InlineKeyboardMarkup(keyboard)
await update.message.reply_text("Choose an option:", reply_markup=reply_markup)
async def button_handler(update: Update, context: ContextTypes.DEFAULT_TYPE):
query = update.callback_query
await query.answer() # Dismiss loading spinner
if query.data == "option_a":
await query.edit_message_text("You chose Option A!")
elif query.data == "option_b":
await query.edit_message_text("You chose Option B!")
elif query.data == "cancel":
await query.edit_message_text("Cancelled.")
app = Application.builder().token("YOUR_TOKEN").build()
app.add_handler(CommandHandler("start", start))
app.add_handler(CallbackQueryHandler(button_handler))
app.run_polling()
callback_data best practices:
- Max 64 bytes — enforce this in your code
- Use structured formats:
"action:id"patterns like"approve:42"make parsing easy - Always call
answerCallbackQuery: Within 10 seconds or the user sees a timeout error - Validate the data server-side: Never trust callback_data to contain only expected values
2. URL Buttons
URL buttons open a link when pressed. Simple and useful for "Read more", "Visit website", or "Download" actions.
keyboard = [[
InlineKeyboardButton("Visit tgram.bot", url="https://tgram.bot"),
InlineKeyboardButton("GitHub", url="https://github.com"),
]]
reply_markup = InlineKeyboardMarkup(keyboard)
await update.message.reply_text("Links:", reply_markup=reply_markup)
Restrictions:
- URL must use HTTPS (or HTTP for IP addresses)
- Deep links to Telegram (
tg://) are allowed - URLs cannot point to Telegram's API endpoints
3. Web App Buttons
Telegram Mini Apps (formerly WebApps) open a full-screen web application inside Telegram. The web_app button type is how you launch them from a message.
from telegram.webappinfo import WebAppInfo
keyboard = [[
InlineKeyboardButton(
"Open App",
web_app=WebAppInfo(url="https://your-miniapp.com")
)
]]
reply_markup = InlineKeyboardMarkup(keyboard)
await update.message.reply_text("Launch the app:", reply_markup=reply_markup)
When the user presses the button, Telegram opens your URL in a sandboxed WebView. Your web app can communicate back to the bot using the Telegram.WebApp JavaScript SDK.
Requirements:
- URL must be HTTPS
- Your server must respond with correct CORS headers
- Include the Telegram WebApp SDK:
<script src="https://telegram.org/js/telegram-web-app.js"></script>
4. switch_inline_query Buttons
These buttons switch the user to inline mode — the user is redirected to a chat of their choice and the bot's inline query is pre-filled. Useful for sharing bot content with others.
keyboard = [[
InlineKeyboardButton(
"Share this bot",
switch_inline_query="" # Empty string = user types the query
),
InlineKeyboardButton(
"Search bots",
switch_inline_query="telegram bots" # Pre-filled query
),
]]
To use inline mode, you must enable it via @BotFather with /setinline.
5. switch_inline_query_current_chat
Same as above but stays in the current chat instead of asking the user to choose a chat. Ideal for in-chat search or autocomplete patterns.
keyboard = [[
InlineKeyboardButton(
"Search in this chat",
switch_inline_query_current_chat="search term"
)
]]
6. Pay Buttons (Telegram Payments)
The pay field creates a payment button for Telegram Payments. It must be the first button in the first row and can only be used in response to a message sent with an Invoice.
from telegram import LabeledPrice
await context.bot.send_invoice(
chat_id=update.effective_chat.id,
title="Premium Subscription",
description="1 month of premium access",
payload="sub_monthly",
provider_token="YOUR_PAYMENT_PROVIDER_TOKEN",
currency="USD",
prices=[LabeledPrice("1 Month Premium", 499)], # 499 = $4.99
reply_markup=InlineKeyboardMarkup([[
InlineKeyboardButton("Pay $4.99", pay=True)
]])
)
Building Complex Keyboard Layouts
The InlineKeyboardMarkup takes a list of rows, where each row is a list of buttons. You can create any grid layout:
# 2x2 grid
keyboard = [
[btn_a, btn_b],
[btn_c, btn_d],
]
# One wide button + two narrow below
keyboard = [
[InlineKeyboardButton("Wide Button", callback_data="wide")],
[
InlineKeyboardButton("Left", callback_data="left"),
InlineKeyboardButton("Right", callback_data="right"),
],
]
# 3 columns, 2 rows
keyboard = [
[btn1, btn2, btn3],
[btn4, btn5, btn6],
]
Editing Keyboards After Sending
One of the most useful patterns: update a message's keyboard without resending it. Use editMessageReplyMarkup:
async def approve_handler(update: Update, context: ContextTypes.DEFAULT_TYPE):
query = update.callback_query
await query.answer("Approved!")
# Replace the keyboard with a "done" indicator
await query.edit_message_reply_markup(
reply_markup=InlineKeyboardMarkup([[
InlineKeyboardButton("✅ Approved", callback_data="noop")
]])
)
This pattern is excellent for approval flows, multi-step wizards, and toggles (e.g. on/off settings buttons that flip state).
JavaScript (Grammy) Examples
For Node.js developers using the Grammy framework:
import { Bot, InlineKeyboard } from "grammy";
const bot = new Bot("YOUR_TOKEN");
bot.command("start", async (ctx) => {
const keyboard = new InlineKeyboard()
.text("Option A", "option_a")
.text("Option B", "option_b")
.row()
.url("Visit tgram.bot", "https://tgram.bot");
await ctx.reply("Choose:", { reply_markup: keyboard });
});
bot.callbackQuery("option_a", async (ctx) => {
await ctx.answerCallbackQuery();
await ctx.editMessageText("You chose A!");
});
bot.start();
Common Mistakes
- Not calling answerCallbackQuery: The loading spinner stays on the button. Always answer within 10 seconds.
- callback_data over 64 bytes: The message will fail silently in some libraries. Check the byte length, not character length (UTF-8 characters can be multi-byte).
- Multiple functional fields on one button: You get a 400 Bad Request. Each button has exactly one action field.
- Using pay button outside invoice context: Pay buttons only work when sent alongside a Telegram invoice message.
- Too many buttons: Telegram limits inline keyboards to 100 buttons total. More than 8-10 buttons in a single message is also a UX problem.
Conclusion
InlineKeyboardButton is the primary interaction primitive of the Telegram Bot API. Master callback_data for flows, url for external links, web_app for Mini Apps, and pay for monetization — and you'll have the building blocks for any sophisticated bot interaction.
The pattern that works for almost every bot: start simple with callback buttons, add URL buttons for external resources, and when you need a rich UI, reach for the web_app type to launch a Mini App. Keep keyboards focused (3-5 buttons max per message) and always answer callback queries promptly.
Share this article