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:
| Feature | What It Looks Like |
|---|---|
| Respond to /start | "Welcome! Choose an option:" with buttons |
| Show inline keyboards | Tappable buttons that trigger different responses |
| Handle callbacks | Bot reacts differently to each button tap |
| Store user data | Remembers user's name and choices in a database |
| Run 24/7 | Deployed to a server, always online |
Prerequisites Checklist
Before we write any code, make sure you have these installed:
| Requirement | How to Check | How to Install |
|---|---|---|
| Python 3.11+ | python --version | python.org |
| pip | pip --version | Comes with Python |
| Telegram account | You have one if you are reading this | ā |
| Code editor | VS Code, PyCharm, or any editor | code.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:
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:
| Question | What to Enter | Example |
|---|---|---|
| "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:
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:AAH1bGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9That 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:
| Command | What It Does | Example |
|---|---|---|
/setdescription | Bot profile description | "A helpful bot that answers your questions" |
/setabouttext | "About" text in bot profile | "Built with Python and aiogram" |
/setuserpic | Bot profile photo | Upload a 512x512 PNG |
/setcommands | Show command hints when user types / | See below |
Setting up command hints:
Send /setcommands to BotFather, select your bot, then paste:
1start - Start the bot
2help - Show help message
3menu - Show main menuNow when users type "/" in your chat, Telegram will show these commands as suggestions.
Security Reminder
| Do | Do NOT |
|---|---|
| Store token in .env file | Hardcode token in source code |
| Use environment variables | Commit token to Git |
| Regenerate if compromised | Share 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:
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 aiosqliteWhat Each Package Does
| Package | Version | What It Does |
|---|---|---|
| aiogram | 3.x | Telegram Bot API framework (async, modern) |
| python-dotenv | 1.x | Loads .env file for secret tokens |
| aiosqlite | 0.x | Async SQLite database driver |
Create the Config File
Create a file called config.py:
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):
1BOT_TOKEN=7123456789:AAH1bGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9Replace the token with the one you got from BotFather in Step 1.
Create requirements.txt
Create a file called requirements.txt:
1aiogram>=3.7.0
2python-dotenv>=1.0.0
3aiosqlite>=0.20.0Verify Everything Works
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
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:
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
1python bot.pyYou 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
| Step | What the Code Does |
|---|---|
| 1 | Bot(token=BOT_TOKEN) creates a connection to Telegram's API |
| 2 | Dispatcher() creates a message router |
| 3 | @router.message(CommandStart()) registers a handler for /start |
| 4 | dp.start_polling(bot) starts checking Telegram for new messages |
| 5 | When you send /start, Telegram sends it to your bot |
| 6 | Your handler receives it and sends a reply |
Understanding the Code
| Concept | What It Means | Analogy |
|---|---|---|
| Bot | Connection to Telegram API | A phone line |
| Dispatcher | Routes messages to handlers | A receptionist |
| Router | Groups related handlers | A department |
| Handler | Function that processes a message | An employee |
| Polling | Bot checks for new messages repeatedly | Checking your mailbox |
Try These Interactions
| You Send | Bot Responds |
|---|---|
/start | Welcome message with your name |
/help | List 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?
| Keyboard Type | Where It Appears | Use Case |
|---|---|---|
| Inline keyboard | Below a specific message | Menus, options, actions |
| Reply keyboard | At 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):
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:
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
1python bot.pySend /menu to your bot. You should see an interactive menu with buttons. Tap each button to see different responses.
How Inline Keyboards Work
| Component | Purpose | Example |
|---|---|---|
| InlineKeyboardButton | A single tappable button | "š Food Menu" |
| callback_data | Hidden data sent when button is tapped | "menu:food" |
| InlineKeyboardMarkup | Container for buttons | Groups buttons in rows |
| @router.callback_query | Handler for button taps | Processes the callback_data |
| callback.answer() | Acknowledges the tap | Removes loading indicator |
Key Patterns
| Pattern | Code | When to Use |
|---|---|---|
| Filter by prefix | F.data.startswith("menu:") | Menu navigation |
| Filter by exact match | F.data == "back:main" | Specific actions |
| Edit message | callback.message.edit_text() | Update content in place |
| Answer callback | await 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
| Without Database | With Database |
|---|---|
| Bot forgets users after restart | Bot remembers everyone |
| Cannot track user preferences | Personalized experience |
| No order history | Full conversation history |
| No analytics | Usage statistics |
Create database.py
Create a file called database.py:
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:
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
1python bot.pySend /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
| Table | Fields | Purpose |
|---|---|---|
| users | user_id, username, first_name, last_name, registered_at, last_active, menu_clicks | User profiles |
| orders | id, user_id, item, price, created_at | Order history |
When to Upgrade from SQLite
| Users | Database | Why |
|---|---|---|
| Under 10,000 | SQLite | Simple, no server needed |
| 10,000ā100,000 | PostgreSQL | Better concurrency, more features |
| 100,000+ | PostgreSQL + Redis | Caching 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
| Option | Cost | Difficulty | Best For |
|---|---|---|---|
| DigitalOcean Droplet | $4ā$6/month | Easy | Most bots |
| Hetzner VPS | $4ā$5/month | Easy | Budget-friendly |
| Railway | $5/month | Very easy | Quick deploy |
| AWS EC2 | $5ā$15/month | Medium | Enterprise |
| Your own server | $0 | Medium | If 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
1ssh root@YOUR_SERVER_IP3. Install Python and dependencies
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-bot4. Upload your bot files
From your local machine (in a new terminal):
1# Copy files to server
2scp -r ./* root@YOUR_SERVER_IP:/opt/my-telegram-bot/5. Set up the environment on the server
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.txt6. Create a systemd service
This ensures your bot restarts automatically if it crashes or the server reboots:
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-bot7. Verify it is running
1# Check status
2systemctl status telegram-bot
3
4# View logs
5journalctl -u telegram-bot -fUseful Server Commands
| Command | What It Does |
|---|---|
systemctl status telegram-bot | Check if bot is running |
systemctl restart telegram-bot | Restart the bot |
systemctl stop telegram-bot | Stop the bot |
journalctl -u telegram-bot -f | View live logs |
journalctl -u telegram-bot --since "1 hour ago" | View recent logs |
Deployment Checklist
| Step | Status |
|---|---|
| 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
| Aspect | Polling | Webhook |
|---|---|---|
| How it works | Bot asks Telegram for updates every few seconds | Telegram sends updates to your URL when they arrive |
| Server requirements | Any server (no public URL needed) | Public URL with HTTPS required |
| Setup complexity | Simple (3 lines of code) | Medium (need SSL certificate, web server) |
| Latency | 100ā500ms (depends on poll interval) | Near-instant |
| Resource usage | Higher (constant polling) | Lower (event-driven) |
| Best for | Development, small bots (< 1,000 users) | Production, large bots (1,000+ users) |
| Reliability | Good | Excellent |
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:
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
| Requirement | How to Get It |
|---|---|
| Domain name | Buy from Namecheap ($8ā$12/year) |
| SSL certificate | Free from Let's Encrypt (certbot) |
| Public IP | Comes with your VPS |
| Web server | nginx or aiohttp (shown above) |
My Recommendation
| Stage | Approach | Why |
|---|---|---|
| Learning | Polling | Simple, no setup |
| MVP / Testing | Polling | Fast iteration |
| Production (< 1K users) | Polling | Works fine, simpler |
| Production (1K+ users) | Webhook | Better performance |
| Enterprise | Webhook + load balancer | Maximum 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
| Your Goal | Learn Next | Resource |
|---|---|---|
| Build bots for clients | Payment integration, CRM, deployment | Telegram bot payments integration |
| Get a job as bot developer | Advanced aiogram, testing, CI/CD | Best Telegram bot frameworks |
| Start a bot business | Monetization, marketing, scaling | Telegram bot for business |
| Build e-commerce bots | Stripe integration, product catalogs | Telegram bot for e-commerce |
| Understand costs | Pricing, budgeting, ROI | Telegram bot development cost |
Features to Add Next
| Feature | Difficulty | Impact | Guide |
|---|---|---|---|
| Payment integration | Medium | High (revenue) | Payments guide |
| CRM integration | Medium | High (lead management) | CRM guide |
| Admin commands | Easy | Medium (management) | Add /stats, /broadcast commands |
| Multi-language | Medium | Medium (reach) | Use gettext or custom i18n |
| AI responses | Hard | High (automation) | Integrate OpenAI API |
| Mini App | Hard | High (UX) | Mini Apps guide |
Recommended Next Project Ideas
| Project | Skills You Will Learn | Difficulty |
|---|---|---|
| Todo list bot | CRUD operations, inline keyboards | Easy |
| Weather bot | API integration, location handling | Easy |
| Quiz bot | State management, scoring | Medium |
| E-commerce bot | Payments, product catalog | Medium |
| Support bot | Human handoff, ticket system | Medium |
| Booking bot | Calendar, time slots, notifications | Hard |
Resources
| Resource | What It Covers |
|---|---|
| aiogram documentation | Official framework docs |
| Telegram Bot API | Official Telegram API reference |
| Telegram Bot Features | All bot capabilities |
| Best Telegram bot frameworks | My 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.
| # | Error Message | Cause | Fix |
|---|---|---|---|
| 1 | telegram.error.Unauthorized | Invalid bot token | Check .env file, regenerate token if needed |
| 2 | ModuleNotFoundError: No module named 'aiogram' | Package not installed | Run pip install aiogram in activated venv |
| 3 | RuntimeError: This event loop is already running | Running in Jupyter/IDE | Use asyncio.run(main()) not loop.run_until_complete() |
| 4 | aiogram.exceptions.TelegramForbiddenError | User blocked the bot | Handle with try/except, remove from DB |
| 5 | Message is not modified | Editing message with same text | Add check before edit_text() |
| 6 | Button_type_invalid | Wrong keyboard type | Use InlineKeyboardButton for inline, not ReplyKeyboard |
| 7 | Conflict: terminated by other getUpdates | Two bot instances running | Stop all other instances before starting |
| 8 | Database is locked | Multiple async writes to SQLite | Use connection pooling or switch to PostgreSQL |
Error #1: Invalid Bot Token
1telegram.error.Unauthorized: UnauthorizedCause: 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)
1aiogram.exceptions.TelegramConflictError: Conflict: terminated by other getUpdatesCause: You have two instances of the bot running ā maybe one in your terminal and one in an IDE.
Fix:
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.pyError #8: Database Locked
1aiosqlite.OperationalError: database is lockedCause: Multiple async operations trying to write to SQLite simultaneously.
Fix: Use a single connection with a lock:
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
| Problem | What to Check |
|---|---|
| Bot does not respond | Is it running? Check terminal for errors |
| Bot responds to others but not you | Did you block/unblock the bot? Send /start again |
| Buttons do not work | Is callback_data correct? Check for typos |
| Database not saving | Is init_db() called before any DB operations? |
| Bot crashes silently | Add logging: logging.basicConfig(level=logging.DEBUG) |
Getting Help
| Resource | Best For |
|---|---|
| aiogram GitHub Issues | Framework bugs |
| Telegram Bot API docs | API reference |
| Stack Overflow | Specific errors |
| My services page | Professional 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 ā
Frequently Asked Questions
Answers to the most popular questions about how to build a telegram bot
![How to Build a Telegram Bot: Complete Tutorial with Code [2026]](/_next/image?url=%2Fimages%2Fseo%2Ftelegram%2Fhow-to-build-telegram-bot.png&w=3840&q=75)