Skip to content
šŸ› ļø Tutorial

How to Build a Telegram Bot: Complete Tutorial with Code [2026]

In 15 minutes, you will have a working Telegram bot. No prior experience needed. I have taught this to 200+ developers.

How to Build a Telegram Bot: Complete Tutorial with Code [2026]
ā± 16 min readšŸ“ ~5500 wordsšŸ“… August 29, 2026šŸ‘¤ Dmitry Malyshev

What You Need Before Starting (2 Minutes of Setup)

In the next 15 minutes, you will have a working Telegram bot that responds to messages, shows inline keyboards, and stores data in a database. No prior experience needed. I have taught this to 200+ developers.

Here is exactly what your bot will do when we are done:

← Swipe →
FeatureWhat It Looks Like
Respond to /start"Welcome! Choose an option:" with buttons
Show inline keyboardsTappable buttons that trigger different responses
Handle callbacksBot reacts differently to each button tap
Store user dataRemembers user's name and choices in a database
Run 24/7Deployed to a server, always online

Prerequisites Checklist

Before we write any code, make sure you have these installed:

← Swipe →
RequirementHow to CheckHow to Install
Python 3.11+python --versionpython.org
pippip --versionComes with Python
Telegram accountYou have one if you are reading this—
Code editorVS Code, PyCharm, or any editorcode.visualstudio.com

That is it. No Docker, no cloud accounts, no API keys (we will get the bot token in Step 1).

Project Structure

Here is the file structure we will build:

CODE
 1my-telegram-bot/
 2ā”œā”€ā”€ bot.py              # Main bot file (Step 3)
 3ā”œā”€ā”€ handlers.py         # Message handlers (Step 3-4)
 4ā”œā”€ā”€ database.py         # Database operations (Step 5)
 5ā”œā”€ā”€ config.py           # Configuration (Step 2)
 6ā”œā”€ā”€ requirements.txt    # Dependencies (Step 2)
 7└── .env                # Secret token (Step 2)

Your first action: open a terminal and run python --version. If you see 3.11 or higher, you are ready to go. If not, download Python from python.org.

šŸ’¬ Prefer to skip the tutorial and get a professional bot built? I build production-ready Telegram bots for businesses — from simple FAQ bots to complex e-commerce systems. Get a working bot in 5-7 days — from $500 →

Step 1: Create Your Bot with BotFather (3 Minutes)

Every Telegram bot starts with BotFather — Telegram's official tool for creating and managing bots. Here is the exact process.

Step-by-Step: Creating a Bot

1. Open Telegram and search for @BotFather

BotFather is a verified bot (look for the blue checkmark). It is Telegram's official bot management tool.

2. Start a conversation and send /newbot

BotFather will ask you two questions:

← Swipe →
QuestionWhat to EnterExample
"Choose a name"Display name (can be anything, can include spaces)"My Awesome Bot"
"Choose a username"Unique username (must end in "bot")"my_awesome_2026_bot"

3. Copy your API token

After you choose the username, BotFather will send you a message like:

CODE
 1Done! Congratulations on your new bot. You will find it at t.me/my_awesome_2026_bot.
 2
 3Use this token to access the HTTP API:
 47123456789:AAH1bGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9

That long string after "token:" is your bot token. Copy it and save it somewhere safe. Never share it publicly — anyone with your token can control your bot.

Optional: Configure Your Bot

While you are in BotFather, set up these basics:

← Swipe →
CommandWhat It DoesExample
/setdescriptionBot profile description"A helpful bot that answers your questions"
/setabouttext"About" text in bot profile"Built with Python and aiogram"
/setuserpicBot profile photoUpload a 512x512 PNG
/setcommandsShow command hints when user types /See below

Setting up command hints:

Send /setcommands to BotFather, select your bot, then paste:

CODE
 1start - Start the bot
 2help - Show help message
 3menu - Show main menu

Now when users type "/" in your chat, Telegram will show these commands as suggestions.

Security Reminder

← Swipe →
DoDo NOT
Store token in .env fileHardcode token in source code
Use environment variablesCommit token to Git
Regenerate if compromisedShare token in screenshots

Your next step: create your bot with BotFather and save the token. This takes 3 minutes and is the foundation for everything else.

šŸ’¬ Need help with BotFather setup? I can walk you through it on a quick call. Or if you want a professional bot without the DIY, get a ready-made bot from $500, delivered in 5-7 days →

Step 2: Set Up Your Python Environment (2 Minutes)

Now let us set up the Python project. This takes 2 minutes.

Create the Project

Open your terminal and run these commands:

Bash
 1# Create project folder
 2mkdir my-telegram-bot
 3cd my-telegram-bot
 4
 5# Create virtual environment (isolates dependencies)
 6python -m venv venv
 7
 8# Activate virtual environment
 9# Windows:
10venv\Scripts\activate
11# macOS/Linux:
12source venv/bin/activate
13
14# Install dependencies
15pip install aiogram python-dotenv aiosqlite

What Each Package Does

← Swipe →
PackageVersionWhat It Does
aiogram3.xTelegram Bot API framework (async, modern)
python-dotenv1.xLoads .env file for secret tokens
aiosqlite0.xAsync SQLite database driver

Create the Config File

Create a file called config.py:

