Skip to content

GramIOBuild Telegram bots, the right way.

Type-safe · Multi-runtime · Extensible

See it in action

ts
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();
ts
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();
ts
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();
ts
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();
ts
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();

Latest Updates

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/.

All changelogs →

Get started

bash
npm create gramio@latest ./bot
bash
yarn create gramio@latest ./bot
bash
pnpm create gramio@latest ./bot
bash
bun create gramio@latest ./bot

Full get started guide →