Skip to main content

A powerful builder library for creating recursive inline keyboard menus in python-telegram-bot

Project description

Telegram Menu Builder

PyPI version Python 3.12+ License: MIT CI Type checked: mypy + pyright Linting: ruff

A type-safe, async-first Python library for building recursive inline keyboard menus in python-telegram-bot v20+. You declare buttons with a fluent MenuBuilder, the library encodes each callback payload to fit Telegram's 64-byte limit (compressing inline or spilling to pluggable storage), and a MenuRouter decodes incoming callbacks and dispatches them to your handlers.

📖 Documentation: https://smoxy.github.io/telegram-menu-builder/ — or jump to the doc index below.

Status: alpha (0.x). The API may change before 1.0. See the changelog.

✨ Features

  • 🏗️ Fluent builder API — chainable, readable menu construction.
  • 📦 Smart callback encoding — automatic inline / short-term / persistent strategy to stay under Telegram's 64-byte limit, with zlib compression and deduplication.
  • 🧭 Routing & middlewareMenuRouter dispatches callbacks to named handlers with before / after / on_error hooks and handler groups.
  • 🔄 Unlimited nesting — submenus and navigation (back / next / exit / cancel) buttons.
  • 🧩 Pluggable storage — built-in in-memory, async SQL, and Redis/Valkey backends are included; or implement the StorageBackend protocol (or subclass BaseStorage) for your own.
  • 🪄 Application-free outputto_markup() / to_raw() build a telegram.InlineKeyboardMarkup or a plain Bot API dict synchronously, with no PTB Application, event loop, or storage required — share one menu definition across codebases.
  • 🔐 Strict typing — full type hints, validated with both mypy --strict and pyright (Pydantic v2 models), shipped with py.typed.
  • 🧪 Well tested — ~90% coverage, CI on every push and pull request, plus a telegram_menu_builder.testing module to drive handlers with no live Telegram.

🚀 Quick start

pip install telegram-menu-builder

Optional extras: telegram-menu-builder[redis] (built-in Redis/Valkey backend), [sql] (plus [postgres] / [mysql] drivers), [dev], [docs]. See Installation.

from telegram import Update
from telegram.ext import Application, CallbackQueryHandler, ContextTypes
from telegram_menu_builder import MenuBuilder, MenuRouter

router = MenuRouter()


