Telegram Bots Commands

Telegram Bots Commands: A Complete Setup and Management Guide

Disclosure: This post contains one or more links to providers we have a real, registered affiliate/referral relationship with. We may earn a commission at no extra cost to you if you sign up through them.

Telegram bots commands are the primary interface users rely on to interact with a bot, and getting them right affects both usability and discoverability inside the Telegram client. This guide covers how telegram bots commands work, how to register them correctly with BotFather and the Bot API, and how to structure command handling for a production bot running on your own infrastructure.

Understanding How Telegram Bots Commands Work

Every Telegram bot exposes a list of slash commands (like /start or /help) that Telegram renders as a native menu in the chat interface. When a user types / in a conversation with your bot, Telegram displays the registered command list with short descriptions, pulled directly from the bot’s metadata rather than from free text you send back. This is a distinct mechanism from simply parsing incoming messages for a / prefix in your own code — the menu itself is a first-class Telegram feature, and telegram bots commands need to be explicitly registered for that menu to populate.

There are two layers to get right:

  • Registration — telling Telegram which commands exist and what they do, via setMyCommands on the Telegram Bot API.
  • Routing — the logic in your bot process that receives an incoming update, matches it against a known command, and executes the corresponding handler.
  • Confusing these two layers is one of the most common mistakes when building telegram bots commands from scratch: a bot can have working command routing internally while showing an empty or stale command menu in the Telegram client, simply because setMyCommands was never called or wasn’t re-run after a change.

    The Command Registration Model

    Telegram supports scoping commands by chat type, by specific chat, and even by user language, via the scope and language_code parameters on setMyCommands. A bot can register one set of telegram bots commands for private chats and a different, smaller set for group chats — useful when a command like /settings only makes sense one-on-one.

    Command Naming Constraints

    Command names have real constraints enforced by Telegram: lowercase Latin letters, digits, and underscores only, 1-32 characters, and no leading slash in the registration payload itself (the slash is added by the client). Violating these silently fails the registration call or drops the offending command, so validating names before calling setMyCommands is worth doing in code rather than by hand.

    Registering Telegram Bots Commands With BotFather

    The fastest way to set an initial command list is through BotFather’s /setcommands conversation, which is fine for early development but doesn’t scale well once you have multiple environments (dev, staging, production) or commands that change based on user role. For anything beyond a prototype, calling setMyCommands programmatically from your bot’s own deployment process is more maintainable, since it keeps the command list in version control alongside the handler code instead of relying on manual BotFather edits that are easy to forget after a refactor.

    A minimal registration call looks like this:

    curl -s -X POST "https://api.telegram.org/bot$BOT_TOKEN/setMyCommands" \
      -H "Content-Type: application/json" \
      -d '{
        "commands": [
          {"command": "start", "description": "Start the bot"},
          {"command": "help", "description": "Show available commands"},
          {"command": "status", "description": "Check current status"}
        ]
      }'

    Running this as part of a deploy script — rather than as a one-off manual step — ensures the visible command menu never drifts from what your handlers actually support.

    Scoping Commands Per Chat Type

    If your bot behaves differently in groups versus private chats, register separate command sets using scope: {"type": "all_group_chats"} versus scope: {"type": "all_private_chats"}. This keeps the in-client menu relevant to context instead of showing admin-only commands to every group member.

    Versioning Command Changes

    Because setMyCommands fully replaces the existing list rather than merging into it, any script that manages telegram bots commands should always submit the complete, current set — partial updates will silently remove commands you didn’t include in the payload.

    Building the Command Routing Layer

    Registration only controls what users see in the menu; your bot process still has to parse each incoming update and dispatch it to the right handler. A typical long-polling or webhook-based bot receives an Update object, extracts message.text, and checks whether it starts with /.

    A simple but effective pattern is to keep a dictionary mapping command strings to handler functions, and fall back to a natural-language intent matcher for everything else:

    COMMAND_HANDLERS = {
        "/start": handle_start,
        "/help": handle_help,
        "/status": handle_status,
    }
    
    def route_update(update):
        text = update.get("message", {}).get("text", "")
        command = text.split()[0].split("@")[0] if text.startswith("/") else None
        handler = COMMAND_HANDLERS.get(command)
        if handler:
            return handler(update)
        return handle_fallback(update)

    Stripping the @botusername suffix (as shown above with .split("@")[0]) matters in group chats, where Telegram appends the bot’s username to disambiguate which bot a command is aimed at when multiple bots are present.

    Handling Command Arguments

    Most non-trivial telegram bots commands take arguments — /queue add <task> or /status <project> — and those arguments arrive as the remainder of the message text after the command token. Parsing them defensively (checking argument count before indexing into a split list) avoids a whole class of index-out-of-range crashes triggered by users sending a bare command with no arguments.

    Long-Polling vs Webhook Delivery

    Command updates can arrive either via long-polling (getUpdates) or a webhook you host yourself. Long-polling is simpler to run and debug locally, while a webhook scales better and avoids holding an open connection per bot — see the Telegram Bot API documentation for the tradeoffs and required webhook certificate setup.

    Deploying and Running a Bot Reliably

    Once command registration and routing are in place, the bot process itself needs to stay running and recover from crashes without manual intervention. Running it as a systemd service, or inside a container managed by Docker Compose, gives you automatic restarts and centralized logs.

    A minimal Compose service for a polling-based bot might look like this:

    services:
      telegram-bot:
        build: .
        restart: unless-stopped
        environment:
          - BOT_TOKEN=${BOT_TOKEN}
        volumes:
          - ./data:/app/data

    If you’re comparing container orchestration approaches for a bot alongside other automation services, the difference between a single Compose stack and a full cluster is covered in Kubernetes vs Docker Compose: Which Should You Use? — for a single bot process, Compose is almost always sufficient.

    Persisting State Across Restarts

    Bots that track per-user or per-chat state (like a pending multi-step command flow) need that state to survive a restart, whether in a file, SQLite, or a proper database. If you’re running Postgres alongside your bot for this purpose, Postgres Docker Compose: Full Setup Guide for 2026 walks through a production-ready setup, and Docker Compose Secrets: Secure Config Management Guide covers keeping the bot token itself out of plain environment variables in source control.

    Monitoring Bot Health

    For a bot handling telegram bots commands in production, basic observability — knowing when polling stops, when a webhook starts erroring, or when a handler throws — is worth building in early. Docker Compose Logs: The Complete Debugging Guide covers reading and filtering container logs, which is usually the first place to look when a command stops responding.

    Choosing Where to Host Your Bot

    A Telegram bot using long-polling doesn’t need inbound ports open, which makes it a good fit for a small, inexpensive VPS. A webhook-based bot needs a stable public HTTPS endpoint, which pushes you toward a VPS with a reverse proxy or a managed platform in front of it. Providers like DigitalOcean and Hetzner both offer small VPS tiers that are more than sufficient for a bot with a modest command set and moderate traffic — the workload is I/O-bound, not compute-bound, so you rarely need to over-provision.

    Common Pitfalls With Telegram Bots Commands

    A few recurring issues show up repeatedly when teams build out their first bot:

  • Forgetting to call setMyCommands after adding a new handler, so the code supports a command the menu never advertises.
  • Not stripping the @botusername suffix in group chats, causing command matching to silently fail.
  • Registering commands with uppercase letters or invalid characters, which Telegram rejects or drops without an obvious error in the bot’s own logs.
  • Sending a partial command list to setMyCommands, unintentionally removing previously registered commands.
  • Blocking the main event loop inside a command handler (e.g., a slow synchronous HTTP call), which delays every other queued update.
  • Testing the actual command menu inside a real Telegram client — not just verifying the API call returned 200 OK — is the only reliable way to catch several of these before users do.


    Recommended: Ready to put this into practice? DigitalOcean is a tool we use for exactly this, and we have a real, disclosed affiliate relationship with them.

    FAQ

    Do telegram bots commands need to start with a slash?
    Users type commands with a leading slash (/start), but when registering commands via setMyCommands you omit the slash — the API expects just the command name, and the client adds the slash in the UI.

    Can I have different telegram bots commands for different chats?
    Yes. The setMyCommands method supports a scope parameter that lets you register distinct command sets for private chats, group chats, specific chat IDs, or specific users, and Telegram will show the most specific matching scope.

    Why isn’t my new command showing up in the Telegram menu?
    Almost always because setMyCommands hasn’t been called with the updated list, or the Telegram client is caching an older menu — restarting the chat with the bot or waiting briefly usually resolves the cache case; a missing API call requires re-running registration.

    How many commands can a bot register?
    Telegram documents a maximum of 100 commands per scope. If you have more functionality than that, group related actions under a single command with subcommands/arguments rather than registering dozens of top-level commands.

    Conclusion

    Telegram bots commands work through two coordinated pieces: the setMyCommands registration that populates the visible in-client menu, and the routing logic in your own bot process that actually executes each command. Getting both right — with correctly scoped registration, defensive argument parsing, and a reliably running deployment — is most of what separates a bot that feels polished from one that quietly breaks the moment a group chat or an unregistered command edge case shows up. Treat command registration as part of your deploy process, not a manual BotFather step, and the rest of the system becomes much easier to keep consistent over time.

    Comments

    Leave a Reply

    Your email address will not be published. Required fields are marked *