Finite State Machine#
A finite-state machine (FSM) or finite-state automaton (FSA, plural: automata), finite automaton, or simply a state machine, is a mathematical model of computation.
It is an abstract machine that can be in exactly one of a finite number of states at any given time. The FSM can change from one state to another in response to some inputs; the change from one state to another is called a transition.
An FSM is defined by a list of its states, its initial state, and the inputs that trigger each transition.
Source: WikiPedia
Usage example#
Not all functionality of the bot can be implemented as single handler, for example you will need to collect some data from user in separated steps you will need to use FSM.

Let’s see how to do that step-by-step
Step by step#
Before handle any states you will need to specify what kind of states you want to handle
15 ReplyKeyboardRemove,
16)
17
18form_router = Router()
And then write handler for each state separately from the start of dialog
Here is dialog can be started only via command /start
, so lets handle it and make transition user to state Form.name
21class Form(StatesGroup):
22 name = State()
23 like_bots = State()
24 language = State()
25
26
27@form_router.message(Command(commands=["start"]))
After that you will need to save some data to the storage and make transition to next step.
48 await message.answer(
49 "Cancelled.",
50 reply_markup=ReplyKeyboardRemove(),
51 )
52
53
54@form_router.message(Form.name)
55async def process_name(message: Message, state: FSMContext) -> None:
56 await state.update_data(name=message.text)
57 await state.set_state(Form.like_bots)
58 await message.answer(
59 f"Nice to meet you, {html.quote(message.text)}!\nDid you like to write bots?",
60 reply_markup=ReplyKeyboardMarkup(
61 keyboard=[
62 [
63 KeyboardButton(text="Yes"),
At the next steps user can make different answers, it can be yes, no or any other
Handle yes
and soon we need to handle Form.language
state
77 "Not bad not terrible.\nSee you soon.",
78 reply_markup=ReplyKeyboardRemove(),
79 )
80 await show_summary(message=message, data=data, positive=False)
81
82
83@form_router.message(Form.like_bots, F.text.casefold() == "yes")
84async def process_like_write_bots(message: Message, state: FSMContext) -> None:
Handle no
66 ],
67 resize_keyboard=True,
68 ),
69 )
70
71
72@form_router.message(Form.like_bots, F.text.casefold() == "no")
73async def process_dont_like_write_bots(message: Message, state: FSMContext) -> None:
74 data = await state.get_data()
And handle any other answers
87 await message.reply(
88 "Cool! I'm too!\nWhat programming language did you use for it?",
89 reply_markup=ReplyKeyboardRemove(),
All possible cases of like_bots step was covered, let’s implement finally step
92
93@form_router.message(Form.like_bots)
94async def process_unknown_write_bots(message: Message, state: FSMContext) -> None:
95 await message.reply("I don't understand you :(")
96
97
98@form_router.message(Form.language)
99async def process_language(message: Message, state: FSMContext) -> None:
100 data = await state.update_data(language=message.text)
101 await state.clear()
102 text = (
And now you have covered all steps from the image, but you can make possibility to cancel conversation, lets do that via command or text
30 await message.answer(
31 "Hi there! What's your name?",
32 reply_markup=ReplyKeyboardRemove(),
33 )
34
35
36@form_router.message(Command(commands=["cancel"]))
37@form_router.message(F.text.casefold() == "cancel")
38async def cancel_handler(message: Message, state: FSMContext) -> None:
39 """
40 Allow user to cancel any action
41 """
42 current_state = await state.get_state()
43 if current_state is None:
44 return
45
Complete example#
1import asyncio
2import logging
3import sys
4from os import getenv
5from typing import Any, Dict
6
7from aiogram import Bot, Dispatcher, F, Router, html
8from aiogram.filters import Command
9from aiogram.fsm.context import FSMContext
10from aiogram.fsm.state import State, StatesGroup
11from aiogram.types import (
12 KeyboardButton,
13 Message,
14 ReplyKeyboardMarkup,
15 ReplyKeyboardRemove,
16)
17
18form_router = Router()
19
20
21class Form(StatesGroup):
22 name = State()
23 like_bots = State()
24 language = State()
25
26
27@form_router.message(Command(commands=["start"]))
28async def command_start(message: Message, state: FSMContext) -> None:
29 await state.set_state(Form.name)
30 await message.answer(
31 "Hi there! What's your name?",
32 reply_markup=ReplyKeyboardRemove(),
33 )
34
35
36@form_router.message(Command(commands=["cancel"]))
37@form_router.message(F.text.casefold() == "cancel")
38async def cancel_handler(message: Message, state: FSMContext) -> None:
39 """
40 Allow user to cancel any action
41 """
42 current_state = await state.get_state()
43 if current_state is None:
44 return
45
46 logging.info("Cancelling state %r", current_state)
47 await state.clear()
48 await message.answer(
49 "Cancelled.",
50 reply_markup=ReplyKeyboardRemove(),
51 )
52
53
54@form_router.message(Form.name)
55async def process_name(message: Message, state: FSMContext) -> None:
56 await state.update_data(name=message.text)
57 await state.set_state(Form.like_bots)
58 await message.answer(
59 f"Nice to meet you, {html.quote(message.text)}!\nDid you like to write bots?",
60 reply_markup=ReplyKeyboardMarkup(
61 keyboard=[
62 [
63 KeyboardButton(text="Yes"),
64 KeyboardButton(text="No"),
65 ]
66 ],
67 resize_keyboard=True,
68 ),
69 )
70
71
72@form_router.message(Form.like_bots, F.text.casefold() == "no")
73async def process_dont_like_write_bots(message: Message, state: FSMContext) -> None:
74 data = await state.get_data()
75 await state.clear()
76 await message.answer(
77 "Not bad not terrible.\nSee you soon.",
78 reply_markup=ReplyKeyboardRemove(),
79 )
80 await show_summary(message=message, data=data, positive=False)
81
82
83@form_router.message(Form.like_bots, F.text.casefold() == "yes")
84async def process_like_write_bots(message: Message, state: FSMContext) -> None:
85 await state.set_state(Form.language)
86
87 await message.reply(
88 "Cool! I'm too!\nWhat programming language did you use for it?",
89 reply_markup=ReplyKeyboardRemove(),
90 )
91
92
93@form_router.message(Form.like_bots)
94async def process_unknown_write_bots(message: Message, state: FSMContext) -> None:
95 await message.reply("I don't understand you :(")
96
97
98@form_router.message(Form.language)
99async def process_language(message: Message, state: FSMContext) -> None:
100 data = await state.update_data(language=message.text)
101 await state.clear()
102 text = (
103 "Thank for all! Python is in my hearth!\nSee you soon."
104 if message.text.casefold() == "python"
105 else "Thank for information!\nSee you soon."
106 )
107 await message.answer(text)
108 await show_summary(message=message, data=data)
109
110
111async def show_summary(message: Message, data: Dict[str, Any], positive: bool = True) -> None:
112 name = data["name"]
113 language = data.get("language", "<something unexpected>")
114 text = f"I'll keep in mind that, {html.quote(name)}, "
115 text += (
116 f"you like to write bots with {html.quote(language)}."
117 if positive
118 else "you don't like to write bots, so sad..."
119 )
120 await message.answer(text=text, reply_markup=ReplyKeyboardRemove())
121
122
123async def main():
124 bot = Bot(token=getenv("TELEGRAM_TOKEN"), parse_mode="HTML")
125 dp = Dispatcher()
126 dp.include_router(form_router)
127
128 await dp.start_polling(bot)
129
130
131if __name__ == "__main__":
132 logging.basicConfig(level=logging.INFO, stream=sys.stdout)
133 asyncio.run(main())