Skip to main content

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 CallbackData base class built on pydantic.BaseModel.
  • Storage backends: InMemoryStorage (dev) and RedisStorage (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

  1. 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.
  2. Only the short key is sent to Telegram as callback_data.
  3. 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 as callback_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

Release files for aiogram-callback-data 1.0.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for aiogram-callback-data 1.0.0
File Size Uploaded
aiogram_callback_data-1.0.0.tar.gz 7.5 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for aiogram-callback-data 1.0.0
File Interpreter ABI Platform
aiogram_callback_data-1.0.0-py3-none-any.whl Python 3 none any Details

Total release size: 18.6 kB

Release files / aiogram_callback_data-1.0.0.tar.gz

Download URL aiogram_callback_data-1.0.0.tar.gz
Size 7.5 kB
Tags Source
SHA-256 checksum
How to use checksums
9354215eb5307ad0e7ef2ca66c8f5bbb2cf25889e217702438cfc6bc10420234
BLAKE2b-256 checksum
How to use checksums
e2b5a5eee67e29f5f0f804acf145420c79dd0635a2ab4f46ad3c8b642d7d1f76
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.13.7

Release files / aiogram_callback_data-1.0.0-py3-none-any.whl

Download URL aiogram_callback_data-1.0.0-py3-none-any.whl
Size 11.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
1f140831e95d4c0484d828236eed1fb8c70707cfddcb644224e35d35ceaadb37
BLAKE2b-256 checksum
How to use checksums
c9d8ba659af85b79fe31bf55232fef1dd4adcb8cd223f84802ebc3263bdbcde8
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.13.7

Release history Release notifications | RSS feed

This release

1.0.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page