Python
 1# config.py — bot configuration
 2# Loads token from .env file
 3
 4import os
 5from dotenv import load_dotenv
 6
 7# Load environment variables from .env
 8load_dotenv()
 9
10# Bot token from BotFather
11BOT_TOKEN = os.getenv("BOT_TOKEN")
12
13# Check that token is set
14if not BOT_TOKEN:
15    raise ValueError("BOT_TOKEN not found! Create a .env file with your token.")

Create the .env File

Create a file called .env (note the dot at the beginning):

CODE
 1BOT_TOKEN=7123456789:AAH1bGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9

Replace the token with the one you got from BotFather in Step 1.

Create requirements.txt

Create a file called requirements.txt:

CODE
 1aiogram>=3.7.0
 2python-dotenv>=1.0.0
 3aiosqlite>=0.20.0

Verify Everything Works

Bash
 1python -c "import aiogram; print(f'aiogram version: {aiogram.__version__}')"

If you see the version number, you are ready for Step 3.

Project Structure So Far

CODE
 1my-telegram-bot/
 2ā”œā”€ā”€ config.py           # āœ… Created
 3ā”œā”€ā”€ .env                # āœ… Created (with your token)
 4ā”œā”€ā”€ requirements.txt    # āœ… Created
 5└── venv/               # āœ… Created (virtual environment)

Your next step: create the 3 files above and verify that import aiogram works. This takes 2 minutes.

šŸ’¬ Stuck on Python setup? I help developers get started with Telegram bot development. Get a 30-minute setup session — or let me build the whole thing from $500 →

Step 3: Your First Message Handler (3 Minutes)

This is where the magic happens. We will write a bot that responds to the /start command with a welcome message. This is the "Hello World" of Telegram bots.

Create bot.py

Create a file called bot.py with this code:

Python
 1# bot.py — main bot file
 2# Here we create the bot and register handlers
 3
 4import asyncio
 5import logging
 6from aiogram import Bot, Dispatcher, Router, F
 7from aiogram.types import Message
 8from aiogram.filters import CommandStart, Command
 9from config import BOT_TOKEN
10
11# ─── Configure logging ───
12# Shows in console what the bot is doing
13logging.basicConfig(level=logging.INFO)
14
15# ─── Create bot and dispatcher ───
16# Bot — object for sending messages
17# Dispatcher — router for incoming messages
18bot = Bot(token=BOT_TOKEN)
19dp = Dispatcher()
20
21# ─── Create router ───
22# Router groups handlers by logic
23router = Router()
24
25# ─── /start command handler ───
26# Triggered when user sends /start
27@router.message(CommandStart())
28async def cmd_start(message: Message):
29    """
30    Welcome message.
31    Shown on first contact with the bot.
32    """
33    await message.answer(
34        f"šŸ‘‹ Hello, <b>{message.from_user.first_name}</b>!\n\n"
35        f"I am your first Telegram bot. "
36        f"I was built in 15 minutes using this tutorial.\n\n"
37        f"Try these commands:\n"
38        f"/help — Show help\n"
39        f"/menu — Show main menu",
40        parse_mode="HTML",
41    )
42
43# ─── /help command handler ───
44@router.message(Command("help"))
45async def cmd_help(message: Message):
46    """Shows help for commands."""
47    await message.answer(
48        "šŸ“– <b>Available Commands:</b>\n\n"
49        "/start — Restart the bot\n"
50        "/help — Show this help message\n"
51        "/menu — Show main menu\n\n"
52        "Or just type a message and I will respond!",
53        parse_mode="HTML",
54    )
55
56# ─── Handler for any text messages ───
57# Triggered on ANY text message
58@router.message(F.text)
59async def echo_message(message: Message):
60    """
61    Echo reply to text messages.
62    Shows that the bot received and processed the message.
63    """
64    await message.answer(
65        f"šŸ”„ You said: <i>{message.text}</i>\n\n"
66        f"I received your message! In a real bot, "
67        f"this is where you would add your business logic.",
68        parse_mode="HTML",
69    )
70
71# ─── Main function ───
72async def main():
73    """
74    Entry point. Register routers and start polling.
75    Polling = bot polls Telegram for new messages.
76    """
77    # Connect router to dispatcher
78    dp.include_router(router)
79
80    # Remove webhooks (in case bot was previously running)
81    await bot.delete_webhook(drop_pending_updates=True)
82
83    # Start polling — bot begins receiving messages
84    print("šŸ¤– Bot is running! Press Ctrl+C to stop.")
85    await dp.start_polling(bot)
86
87# ─── Launch ───
88if __name__ == "__main__":
89    asyncio.run(main())

Run Your Bot

Bash
 1python bot.py

You should see: šŸ¤– Bot is running! Press Ctrl+C to stop.

Now open Telegram, find your bot (search for the username you created), and send /start. You should see the welcome message!

What Just Happened

← Swipe →
StepWhat the Code Does
1Bot(token=BOT_TOKEN) creates a connection to Telegram's API
2Dispatcher() creates a message router
3@router.message(CommandStart()) registers a handler for /start
4dp.start_polling(bot) starts checking Telegram for new messages
5When you send /start, Telegram sends it to your bot
6Your handler receives it and sends a reply

Understanding the Code