async def show_settings(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    menu = (
        MenuBuilder()
        .add_item("🌍 Language", handler="set_language")
        .add_item("👤 Profile", handler="edit_profile", user_id=update.effective_user.id)
        .columns(2)
        .add_back_button()
        .build()
    )
    await update.message.reply_text("⚙️ Settings", reply_markup=menu)


@router.handler("set_language")
async def handle_language(update: Update, context: ContextTypes.DEFAULT_TYPE, params: dict) -> None:
    # `params` are the values you passed to add_item(...), decoded from the callback.
    await update.callback_query.edit_message_text("Choose a language…")


app = Application.builder().token("YOUR_TOKEN").build()
app.add_handler(CallbackQueryHandler(router.route))
app.run_polling()

Tip: in async code, prefer await builder.build_async(). build() is a synchronous convenience wrapper (it also works inside a running event loop). See Building menus.

📚 Examples at a glance

Buttons carry arbitrary parameters — they are encoded into the callback and decoded back into the params dict your handler receives:

menu = (
    MenuBuilder()
    .add_item("📝 Edit", handler="edit_user", user_id=123, field="email")
    .add_item("🗑️ Delete", handler="delete_user", user_id=123, confirm=True)
    .columns(1)
    .add_back_button(handler="user_list", page=2)
    .build()
)

Nested submenus:

users = MenuBuilder().add_item("Add user", handler="add_user").add_back_button()
main = MenuBuilder().add_submenu("👥 Users", users)

Storage backends — in-memory, SQL, and Redis/Valkey backends ship built-in; for anything else, subclass BaseStorage (or satisfy the StorageBackend protocol) and pass it in:

from telegram_menu_builder import MenuBuilder
from telegram_menu_builder.storage import RedisStorage  # one client, also speaks Valkey

builder = MenuBuilder(storage=RedisStorage(url="redis://localhost:6379/0"))
# custom backends: docs/advanced/custom-storage.md

See the runnable examples/ and the guides for complete, copy-pasteable bots — including ConversationHandler integration.

🧠 How callback encoding works

Telegram limits callback_data to 64 bytes. The library chooses a strategy automatically:

  1. Inline (fits in 64 bytes) — JSON → zlib → base64 directly in the callback.
  2. Short-term — stored in the backend with a TTL; the callback carries a short reference.
  3. Persistent — stored without expiry for large/long-lived payloads.

Identical payloads reuse the same key (deduplication). Full details: Callback encoding internals.

📖 Documentation

📚 Rendered site: https://smoxy.github.io/telegram-menu-builder/

Get started Guides Advanced Reference
Installation Building menus Encoding internals MenuBuilder
Quick start Routing callbacks Custom storage MenuRouter
Storage backends Types & models
ConversationHandler Encoding · Storage

Project docs: Changelog · Security policy · Dependency & CVE audit · Python compatibility · Development guide · Contributing

🤝 Contributing

Every contribution is welcome — bug reports, documentation, examples, and code alike. Start with the Contributing guide and the Development guide.

git clone https://github.com/smoxy/telegram-menu-builder.git
cd telegram-menu-builder
python -m venv .venv && source .venv/bin/activate   # Windows: .venv\Scripts\activate
pip install -e ".[dev]"
pre-commit install
make test          # run the suite (≈90% coverage)

Before opening a pull request, make sure the gates pass:

make lint          # ruff + black --check
make type-check    # mypy --strict + pyright
make test          # pytest with coverage

Releases are published to PyPI automatically via GitHub Releases and trusted publishing — no manual token handling needed.

🔐 Security

Found a vulnerability? Please report it privately — see SECURITY.md. Dependency CVEs are tracked in the dependency audit (for example, pydantic is pinned >=2.4 to exclude CVE-2024-3772).

🗺️ Roadmap

  • ✅ Fluent builder, navigation, submenus
  • ✅ Smart callback encoding (inline / short-term / persistent) with compression & dedup
  • MenuRouter with middleware and handler groups
  • ✅ In-memory storage backend, strict typing, CI
  • ✅ Built-in SQL backend via SQLAlchemy (async) for PostgreSQL/Supabase, MySQL/MariaDB, and SQLite (install [sql], plus [postgres] or [mysql] for those drivers — see storage backends)
  • ✅ Built-in Redis backend via redis-py that also speaks Valkey (the RESP-compatible Redis fork we recommend), verified against Redis 7.4.9 & Valkey 8.1.8 — install [redis] (see Redis/Valkey storage)
  • ✅ Application-free build (to_markup / to_raw) + atomic multi-operator claim + testing helpers (v0.4.0)
  • 📅 Helpers for pagination and form/wizard flows

📝 License

Released under the MIT License — see LICENSE. Free for commercial, private, and personal use, modification, and distribution. Using this code to train AI/ML models is explicitly permitted. Attribution is appreciated but not required.


Made with ❤️ for the Telegram Bot community · python-telegram-bot v20+ · Pydantic v2

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

telegram_menu_builder-0.4.0.tar.gz (91.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

telegram_menu_builder-0.4.0-py3-none-any.whl (44.7 kB view details)

Uploaded Python 3

File details

Details for the file telegram_menu_builder-0.4.0.tar.gz.

File metadata

  • Download URL: telegram_menu_builder-0.4.0.tar.gz
  • Upload date:
  • Size: 91.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.13

File hashes

Hashes for telegram_menu_builder-0.4.0.tar.gz
Algorithm Hash digest
SHA256 8ba896e8c63bc8531a9141e13f9ced2c8b08468912015c5c07a0e9e2006526b7
MD5 59ef8aab1e7210b1c5b0e31c322e1dca
BLAKE2b-256 3db729971834aedc0b88bb942aee6101b98a165efda8ece076cb782cd30d0023

See more details on using hashes here.

Provenance

The following attestation bundles were made for telegram_menu_builder-0.4.0.tar.gz:

Publisher: python-publish.yml on smoxy/telegram-menu-builder

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file telegram_menu_builder-0.4.0-py3-none-any.whl.

File metadata

File hashes

Hashes for telegram_menu_builder-0.4.0-py3-none-any.whl
Algorithm Hash digest
SHA256 6f3513160d264ec3b14406f9c93603e27ca2e1ad5bca4f2ad1f92e26b4ba520f
MD5 1781d5c296d7369017a61d152a215f53
BLAKE2b-256 feb0689e5a4c39f9f0a2b3b66a7b2e20d2672268e9cd013c48f7693d8e8b3c83

See more details on using hashes here.

Provenance

The following attestation bundles were made for telegram_menu_builder-0.4.0-py3-none-any.whl:

Publisher: python-publish.yml on smoxy/telegram-menu-builder

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page