Update handling
Start
The start method launches the process of receiving updates from Telegram for your bot. Depending on the parameters provided, the bot can use long-polling or webhook to receive events. This method initializes the bot, loads lazy plugins, and calls the onStart hook.
Signature:
start(options?): Promise<BotInfo>Parameters:
options— an object with launch settings:webhook— parameters for starting via webhook (true, a URL string, or an object with parameters).longPolling— parameters for long-polling (for example, timeouts).dropPendingUpdates— whether to drop pending updates on start.allowedUpdates— a list of update types the bot will receive.deleteWebhook— how to handle an existing webhook when starting long-polling.
IMPORTANT
Parameter details:
If you set
webhook: true, GramIO will not attempt to set the webhook itself — it assumes you have already configured it. In this case, the bot will simply start receiving updates via the existing webhook.The
deleteWebhookparameter controls what to do with an existing webhook when starting long-polling:- If
deleteWebhook: true, the bot will always delete the webhook before starting long-polling. - If
deleteWebhook: "on-conflict-with-polling", the webhook will only be deleted if it interferes with starting long-polling (when Telegram responds togetUpdateswith a conflict error). - If not specified, the default behavior (
on-conflict-with-polling) is used.
- If
import { Bot } from "gramio";
const bot = new Bot(process.env.BOT_TOKEN)
.command("start", (ctx) => ctx.send("Hi!"))
.onStart(console.log);
await bot.start({
longPolling: { timeout: 10 },
dropPendingUpdates: true,
});How it works:
- If webhook is not specified, long-polling is started.
- If webhook is specified, the webhook is set up and the bot starts receiving updates via HTTP.
- Calls the
onStarthook. - You can drop old updates on start.
allowedUpdates — control which updates Telegram sends you
By default, Telegram excludes three update types from getUpdates/setWebhook unless you explicitly list them in allowed_updates: chat_member, message_reaction, and message_reaction_count. Forgetting to opt in is an evergreen footgun — handlers register fine, but the updates never arrive.
GramIO 0.9 fixes this in four ways. Pick whichever fits your bot:
import { Bot, AllowedUpdatesFilter } from "gramio";
const bot = new Bot(token)
.on("chat_member", chatMemberHandler)
.on("message_reaction", reactionHandler);
// 1. Default — no `allowedUpdates` arg.
// GramIO scans your handlers and auto opt-ins to chat_member /
// message_reaction / message_reaction_count when registered.
await bot.start();
// 2. Strict mode — only request the update types your handlers register for.
// Pass the literal string "strict".
await bot.start({ allowedUpdates: "strict" });
// 3. Explicit — fluent immutable Array<AllowedUpdateName>.
await bot.start({
allowedUpdates: AllowedUpdatesFilter.only("message", "callback_query"),
});
// 4. Default set with extras / exclusions.
await bot.start({
allowedUpdates: AllowedUpdatesFilter.default
.add("chat_member")
.except("poll", "poll_answer"),
});Available factories — all return an immutable filter that extends Array<AllowedUpdateName>, so it can be passed wherever allowedUpdates is expected:
| Factory / method | Description |
|---|---|
AllowedUpdatesFilter.all | Every update type, opt-in types included |
AllowedUpdatesFilter.default | Telegram's default set (opt-in types excluded) |
AllowedUpdatesFilter.only(...types) | Exactly the listed types |
.add(...types) | Returns a new filter with these types added (deduplicated) |
.except(...types) | Returns a new filter with these types removed |
NOTE
The auto-derivation in modes 1 and 2 only counts handlers registered via .on(eventName, …), the trigger shorthands (command, callbackQuery, hears, reaction, inlineQuery, chosenInlineResult, startParameter), and event-specific derive. Filter-style .on(filterFn, handler) and global .use() middleware don't declare a specific event, so they're not detected — .add() the relevant types manually if you rely on those.
Stop
The stop method stops receiving updates and gracefully shuts down all internal bot processes. The onStop hook is called, and the update queue is cleared.
Signature:
stop(timeout?): Promise<void>Parameters:
timeout— the time to wait for the update queue to finish processing (default is 3000 ms).
Example usage:
await bot.stop();How it works:
- Stops long-polling or webhook (if it was running).
- Waits for all current updates to finish processing.
- Calls the
onStophook.
Context
Listen to all events
bot.use((context, next) => {
// ...
});Listen only to specific events
bot.on("message", (context, next) => {
// ...
});
// or
bot.on(["message", "callback_query"], (context, next) => {
// ...
});You can read API Reference for contexts here.
Context injection
Derive
Derive allows you to inject what's you want to context with access to existing context data and type-safety. A handler will be called every update.
Global derive
import { Bot } from "gramio";
const bot = new Bot(process.env.BOT_TOKEN as string)
.derive((context) => {
return {
key: 1,
};
})
.on("message", (context) => {
context.key;
})
.use((context, next) => {
context.key;
return next();
})
.on("callback_query", (context) => {
context.key;
});Scoped derive
You can scope the derive to specific update (or updates) with full type support.
import { Bot } from "gramio";
// Simple example
export function findOrRegisterUser(id: number) {
return {} as Promise<{ id: number; name: string; balance: number }>;
}
const bot = new Bot(process.env.BOT_TOKEN as string)
.derive(["message", "callback_query"], async (context) => {
const fromId = context?.from?.id;
if(!fromId) throw new Error("No from id");
return {
fromId,
}
})
.derive("message", async (context) => {
const user = await findOrRegisterUser(context.fromId);
return {
user,
};
})
.on("message", (context) => {
context.user.; //
})
.use((context, next) => {
if (context.is("message")) context.user;
return next();
})
.on("callback_query", (context) => {
context.user; context.fromId
});Decorate
With decorate you can inject static values and it will be injected to contexts only when decorate called (so it will not evaluate every update).
import { Bot } from "gramio";
const bot = new Bot(process.env.BOT_TOKEN as string)
.decorate("TEST", "hi!! you're cute" as const)
.decorate({
key: "value",
key2: "value",
})
.use((context) => {
context.TEST;
//
context.k; });