← Swipe →
ConceptWhat It MeansAnalogy
BotConnection to Telegram APIA phone line
DispatcherRoutes messages to handlersA receptionist
RouterGroups related handlersA department
HandlerFunction that processes a messageAn employee
PollingBot checks for new messages repeatedlyChecking your mailbox

Try These Interactions

← Swipe →
You SendBot Responds
/startWelcome message with your name
/helpList of available commands
"Hello"Echo: "You said: Hello"
"What is 2+2?"Echo: "You said: What is 2+2?"

The echo handler catches everything — we will replace it with real logic in the next steps.

Your first action: run python bot.py, open Telegram, and send /start to your bot. If you see the welcome message, you have successfully built your first Telegram bot!

šŸ’¬ Want to skip the tutorial and get a professional bot? I build production-ready Telegram bots for businesses — with database, payments, and deployment included. Get a working bot in 5-7 days — from $500 →

Step 4: Add Inline Keyboards (Interactive Buttons)

Inline keyboards are the heart of Telegram bot UX. They turn a text-based conversation into an interactive experience. Let me show you how to build them.

What Are Inline Keyboards?

← Swipe →
Keyboard TypeWhere It AppearsUse Case
Inline keyboardBelow a specific messageMenus, options, actions
Reply keyboardAt the bottom of chat (like a custom keyboard)Persistent navigation

We will focus on inline keyboards — they are more flexible and more commonly used.

Add Inline Keyboards to Your Bot

Update your bot.py (or create a new handlers.py file):

Python
 1# handlers.py — handlers with inline keyboards
 2# Shows how to create interactive buttons
 3
 4from aiogram import Router, F
 5from aiogram.types import (
 6    Message,
 7    CallbackQuery,
 8    InlineKeyboardButton,
 9    InlineKeyboardMarkup,
10)
11from aiogram.utils.keyboard import InlineKeyboardBuilder
12from aiogram.filters import Command
13
14router = Router()
15
16# ─── /menu command — main menu with buttons ───
17@router.message(Command("menu"))
18async def show_menu(message: Message):
19    """
20    Shows main menu with inline buttons.
21    Each button has callback_data — a string,
22    the bot receives on click.
23    """
24    # Create keyboard via Builder (convenient method)
25    builder = InlineKeyboardBuilder()
26
27    # Add buttons (one per row)
28    builder.row(
29        InlineKeyboardButton(
30            text="šŸ• Food Menu",
31            callback_data="menu:food"
32        ),
33    )
34    builder.row(
35        InlineKeyboardButton(
36            text="šŸ“ Our Location",
37            callback_data="menu:location"
38        ),
39    )
40    builder.row(
41        InlineKeyboardButton(
42            text="šŸ“ž Contact Us",
43            callback_data="menu:contact"
44        ),
45    )
46    builder.row(
47        InlineKeyboardButton(
48            text="ā“ FAQ",
49            callback_data="menu:faq"
50        ),
51    )
52
53    # Send message with keyboard
54    await message.answer(
55        "šŸ“‹ <b>Main Menu</b>\n\n"
56        "Choose an option below:",
57        reply_markup=builder.as_markup(),
58        parse_mode="HTML",
59    )
60
61# ─── Button press handler ───
62# Triggered when user presses an inline button
63@router.callback_query(F.data.startswith("menu:"))
64async def handle_menu_callback(callback: CallbackQuery):
65    """
66    Handles main menu button presses.
67    F.data.startswith("menu:") filters only menu buttons.
68    """
69    # Extract action from callback_data
70    action = callback.data.split(":")[1]
71
72    # Handle each action
73    if action == "food":
74        await callback.message.edit_text(
75            "šŸ• <b>Food Menu</b>\n\n"
76            "• Margherita Pizza — $12\n"
77            "• Pepperoni Pizza — $14\n"
78            "• Caesar Salad — $9\n"
79            "• Pasta Carbonara — $13\n\n"
80            "Tap an item to order:",
81            reply_markup=InlineKeyboardMarkup(inline_keyboard=[
82                [InlineKeyboardButton(
83                    text="šŸ• Margherita — $12",
84                    callback_data="order:margherita"
85                )],
86                [InlineKeyboardButton(
87                    text="šŸ• Pepperoni — $14",
88                    callback_data="order:pepperoni"
89                )],
90                [InlineKeyboardButton(
91                    text="šŸ„— Caesar Salad — $9",
92                    callback_data="order:caesar"
93                )],
94                [InlineKeyboardButton(
95                    text="šŸ”™ Back to Menu",
96                    callback_data="back:main"
97                )],
98            ]),
99            parse_mode="HTML",
100        )
101
102    elif action == "location":
103        await callback.message.edit_text(
104            "šŸ“ <b>Our Location</b>\n\n"
105            "123 Main Street, Suite 100\n"
106            "New York, NY 10001\n\n"
107            "šŸ• Hours: Mon-Fri 9am-6pm",
108            reply_markup=InlineKeyboardMarkup(inline_keyboard=[
109                [InlineKeyboardButton(
110                    text="šŸ”™ Back to Menu",
111                    callback_data="back:main"
112                )],
113            ]),
114            parse_mode="HTML",
115        )
116
117    elif action == "contact":
118        await callback.message.edit_text(
119            "šŸ“ž <b>Contact Us</b>\n\n"
120            "Email: hello@example.com\n"
121            "Phone: (555) 123-4567\n"
122            "Telegram: @your_support_bot",
123            reply_markup=InlineKeyboardMarkup(inline_keyboard=[
124                [InlineKeyboardButton(
125                    text="šŸ”™ Back to Menu",
126                    callback_data="back:main"
127                )],
128            ]),
129            parse_mode="HTML",
130        )
131
132    elif action == "faq":
133        await callback.message.edit_text(
134            "ā“ <b>Frequently Asked Questions</b>\n\n"
135            "<b>Q: What are your hours?</b>\n"
136            "A: Mon-Fri, 9am-6pm EST.\n\n"
137            "<b>Q: Do you deliver?</b>\n"
138            "A: Yes, within Manhattan. Free over $30.\n\n"
139            "<b>Q: How do I pay?</b>\n"
140            "A: We accept all major credit cards.",
141            reply_markup=InlineKeyboardMarkup(inline_keyboard=[
142                [InlineKeyboardButton(
143                    text="šŸ”™ Back to Menu",
144                    callback_data="back:main"
145                )],
146            ]),
147            parse_mode="HTML",
148        )
149
150    # Answer callback (remove loading indicator)
151    await callback.answer()
152
153# ─── "Back" button handler ───
154@router.callback_query(F.data == "back:main")
155async def handle_back(callback: CallbackQuery):
156    """Returns user to main menu."""
157    builder = InlineKeyboardBuilder()
158    builder.row(
159        InlineKeyboardButton(text="šŸ• Food Menu", callback_data="menu:food"),
160    )
161    builder.row(
162        InlineKeyboardButton(text="šŸ“ Our Location", callback_data="menu:location"),
163    )
164    builder.row(
165        InlineKeyboardButton(text="šŸ“ž Contact Us", callback_data="menu:contact"),
166    )
167    builder.row(
168        InlineKeyboardButton(text="ā“ FAQ", callback_data="menu:faq"),
169    )
170
171    await callback.message.edit_text(
172        "šŸ“‹ <b>Main Menu</b>\n\nChoose an option below:",
173        reply_markup=builder.as_markup(),
174        parse_mode="HTML",
175    )
176    await callback.answer()
177
178# ─── Order handler ───
179@router.callback_query(F.data.startswith("order:"))
180async def handle_order(callback: CallbackQuery):
181    """Handles product selection."""
182    item = callback.data.split(":")[1]
183
184    item_names = {
185        "margherita": "Margherita Pizza ($12)",
186        "pepperoni": "Pepperoni Pizza ($14)",
187        "caesar": "Caesar Salad ($9)",
188    }
189
190    item_name = item_names.get(item, item)
191
192    await callback.message.edit_text(
193        f"āœ… <b>Order Confirmed!</b>\n\n"
194        f"You selected: {item_name}\n\n"
195        f"In a real bot, this would:\n"
196        f"• Save to database\n"
197        f"• Process payment\n"
198        f"• Send confirmation",
199        reply_markup=InlineKeyboardMarkup(inline_keyboard=[
200            [InlineKeyboardButton(
201                text="šŸ”™ Back to Menu",
202                callback_data="back:main"
203            )],
204        ]),
205        parse_mode="HTML",
206    )
207    await callback.answer("Order placed! šŸŽ‰")

