Skip to content
All posts
Development5 min read

Your first Telegram bot with aiogram 3

From a BotFather token to a working bot with a command, a reply keyboard and an echo handler, written with the current aiogram 3 API.

On this page

A Telegram bot is a small program that talks to people through Telegram. In this post we will build one in Python with aiogram 3, a popular asynchronous framework for bots. Our bot greets the user, shows a keyboard and echoes messages back. It takes about twenty minutes, and the same structure grows into real projects.

What you need

  • Python 3.12 or newer. Check with python3 --version (on Windows, python --version). If you install it from python.org on Windows, tick “Add python.exe to PATH” in the installer.
  • A Telegram account.
  • Any code editor.

Get a token from BotFather

Every bot is created through @BotFather, Telegram’s official bot for managing bots. Open it, send /newbot, choose a display name, then a username that ends in bot, such as my_first_aiogram_bot. BotFather replies with a token that looks like 123456789:AAH.... While you are there, /setdescription and /setuserpic give the bot a description and a picture: they are the first things people see when they open it.

The token is the bot’s password. Anyone who has it controls your bot, so treat it like one.

Set up the project

Create a folder with a virtual environment, so the bot’s packages stay separate from the rest of your system, and install aiogram:

Bash
mkdir my-bot && cd my-bot
python3 -m venv .venv
source .venv/bin/activate   # Windows: .venv\Scripts\activate
pip install -U aiogram

Later, pip freeze > requirements.txt saves the exact versions, so the bot can be installed the same way on a server.

Write the bot

Create a file called bot.py:

Python
import asyncio
import logging
import os

from aiogram import Bot, Dispatcher, F, Router, html
from aiogram.client.default import DefaultBotProperties
from aiogram.enums import ParseMode
from aiogram.filters import CommandStart
from aiogram.types import KeyboardButton, Message, ReplyKeyboardMarkup

router = Router()

menu = ReplyKeyboardMarkup(
    keyboard=[[KeyboardButton(text="About"), KeyboardButton(text="Help")]],
    resize_keyboard=True,
)


@router.message(CommandStart())
async def start(message: Message) -> None:
    name = html.quote(message.from_user.full_name)
    await message.answer(
        f"Hi, <b>{name}</b>! Send me any text.",
        reply_markup=menu,
    )


@router.message(F.text == "About")
async def about(message: Message) -> None:
    await message.answer("I'm a demo bot built with aiogram 3.")


@router.message(F.text == "Help")
async def help_(message: Message) -> None:
    await message.answer("Type anything and I'll send it back.")


@router.message(F.text)
async def echo(message: Message) -> None:
    await message.answer(html.quote(message.text))


async def main() -> None:
    bot = Bot(
        token=os.environ["BOT_TOKEN"],
        default=DefaultBotProperties(parse_mode=ParseMode.HTML),
    )
    dp = Dispatcher()
    dp.include_router(router)
    await dp.start_polling(bot)


if __name__ == "__main__":
    logging.basicConfig(level=logging.INFO)
    asyncio.run(main())

How it works

  • Router groups handlers. A tiny bot can attach handlers straight to the Dispatcher, but routers keep a growing bot tidy: one router per feature, each included into the dispatcher.
  • Handlers are async functions with a filter in the decorator. CommandStart() matches /start, F.text == "About" matches that exact text, and F.text matches any text message. aiogram tries handlers in the order they were registered and stops at the first match, which is why the catch-all echo comes last.
  • DefaultBotProperties sets HTML formatting for every message, so <b> turns into bold. It is also why user text goes through html.quote: a message containing < would otherwise break the formatting, and Telegram would reject the reply.
  • start_polling keeps asking Telegram for new updates. It is the simplest way to run a bot and needs no public address, domain or certificate.
  • message.answer replies in the same chat the message came from, so you never pass a chat ID by hand.
  • async is what lets one small process serve many people: while one handler waits for Telegram to answer, the others keep working.

Run and stop it

Put the token in an environment variable and start the bot:

Bash
export BOT_TOKEN="123456789:AAH..."   # PowerShell: $env:BOT_TOKEN = "..."
python bot.py

Open your bot in Telegram and press Start. The terminal logs every update it receives. Press Ctrl+C to stop it, and aiogram closes the connection cleanly. Close the terminal and the bot stops too; to keep it running, it needs a server, more on that below. If you forget the variable, os.environ["BOT_TOKEN"] fails right away with a KeyError, which beats a confusing error later.

Two things surprise people. A bot can’t write to someone first: a person has to open it and press Start before it can send them anything. And only one copy of the bot can poll at a time; start a second one, on your laptop and a server for example, and Telegram answers with a conflict error.

Keep the token secret

Never write the token in your code and never commit it to Git. Public repositories are scanned for secrets all the time, and whoever finds the token controls the bot.

If typing export every time gets tiresome, keep the token in a .env file, add .env to .gitignore, and load it with a library such as python-dotenv. Add .venv to .gitignore too; it doesn’t belong in the repository. If a token ever leaks, send /revoke to BotFather to get a new one. The old token stops working at once.

Where to go next

  • FSM (finite state machine) for multi-step conversations, like a form that asks for a name and then a phone number.
  • Inline keyboards and callback queries for buttons attached to a message.
  • Webhooks instead of polling, once the bot lives on a server with HTTPS.
  • A database, for example PostgreSQL with SQLAlchemy, to remember users and their settings.
  • systemd or Docker to keep the bot running and restart it after a crash or a reboot.

The aiogram documentation (opens in a new tab) covers each of these with examples. Build something small that you or your friends will actually use; that is the fastest way to learn.

Keep reading

Let’s build something great.

Write to me about a new project, a collaboration, or just to talk about technology.

  • Tashkent, UzbekistanOpen to remote work

  • Now in Tashkent (GMT+5)