Zero to bot in 30 seconds
npm create gramio@latest scaffolds a full project — TypeScript, ORM, linting, plugins, Docker — your choice.
Type-safe · Multi-runtime · Extensible
import { Bot, format, bold, link } from "gramio";
const bot = new Bot(process.env.BOT_TOKEN as string)
.command("start", (ctx) =>
ctx.send(
format`${bold`Hello, ${ctx.from?.first_name ?? "stranger"}!`}
Welcome to ${link("GramIO", "https://gramio.dev")} — build Telegram bots the right way.`
)
)
.onError(({ kind, error }) => console.error(kind, error))
.start();import { Bot, InlineKeyboard, CallbackData } from "gramio";
const voteData = new CallbackData("vote").string("choice");
const bot = new Bot(process.env.BOT_TOKEN as string)
.command("poll", (ctx) =>
ctx.send("Do you like GramIO?", {
reply_markup: new InlineKeyboard()
.text("Yes ✅", voteData.pack({ choice: "yes" }))
.text("Absolutely 🔥", voteData.pack({ choice: "yes2" })),
})
)
.callbackQuery(voteData, (ctx) => {
ctx.queryData.choice; // ^? string
return ctx.answer();
})
.start();import { defineI18n, type LanguageMap, type ShouldFollowLanguage } from "@gramio/i18n";
import { Bot, format, bold } from "gramio";
const en = {
welcome: (name: string) => format`Hello, ${bold(name)}!`,
items: (n: number) => `You have ${n} item${n === 1 ? "" : "s"}`,
} satisfies LanguageMap;
const ru = {
welcome: (name: string) => format`Привет, ${bold(name)}!`,
items: (n: number) => `У вас ${n} предмет${n === 1 ? "" : "ов"}`,
} satisfies ShouldFollowLanguage<typeof en>; // enforces matching keys & signatures
const i18n = defineI18n({ primaryLanguage: "en", languages: { en, ru } });
const bot = new Bot(process.env.BOT_TOKEN as string)
.derive((ctx) => ({
t: i18n.buildT(ctx.from?.language_code ?? "en"),
}))
.command("start", (ctx) =>
ctx.send(ctx.t("welcome", ctx.from?.first_name ?? "stranger"))
)
.start();import { Bot } from "gramio";
import { Scene, scenes } from "@gramio/scenes";
import { redisStorage } from "@gramio/storage-redis";
import { Redis } from "ioredis";
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) => {
if (ctx.scene.step.firstTime) return ctx.send(`Hi, ${ctx.scene.state.name}! Your email?`);
return ctx.scene.update({ email: ctx.text });
})
.step("message", (ctx) =>
ctx.send(`Registered: ${ctx.scene.state.name} — ${ctx.scene.state.email} ✅`)
);
const bot = new Bot(process.env.BOT_TOKEN as string)
.extend(scenes([registerScene], { storage: redisStorage(new Redis()) }))
.command("register", (ctx) => ctx.scene.enter(registerScene))
.start();import { Bot } from "gramio";
import { Composer } from "@gramio/composer";
// Shared middleware — typed context available in every module
const withUser = new Composer()
.derive(async (ctx) => ({
user: await db.getUser(ctx.from?.id ?? 0),
}))
.as("scoped");
// Feature module — guard + commands in one chain
const adminRouter = new Composer()
.extend(withUser)
.guard((ctx) => ctx.user.role === "admin")
.command("ban", (ctx) => ctx.send(`Banned by ${ctx.user.name}`))
.command("stats", (ctx) => ctx.send("Stats..."));
const bot = new Bot(process.env.BOT_TOKEN as string)
.extend(withUser) // ctx.user — typed everywhere below
.extend(adminRouter) // withUser inside → deduplicated, runs once
.command("profile", (ctx) => // no guard — runs for every user
ctx.send(`Hello, ${ctx.user.name}!`)
// ^? fully typed
)
.start();Testing Gets Richer, CallbackData Gets Safer, TypeScript API Reference Launches — February 18–22, 2026
@gramio/test v0.3.0 adds 9 new methods: edit messages, forward, pin, send media groups, and click inline buttons by label text. @gramio/callback-data v0.1.0 ships safeUnpack() — never crash on stale buttons. The full TypeScript API reference launches at /api/.
npm create gramio@latest ./botyarn create gramio@latest ./botpnpm create gramio@latest ./botbun create gramio@latest ./bot