Register the Router

In your bot.py, add the new router:

Python
 1# In bot.py, add import and registration:
 2from handlers import router as handlers_router
 3
 4# In the main() function:
 5dp.include_router(handlers_router)

Run and Test

Bash
 1python bot.py

Send /menu to your bot. You should see an interactive menu with buttons. Tap each button to see different responses.

How Inline Keyboards Work

← Swipe →
ComponentPurposeExample
InlineKeyboardButtonA single tappable button"šŸ• Food Menu"
callback_dataHidden data sent when button is tapped"menu:food"
InlineKeyboardMarkupContainer for buttonsGroups buttons in rows
@router.callback_queryHandler for button tapsProcesses the callback_data
callback.answer()Acknowledges the tapRemoves loading indicator

Key Patterns

← Swipe →
PatternCodeWhen to Use
Filter by prefixF.data.startswith("menu:")Menu navigation
Filter by exact matchF.data == "back:main"Specific actions
Edit messagecallback.message.edit_text()Update content in place
Answer callbackawait callback.answer()Always do this (removes loading)

Your next action: run the bot, send /menu, and tap every button. Notice how the message updates in place — no new messages cluttering the chat.

šŸ’¬ Want a bot with professional inline keyboards and custom flows? I build interactive Telegram bots for restaurants, stores, clinics, and service businesses. Get a working bot in 5-7 days — from $500 →

Step 5: Add a Database (Remember Users and Data)

Right now, your bot forgets everything when it restarts. Let us fix that by adding a database. We will use SQLite — it is simple, requires no server, and works perfectly for bots with under 10,000 users.

Why a Database Matters

← Swipe →
Without DatabaseWith Database
Bot forgets users after restartBot remembers everyone
Cannot track user preferencesPersonalized experience
No order historyFull conversation history
No analyticsUsage statistics

Create database.py

Create a file called database.py:

Python
 1# database.py — SQLite database operations
 2# Stores users and their data
 3
 4import aiosqlite
 5from datetime import datetime
 6
 7# Path to database file
 8DB_PATH = "bot_database.db"
 9
