Guides writing grammY Telegram bot handlers, middleware, and plugins. Use when creating or modifying bot commands, inline queries, callback queries, message handlers, or middleware.
100
100%
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Advisory
Suggest reviewing before use
Each file is a Composer scoped to a business domain (one file per domain):
import { Composer } from "grammy";
import type { Context } from "@/types";
const composer = new Composer<Context>();
const privateChat = composer.chatType("private");
const groupChat = composer.chatType(["group", "supergroup"]);
// ... handlers ...
export default composer;Register on the error boundary in src/index.ts:
boundary.use(featureHandler);NEVER use if + early-return inside handlers. Instead, split every case into separate .filter() chains with mutually exclusive predicates. This applies to .command(), .on(), and all handler methods.
// CORRECT: Validation handler responds when precondition fails
privateChat.command("find").filter(
(ctx) => ctx.message.reply_to_message?.photo === undefined,
async (ctx) => {
await ctx.reply("Please reply to a photo with /find command.");
},
);
// CORRECT: Processing handler runs when precondition is met
privateChat.command("find").filter(
(ctx) => ctx.message.reply_to_message?.photo !== undefined,
async (ctx) => {
// Process the photo...
},
);// WRONG: Mixed validation and processing
privateChat.command("find", async (ctx) => {
if (!ctx.message.reply_to_message?.photo) {
await ctx.reply("Please reply to a photo.");
return;
}
// Process...
});.on() with .filter() for message routingUse grammY's filter query language with .on() to narrow update types, then chain .filter() for custom predicates:
// Filter text messages that are URLs
feature.on(":text").filter(
(ctx) => ctx.msg.text.startsWith("https://"),
async (ctx) => {
/* handle link */
},
);
// Filter messages by sender context
groupChat.on("message").filter(
(ctx) => shouldReplyToMessage(ctx, ctx.message),
async (ctx) => {
/* generate AI reply */
},
);Examples at https://grammy.dev/guide/filter-queries
The project composes context flavors in src/types.ts:
type Context = FileFlavor<HydrateFlavor<BaseContext & ExtendedContext>>;ExtendedContext adds logger, user, userChat, userChatMember, and currentMessageAttachments. These are attached via middleware. Do not re-declare them.
privateChat, groupChat).filter() chains, no if + early-returnConsult https://grammy.dev for unfamiliar APIs and https://core.telegram.org/bots/api for the Telegram Bot API.
06a2962
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.