Home › Guides › Telegram bot starter code
Guide · Telegram · PythonTelegram bot starter code in Python
Every Telegram bot starts with the same pieces: a token, a few commands, a handler for messages, error handling and a loop that fetches updates. This page gives you a small, complete starting point with python-telegram-bot v22 that you can copy, run in five minutes and then extend. No framework magic, no database, no web server.
1. Get a bot token from @BotFather
- In Telegram, open @BotFather (check the blue verification mark) and send
/newbot. - Choose a display name and a username that ends in
bot. - BotFather replies with a token like
123456789:AA…. Treat it like a password: do not paste it into the code, do not commit it to Git. If it leaks, send/revoketo BotFather to get a new one.
2. Install python-telegram-bot
You need Python 3.10 or newer.
python3 -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install "python-telegram-bot==22.8"
Pinning the version means the code keeps working the way it was tested. python-telegram-bot is fully async; all handlers below are async def functions.
3. The complete starter code
Save this as starter_bot.py:
"""Minimal Telegram bot starter with python-telegram-bot v22.
Commands: /start, /help. In private chats it echoes text messages back.
Set BOT_TOKEN in the environment, then run: python starter_bot.py
"""
import logging
import os
import sys
from telegram import BotCommand, Update
from telegram.ext import Application, CommandHandler, ContextTypes, MessageHandler, filters
logging.basicConfig(format="%(asctime)s %(levelname)s %(name)s: %(message)s", level=logging.INFO)
logging.getLogger("httpx").setLevel(logging.WARNING) # do not log every polling request
log = logging.getLogger("starter")
HELP_TEXT = (
"I am a starter bot.\n"
"/start - say hello\n"
"/help - show this message\n"
"In a private chat I repeat what you write."
)
async def start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
user = update.effective_user
name = user.first_name if user else "there"
await update.effective_message.reply_text(f"Hi {name}! Send /help to see what I can do.")
async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
await update.effective_message.reply_text(HELP_TEXT)
async def echo(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
message = update.effective_message
await message.reply_text(message.text[:4000]) # Telegram limit is 4096 characters
async def on_error(update: object, context: ContextTypes.DEFAULT_TYPE) -> None:
# Log the problem instead of crashing; the bot keeps running.
log.error("Error while handling an update", exc_info=context.error)
async def post_init(app: Application) -> None:
# Fills the command menu that users see next to the message field.
await app.bot.set_my_commands([BotCommand("start", "Say hello"), BotCommand("help", "Show help")])
def build_app(token: str) -> Application:
app = Application.builder().token(token).post_init(post_init).build()
app.add_handler(CommandHandler("start", start))
app.add_handler(CommandHandler("help", help_command))
app.add_handler(MessageHandler(filters.ChatType.PRIVATE & filters.TEXT & ~filters.COMMAND, echo))
app.add_error_handler(on_error)
return app
def main() -> None:
token = os.environ.get("BOT_TOKEN")
if not token:
sys.exit("Please set the BOT_TOKEN environment variable (token from @BotFather).")
log.info("Starting bot, press Ctrl+C to stop")
build_app(token).run_polling(allowed_updates=Update.ALL_TYPES)
if __name__ == "__main__":
main()
4. What each part does
- Logging:
basicConfigprints timestamps and errors to the terminal. Thehttpxlogger is set to WARNING because it would otherwise log every polling request. - Handlers:
CommandHandler("start", start)runsstart()for/start. TheMessageHandleruses filters: only text, no commands, and only in private chats – so the echo does not spam groups. update.effective_message/effective_userwork for normal messages and edited messages alike.effective_usercan be empty (for example in channels), which is whystart()checks it.- Error handler: without it, an exception in a handler is only logged by the library.
add_error_handlergives you one place to log or report errors; the bot keeps running. post_initruns once after start-up and fills the command menu users see next to the message field.build_app()is separate frommain()so you can build the application in tests without starting the bot.run_polling()asks Telegram for new updates in a loop (long polling). No domain, open port or HTTPS certificate is needed; stop it with Ctrl+C.
5. Run it
export BOT_TOKEN="123456789:AA...your-token..." # Windows PowerShell: $env:BOT_TOKEN="..."
python starter_bot.py
Open a chat with your bot in Telegram and send /start, /help and any text. The bot only answers while the script runs. For 24/7 operation, run it on a small server with a process manager (systemd, or Docker with a restart policy) – the hosting costs are yours, and you should keep the token in an environment variable or a .env file that is not shared.
How we tested this code: with python-telegram-bot 22.8 on Python 3.13 we ran ruff (no findings), an import check, and 13 offline tests: the handlers with mocked messages, the echo filter for private chats, groups, supergroups and commands, the error handler, the command menu, the missing-token message, and a full run of /start, /help and a text message through the real Application with the Telegram API mocked. We did not run it against the live Telegram API for this guide.
6. Common problems
telegram.error.InvalidTokenor “Unauthorized”: the token is wrong or was revoked; copy it again from BotFather.- “Conflict: terminated by other getUpdates request”: the same token runs twice (for example on your laptop and a server). Stop one of them.
- The bot ignores messages in a group: that is intended for the echo here. In general, bots in privacy mode (the default) only receive commands and replies in groups; change it with
/setprivacyin BotFather if you really need all messages. RuntimeWarning: coroutine … was never awaited: you forgotawaitbefore a call such asreply_text.- Long replies fail above 4,096 characters per message; the echo cuts text at 4,000 characters.
7. Next steps
- Protect a group against bots with the captcha guide.
- Add crypto prices with the
/pricecommand guide. - Filter links and banned words with our free anti-spam module (code and tests, no sign-up).
Want a complete group bot instead of starting from scratch?
The Telegram Group Bot Starter Kit ($19, source code) builds on this structure and adds welcome messages, a captcha, link and banned-word filters, flood protection, /price, admin commands, a Docker setup, a step-by-step README and 67 offline tests. You host it yourself. Together with the crypto portfolio tracker it is available as a bundle. The code on this page is free to use as it is.