10async def init_db():
11    """
12    Initializes the database. Creates tables if they don't exist.
13    Called once at bot startup.
14    """
15    async with aiosqlite.connect(DB_PATH) as db:
16        # Users table
17        await db.execute("""
18            CREATE TABLE IF NOT EXISTS users (
19                user_id INTEGER PRIMARY KEY,
20                username TEXT,
21                first_name TEXT,
22                last_name TEXT,
23                registered_at TEXT,
24                last_active TEXT,
25                menu_clicks INTEGER DEFAULT 0
26            )
27        """)
28
29        # Orders table (example)
30        await db.execute("""
31            CREATE TABLE IF NOT EXISTS orders (
32                id INTEGER PRIMARY KEY AUTOINCREMENT,
33                user_id INTEGER,
34                item TEXT,
35                price REAL,
36                created_at TEXT,
37                FOREIGN KEY (user_id) REFERENCES users(user_id)
38            )
39        """)
40
41        await db.commit()
42        print("āœ… Database initialized successfully")
43
44async def add_user(user_id: int, username: str, first_name: str, last_name: str):
45    """
46    Adds user to database. If user already exists — updates last_active.
47    """
48    now = datetime.utcnow().isoformat()
49
50    async with aiosqlite.connect(DB_PATH) as db:
51        await db.execute("""
52            INSERT INTO users (user_id, username, first_name, last_name, registered_at, last_active)
53            VALUES (?, ?, ?, ?, ?, ?)
54            ON CONFLICT(user_id) DO UPDATE SET
55                last_active = excluded.last_active,
56                username = excluded.username
57        """, (user_id, username, first_name, last_name, now, now))
58        await db.commit()
59
60async def get_user(user_id: int) -> dict | None:
61    """
62    Gets user from database. Returns a dict with data or None.
63    """
64    async with aiosqlite.connect(DB_PATH) as db:
65        db.row_factory = aiosqlite.Row
66        cursor = await db.execute(
67            "SELECT * FROM users WHERE user_id = ?", (user_id,)
68        )
69        row = await cursor.fetchone()
70        if row:
71            return dict(row)
72        return None
73
74async def increment_menu_clicks(user_id: int):
75    """Increments menu click counter."""
76    async with aiosqlite.connect(DB_PATH) as db:
77        await db.execute(
78            "UPDATE users SET menu_clicks = menu_clicks + 1 WHERE user_id = ?",
79            (user_id,)
80        )
81        await db.commit()
82
83async def add_order(user_id: int, item: str, price: float):
84    """Adds order to database."""
85    now = datetime.utcnow().isoformat()
86
87    async with aiosqlite.connect(DB_PATH) as db:
88        await db.execute(
89            "INSERT INTO orders (user_id, item, price, created_at) VALUES (?, ?, ?, ?)",
90            (user_id, item, price, now)
91        )
92        await db.commit()
93
94async def get_user_orders(user_id: int) -> list[dict]:
95    """Gets all user orders."""
96    async with aiosqlite.connect(DB_PATH) as db:
97        db.row_factory = aiosqlite.Row
98        cursor = await db.execute(
99            "SELECT * FROM orders WHERE user_id = ? ORDER BY created_at DESC",
100            (user_id,)
101        )
102        rows = await cursor.fetchall()
103        return [dict(row) for row in rows]
104
105async def get_stats() -> dict:
106    """Gets bot statistics."""
107    async with aiosqlite.connect(DB_PATH) as db:
108        cursor = await db.execute("SELECT COUNT(*) FROM users")
109        total_users = (await cursor.fetchone())[0]
110
111        cursor = await db.execute("SELECT COUNT(*) FROM orders")
112        total_orders = (await cursor.fetchone())[0]
113
114        cursor = await db.execute("SELECT SUM(price) FROM orders")
115        total_revenue = (await cursor.fetchone())[0] or 0
116
117        return {
118            "total_users": total_users,
119            "total_orders": total_orders,
120            "total_revenue": round(total_revenue, 2),
121        }

Integrate Database into Your Bot

Update your bot.py to use the database:

Python
 1# In bot.py, add import:
 2from database import init_db, add_user, get_user, increment_menu_clicks, add_order, get_user_orders, get_stats
 3
 4# In /start handler — save user:
 5@router.message(CommandStart())
 6async def cmd_start(message: Message):
 7    # Save user to database
 8    await add_user(
 9        user_id=message.from_user.id,
10        username=message.from_user.username or "",
11        first_name=message.from_user.first_name or "",
12        last_name=message.from_user.last_name or "",
13    )
14
15    await message.answer(
16        f"šŸ‘‹ Welcome back, <b>{message.from_user.first_name}</b>!\n\n"
17        f"I remember you from our last conversation.",
18        parse_mode="HTML",
19    )
20
21# In menu handler — count clicks:
22@router.message(Command("menu"))
23async def show_menu(message: Message):
24    await increment_menu_clicks(message.from_user.id)
25    # ... rest of menu code
26
27# In order handler — save order:
28@router.callback_query(F.data.startswith("order:"))
29async def handle_order(callback: CallbackQuery):
30    item = callback.data.split(":")[1]
31    prices = {"margherita": 12.0, "pepperoni": 14.0, "caesar": 9.0}
32    price = prices.get(item, 0)
33
34    await add_order(
35        user_id=callback.from_user.id,
36        item=item,
37        price=price,
38    )
39    # ... rest of order code
40
41# In main() function — initialize database:
42async def main():
43    await init_db()  # Create tables at startup
44    dp.include_router(router)
45    dp.include_router(handlers_router)
46    await bot.delete_webhook(drop_pending_updates=True)
47    print("šŸ¤– Bot is running! Press Ctrl+C to stop.")
48    await dp.start_polling(bot)

