Wiki
猫Table of Contents
- Inline Keyboards and Payloads
- The important model first
- Building a keyboard
- Row behavior
- Callback payload structure
- JSON vs Base64 payloads
- Callback payload size strategy
- Bot default vs keyboard-local override
- Strict vs tolerant decoding
- Debug logging and payload decoding
- Choosing an encoding
- Button builder for advanced cases
- Styled and URL buttons
- Long replies with keyboards
- Routing payloads to handlers
- Practical recommendations
- Related pages
Inline Keyboards and Payloads
Russian version: Inline-Keyboards-and-Payloads-RU
Inline keyboards in Laniakea are built explicitly: you choose a row width, add URL or callback buttons, and decide how callback payloads are encoded. The high-level goal is simple: keep button construction ergonomic while keeping payload routing predictable in handlers.
The important model first
InlineKeyboardbuilds Telegram inline keyboard markup row by row.- callback buttons store a
CallbackDatapayload with a command name and string arguments. - payloads can be encoded as JSON or Base64.
- the bot has a default payload type, but each keyboard can override it locally.
- payload decoding can be tolerant or strict depending on bot configuration.
- callback payload handlers are normal plugin payload handlers, not a separate routing subsystem.
Building a keyboard
The most direct constructors are:
laniakea.NewInlineKeyboardJson(maxRow)laniakea.NewInlineKeyboardBase64(maxRow)laniakea.NewInlineKeyboard(payloadType, maxRow)
maxRow controls how many buttons fit into one row before the builder automatically starts the next row.
Example:
kb := laniakea.NewInlineKeyboardJson(2).
AddCallbackButton("Open", "open", 42).
AddCallbackButton("Delete", "delete", 42).
AddUrlButton("Docs", "https://example.com/docs")
In that example:
- the first two buttons share the first row;
- the URL button starts the second row automatically.
Use AddLine() when you want to end the current row manually.
When the keyboard is ready, Laniakea usually converts it for you through helpers such as:
ctx.Keyboard(...)ctx.KeyboardLong(...)ctx.EditCallback(...)
If you need the raw Telegram markup yourself, call kb.Get().
Row behavior
maxRow controls automatic wrapping.
Behavior:
- buttons are appended to the current row until it reaches
maxRow; - once full, the next button starts a new row automatically;
AddLine()forces a row break early;Get()flushes the current unfinished row automatically.
This makes the builder easy to use in loops without manually managing row slices.
Callback payload structure
Callback buttons carry a CallbackData value:
type CallbackData struct {
Command string `json:"cmd"`
Args []string `json:"args"`
}
You normally do not build JSON by hand. Use:
AddCallbackButton(text, cmd, args...)AddCallbackButtonStyle(text, style, cmd, args...)NewCallbackData(cmd, args...)
All payload arguments are converted with fmt.Sprint, so handlers receive strings regardless of whether you originally passed int, bool, or another basic value.
Example:
kb := laniakea.NewInlineKeyboardJson(1).
AddCallbackButton("Ban", "ban_user", 12345, "spam")
That produces a payload equivalent to:
{"cmd":"ban_user","args":["12345","spam"]}
Inside the payload handler, those values are available through:
ctx.Argsfor the decoded argument list;- the payload command name used to choose the matched handler.
There is no separate ctx.Payload object in the current API. Payload handlers receive the same MessageContext structure used elsewhere, with ctx.Args populated from callback data.
JSON vs Base64 payloads
Laniakea supports two payload encodings:
BotPayloadJsonBotPayloadBase64
JSON is easier to inspect in logs and tests. Base64 is useful when you want a denser transport representation of the same JSON payload.
The encoding choice affects how callback data is serialized into the Telegram button. It does not change the logical payload shape seen by your handler after decoding.
Callback payload size strategy
Even though Laniakea makes encoding ergonomic, callback data should still stay compact.
Good payloads usually contain:
- an action name;
- a small identifier;
- one or two short arguments.
Avoid putting large serialized state into callback data. A better pattern is:
- store compact identifiers in the payload;
- look up the full state on the server side.
Bot default vs keyboard-local override
The bot has a default callback encoding:
bot.SetPayloadType(laniakea.BotPayloadBase64)
That default is copied into MessageContext, so ctx.NewInlineKeyboard(...) starts with the bot’s current payload type.
For a single keyboard, you can override it locally:
kb := ctx.NewInlineKeyboard(2).
SetPayloadType(laniakea.BotPayloadJson).
AddCallbackButton("Inspect", "inspect", 7)
Use a keyboard-local override when:
- one flow benefits from human-readable JSON during debugging;
- an older button set still needs a different encoding during migration;
- you want explicit control instead of relying on the bot-wide default.
Strict vs tolerant decoding
When a callback arrives, the bot first tries to decode it using the bot’s configured payload type.
Default behavior is tolerant:
- if the configured type is Base64 and Base64 decoding fails, Laniakea falls back to JSON;
- if the configured type is JSON and JSON decoding fails, Laniakea falls back to Base64.
This is useful when:
- old buttons are still in chats after a payload-format change;
- different keyboards were generated with different local overrides;
- you want compatibility during rollout.
Strict mode disables that fallback:
opts := (&laniakea.BotOpts{}).SetStrictPayloadType(true)
or:
bot.SetStrictPayloadType(true)
In strict mode, a mismatched payload format returns ErrPayloadTypeMismatch instead of silently trying the other decoder.
Use strict mode when payload-format drift should be treated as a real bug rather than a migration convenience.
Debug logging and payload decoding
When the bot is in debug mode and a callback is decoded from Base64, Laniakea logs the decoded JSON form for inspection.
That is useful when:
- you want readable payload traces in logs;
- the bot default is Base64 but you still want observability during development.
Choosing an encoding
Use JSON when:
- you want debuggable callback payloads;
- payloads are already short;
- you are writing tests and want readable expectations.
Use Base64 when:
- you want the bot-wide default to stay compact and opaque;
- you prefer one stable encoded transport form everywhere;
- you are migrating from earlier Base64-based usage and want consistency.
In both cases, keep payloads short and intentional. Telegram callback data is limited, so do not treat buttons as a place to serialize large state blobs. Prefer storing compact identifiers in the payload and looking up the rest on the server side.
Button builder for advanced cases
InlineKbButtonBuilder is the flexible path when you want button-specific styling or custom emoji icons.
Example:
button := laniakea.NewInlineKbButton("Confirm").
SetStyle(laniakea.ButtonStyleSuccess).
SetCallbackDataJson("confirm_order", 99)
kb := laniakea.NewInlineKeyboardJson(2).AddButton(button)
The builder supports:
SetStyle(...)SetUrl(...)SetIconCustomEmojiId(...)SetCallbackDataJson(...)SetCallbackDataBase64(...)
Use it when AddCallbackButton(...) is not expressive enough.
Styled and URL buttons
Laniakea exposes three convenience style constants:
ButtonStylePrimaryButtonStyleSuccessButtonStyleDanger
You can use them with:
AddUrlButtonStyle(...)AddCallbackButtonStyle(...)InlineKbButtonBuilder.SetStyle(...)
URL buttons and callback buttons can live in the same keyboard. Use URL buttons for external navigation and callback buttons for bot-side actions.
Long replies with keyboards
If your plain-text reply may exceed Telegram’s message length limit, use:
ctx.AnswerLong(...)ctx.AnswerLongf(...)ctx.KeyboardLong(...)
KeyboardLong(...) attaches the inline keyboard only to the final message chunk. That keeps multi-part replies readable and avoids duplicating the same keyboard on every chunk.
Routing payloads to handlers
Payloads are registered on plugins with:
Plugin.NewPayload(...)Plugin.AddPayload(...)
Example:
plugin.NewPayload(func(ctx *laniakea.MessageContext, db *App) error {
id := ctx.Args[0]
ctx.AnswerCbQueryText("Handled " + id)
return nil
}, "inspect")
When a callback arrives:
- the bot decodes the payload;
- finds the plugin payload handler by command name;
- copies decoded arguments into
ctx.Args; - runs plugin middleware;
- runs command-specific payload middleware if configured;
- executes the payload handler.
Related page:
Practical recommendations
- Use
ctx.NewInlineKeyboard(...)when you want the keyboard to inherit the bot’s current payload policy. - Use keyboard-local payload overrides sparingly and intentionally.
- Prefer JSON payloads when debugging and Base64 when you want one opaque default across the project.
- Keep callback arguments compact and reconstruct richer state on the server side.
Related pages
- Commands-and-Plugins for payload handler registration and routing.
- MessageContext for reply, edit, and callback helpers.
- tgapi-Overview for lower-level Telegram method access when keyboard helpers are not enough.
Navigation
Start here
Runtime and Architecture
- Bot-Lifecycle
- Webhook-Runtime
- Middleware
- Runners
- Error-Handling
- Logging
- Update-Routing-Model
- Policies
- Scenes
Interaction and Telegram API
- Inline-Keyboards-and-Payloads
- Auto-Generated-Commands
- Drafts
- Rich-Messages
- Localization
- Rate-Limiting
- tgapi-Overview