Plugins Overview
GramIO's plugin system is built on the same .extend() mechanism that powers the whole framework. Every plugin composes cleanly — plugins can add context properties, register handlers, and hook into the bot lifecycle, all with full type propagation to every downstream handler.
const bot = new Bot(token)
.extend(session()) // ctx.session is now typed
.extend(scenes([...])) // ctx.scene is now typed
.extend(i18n()) // ctx.i18n is now typed
.on("message", (ctx) => {
ctx.session; // ✅
ctx.scene; // ✅
ctx.i18n; // ✅
});Official Plugins
Scenes — @gramio/scenes
Multi-step conversation flows. Build wizards, onboarding sequences, and multi-step forms where each step waits for the user's next message.
- Steps are typed: state flows from step to step
- Enter a scene from any handler; exit or jump to any step
- Works with session for persistent state across restarts
const registerScene = new Scene("register")
.step("message", (ctx) => {
if (ctx.scene.step.firstTime) return ctx.send("What's your name?");
return ctx.scene.update({ name: ctx.text });
})
.step("message", (ctx) => ctx.send(`Welcome, ${ctx.scene.state.name}!`));Session — @gramio/session
Per-user persistent state across messages. Reads and writes are transparent — just use ctx.session like a plain object.
- In-memory by default; plug in any storage adapter
- Initial state factory is typed end-to-end
- Works great with Scenes for storing scene state
const bot = new Bot(token).extend(
session({ initial: () => ({ count: 0, name: "" }) }),
);
bot.command("count", (ctx) => {
ctx.session.count++;
return ctx.send(`Count: ${ctx.session.count}`);
});I18n — @gramio/i18n
TypeScript-native internationalization with full compile-time type safety — no codegen, no .ftl files. Define translations as plain TS functions; ShouldFollowLanguage ensures every locale matches the primary language's keys and signatures.
- Translation values are plain functions — use
format/bold/etc. directly - Per-user locale from any source (database, session, Telegram
language_code) - Auto-completes keys and argument types in your editor
- Fluent
.ftlsupport also available if you need advanced pluralization
import {
defineI18n,
type LanguageMap,
type ShouldFollowLanguage,
} from "@gramio/i18n";
import { format, bold } from "gramio";
const en = {
welcome: (name: string) => format`Hello, ${bold(name)}!`,
} satisfies LanguageMap;
const ru = {
welcome: (name: string) => format`Привет, ${bold(name)}!`,
} satisfies ShouldFollowLanguage<typeof en>;
const i18n = defineI18n({ primaryLanguage: "en", languages: { en, ru } });
const bot = new Bot(token)
// build a per-request t() scoped to the user's locale
.derive((ctx) => ({
t: i18n.buildT(ctx.from?.language_code ?? "en"),
}));
bot.command("start", (ctx) =>
ctx.send(ctx.t("welcome", ctx.from?.firstName ?? "stranger")),
);Autoload — @gramio/autoload
File-based handler registration. Drop a file in src/commands/ and it registers automatically — no manual imports, no central registry.
- Glob pattern configurable (
src/commands/**/*.tsby default) - Each file exports a default function
(bot: Bot) => Bot - Works great with large bots split across many feature files
// src/index.ts
const bot = new Bot(token).extend(autoload());
// src/commands/start.ts
export default (bot: Bot) => bot.command("start", (ctx) => ctx.send("Hi!"));Prompt — @gramio/prompt
Awaitable single-reply prompts. Ask a question and await the user's response in a linear, readable flow — no need for state machines.
- Pauses execution and resumes when the user replies
- Optional timeout
- Pairs naturally with Scenes for complex flows
bot.command("rename", async (ctx) => {
const reply = await ctx.prompt("What's your new name?");
await ctx.send(`Name updated to: ${reply.text}`);
});Auto Retry — @gramio/auto-retry
Automatic handling of Telegram rate limits. When Telegram responds with retry_after, the plugin waits and retries transparently — no changes to your code needed.
- Handles
Too Many Requests(429) errors automatically - Configurable max retries and delay strategy
- Works for broadcasts, high-throughput bots
const bot = new Bot(token).extend(autoRetry());
// All API calls now retry automatically on rate limitAuto Answer Callback Query — @gramio/auto-answer-callback-query
Never forget to acknowledge a callback query. Automatically answers all callback_query updates that weren't already answered — Telegram requires a response within 10 seconds or shows an error.
const bot = new Bot(token).extend(autoAnswerCallbackQuery());
// Now every inline button click is auto-acknowledgedMedia Cache — @gramio/media-cache
Upload a file once, reuse its file_id forever. Transparently caches file_id for uploaded media so Telegram doesn't re-upload the same file on every send.
- Works with any storage backend
- Reduces upload latency for repeated sends
- Transparent — no changes to send calls
Media Group — @gramio/media-group
Aggregate media group updates. Telegram sends media_group messages as individual updates. This plugin buffers them and delivers the full group to your handler as one event.
bot.on("message", (ctx) => {
if (ctx.mediaGroup) {
// ctx.mediaGroup is the full array of media group items
ctx.send(`Got ${ctx.mediaGroup.length} photos`);
}
});Pagination — @gramio/pagination
Typed paginated inline keyboards. Build multi-page lists with type-safe page data, next/prev buttons, and clean handler logic.
const paginationData = new CallbackData("page").number("offset");
const keyboard = pagination({
data: paginationData,
total: items.length,
pageSize: 5,
current: offset,
});Views — @gramio/views
Reusable message templates. Define a message (text + keyboard + options) once and render it anywhere, keeping UI and logic separate.
JSX — @gramio/jsx
Write bot messages using JSX. Use familiar React-like syntax to compose rich messages with formatting entities.
bot.command("start", (ctx) =>
ctx.send(<b>Hello!</b> <i>Welcome to my bot.</i>)
);PostHog — @gramio/posthog
Analytics for your bot. Track user commands, events, and behavior in PostHog with automatic event capture.
OpenTelemetry — @gramio/opentelemetry
Distributed tracing. Instrument your bot with OpenTelemetry spans for full observability — traces every API call, handler, and hook.
Sentry — @gramio/sentry
Error monitoring. Automatically captures unhandled errors and sends them to Sentry with context (update type, user ID, etc.).
Split — @gramio/split
Multi-instance routing. Split incoming updates across multiple bot instances or workers for high-throughput scenarios.
Which plugin do I need?
| Scenario | Use |
|---|---|
| Multi-step forms / wizards | Scenes |
| Store user preferences / state | Session |
| Multi-language bot | I18n |
| Large bot with many command files | Autoload |
| Ask a question, await response | Prompt |
| Handle rate limits automatically | Auto Retry |
| Avoid unacknowledged callback errors | Auto Answer Callback Query |
| Avoid re-uploading the same files | Media Cache |
| Handle album/media group messages | Media Group |
| Paginated lists with inline buttons | Pagination |
| Track user events and funnels | PostHog |
| Error monitoring in production | Sentry |
| Distributed tracing / observability | OpenTelemetry |
Stacking plugins
Plugins compose — extend your bot with as many as you need, and the types stay correct throughout:
const bot = new Bot(token)
.extend(session({ initial: () => ({ locale: "en", name: "" }) }))
.extend(i18n())
.extend(scenes([registerScene]))
.extend(autoAnswerCallbackQuery())
.extend(autoRetry());
// In any handler: ctx.session, ctx.i18n, ctx.scene — all typed ✅Write your own plugin
Any Composer can be packaged as a plugin. The Plugin class adds a name and a clean .extend() interface.
import { Plugin } from "gramio";
export const myPlugin = new Plugin("my-plugin").derive((ctx) => ({
isAdmin: ctx.from?.id === ADMIN_ID,
}));
// In consuming bot:
bot.extend(myPlugin);
// ctx.isAdmin is now typed everywhere ✅