Run and Test

Bash
 1python bot.py

Send /start to your bot. Now check your project folder — you should see a bot_database.db file. Your user data is stored there!

Database Schema

← Swipe →
TableFieldsPurpose
usersuser_id, username, first_name, last_name, registered_at, last_active, menu_clicksUser profiles
ordersid, user_id, item, price, created_atOrder history

When to Upgrade from SQLite

← Swipe →
UsersDatabaseWhy
Under 10,000SQLiteSimple, no server needed
10,000–100,000PostgreSQLBetter concurrency, more features
100,000+PostgreSQL + RedisCaching for speed

For most bots, SQLite is sufficient. I have built bots handling 5,000+ daily users on SQLite without issues.

Your next action: run the bot, send /start, and verify that the bot_database.db file was created. Then send /menu a few times and check the database.

šŸ’¬ Need a bot with a production database and admin panel? I build Telegram bots with PostgreSQL, Redis caching, and web dashboards. Get a production-ready bot — from $500, delivered in 5-7 days →

Step 6: Deploy to Production (Make It Run 24/7)

Your bot works on your computer, but it stops when you close the terminal. Let us deploy it to a server so it runs 24/7.

Deployment Options

← Swipe →
OptionCostDifficultyBest For
DigitalOcean Droplet$4–$6/monthEasyMost bots
Hetzner VPS$4–$5/monthEasyBudget-friendly
Railway$5/monthVery easyQuick deploy
AWS EC2$5–$15/monthMediumEnterprise
Your own server$0MediumIf you have one

I recommend DigitalOcean or Hetzner for beginners — they are cheap, reliable, and have great documentation.

Step-by-Step: Deploy to DigitalOcean

1. Create a VPS (Droplet)

  • Go to digitalocean.com and create an account
  • Create a Droplet: Ubuntu 22.04, Basic plan, $4/month
  • Note the IP address and root password

2. Connect to your server

Bash
 1ssh root@YOUR_SERVER_IP

3. Install Python and dependencies

Bash
 1# Update packages
 2apt update && apt upgrade -y
 3
 4# Install Python and pip
 5apt install python3 python3-pip python3-venv -y
 6
 7# Create folder for bot
 8mkdir /opt/my-telegram-bot
 9cd /opt/my-telegram-bot

4. Upload your bot files

From your local machine (in a new terminal):

