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?.firstName ?? "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?.firstName ?? "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";

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

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.

All changelogs →

Get started

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

Full get started guide →