This guide explains how to handle different types of updates from Telegram in your ExGram bot.
The handle/2 Function
Every bot must implement the ExGram.Handler.handle/2 function. It receives:
- Update tuple - Different tuple patterns for different update types
- Context - A
ExGram.Cnt.t/0struct with update information
def handle(update_tuple, context) do
# Process the update and return context
context
endThe context contains:
update- The full Update objectname- Your bot's name (important for multiple bots)bot_info- You bot's information, extracted withExGram.get_me/1at startupextra- Custom data from middlewares- Internal fields used by ExGram
Update Patterns
ExGram parses updates into convenient tuples for pattern matching.
Commands
Matches messages starting with /command or /command@your_bot.
def handle({:command, "start", msg}, context) do
answer(context, "Welcome! You sent: #{msg}")
end
def handle({:command, "help", _msg}, context) do
answer(context, """
Available commands:
/start - Start the bot
/help - Show this help
/settings - Configure settings
""")
endThe msg parameter contains any text after the command:
/start→msg = ""/start hello world→msg = "hello world"
You can also declare commands at the module level:
command("start")
command("help", description: "Show help message")
command("settings", description: "Configure your settings")With setup_commands: true, these are automatically registered with Telegram.
And, once you declare them, you will receive the commands as atoms:
def handle({:command, :start, msg}, context) do
# ...
end
def handle({:command, :help, _msg}, context)
def handle({:command, :settings, _msg}, context)
# You can still handle not defined commands
def handle({:command, "othercommand", _msg}, context)This is really important if you want to provide command translations or commands for different roles.
Check the commands guide if you want to know more.
Plain Text
Matches regular text messages (respects privacy mode).
def handle({:text, text, message}, context) do
cond do
String.contains?(text, "hello") ->
answer(context, "Hello to you too!")
String.length(text) > 100 ->
answer(context, "That's a long message!")
true ->
answer(context, "You said: #{text}")
end
endRegex Patterns
Define regex patterns at module level and match against them:
defmodule MyBot.Bot do
use ExGram.Bot, name: :my_bot
# Define regex patterns
regex(:email, ~r/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/)
regex(:phone, ~r/\b\d{3}[-.]?\d{3}[-.]?\d{4}\b/)
def handle({:regex, :email, message}, context) do
answer(context, "I detected an email address in your message!")
end
def handle({:regex, :phone, message}, context) do
answer(context, "That looks like a phone number!")
end
endCallback Queries
Handles button presses from inline keyboards.
def handle({:callback_query, %{data: "button_" <> id} = callback}, context) do
context
|> answer_callback("Processing button #{id}")
|> answer("You clicked button #{id}")
end
def handle({:callback_query, %{data: "delete"}}, context) do
context
|> answer_callback("Deleting message...")
|> delete()
endSee Sending Messages for creating inline keyboards.
Inline Queries
Handles inline queries (e.g., @yourbot search term).
def handle({:inline_query, query}, context) do
results = [
%{
type: "article",
id: "1",
title: "Result 1",
input_message_content: %{message_text: "You selected result 1"}
},
%{
type: "article",
id: "2",
title: "Result 2",
input_message_content: %{message_text: "You selected result 2"}
}
]
answer_inline_query(context, results)
endLocation Messages
Handles location sharing.
def handle({:location, %ExGram.Model.Location{latitude: lat, longitude: lon}}, context) do
answer(context, "You're at #{lat}, #{lon}. Thanks for sharing!")
endEdited Messages
Handles message edits.
def handle({:edited_message, edited_msg}, context) do
# You can choose to process edited messages differently
# or ignore them entirely
Logger.info("Message #{edited_msg.message_id} was edited")
context
endGeneric Message Handler
Catches any message that doesn't match other patterns.
def handle({:message, message}, context) do
cond do
message.photo ->
answer(context, "Nice photo!")
message.document ->
answer(context, "Thanks for the document!")
message.sticker ->
answer(context, "Cool sticker!")
message.voice ->
answer(context, "I received your voice message!")
true ->
answer(context, "I received your message, but I'm not sure what to do with it.")
end
endDefault Handler
Catches all other updates.
ExGram will slowly add more specific handlers to make it easier to differentiate all the possible update types.
def handle({:update, update}, context) do
Logger.debug("Received unhandled update: #{inspect(update)}")
context
endThe Context (ExGram.Cnt.t/0)
The context struct contains:
%ExGram.Cnt{
update: %ExGram.Model.Update{}, # Full Telegram update, useful to get more information about the update in specific handlers
name: :my_bot, # Your bot's name (the one from "use ExGram.Bot, name: :my_bot")
bot_info: %ExGram.Model.User{} | nil, # The bot's information, extracted with ExGram.get_me at bot's startup
extra: %{} # Custom data from middlewares
# More fields used internally
}Adding Extra Data
Middlewares can add custom data to context.extra:
# In a middleware
use ExGram.Middleware
def call(context, _opts) do
user_id = extract_id(context)
extra_data = %{user_role: fetch_user_role(user_id)}
add_extra(context, extra_data)
end
# In your handler
def handle({:command, "admin", _msg}, context) do
case context.extra[:user_role] do
:admin -> answer(context, "Admin panel: ...")
_ -> answer(context, "Access denied")
end
endRead more about middlewares in this guide
The ExGram.Handler.init/1 Callback
The optional ExGram.Handler.init/1 callback runs once before processing updates. Use it to initialize your bot:
def init(opts) do
# opts contains [:bot, :token]
ExGram.set_my_description!(
description: "This bot helps you manage tasks",
bot: opts[:bot]
)
ExGram.set_my_name!(
name: "TaskBot",
token: opts[:token]
)
# Do some logic you need before starting your bots
# MyBot.notify_admins_restart(opts[:bot])
:ok
endNote: If you use setup_commands: true, commands are automatically registered. Use init/1 for additional setup.
Pattern Matching Tips
Multiple Clauses
Use multiple function clauses for clean code:
def handle({:command, :start, _}, context), do: answer(context, "Welcome!")
def handle({:command, :help, _}, context), do: show_help(context)
def handle({:command, :about, _}, context), do: show_about(context)
def handle({:callback_query, %{data: "yes"}}, context) do
answer_callback(context, "You chose yes!")
end
def handle({:callback_query, %{data: "no"}}, context) do
answer_callback(context, "You chose no!")
end
def handle({:text, text, _msg}, context) when is_binary(text) do
answer(context, "Echo: #{text}")
end
def handle(_update, context), do: contextGuards
Use guards for additional filtering:
def handle({:text, text, _msg}, context) when byte_size(text) > 500 do
answer(context, "Please send shorter messages (max 500 characters)")
end
def handle({:text, text, _msg}, context) when text in ["hi", "hello", "hey"] do
answer(context, "Hello there!")
endExtracting Data
Pattern match to extract specific fields:
def handle({:message, %{from: %{id: user_id, username: username}}}, context) do
answer(context, "Hello @#{username} (ID: #{user_id})")
end
def handle({:callback_query, %{from: user, data: data}}, context) do
Logger.info("User #{user.id} clicked: #{data}")
answer_callback(context, "Got it!")
endNext Steps
- Sending Messages - Learn the DSL for building responses
- Message Entities - Format messages without Markdown or HTML
- Middlewares - Add preprocessing logic to your bot
- Low-Level API - Direct API calls for complex scenarios
- Cheatsheet - Quick reference for all patterns