Bash
 1# Copy files to server
 2scp -r ./* root@YOUR_SERVER_IP:/opt/my-telegram-bot/

5. Set up the environment on the server

Bash
 1cd /opt/my-telegram-bot
 2
 3# Create virtual environment
 4python3 -m venv venv
 5source venv/bin/activate
 6
 7# Install dependencies
 8pip install -r requirements.txt

6. Create a systemd service

This ensures your bot restarts automatically if it crashes or the server reboots:

Bash
 1# Create service file
 2cat > /etc/systemd/system/telegram-bot.service << 'EOF'
 3[Unit]
 4Description=Telegram Bot
 5After=network.target
 6
 7[Service]
 8Type=simple
 9User=root
10WorkingDirectory=/opt/my-telegram-bot
11ExecStart=/opt/my-telegram-bot/venv/bin/python bot.py
12Restart=always
13RestartSec=10
14Environment=PYTHONUNBUFFERED=1
15
16[Install]
17WantedBy=multi-user.target
18EOF
19
20# Enable and start service
21systemctl daemon-reload
22systemctl enable telegram-bot
23systemctl start telegram-bot

7. Verify it is running

Bash
 1# Check status
 2systemctl status telegram-bot
 3
 4# View logs
 5journalctl -u telegram-bot -f

Useful Server Commands

← Swipe →
CommandWhat It Does
systemctl status telegram-botCheck if bot is running
systemctl restart telegram-botRestart the bot
systemctl stop telegram-botStop the bot
journalctl -u telegram-bot -fView live logs
journalctl -u telegram-bot --since "1 hour ago"View recent logs

Deployment Checklist

← Swipe →
StepStatus
VPS created and accessible via SSH☐
Python 3.11+ installed☐
Bot files uploaded to server☐
.env file with BOT_TOKEN created on server☐
Virtual environment and dependencies installed☐
systemd service created and enabled☐
Bot responds to /start from your phone☐
Logs show no errors☐

Your next step: create a $4/month DigitalOcean Droplet and follow the steps above. Your bot will be running 24/7 in under 30 minutes.

šŸ’¬ Want me to handle deployment for you? I deploy Telegram bots to production servers with monitoring, auto-restart, and logging included. Get a fully deployed bot — from $500, delivered in 5-7 days →

Webhook vs Polling: Which Should You Use?

We have been using polling so far — the bot repeatedly asks Telegram "any new messages?" Webhooks work differently: Telegram sends messages to your server when they arrive. Here is when to use each.

How Each Approach Works

← Swipe →
AspectPollingWebhook
How it worksBot asks Telegram for updates every few secondsTelegram sends updates to your URL when they arrive
Server requirementsAny server (no public URL needed)Public URL with HTTPS required
Setup complexitySimple (3 lines of code)Medium (need SSL certificate, web server)
Latency100–500ms (depends on poll interval)Near-instant
Resource usageHigher (constant polling)Lower (event-driven)
Best forDevelopment, small bots (< 1,000 users)Production, large bots (1,000+ users)
ReliabilityGoodExcellent

When to Use Polling

  • Development and testing — no server setup needed
  • Small bots with under 1,000 users
  • Quick prototypes — get running in minutes
  • Behind NAT/firewall — no incoming connections needed

When to Use Webhook

  • Production bots with real users
  • High-traffic bots (1,000+ messages/day)
  • Serverless deployments (AWS Lambda, Vercel)
  • When latency matters (real-time interactions)

Switching to Webhook

Here is how to switch from polling to webhook in aiogram:

Python
 1# webhook_bot.py — running bot in webhook mode
 2# Use in production instead of polling
 3
 4import asyncio
 5from aiohttp import web
 6from aiogram import Bot, Dispatcher
 7from aiogram.webhook.aiohttp_server import SimpleRequestHandler, setup_application
 8from config import BOT_TOKEN
 9
10# ─── Webhook settings ───
11WEBHOOK_HOST = "https://your-domain.com"  # Your domain with SSL
12WEBHOOK_PATH = "/webhook"                  # Webhook path
13WEBHOOK_URL = f"{WEBHOOK_HOST}{WEBHOOK_PATH}"
14
15# Path to SSL certificates (if using your own server)
16WEBAPP_HOST = "0.0.0.0"  # Listen on all interfaces
17WEBAPP_PORT = 8443        # Standard port for Telegram webhook
18
19async def on_startup(bot: Bot):
20    """
21    Called at bot startup. Sets webhook URL in Telegram.
22    """
23    await bot.set_webhook(
24        url=WEBHOOK_URL,
25        # You can specify allowed_updates if only certain types are needed
26    )
27    print(f"āœ… Webhook set to: {WEBHOOK_URL}")
28
29async def on_shutdown(bot: Bot):
30    """Called at bot shutdown."""
31    await bot.delete_webhook()
32    print("āŒ Webhook removed")
33
34def main():
35    # Create bot and dispatcher
36    bot = Bot(token=BOT_TOKEN)
37    dp = Dispatcher()
38
39    # Register routers (same as in polling version)
40    from handlers import router
41    dp.include_router(router)
42
43    # Register startup/shutdown hooks
44    dp.startup.register(on_startup)
45    dp.shutdown.register(on_shutdown)
46
47    # Create aiohttp application
48    app = web.Application()
49
50    # Configure webhook handler
51    webhook_requests_handler = SimpleRequestHandler(
52        dispatcher=dp,
53        bot=bot,
54    )
55    webhook_requests_handler.register(app, path=WEBHOOK_PATH)
56
57    # Connect dispatcher to application
58    setup_application(app, dp, bot=bot)
59
60    # Start web server
61    print(f"šŸ¤– Webhook bot running on {WEBAPP_HOST}:{WEBAPP_PORT}")
62    web.run_app(app, host=WEBAPP_HOST, port=WEBAPP_PORT)
63
64if __name__ == "__main__":
65    main()

Webhook Requirements

← Swipe →
RequirementHow to Get It
Domain nameBuy from Namecheap ($8–$12/year)
SSL certificateFree from Let's Encrypt (certbot)
Public IPComes with your VPS
Web servernginx or aiohttp (shown above)

My Recommendation

← Swipe →
StageApproachWhy
LearningPollingSimple, no setup
MVP / TestingPollingFast iteration
Production (< 1K users)PollingWorks fine, simpler
Production (1K+ users)WebhookBetter performance
EnterpriseWebhook + load balancerMaximum reliability

Your next step: stick with polling for now. When you are ready for production with real users, switch to webhook using the code above.

šŸ’¬ Need help setting up webhook with SSL and nginx? I configure production Telegram bot deployments with monitoring and auto-restart. Get a production deployment — from $200 →

Next Steps: Where to Go from Here

Congratulations — you have built a working Telegram bot with message handling, inline keyboards, and database integration. Here is what to learn next, depending on your goals.

Learning Path by Goal

← Swipe →
Your GoalLearn NextResource
Build bots for clientsPayment integration, CRM, deploymentTelegram bot payments integration
Get a job as bot developerAdvanced aiogram, testing, CI/CDBest Telegram bot frameworks
Start a bot businessMonetization, marketing, scalingTelegram bot for business
Build e-commerce botsStripe integration, product catalogsTelegram bot for e-commerce
Understand costsPricing, budgeting, ROITelegram bot development cost

Features to Add Next

← Swipe →
FeatureDifficultyImpactGuide
Payment integrationMediumHigh (revenue)Payments guide
CRM integrationMediumHigh (lead management)CRM guide
Admin commandsEasyMedium (management)Add /stats, /broadcast commands
Multi-languageMediumMedium (reach)Use gettext or custom i18n
AI responsesHardHigh (automation)Integrate OpenAI API
Mini AppHardHigh (UX)Mini Apps guide

Recommended Next Project Ideas

← Swipe →
ProjectSkills You Will LearnDifficulty
Todo list botCRUD operations, inline keyboardsEasy
Weather botAPI integration, location handlingEasy
Quiz botState management, scoringMedium
E-commerce botPayments, product catalogMedium
Support botHuman handoff, ticket systemMedium
Booking botCalendar, time slots, notificationsHard

Resources

← Swipe →
ResourceWhat It Covers
aiogram documentationOfficial framework docs
Telegram Bot APIOfficial Telegram API reference
Telegram Bot FeaturesAll bot capabilities
Best Telegram bot frameworksMy framework comparison guide

Your next step: pick one project idea from the table above and build it. The best way to learn is by building real things.

šŸ’¬ Want to go from tutorial to production? I mentor developers and build Telegram bots for businesses. Whether you need guidance or a done-for-you solution, I can help. Get in touch — bots from $500, consultations free →

Common Errors and How to Fix Them

Every developer hits these errors when building their first Telegram bot. Here are the 8 most common ones — and the exact fix for each.

← Swipe →
#Error MessageCauseFix
1telegram.error.UnauthorizedInvalid bot tokenCheck .env file, regenerate token if needed
2ModuleNotFoundError: No module named 'aiogram'Package not installedRun pip install aiogram in activated venv
3RuntimeError: This event loop is already runningRunning in Jupyter/IDEUse asyncio.run(main()) not loop.run_until_complete()
4aiogram.exceptions.TelegramForbiddenErrorUser blocked the botHandle with try/except, remove from DB
5Message is not modifiedEditing message with same textAdd check before edit_text()
6Button_type_invalidWrong keyboard typeUse InlineKeyboardButton for inline, not ReplyKeyboard
7Conflict: terminated by other getUpdatesTwo bot instances runningStop all other instances before starting
8Database is lockedMultiple async writes to SQLiteUse connection pooling or switch to PostgreSQL

Error #1: Invalid Bot Token

CODE
 1telegram.error.Unauthorized: Unauthorized

Cause: The token in your .env file is wrong or has extra spaces/characters.

Fix: 1. Open .env and check the token 2. Make sure there are no spaces before or after the token 3. If unsure, go to BotFather → /token → regenerate

Error #7: Conflict (Most Common in Development)

CODE
 1aiogram.exceptions.TelegramConflictError: Conflict: terminated by other getUpdates

Cause: You have two instances of the bot running — maybe one in your terminal and one in an IDE.

Fix:

Bash
 1# Find and kill all Python processes running your bot
 2# Windows:
 3taskkill /F /IM python.exe
 4
 5# macOS/Linux:
 6pkill -f bot.py
 7
 8# Then restart:
 9python bot.py

Error #8: Database Locked

CODE
 1aiosqlite.OperationalError: database is locked

Cause: Multiple async operations trying to write to SQLite simultaneously.

Fix: Use a single connection with a lock:

Python
 1# In database.py, use connection pool:
 2import aiosqlite
 3
 4# Global connection (opened once)
 5_db = None
 6
 7async def get_db():
 8    """Returns global database connection."""
 9    global _db
10    if _db is None:
11        _db = await aiosqlite.connect(DB_PATH)
12    return _db
13
14async def add_user(user_id: int, username: str, first_name: str, last_name: str):
15    """Adds user (with global connection)."""
16    db = await get_db()
17    now = datetime.utcnow().isoformat()
18    await db.execute(
19        "INSERT OR REPLACE INTO users (user_id, username, first_name, last_name, last_active) "
20        "VALUES (?, ?, ?, ?, ?)",
21        (user_id, username, first_name, last_name, now)
22    )
23    await db.commit()

Debugging Tips

← Swipe →
ProblemWhat to Check
Bot does not respondIs it running? Check terminal for errors
Bot responds to others but not youDid you block/unblock the bot? Send /start again
Buttons do not workIs callback_data correct? Check for typos
Database not savingIs init_db() called before any DB operations?
Bot crashes silentlyAdd logging: logging.basicConfig(level=logging.DEBUG)

Getting Help

← Swipe →
ResourceBest For
aiogram GitHub IssuesFramework bugs
Telegram Bot API docsAPI reference
Stack OverflowSpecific errors
My services pageProfessional help

The most important debugging tip: always check your terminal output. 90% of bot errors have clear error messages — you just need to read them.

šŸ’¬ Stuck on an error you cannot solve? I have debugged hundreds of Telegram bot issues. Get expert help — consultations from $50, full bot builds from $500 →

šŸ”— Related Resources
Detailed comparison of aiogram, grammY, and other frameworksbest Telegram bot frameworks
How much it costs to build a professional botTelegram bot development cost
When to hire a pro instead of building yourselfhire a Telegram bot developer
Turn your bot into an online storeTelegram bot for e-commerce
Real projects with results and ROI numbersTelegram bot case studies

Frequently Asked Questions

Answers to the most popular questions about how to build a telegram bot

Want a production-ready bot without the learning curve?

I build Telegram bots for businesses — from simple FAQ bots to complex e-commerce and CRM integrations. Get a working bot in 5-7 days.

šŸ’¬