yet another telegram bot api library
type-safe · chainable · plugin-first · batteries included
docs · playground · npmx
yaebal is a type-safe telegram bot framework for typescript. build your bot as one chain:
derive, decorate, install, guard, filters and plugins all carry the context type forward,
so handlers see exactly what earlier steps added.
import { InlineKeyboard, callbackData, createBot, html, session } from "yaebal";
const vote = callbackData("vote", { id: Number });
const bot = createBot(process.env.BOT_TOKEN!)
.install(session({ initial: () => ({ count: 0 }) }))
.derive((ctx) => ({ name: ctx.from?.first_name ?? "friend" }));
bot.command("start", (ctx) =>
ctx.reply(html`<b>hey ${ctx.name}</b>, pick one`, {
reply_markup: new InlineKeyboard()
.text("yes", vote.pack({ id: 1 }))
.text("no", vote.pack({ id: 2 }))
.build(),
}),
);
bot.on("message:text", (ctx) => ctx.reply(`typed text: ${ctx.text}`));
bot.on("callback_query:data", (ctx) => {
const data = vote.unpack(ctx.callbackQuery.data);
if (data) ctx.session.count += data.id;
return ctx.answer(`count: ${ctx.session.count}`);
});
await bot.start();BotextendsComposer, so there is one middleware engine instead of a bot/router split.- filter queries such as
on("message:text")narrow the handler context without casts. - generated contexts give update-specific shortcuts such as
ctx.react,ctx.answer, andctx.editText. - first-party plugins cover sessions, keyboards, callback data, formatting, webhooks, retries, testing, routing and more.
- explicit plugin dependencies make wrong install order a type error instead of a production surprise.
pnpm add yaebalor scaffold a fresh bot — templates, plugins, a docker/fly/railway/cloudflare/vercel
deploy target and a ci workflow are all one flag away (see create a bot):
pnpm create yaebal| need | start here |
|---|---|
| build your first bot | getting started |
| remember the api fast | cheat sheet |
| understand context type flow | typed examples |
| ship webhooks or edge bots | webhooks & deploy |
| harden a production bot | production guide |
| debug common bot issues | troubleshooting |
| move from another framework | migration guides |
| build payments, inline mode or mini apps | telegram guides |
| ask an ai assistant to write yaebal code | llms.txt |
if yaebal saves you time, star the repo. it helps people find a young project.
runnable bots live under examples/. clone the repo, drop a token in .env, and run one package. the full plugin coverage matrix is in examples/readme.md.
| example | what it shows | run |
|---|---|---|
| core-echo | bare @yaebal/core: middleware, filter narrowing, format, raw typed api.call |
pnpm --filter @yaebal/example-core-echo dev |
| tgcloud | echo bot with a per-chat counter, bundled into telegram serverless handlers | pnpm --filter @yaebal/example-tgcloud dev |
| basic | whole-stack tour on yaebal: keyboard, callback-data, session, morda, i18n, scenes, prompt, filters, fmt, retry, throttle |
pnpm --filter @yaebal/example-basic dev |
| again | awaited retry on structured retry_after and transient 5xx |
pnpm --filter @yaebal/example-again dev |
| ai-chat | llm chat on @yaebal/ai: streamed draft replies, per-user memory, polite rate limits |
pnpm --filter @yaebal/example-ai-chat dev |
| throttle | outbound scheduler with buckets, priority, cancellation and metrics | pnpm --filter @yaebal/example-throttle dev |
| broadcast | typed broadcast jobs with progress, retry, pause, resume and cancel | pnpm --filter @yaebal/example-broadcast dev |
| keyboard | every keyboard button type, dynamic grids, reply keyboards and request buttons | pnpm --filter @yaebal/example-keyboard dev |
| commands | typed command registry: localized menus, scopes, aliases, hidden commands, diff sync | pnpm --filter @yaebal/example-commands dev |
| ephemeral | ephemeral commands, rich/media edits, callback overlays, private-chat fallback | pnpm --filter @yaebal/example-ephemeral dev |
| pagination | lazy paginated lists, item buttons with onSelect, typed payload, menu morphing, ownership filter |
pnpm --filter @yaebal/example-pagination dev |
| session | session v2: dirty-checked saves, file storage, two independent sessions, ttl() fields, clearSession, migrations |
pnpm --filter @yaebal/example-session dev |
| simple | toml routes with a typescript handler registry | pnpm --filter @yaebal/example-simple dev |
| onboarding | first-run product tour with typed flow controls and opt-out state | pnpm --filter @yaebal/example-onboarding dev |
| rich-messages | rich block builders, attached uploads, structured drafts and readback | pnpm --filter @yaebal/example-rich-messages dev |
| panel | operator dashboard with media viewer, keyboards, callbacks and events | pnpm --filter @yaebal/example-panel dev |
| commerce-suite | shop bot: cart session, i18n, pagination, callback data, command registry, ratelimiter | pnpm --filter @yaebal/example-commerce-suite dev |
| dialog-quest | morda cockpit, scenes, prompt, conversation and session profile | pnpm --filter @yaebal/example-dialog-quest dev |
| state-machine | typed events driving transitions, a guard you can trip interactively, per-state onEnter hooks, reset() | pnpm --filter @yaebal/example-state-machine dev |
| morda-jsx | jsx dialog screens: persisted hooks, dialog data, widgets, free-text input | pnpm --filter @yaebal/example-morda-jsx dev |
| media-studio | albums, file metadata + links, file_id introspection, media cache, svg previews, entity-aware splitting | pnpm --filter @yaebal/example-media-studio dev |
| modular-router | typed define*() file-based routes, a nested guard, syncCommands, watchRoutes hot-reload |
pnpm --filter @yaebal/example-modular-router dev |
| webhook-edge | serve() on node, sequentialize + dedupe, secret token, setWebhook / getWebhookInfo |
pnpm --filter @yaebal/example-webhook-edge dev |
| runner-workers | concurrent polling plus worker thread offload | pnpm --filter @yaebal/example-runner-workers dev |
| testing-lab | bot factory with real actor-driven node:test coverage |
pnpm --filter @yaebal/example-testing-lab test |
| inline-search | core + @yaebal/contexts by hand: contextFor, inline.answer(), offset pagination, chosen-result analytics |
pnpm --filter @yaebal/example-inline-search dev |
| payments-stars | telegram stars invoices, pre-checkout approval, successful payment and refund | pnpm --filter @yaebal/example-payments-stars dev |
| mini-app | HMAC + Ed25519 initData validation, Authorization: tma backend demo, answerWebAppQuery, link builders |
pnpm --filter @yaebal/example-mini-app dev |
| audit-log | correlated, redacted-by-default logging, applyRedaction, memorySink, chatSink, auditAdmin ops commands |
pnpm --filter @yaebal/example-audit-log dev |
| package | what |
|---|---|
| @yaebal/again | awaited retry on structured retry_after / transient 5xx |
| @yaebal/ai | llm superpowers: ctx.ai.replyStream() with native draft streaming, memory, limits, any provider — plus an mcp server and agent installer for ai-assisted development |
| @yaebal/session | typed sessions: dirty-checked saves, lazy mode, multi-session, ttl fields, migrations |
| @yaebal/sklad | zero-dep storage adapters: memory (ttl/lru), redis, sqlite, cloudflare kv, file |
| @yaebal/cache | ctx.cache.get/set/wrap — ttl memoization, stale-while-revalidate, sliding expiry, negative caching, prefix invalidation, typed key catalog |
| @yaebal/feature-flags | ctx.flags.isEnabled(key)/getVariant(key) — typed boolean & A/B/n flags, telegram-native targeting, global overrides, guard/whenFlag, admin commands, LaunchDarkly/GrowthBook/env adapters |
| @yaebal/keyboard | fluent inline and reply keyboard builders |
| @yaebal/callback-data | typed callback_data pack / unpack |
| @yaebal/payments | typed invoice builder (stars / external providers), pre-checkout & successful-payment hooks, star subscriptions |
| @yaebal/mini-app | Telegram Mini Apps server protocol: HMAC + Ed25519 (third-party) initData validation, typed parser & test signer, Authorization: tma header helper, answerWebAppQuery, WebAppInfo/deep-link url generator |
| @yaebal/link-preview | fluent builder for link_preview_options |
| @yaebal/inline-results | typed builders for every InlineQueryResult / InputMessageContent variant |
| @yaebal/auto-answer | auto-clears the callback-query loading spinner, no manual answerCallbackQuery call |
| @yaebal/typing | ctx.typing(fn) keeps the "is typing…" indicator alive for an async call, no manual sendChatAction |
| @yaebal/ephemeral | ctx.replyEphemeral() — answer in a group so only the asker sees it: typed edit/delete handle over ephemeral messages, private-chat fallback, expiry policies |
| @yaebal/filters | composable, type-narrowing update filters: and/or/not, deep links, async |
| @yaebal/guards | reusable bot.guard() predicates: isAdmin, hasPermission, cached membership(), guardOr, anonymous-admin aware |
| @yaebal/fmt | html / md tagged templates with auto-escaping |
| @yaebal/rich | sendRichMessage / sendRichMessageDraft: block builder and streaming drafts |
| @yaebal/morda | dialogs engine and jsx/hooks for telegram ui |
| @yaebal/i18n | typed ctx.t (keys + params), Intl plurals, language_code detection |
| @yaebal/scenes | durable wizards: typed state, ask() validation, navigation, sub-scenes, ttl |
| @yaebal/state-machine | declarative finite-state machines: typed events, guarded transitions, onEnter/onLeave hooks |
| @yaebal/onboarding | declarative first-run tutorials with inline controls |
| @yaebal/conversation | await-style multi-step dialogs — coroutine by default, durable replay engine opt-in |
| @yaebal/prompt | ask a question, handle the next message |
| @yaebal/router | typed file-based routing — define*() routes, load-time validation, nested guards, watchRoutes hot-reload |
| @yaebal/toml | declarative toml routes with a handler registry |
| @yaebal/throttle | priority outbound scheduler with global/private/group buckets |
| @yaebal/files | inspect, link, stream and download files; local Bot API server aware |
| @yaebal/file-id | parse and re-serialize file_id / file_unique_id strings (zero deps) |
| @yaebal/ratelimiter | drop updates from users who spam |
| @yaebal/broadcast | typed broadcast jobs with storage, retry, progress and controls |
| @yaebal/cron | typed cron jobs: timezones, retries, catch-up, distributed locks, and a chat-native admin surface |
| @yaebal/panel | framework-agnostic operator panel with media, keyboards and event timeline |
| @yaebal/analytics | typed ctx.track(event, properties) with auto-capture, privacy controls, an in-chat admin surface, and pluggable sinks: posthog, plausible, sqlite, clickhouse, http |
| @yaebal/sentry | sentry error tracking — an isolation scope per update, captured handler failures, update and api breadcrumbs |
| @yaebal/otel | opentelemetry tracing — a span per update, a child span per bot api call and retry, ctx.otel.record |
| @yaebal/audit-log | correlated, redacted-by-default audit logging — sinks, formatters, filters, sampling, chatSink, auditAdmin |
| @yaebal/web | webhooks on any runtime — edge, node servers, serverless, fetch frameworks; adapters, sequentialize, dedupe, lifecycle |
| @yaebal/tgcloud | build a bot into telegram serverless handlers — one self-contained bundle per update type, bot api calls served by sdk instead of http |
| @yaebal/runner | concurrent long-polling for scale |
| @yaebal/media-group | collect albums into one handler call or a ctx.mediaGroup pass-through |
| @yaebal/media-cache | upload once, reuse the file_id — self-heals when telegram rejects it |
| @yaebal/hydrate | hydrated api results — the returned Message gains editText/delete/pin/forward/copy/react |
| @yaebal/split | long text as multiple messages — entity-aware splits, partial-failure reports |
| @yaebal/commands | one registry for handlers and the / menu — localized, scoped, diff-synced |
| @yaebal/pagination | paginated lists over any source — lazy fetch, item buttons, typed payload |
| @yaebal/preview | render telegram-style chats to svg — reply quotes, reactions, link previews, custom themes |
| @yaebal/workers | typed worker_threads pool — queueing, timeouts, aborts, crash recovery |
- packages tree contains the framework and every plugin.
- @yaebal/core contains
Bot,Composer, context, filter queries, media and hooks. - yaebal is the batteries-included meta package.
- create-yaebal scaffolds new bots with
pnpm create yaebal. - @yaebal/types contains generated telegram bot api types.
- @yaebal/contexts contains generated per-update context classes.
- @yaebal/test tests bots without calling telegram.
- docs app powers yaebal.mom.
- architecture notes explain the design, plugin catalog and roadmap.