Skip to main content

sdk-patterns

grammY (Telegram bot framework) code patterns - creating a Bot, reading the Context (ctx.from, ctx.chat), handlers with bot.command, bot.hears and bot.on, ctx.reply versus ctx.api or bot.api for other chats and scheduled posts, editing and deleting messages, bot.init, and typing a custom Context with plugin flavors (session). Use for how-do-I-write-this questions in grammY code. For filter queries use filter-queries, for keyboards commands-and-keyboards, for session storage sessions.

Source facts

Repository
Agents-Store/codex-plugins
Last source activity
November 1, 2026 at 02:46
Detected SKILL.md language
English
Stars
0
Forks
0

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

File Explorer
2 files

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
sdk-patterns
description
grammY (Telegram bot framework) code patterns - creating a Bot, reading the Context (ctx.from, ctx.chat), handlers with bot.command, bot.hears and bot.on, ctx.reply versus ctx.api or bot.api for other chats and scheduled posts, editing and deleting messages, bot.init, and typing a custom Context with plugin flavors (session). Use for how-do-I-write-this questions in grammY code. For filter queries use filter-queries, for keyboards commands-and-keyboards, for session storage sessions.
# grammY — SDK Patterns Idiomatic code patterns for the everyday surface of grammY: creating a `Bot`, handling updates via the `Context` object, calling the Bot API, and typing context flavors. ## Create a Bot ```typescript import { Bot } from "grammy"; const bot = new Bot(process.env.BOT_TOKEN!); ``` The single positional argument is your BotFather token. A second optional argument lets you pass `botInfo`, `client` options (timeouts, base URL), or a custom `ContextConstructor`. ## The Context object (ctx) Every middleware receives one argument — `ctx` — which represents *the current update being processed*. It bundles: | Property | What it is | |---|---| | `ctx.update` | The full Telegram `Update` object | | `ctx.message`, `ctx.editedMessage`, `ctx.channelPost` … | Aliases for the matching update type | | `ctx.from` | The sender's `User` | | `ctx.chat` | The current `Chat` | | `ctx.callbackQuery` | Present for inline-button presses | | `ctx.api` | Direct Bot API client — `ctx.api.sendMessage(chatId, text)` | | `ctx.me` | Info about the bot itself (filled by `bot.init()`) | ### Reply aliases — always prefer these Instead of `ctx.api.sendMessage(ctx.chat.id, "hi")`, write `ctx.reply("hi")`. grammY provides one alias per Bot API send method, automatically using the current chat id: ```typescript await ctx.reply("text"); await ctx.replyWithPhoto("https://example.com/img.png", { caption: "Look" }); await ctx.replyWithDocument(new InputFile("./report.pdf")); await ctx.replyWithAudio("file_id_from_earlier"); await ctx.replyWithVideo(new InputFile("./demo.mp4")); await ctx.replyWithSticker("CAACAgIA..."); await ctx.replyWithChatAction("typing"); await ctx.editMessageText("updated text"); await ctx.deleteMessage(); await ctx.answerCallbackQuery({ text: "noted" }); await ctx.answerInlineQuery([...]); ``` ### Use ctx.api for *other* chats `ctx.reply` only targets the chat the update came from. To send a message to a different chat (broadcast, admin notification, scheduled post): ```typescript await ctx.api.sendMessage(adminChatId, "alert: user reported abuse"); ``` ## Register handlers ```typescript bot.command("start", (ctx) => ctx.reply("Welcome")); bot.command(["help", "h"], (ctx) => ctx.reply("Help text")); // multiple aliases bot.hears("ping", (ctx) => ctx.reply("pong")); // plain text match bot.hears(/^echo (.+)$/i, (ctx) => ctx.reply(ctx.match[1])); // regex with capture bot.on("message:text", (ctx) => { /* … */ }); // filter query bot.on("callback_query:data", (ctx) => { /* … */ }); bot.on(":file", async (ctx) => { const file = await ctx.getFile(); // returns File obj with file_path console.log(file.file_path); }); ``` For the full filter-query DSL, open `filter-queries`. ## TypeScript context flavors The base `Context` type is augmented by plugins. Whenever you use a plugin that adds methods or properties to `ctx`, *combine* its flavor with `Context` and pass it as a generic to `Bot<C>`: ```typescript import { Bot, Context, SessionFlavor, session } from "grammy"; import { ConversationFlavor, conversations } from "@grammyjs/conversations"; import { HydrateFlavor, hydrate } from "@grammyjs/hydrate"; interface SessionData { counter: number } type MyContext = HydrateFlavor< ConversationFlavor<Context & SessionFlavor<SessionData>> >; const bot = new Bot<MyContext>(process.env.BOT_TOKEN!); bot.use(session({ initial: () => ({ counter: 0 }) })); bot.use(conversations()); bot.use(hydrate()); ``` Order of flavors matches order of `bot.use()` installation. Forgetting a flavor is the #1 source of `Property 'session' does not exist on type 'Context'` errors. ## API options object Send methods accept an optional `other` object with all the Bot API parameters that aren't chat_id / text / file: ```typescript await ctx.reply("Bold *italic* `code`", { parse_mode: "MarkdownV2", disable_notification: true, reply_parameters: { message_id: ctx.msg.message_id }, link_preview_options: { is_disabled: true }, }); ``` ## Initialization (optional, advanced) By default `bot.start()` calls `bot.init()` internally. Call it manually only when: - You need `ctx.me` available before the first update (e.g. serverless cold starts — see `deployment-hosting`). - You're constructing several `Bot` instances and want to skip duplicate `getMe` calls by passing `{ botInfo }` from a cache. ```typescript await bot.init(); // populates bot.botInfo console.log(bot.botInfo); // { id, username, ... } ``` ## Graceful shutdown ```typescript process.once("SIGINT", () => bot.stop()); process.once("SIGTERM", () => bot.stop()); ``` `bot.stop()` waits for in-flight updates to finish before exiting. ## Bot.api — call any Bot API method `bot.api` (and `ctx.api`) is a typed proxy for every Bot API method, plus options: ```typescript await bot.api.setMyCommands([ { command: "start", description: "Begin" }, { command: "help", description: "Show help" }, ]); await bot.api.setMyDescription("This bot helps you …"); await bot.api.getMe(); await bot.api.deleteWebhook({ drop_pending_updates: true }); ``` The full method list is curated in the `api-reference` skill (manual-trigger).
View on GitHub