Per-user Finite State Machine backed by Pockets (ETS).
This module provides per-user state management for conversational bots. Each user (identified by chat ID) can have their own state and associated data, allowing you to build multi-step workflows, wizards, and stateful interactions.
The FSM stores tuples of {state, data} keyed by chat ID in an ETS table
managed by the Pockets library.
State Management
- State: An atom representing the current conversation state (e.g.,
:waiting_name,:confirming) - Data: Any Elixir term to store context (e.g.,
%{step: 1, answers: []})
Usage
# Get current state and data
{state, data} = FSM.get_state(:my_bot, chat_id)
# Set state only (keeps existing data)
FSM.set_state(:my_bot, chat_id, :waiting_name)
# Set state and data
FSM.set_state(:my_bot, chat_id, :waiting_name, %{step: 1})
# Reset state (removes entry)
FSM.reset_state(:my_bot, chat_id)Using with defstate
The defstate/2 macro allows you to define handlers that only match
when the user is in a specific state:
defstate :waiting_name do
def handle_message(%{text: text, chat: chat}, ctx) do
# This only runs when user is in :waiting_name state
{:transition, :waiting_age, Map.put(ctx.data, :name, text)}
end
end
Summary
Functions
Defines handlers that only execute when the user is in a specific FSM state.
Retrieves the current FSM state and data for a user.
Initializes the FSM storage for a bot.
Resets the FSM state for a user, removing their stored state and data.
Sets the FSM state for a user, keeping existing data.
Sets the FSM state and data for a user.
Types
@type chat_id() :: TelegramEx.Types.chat_id()
Functions
Defines handlers that only execute when the user is in a specific FSM state.
This macro automatically injects state pattern matching into handler functions, so they only run when the user's FSM state matches the specified state.
Parameters
state- The state atom to matchdo- Block containing handler function definitions
Examples
defstate :waiting_name do
def handle_message(%{text: text, chat: chat}, ctx) do
# Only runs when user is in :waiting_name state
ctx
|> Message.text("Got your name: #{text}")
|> Message.send(chat["id"])
{:transition, :waiting_age, Map.put(ctx.data, :name, text)}
end
end
defstate :waiting_age do
def handle_message(%{text: age, chat: chat}, ctx) do
# Only runs when user is in :waiting_age state
name = ctx.data.name
ctx
|> Message.text("Hello #{name}, you are #{age} years old")
|> Message.send(chat["id"])
FSM.reset_state(:my_bot, chat["id"])
end
endNote
The ctx parameter in handlers defined within defstate will have
ctx.state automatically matched to the specified state atom.
Retrieves the current FSM state and data for a user.
Parameters
name- The bot name (atom)id- The chat ID
Returns
A tuple {state, data} where:
state- Current state atom (ornilif no state set)data- Associated data (ornilif no data set)
Examples
{state, data} = FSM.get_state(:my_bot, 123456)
# => {:waiting_name, %{step: 1}}
{state, data} = FSM.get_state(:my_bot, 999999)
# => {nil, nil} # User has no state
Initializes the FSM storage for a bot.
This is called automatically by TelegramEx.Server when the bot starts.
You typically don't need to call this manually.
Parameters
name- The bot name (atom)
Returns
:ok- FSM initialized successfully{:error, reason}- Initialization failed
Resets the FSM state for a user, removing their stored state and data.
Parameters
name- The bot name (atom)id- The chat ID
Returns
:ok- State reset successfully{:error, reason}- Failed to reset state
Examples
FSM.reset_state(:my_bot, 123456)
# User's state is now cleared
Sets the FSM state for a user, keeping existing data.
Parameters
name- The bot name (atom)id- The chat IDstate- New state atom
Returns
:ok- State set successfully{:error, reason}- Failed to set state
Examples
FSM.set_state(:my_bot, 123456, :waiting_age)
# State changed to :waiting_age, data preserved
Sets the FSM state and data for a user.
Parameters
name- The bot name (atom)id- The chat IDstate- New state atomdata- New data to store
Returns
:ok- State and data set successfully{:error, reason}- Failed to set state
Examples
FSM.set_state(:my_bot, 123456, :waiting_age, %{name: "John", step: 2})
# State and data both updated