Packages
ex_gram
0.66.0
0.67.0
0.66.0
0.65.0
0.64.0
0.63.0
0.62.0
0.61.0
0.60.0
0.58.0
0.57.0
0.56.1
0.56.0
0.55.1
0.55.0
0.54.0
0.53.0
0.52.2
0.52.1
0.52.0
0.51.1
0.51.0
0.50.2
0.50.1
0.50.0
0.41.0
0.40.0
0.34.0
0.33.0
0.32.0
0.31.0
0.30.0
0.29.0
0.28.0
0.27.0
0.26.0
0.25.0
0.24.1
0.24.0
0.23.0
0.22.0
0.21.0
0.20.0
0.15.0
0.14.0
0.13.0
0.12.0
0.11.0
0.10.0
0.9.0
0.8.1
0.8.0
0.7.1
0.7.0
0.6.2
0.6.1
0.6.0
0.5.0
0.5.0-rc6
0.5.0-rc5
0.5.0-rc4
0.5.0-rc3
0.5.0-rc2
Telegram Bot API low level and framework
Current section
Files
Jump to
Current section
Files
guides/commands.md
# Commands
`ExGram` provides a `command` macro to declare bot commands with descriptions, visibility scopes, and language translations. When `setup_commands: true` is set on the bot, these declarations are automatically registered with the Telegram API on startup, making your commands appear in the autocomplete menu for users.
## Basic usage
The simplest way to declare a command is with just a name and a description:
```elixir
defmodule MyBot do
use ExGram.Bot, name: :my_bot, setup_commands: true
command(:start, description: "Start the bot")
def handle({:command, :start, _msg}, context) do
answer(context, "Welcome!")
end
end
```
Commands without a `description` are still valid - they will be handled by your bot but won't be registered in the Telegram command menu. This is useful for "hidden" commands or not useful commands in day-to-day:
```elixir
command(:start) # no description - works but won't show in menu
command(:debug) # no description - works but won't show in menu
def handle({:command, :start, _msg}, context) do
answer(context, "Hey!")
end
def handle({:command, :debug, _msg}, context) do
answer(context, "Debug mode")
end
```
## Options reference
- `:description` (string) - text shown in Telegram's command menu. Required if any other of this options are set.
- `:scopes` (list) - scopes where the command is visible. See [Scopes](#scopes).
- `:lang` (keyword list) - language-specific overrides, IETF language code atom. See [Language translations](#language-translations).
- `:name` (atom) - atom used for dispatch pattern matching. Defaults to the command name as an atom. Useful when you want a different name in the handler.
```elixir
# Override the handler atom - dispatches as {:command, :begin, msg}
command(:start, name: :begin, description: "Start the bot")
def handle({:command, :begin, _msg}, context) do
answer(context, "Welcome!")
end
```
## Scopes
Telegram's [BotCommandScope](https://core.telegram.org/bots/api#botcommandscope) controls where commands appear in the autocomplete menu. By declaring scopes you can show different command sets to different audiences - for example, showing admin commands only to group administrators.
### Simple scopes
These are plain atoms:
| Scope | Who sees it |
|----------------------------|-----------------------------------------------|
| `:default` | All users when no more specific scope applies |
| `:all_private_chats` | Users in any private (1-on-1) chat |
| `:all_group_chats` | Users in any group or supergroup chat |
| `:all_chat_administrators` | Administrators in any group chat |
```elixir
command(:help,
description: "Get help",
scopes: [:all_private_chats, :all_group_chats]
)
command(:ban,
description: "Ban a user",
scopes: [:all_chat_administrators]
)
```
### Parametric scopes
These are tuples that target specific chats or users:
- `{:chat, chat_ids: [100, 200]}` - visible in the listed chats. Expands to one API registration per chat.
- `{:chat_administrators, chat_ids: [100]}` - visible to administrators of the listed chats.
- `{:chat_member, chat_id: 1, user_ids: [10, 20]}` - visible to specific users in a specific chat.
```elixir
command(:notify,
description: "Send a notification",
scopes: [{:chat, chat_ids: [123_456, 789_012]}]
)
command(:secret,
description: "Secret command",
scopes: [{:chat_member, chat_id: 123_456, user_ids: [111, 222]}]
)
```
### Scope inheritance
The `scopes` option controls not just where a command appears, but how it interacts with the rest of your command list.
**Commands without `scopes`** (or with `scopes: nil`). All the other scopes will inherit this command, meaning they will appear everywhere other commands appear.
**Commands with `scopes: []`** (empty list) fall back to `:default`. It will only appear to users with the default scope.
**If no command defines any scope**, everything falls back to `:default`.
**Commands with explicit scopes** appear **only** in those scopes.
```elixir
# "help" has no scopes
command(:help, description: "Get help")
# "stats" is only in :all_private_chats
command(:stats,
description: "Your stats",
scopes: [:all_private_chats]
)
```
In this example, both `:stats` and `:help` appear in `:all_private_chats`. And on the `:default` scope (group chats for example) only the `:help` command would appear.
## Language translations
The `:lang` option lets you provide per-language overrides for the command name and description. Each key is an IETF language code atom (`:es`, `:pt`, `:it`, etc.) and the value is a keyword list with `:command` and/or `:description`.
### Translating descriptions
```elixir
command(:start,
description: "Start the bot",
scopes: [:default],
lang: [
es: [description: "Iniciar el bot"],
pt: [description: "Iniciar o bot"]
]
)
```
Spanish users see "Iniciar el bot", Portuguese users see "Iniciar o bot", everyone else sees "Start the bot".
### Translating command names
You can also change the command name itself for a language:
```elixir
command(:help,
description: "Get help",
scopes: [:default],
lang: [es: [command: "ayuda", description: "Obtener ayuda"]]
)
```
Spanish users see `/ayuda` in their menu. ExGram automatically registers `/ayuda` as a dispatch alias, so it routes to the same `:help` handler - no extra `handle` clause needed:
```elixir
def handle({:command, :help, _msg}, context) do
# Here the user will have `language_code` if you want to send translated messages
answer(context, "Here is some help!")
end
```
### Inheriting values
A lang entry does not need to override both fields:
- Omitting `:description` inherits the base description.
- Omitting `:command` keeps the base command name.
```elixir
command(:help,
description: "Get help",
lang: [es: [command: "ayuda"]] # description inherited from base
)
```
### Merge behavior
Untranslated commands are automatically merged into every language group. This ensures users of any language always see the full command list - translated commands appear in their translated form, untranslated commands fall back to their base form.
```elixir
command(:start,
description: "Start the bot",
lang: [es: [description: "Iniciar el bot"]]
)
command(:help, description: "Get help")
```
Spanish users see both `start` ("Iniciar el bot") and `help` ("Get help"). The untranslated `:help` is merged in automatically.
If a command renames itself in a translation (e.g. `help` -> `ayuda`), the original name (`help`) is excluded from that language group to avoid showing duplicate entries.
## Scopes and languages combined
Translations apply independently to each scope. If a command appears in multiple scopes, each (scope + language) combination gets its own Telegram API registration.
```elixir
command(:greet,
description: "Greet users",
scopes: [:all_private_chats, :all_group_chats],
lang: [es: [description: "Saludar usuarios"]]
)
```
This produces four registrations:
- `:all_private_chats` (no lang) - "Greet users"
- `:all_group_chats` (no lang) - "Greet users"
- `:all_private_chats` + `"es"` - "Saludar usuarios"
- `:all_group_chats` + `"es"` - "Saludar usuarios"
## Full example
```elixir
defmodule MyBot do
use ExGram.Bot, name: :my_bot, setup_commands: true
middleware(ExGram.Middleware.IgnoreUsername)
# Visible to all users in all contexts
command(:start,
description: "Start the bot",
lang: [
es: [description: "Iniciar el bot"],
pt: [description: "Iniciar o bot"]
]
)
# Only visible in private chats, with a translated name for Spanish
command(:help,
description: "Get help",
scopes: [:all_private_chats],
lang: [es: [command: "ayuda", description: "Obtener ayuda"]]
)
# Only visible to group administrators
command(:ban,
description: "Ban a user",
scopes: [:all_chat_administrators],
lang: [es: [description: "Prohibir usuario"]]
)
# Hidden command - no description, won't appear in the menu
command(:debug)
def handle({:command, :start, _msg}, context) do
answer(context, "Welcome! Use /help for a list of commands.")
end
# Handles both /help and /ayuda (Spanish alias)
def handle({:command, :help, _msg}, context) do
answer(context, "Here is some help!")
end
def handle({:command, :ban, %{text: target}}, context) do
answer(context, "Banned #{target}")
end
def handle({:command, :debug, _msg}, context) do
answer(context, "Debug info: ...")
end
def handle(_, _context), do: :ok
end
```