Telegram Inline Keyboard Builder
Design the buttons, copy the JSON.
Pick a plan to join the channel.
12:45
Preview only — the JSON below is just the keyboard.
Opens a URL. http, https and tg:// are accepted; Telegram warns the user before leaving the app.
{
"inline_keyboard": [
[
{
"text": "Join the channel",
"url": "https://t.me/memberpilot"
}
],
[
{
"text": "Monthly",
"callback_data": "plan:monthly"
},
{
"text": "Yearly",
"callback_data": "plan:yearly"
}
]
]
}Building the checkout buttons for a paid channel?
MemberPilot already ships that flow — payment, single-use invite, renewal and removal — so the keyboard you are building can be one button pointing at it.
The shape Telegram expects
An inline keyboard is an array of rows, and each row is an array of buttons. That nesting is the part people get wrong first: a flat array of buttons produces one very long row, and a doubly nested one is rejected outright.
Every button carries a text and exactly one action field. Two action fields on the same button is an error, and so is none — a button that does nothing cannot be expressed, which is why the editor above offers a single type per button rather than a form full of optional inputs.
callback_data is smaller than it looks
Telegram allows 64 bytes. Not characters, bytes — so plan:pro costs eight and план:про costs sixteen. The counter beside the field measures the encoded length, because the character count is the number that lies to you.
The pattern that scales is to treat callback data as a key rather than a payload: put an identifier in the button and keep the state in your own store. It also means you can change what a button does without invalidating messages already sent.
Callback data is not private
When the message still fails
If the keyboard validates here and Telegram still refuses the message, the problem is usually elsewhere in the request: text too long, an unescaped character under parse_mode, or a chat the bot cannot post to. Paste the response into the Bot API error decoder and it will name the likely causes.
For the formatting case specifically, the MarkdownV2 escaper handles the eighteen reserved characters that break otherwise ordinary message text.
Common questions
What is reply_markup in the Telegram Bot API?
It is the field on sendMessage and related methods that attaches a keyboard to a message. For inline buttons it holds an InlineKeyboardMarkup: an array of rows, each row an array of buttons. The builder above produces exactly that structure.
How long can callback_data be?
64 bytes, not 64 characters. Multi-byte characters count for more than one byte each, so an emoji or an accented word can push a short-looking string over the limit. Above it, Telegram rejects the whole message with BUTTON_DATA_INVALID. Send a short identifier and look the rest up on your side.
How many buttons can one row have?
Telegram does not document a hard maximum, but narrow phone screens do the enforcing: past three or four buttons the labels truncate. Two or three per row is the practical ceiling for readable text, and long labels belong on a row of their own.
Why does my keyboard cause a 400 error?
Almost always one of three things: callback_data over 64 bytes, a URL button with no scheme, or a button carrying more than one action field. A button may set exactly one of url, callback_data, web_app, switch_inline_query, switch_inline_query_current_chat or copy_text alongside its text.
Do Mini App buttons work in groups?
web_app buttons on an inline keyboard work in private chats. In groups, launch a Mini App from a direct link or a menu button instead. Telegram's InlineKeyboardButton documentation states the restriction per field, and it changes between Bot API versions, so check it against the version you target.
Sources
Everything this tool asserts about Telegram comes from Telegram’s own documentation.