Skip to Content
Nextra 2 Alpha

Custom commands

Text commands and button commands are built from the same modal, in the dashboard’s Commands tab. Whether a saved command becomes one or the other is decided entirely by whether it has any buttons attached when it’s saved; editing can flip an existing command the other way.

One modal, two kinds of command

The Commands tab lists every text and button command for the selected bot in one table — columns Trigger, Response, Active, Actions — and adds new ones from one Add Command button.

The add modal is titled Add Filter Command; editing an existing command opens the same modal titled Edit Command. The trigger field is a bare, /-prefixed field sanitised to letters, numbers and underscore; the response field is a rich-text field.

What gets saved is decided by the button board under the response field, not by a separate switch. Submit with zero buttons attached and the dashboard calls the text-command create action; attach at least one button first and it calls the button-command create action instead. The same check runs when saving an edit to an existing command: clear every button off a button command and saving converts it to a text command; add a first button to a text command and saving converts it to a button command.

The button board

The button board appears under the response field once the first button is added. Each button opens its own edit popover, titled Button, with a label field, a URL field, a colour picker, a layout switch, and an optional premium-emoji icon.

Button board limits

SettingValue
Buttons per command12 max — the Add Button control disables at the cap
Label length64 characters (field placeholder: Label (e.g. Buy Now))
URL schemehttps:// only. A bare host is normalised to https:// automatically; an explicit http:// URL is not blocked while typing but is rejected on save with Button URLs must use HTTPS
Colours4 — Gray (default, no style set), Blue (primary), Green (success), Red (danger)
LayoutHorizontal or StackedStacked is the default for a new command and for the first button added to one

A button can also carry one premium emoji next to its label — a Telegram custom_emoji_id, picked from the owner’s own emoji packs or pasted directly. The bot keeps it only if it’s 5 to 32 digits; anything else is silently dropped when the live config is built (config.py’s button-command loader).

The same caps are enforced again on the server when a command is saved (services/managedBotFeatures.ts validateButtonCommandData): at least 1 button and at most 12, every button needs a label and an https:// URL, and the layout value must be inline or stacked.

The response editor

Selecting text inside the response field pops up a floating formatting toolbar.

Selection toolbar

ButtonDoes
Bold, Italic, Underline, StrikethroughStandard text formatting
MonospaceWraps the selection in a code span
SpoilerWraps the selection in Telegram’s spoiler span
HyperlinkTurns the selection into a link — the typed URL must start with https:// literally, or nothing is inserted (no auto-prefixing here, unlike the button board’s URL field)
Remove linkStrips a link back to plain text

Two more controls sit in the modal’s own toolbar rather than the floating one: Undo / Redo, an emoji inserter (“Drop a premium emoji, or a whole banner, into the response”), and a divider inserter (“Drop a divider line into the response”).

The divider inserter draws from an 11-cell catalog and also offers an invisible spacer glyph — “adds space where Telegram normally allows none” — insertable on its own alongside the divider lines.

The trigger namespace

A trigger must be unique across every text command, every button command, and every character’s trigger command on the same bot. Creating or renaming one is checked against all three.

Trigger validation errors

SituationMessage
Bad formatTrigger must be alphanumeric (letters, numbers, underscore only)
Matches a character’s triggerTrigger cannot be the same as a character command (/x)
Matches another text commandCommand with trigger "x" already exists
Matches another button commandButton command with trigger "x" already exists
A text and a button command would share one triggerA button command with trigger "x" already exists. Triggers must be unique across text and button commands.

A trigger matches with the leading slash optional — the matcher is ^/?{trigger}(@botname)?(\s|$), case-insensitive (scripts/telegram/utils/prims.py:265-279).

Slash optional

InputOutput
Saved triggerca
All of these match/ca /ca@yourbotusername ca

The Commands tab’s own subtitle says as much: “users type /ca or just ca to get the response.”

Custom commands are matched only after every built-in slash command has had a chance to: the fixed command router runs first, and any match there consumes the message before a custom command is even checked. Custom commands are not gated by the group authorization allowlist themselves — neither the matcher (match_text_or_button_command) nor the dispatchers (dispatch_text_command, dispatch_button_command) make an is_authorized call, and nothing upstream of them in the message pipeline does either.

7 trigger words are reserved as built-ins:

  • start
  • commands
  • getid
  • whoisactive
  • holders
  • ban
  • mute

Where the reserved list is actually enforced

This list (RESERVED_BUILTIN_TRIGGERS, services/managedBotFeatures.ts) blocks reassigning a character’s trigger command to one of these 7 words. Text-command and button-command triggers are validated by a different check (validateTextCommandTrigger) that does not consult this list — saving a text or button command using one of these 7 words as its trigger is not blocked by name alone.

Limits

Per-command limits

SettingValue
Visible reply length4096 characters — Telegram’s own cap, measured after HTML tags are stripped and entities collapsed to 1 character each
Stored markup size64 KiB (65,536 characters) of raw response markup
Batch create50 commands per API call (an items array on the create action)
Management API rate limit60 requests / 60 seconds per IP — shared across every /api/bots/management resource, not specific to commands

Over either size limit, saving fails with Response markup is too large (over 64 KiB) or Response exceeds Telegram's 4096 character limit (N visible characters) (over 4096 visible characters, markup itself still under 64 KiB).

Beta

Hyperlink navigation is currently in beta.

A finished hyperlink nav is stored as an ordinary textCommands[] entry carrying one extra field, hyperlinkNav — the nav’s preset, colour, link list, and the site data it was discovered from. The bot itself never reads that object: config.py’s text-command loader (config.py:451-457) reads only trigger and response into the running config (and isActive, to decide whether to include the command at all) — hyperlinkNav is dropped entirely.

The Commands tab can edit one. Opening a command for editing checks for hyperlinkNav first and, if present, opens the hyperlink-nav builder instead of the ordinary text editor (CommandsCreator.tsx:339-348). There’s no button in the Commands tab to start a new one — creation only happens from the beta tab’s own nav builder.

A nav entry can’t be cloned — the Clone action doesn’t render for it in the table — and can’t be converted to a button command: attempting that conversion fails on the server with Hyperlink navigations cannot be converted to regular commands (app/api/bots/management/route.ts:455).