Django Telegram Bot
The fastest way to build a production-ready Telegram bot on Django.
🚀 Overview
django-telegram
A typed Telegram Bot API client for Django.
django-telegram gives you every Telegram Bot API method as a plain Python method, and every Telegram type as a Pydantic model. You call a method, and you get a typed object back instead of a raw dictionary.
What it does
- All Bot API methods in one place. Sending a message, answering a callback query, setting a webhook, and the rest of the API are available as methods on a single
Telegramclient. - All Telegram types included. Updates, messages, users, chats, keyboards, and every other object defined by Telegram are Pydantic models, so you get validation and editor autocompletion.
- Responses are objects. Access
message.chat.idorupdate.callback_query.datadirectly instead of digging through nested dictionaries. - Built for Django. Configuration is read from your Django settings, and the library fits into your own views and URLs.
- Synchronous. No event loop or async runtime required.
⚙️ Installation
pip install django_telegram
or with uv:
uv add django_telegram
⚡ Quick Start
Here's the minimal setup to get a bot responding to /start.
1. Add the app and configure settings
1. Configure settings
Add a TELEGRAM dictionary to your Django settings. You do not need to add django_telegram to INSTALLED_APPS.
# settings.py
TELEGRAM = {
"BOT_TOKEN": "your-telegram-bot-token",
"TM_WEBHOOK_URL": "https://api.telegram.org",
}
| Key | Required | Default | Description |
|---|---|---|---|
BOT_TOKEN |
Yes | n/a | Your bot token from @BotFather. |
TM_WEBHOOK_URL |
Yes | n/a | Base URL of the Telegram Bot API. Use https://api.telegram.org, or the address of your own local Bot API server. |
PROXY_SOCKS |
No | "" |
SOCKS proxy for outgoing requests, e.g. 127.0.0.1:1080. |
If a required key is missing, ImproperlyConfigured is raised and the message tells you which one.
Once the settings are in place, the client needs no arguments:
from django_telegram import Telegram
bot = Telegram() # reads BOT_TOKEN and TM_WEBHOOK_URL from settings
You can still pass a token explicitly, for example in tests or when running several bots:
bot = Telegram(token="123456:ABC-DEF")
2. Add the webhook URL
Create a Django app for your bot (here it is called bot):
python manage.py startapp bot
Add a view that receives Telegram's updates and passes them to the dispatcher:
# bot/views.py
import logging
from django_telegram import Dispatcher, Update
from rest_framework import status
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.views import APIView
from .handlers import command_handlers # noqa: F401 (registers your handler classes)
logger = logging.getLogger(__name__)
class TelegramWebhookView(APIView):
authentication_classes = []
permission_classes = []
def post(self, request: Request) -> Response:
try:
update = Update(**request.data)
Dispatcher(update).dispatch()
except Exception:
logger.exception("Error while processing Telegram update")
# Always answer 200, otherwise Telegram keeps retrying the same update.
return Response({"ok": True}, status=status.HTTP_200_OK)
Add the route to the app:
# bot/urls.py
from django.urls import path
from .views import TelegramWebhookView
app_name = "bot"
urlpatterns = [
path("webhook/", TelegramWebhookView.as_view(), name="webhook"),
]
Include it in your project URLs:
# config/urls.py
from django.urls import include, path
urlpatterns = [
path("bot/", include("bot.urls", namespace="bot")),
]
Your webhook is now served at https://your-domain.com/bot/webhook/.
3. Write your handlers
Create a handlers package inside your app and add one file per handler type:
bot/
├── __init__.py
├── urls.py
├── views.py
└── handlers/
├── __init__.py
└── command_handlers.py
Handlers inherit from one of the base classes and implement handle(self, update). Commands (any message that starts with /) go to CommandHandler:
# bot/handlers/command_handlers.py
from django.conf import settings
from django_telegram import CommandHandler, Telegram, Update
from django_telegram.telegram_models import KeyboardButton, ReplyKeyboardMarkup
bot = Telegram(token=settings.TELEGRAM["BOT_TOKEN"])
class Commands(CommandHandler):
def handle(self, update: Update):
chat_id = update.message.chat.id
text = update.message.text
if text.startswith("/start"):
bot.send_message(chat_id=chat_id, text="hello 3>")
elif text.startswith("/products"):
bot.send_message(
chat_id=chat_id,
text="choices one !",
reply_markup=ReplyKeyboardMarkup(
keyboard=[
[
KeyboardButton(text="product 1"),
KeyboardButton(text="product 2"),
],
[
KeyboardButton(text="product 3"),
KeyboardButton(text="product 4"),
],
],
resize_keyboard=True,
),
)
The dispatcher does not look at the command name. Every command is delivered to your CommandHandler subclass, and you decide what to do for /start, /products, and so on inside handle().
When your app grows to several handler files, import them all from the package so the view only needs one import:
# bot/__init__.py
from . import command_handlers # noqa: F401
# from . import message_handlers # noqa: F401
# from . import callback_query_handlers # noqa: F401
4. Handle plain messages
Any message that is not a command (no leading /) and has no media goes to MessageHandler. Create a new file next to your command handlers:
bot/handlers/
├── __init__.py
├── command_handlers.py
└── message_handlers.py ← new
In this example, when the user sends product 1 (the text of the reply keyboard button from the previous step), the bot answers with a message that has an inline button attached:
# bot/handlers/message_handlers.py
from django.conf import settings
from django_telegram import MessageHandler, Telegram, Update
from django_telegram.telegram_models import InlineKeyboardButton, InlineKeyboardMarkup
bot = Telegram(token=settings.TELEGRAM["BOT_TOKEN"])
class MyMessageHandler(MessageHandler):
def handle(self, update: Update):
chat_id = update.message.chat.id
text = update.message.text
if text == "product 1":
bot.send_message(
chat_id=chat_id,
text="product 1",
reply_markup=InlineKeyboardMarkup(
inline_keyboard=[
[
InlineKeyboardButton(text="product 1", callback_data="pro1"),
],
],
),
)
The callback_data value ("pro1") is what Telegram sends back to your bot when the user presses the button. You will handle it in the next step.
Now register the new file. A handler class only exists once its file has been imported, so add it to handlers/__init__.py:
# bot/handlers/__init__.py
from . import (
command_handlers, # noqa: F401
message_handlers, # noqa: F401
)
Send product 1 to your bot: it replies with a message and an inline button.
5. Handle inline button presses (callback queries)
When the user presses an inline button, Telegram sends a callback query instead of a message. These go to CallbackQueryHandler. Create another file:
bot/handlers/
├── __init__.py
├── callback_query_handlers.py ← new
├── command_handlers.py
└── message_handlers.py
# bot/handlers/callback_query_handlers.py
from django.conf import settings
from django_telegram import CallbackQueryHandler, Telegram, Update
bot = Telegram(token=settings.TELEGRAM["BOT_TOKEN"])
class MyCallbackQueryHandler(CallbackQueryHandler):
def handle(self, update: Update):
callback_data = update.callback_query.data
if callback_data == "pro1":
bot.answer_callback_query(
callback_query_id=update.callback_query.id,
text="hi this is product 1",
show_alert=True,
)
answer_callback_query is required for every callback query: it stops the loading spinner on the button. With show_alert=True the text is shown as a pop-up alert instead of a small toast.
Register the file the same way:
# bot/handlers/__init__.py
from . import (
callback_query_handlers, # noqa: F401
command_handlers, # noqa: F401
message_handlers, # noqa: F401
)
Summary
| User action | Update type | Base class | Example file |
|---|---|---|---|
Sends /start |
command | CommandHandler |
command_handlers.py |
Sends product 1 |
text message | MessageHandler |
message_handlers.py |
| Presses an inline button | callback query | CallbackQueryHandler |
callback_query_handlers.py |
Each new handler file needs one line in handlers/__init__.py. Your webhook view only needs from . import handlers.
Metadata
Release files for django-telegram-bot-api 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| django_telegram_bot_api-0.1.0.tar.gz | 47.5 kB | Without provenance | Details for django_telegram_bot_api-0.1.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| django_telegram_bot_api-0.1.0-py3-none-any.whl | Python 3 | none | any | Details for django_telegram_bot_api-0.1.0-py3-none-any.whl |
Total release size: 97.6 kB
Release files / django_telegram_bot_api-0.1.0.tar.gz
| Download URL | django_telegram_bot_api-0.1.0.tar.gz |
|---|---|
| Size | 47.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
1821973b8c8c97e7563c18de6c47dffe658bac3329b494bac4df500810c04428
|
|
BLAKE2b-256 checksum How to use checksums |
2c575cf1e755346036c5d1345c5b0c88c3cc6194b72c58fa5cf388fbb04c884d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.9.18 {"installer":{"name":"uv","version":"0.9.18","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Arch Linux","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / django_telegram_bot_api-0.1.0-py3-none-any.whl
| Download URL | django_telegram_bot_api-0.1.0-py3-none-any.whl |
|---|---|
| Size | 50.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
8dac4c33efe2c3611583bf2ccbbcdec0b5d5a782ea8388bb9a8f1c46501f554a
|
|
BLAKE2b-256 checksum How to use checksums |
1b65da799eda7e2bcd79e57c9cbd5c35887ff84b153a4644efe2deb118a5f61d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.9.18 {"installer":{"name":"uv","version":"0.9.18","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Arch Linux","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Security for django-telegram-bot-api 0.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.