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?.firstName ?? "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?.firstName ?? "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";
// 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();Rich Messages Land, Ephemeral Replies Go Private, and Bot API 10.3 Spans the Stack — May 31 – August 25, 2026
Telegram Bot API 10.1–10.3 now runs through the whole GramIO stack: private ephemeral replies, structured Rich Messages, recursive uploads, stoppable drafts, Community and subscription contexts, disabled buttons, force reply, rich JSX, and realistic testing utilities. The release train targets gramio v0.14 after the corrected @gramio/types 10.3.1 declaration set reaches npm and JSR.
npm create gramio@latest ./botyarn create gramio ./botpnpm create gramio@latest ./botbun create gramio@latest ./bot