Claude
Skills
Sign in
Back

gramio

Included with Lifetime
$97 forever

Invoke for ANY Telegram bot code — `gramio`/`@gramio/*` imports, `new Bot()`, `bot.command`/`bot.callbackQuery`/`bot.on`/`bot.updates.on`, scenes/FSM, inline & reply keyboards, message entities, file uploads, sessions, plugins, webhooks vs long-polling, Telegram Stars, Mini Apps (TMA), broadcasting, Docker. Type-safe TypeScript framework (Node.js/Bun/Deno) with full Bot API coverage. Also covers migrations from Telegraf/grammY/puregram/node-telegram-bot-api. When delegating to a subagent, pass relevant reference files (callback-data, scenes, formatting, context, middleware-routing, ux-patterns) inline — this skill does not auto-load in subagent sessions.

Design

What this skill does


# GramIO

GramIO is a modern, type-safe Telegram Bot API framework for TypeScript. It runs on **Node.js**, **Bun**, and **Deno** with full Bot API coverage, a composable plugin system, and first-class TypeScript support.

## When to Use This Skill

- Creating or modifying Telegram bots
- Setting up bot commands, callbacks, inline queries, or reactions
- Building keyboards (reply, inline, remove, force reply)
- Formatting messages with entities (bold, italic, code, links, mentions)
- Uploading/downloading files and media
- Managing user sessions or multi-step conversation flows (scenes)
- Writing custom plugins
- Configuring webhooks or long polling
- Handling payments with Telegram Stars
- Broadcasting messages with rate limit handling
- Building Telegram Mini Apps (TMA) with backend auth
- Containerizing bots with Docker
- Using standalone `@gramio/types` for custom Bot API wrappers
- Writing and publishing custom plugins
- Migrating bots from puregram, grammY, Telegraf, or node-telegram-bot-api to GramIO

## Quick Start

```bash
npm create gramio bot-name
cd bot-name
npm run dev
```

## Basic Pattern

```typescript
import { Bot } from "gramio";

const bot = new Bot(process.env.BOT_TOKEN as string)
    .command("start", (context) => context.send("Hello!"))
    .onStart(({ info }) => console.log(`@${info.username} started`))
    .onError(({ context, kind, error }) => console.error(`[${kind}]`, error));

bot.start();
```

## Introspection Tools

This skill ships four Node.js scripts under `tools/` that parse the **installed** `@gramio/*` and `gramio` packages on disk. Prefer these over URL-fetching or loading the telegram-api-index — each call returns one focused, version-accurate signature instead of a wall of docs.

Run from the user's project root (where `node_modules/` lives). Tools print to stdout; errors and auto-correct hints go to stderr.

| Tool | Use it when |
|------|-------------|
| `tools/get-bot-api-method.mjs <name>` | You need the signature of a Bot API method (e.g. `sendMessage`, `createChatInviteLink`) — returns JSDoc + params/return type from `@gramio/types`. `--list` shows all methods, `--search <term>` filters by name/description. |
| `tools/get-bot-api-type.mjs <name>` | You need a Telegram type definition (e.g. `Message`, `ChatInviteLink`). Accepts short (`Message`) or full (`TelegramMessage`) names. Same `--list` / `--search` flags. |
| `tools/get-context-getter.mjs <ClassName>` | You need to know what getters/methods a context exposes (`MessageContext`, `CallbackQueryContext`, `User`, `Chat`). Add `--deep` to pull in mixins + merged interfaces recursively. `--search <name>` finds every class that exposes a given getter/method (e.g. `firstName` lives on `User`, `Chat`, `Contact`, `SharedUser`). |
| `tools/get-plugin.mjs <name>` | You need a plugin's entry function signature + what it derives onto context (e.g. `session`, `scenes`, `i18n`). `--list` shows every installed `@gramio/*` package. |

```bash
node tools/get-bot-api-method.mjs sendMessage
node tools/get-bot-api-type.mjs InlineKeyboardMarkup
node tools/get-context-getter.mjs MessageContext --deep
node tools/get-context-getter.mjs --search chatId
node tools/get-plugin.mjs session
```

The scripts fuzzy-match (`sendMesage` → `sendMessage`) and suggest alternatives on miss. They require the relevant package to be installed — if not, they print the exact `npm install` hint.

## Critical Concepts

