aiogram 3 extension that lifts Telegram's 64-byte callback_data limit by storing payloads in an external storage keyed by a short hash.
Project description
aiogram-callback-data
A small extension for aiogram 3 that lifts Telegram's 64-byte callback_data limit.
Instead of serializing your payload into the callback string itself, this library stores the full payload in an external storage (in-memory or Redis) and sends only a short lookup key to Telegram. This lets you pass arbitrarily large or deeply nested models through inline buttons.
Features
- Drop-in
CallbackDatabase class built onpydantic.BaseModel. - Storage backends:
InMemoryStorage(dev) andRedisStorage(prod). - Optional TTL on every
pack()call. - One-shot buttons — the entry is deleted on the first click.
- Fully typed, compatible with aiogram's filter/DI system.
Installation (pip)
pip install aiogram-callback-data
# with Redis support
pip install "aiogram-callback-data[redis]"
Installation (uv)
uv add aiogram-callback-data
# With Redis support
uv add "aiogram-callback-data[redis]"
Python >= 3.10 is required.
Quick start
from __future__ import annotations
import asyncio
from aiogram import Bot, Dispatcher
from aiogram.types import Message, CallbackQuery
from aiogram.utils.keyboard import InlineKeyboardBuilder
from aiogram_callback_data import CallbackData, setup
from aiogram_callback_data.storages import InMemoryStorage
bot = Bot(token="YOUR_TOKEN")
dp = Dispatcher()
storage = InMemoryStorage()
class UserAction(CallbackData, prefix="user"):
user_id: int
action: str
# arbitrarily large payload — no 64-byte limit anymore
context: dict[str, str] = {}
@dp.message()
async def send_menu(msg: Message) -> None:
builder = InlineKeyboardBuilder()
approve_key = await UserAction(
user_id=msg.from_user.id,
action="approve",
context={"source": "menu", "version": "v2"},
).pack(ttl=300)
reject_key = await UserAction(
user_id=msg.from_user.id,
action="reject",
).pack(ttl=300)
builder.button(text="✅ Approve", callback_data=approve_key)
builder.button(text="❌ Reject", callback_data=reject_key)
await msg.answer("Choose:", reply_markup=builder.as_markup())
@dp.callback_query(UserAction.filter())
async def handle_action(call: CallbackQuery, callback_data: UserAction) -> None:
await call.answer(
f"User {callback_data.user_id} → {callback_data.action}",
show_alert=True,
)
if __name__ == "__main__":
setup(storage, dp)
asyncio.run(dp.start_polling(bot))
One-shot buttons
Pass once=True to filter() to make a button usable exactly once. The storage entry is deleted atomically on the first match, so subsequent clicks raise HashNotFoundError.
@dp.callback_query(UserAction.filter(once=True))
async def handle_once(call: CallbackQuery, callback_data: UserAction) -> None:
await call.answer("Accepted. This button is now dead.")
Redis backend
from aiogram_callback_data.storages import RedisStorage
storage = RedisStorage.from_url("redis://localhost:6379/0")
setup(storage, dp)
RedisStorage uses redis.asyncio under the hood and implements GETDEL for one-shot buttons.
How it works
CallbackData.pack()dumps the model to JSON, hashes it (md5, 32 chars), builds a key of the form"<prefix>:<hash>", and saves the JSON under that key.- Only the short key is sent to Telegram as
callback_data. - On incoming
CallbackQuery,CallbackData.filter()matches the prefix, loads the JSON from storage, and validates it back into your model — which is then injected into the handler ascallback_data.
Prefix length is capped at 31 characters (64-byte Telegram limit − ":" − 32-char hash) and is validated at class-declaration time.
Error handling
If the storage entry is missing or expired (e.g. the user clicks an old button), the filter raises HashNotFoundError:
from aiogram import Router
from aiogram.types import ErrorEvent
from aiogram_callback_data.errors import HashNotFoundError
router = Router()
@router.errors()
async def on_error(event: ErrorEvent) -> bool:
if isinstance(event.exception, HashNotFoundError):
call = event.update.callback_query
if call:
await call.answer("This button has expired.", show_alert=True)
return True
return False
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file aiogram_callback_data-1.0.0.tar.gz.
File metadata
- Download URL: aiogram_callback_data-1.0.0.tar.gz
- Upload date:
- Size: 7.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9354215eb5307ad0e7ef2ca66c8f5bbb2cf25889e217702438cfc6bc10420234
|
|
| MD5 |
a430779e908b199abbf8a867da279c55
|
|
| BLAKE2b-256 |
e2b5a5eee67e29f5f0f804acf145420c79dd0635a2ab4f46ad3c8b642d7d1f76
|
File details
Details for the file aiogram_callback_data-1.0.0-py3-none-any.whl.
File metadata
- Download URL: aiogram_callback_data-1.0.0-py3-none-any.whl
- Upload date:
- Size: 11.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1f140831e95d4c0484d828236eed1fb8c70707cfddcb644224e35d35ceaadb37
|
|
| MD5 |
6d8fd2fc61959c6902142392e948a3a8
|
|
| BLAKE2b-256 |
c9d8ba659af85b79fe31bf55232fef1dd4adcb8cd223f84802ebc3263bdbcde8
|