1. **Callback routing — NEVER parse callback data manually.** `CallbackData.pack()` produces a **6-character hash prefix** (sha1-base64url of the schema name) + serialized payload — NOT a literal prefix like `"nav:"`. Checks like `ctx.data.startsWith("nav:")` always fail at runtime. Use one of these four patterns, picked by shape of data:

   ```typescript
   // Fixed string → exact string match
   bot.callbackQuery("refresh", (ctx) => ctx.answer("Refreshed"));

   // Pattern / variable slug → RegExp with capture groups
   bot.callbackQuery(/^user_(\d+)$/, (ctx) => {
       const [, id] = ctx.match!;
       ctx.answer(`User ${id}`);
   });

   // Structured data → CallbackData schema (preferred for multi-field payloads)
   import { CallbackData } from "gramio";
   const nav = new CallbackData("nav").enum("to", ["home", "history", "admin"]);
   bot.callbackQuery(nav, (ctx) => {
       ctx.queryData.to; // "home" | "history" | "admin" — fully typed
   });

   // Stale-safe unpack (when inline keyboard may outlive a schema change)
   const result = nav.safeUnpack(ctx.data!);
   if (!result.success) return ctx.answer("Button expired");
   result.data.to; // typed
   ```

   ```typescript
   // ❌ NEVER — hashed prefix means string compare won't match,
   //    and you lose full type safety.
   if (ctx.data?.startsWith("nav:")) {
       const [, to] = ctx.data.slice(4).split(":");
       // ...
   }
   ```

   See [callback-data](references/callback-data.md) and [middleware-routing](references/middleware-routing.md) for overlapping-handler behavior across plugins.

2. **Method chaining** — handlers, hooks, and plugins chain via `.command()`, `.on()`, `.extend()`, etc. Order matters: when two handlers match the same update, the **first-registered** one wins unless it calls `next()`. See [middleware-routing](references/middleware-routing.md).

3. **Type-safe context** — context is automatically typed based on the update type. Use `context.is("message")` for type narrowing in generic handlers. After `.derive()`/`.decorate()`/`.extend(plugin)`, new fields appear on the inferred context type automatically — **never** cast with `ctx as unknown as { myField }`. Export the bot's context type and reuse it (see [context](references/context.md) → "Context typing after derive").

4. **Context getters — always camelCase; never touch `ctx.payload` or `ctx.update.*`** — every Telegram field is exposed as a camelCase getter (`ctx.from`, `ctx.firstName`, `ctx.chatId`, `ctx.messageId`, `ctx.text`, `ctx.data`, `ctx.queryData`). Both `ctx.payload` AND `ctx.update` are raw snake_case internal objects — treat them as off-limits in handler code.

5. **Plugin system** — `new Plugin("name").derive(() => ({ ... }))` adds typed properties to context. Register via `bot.extend(plugin)`.

6. **Hooks lifecycle** — onStart → (updates with onError) → onStop. API calls: preRequest → call → onResponse/onResponseError.

7. **Error suppression** — `bot.api.method({ suppress: true })` returns error instead of throwing.

8. **Lazy plugins** — async plugins (without `await`) load at `bot.start()`. Use `await` for immediate loading.

9. **Derive vs Decorate** — `.derive()` runs per-update (computed), `.decorate()` injects static values once.

10. **Formatting — four critical rules** (read [formatting](references/formatting.md) before writing any message text):
    - **Never use `parse_mode`** — `format` produces `MessageEntity` arrays, not HTML/Markdown strings. Adding `parse_mode: "HTML"` or `"MarkdownV2"` will break the message. GramIO passes entities automatically.
    - **Never use native `.join()`** on arrays of formatted values — it calls `.toString()` on each `Formattable`, silently destroying all styling. Always use the `join` helper: `join(items, (x) => bold(x), "\n")`.
    - **Always wrap styled content in `format\`\``** when composing or reusing — embedding a `Formattable` in a plain template literal (`` `${bold`x`}` ``) strips all entities. Use `format\`${bold\`x\`}\`` instead.
    - **Never call `.toString()` on a `FormattableString`** — pass it directly as the `text:`/`caption:` param to `send`, `editMessageText`, `editMessageCaption`, etc. Calling `.toString()` strips all entities. This is the #1 reason magic-links and formatted entities "stop working" after an edit.

11. **Scenes — step semantics and update-type filtering** — `context.scene.step.go(N)` and `context.scene.step.next()` run the scene's middleware chain **immediately**, but each `.step(updateName, handler)` filters by `context.is(updateName)`. If 
Files: 80
Size: 463.9 KB
Complexity: 76/100
Category: Design

Related in Design