Bot API 10.0 Lands Ecosystem-Wide & Scenes Become Composers
May 8 – 31, 2026
Two headline stories this cycle. First, Telegram Bot API 10.0 rolls out across the entire stack — @gramio/types v10, @gramio/contexts v0.7, @gramio/files v0.5, @gramio/format v0.8, and gramio v0.10 — bringing live photos, guest messages, poll media, message-reaction permissions, and bot access settings. Second, @gramio/scenes v0.7 ships the long-promised scene-as-composer redesign: every Scene is now a full EventComposer, each step is its own sub-composer with .enter/.exit/.fallback, scenes compose into reusable step modules, and there's a new onExit lifecycle hook. Plus @gramio/onboarding v0.2 makes ctx.onboarding flow through bot.extend() with zero ceremony.
Bot API 10.0 — Live Photos, Guest Messages, Poll Media
@gramio/types v10.0.0 regenerates the full type surface for Telegram Bot API 10.0, and @gramio/contexts v0.7.0 lights it up with first-class context getters and mixins.
New attachment: live photos
A new LivePhotoAttachment plus Message.livePhoto, ExternalReplyInfo.livePhoto, and a sendLivePhoto mixin:
bot.on("message", (ctx) => {
if (ctx.livePhoto) return ctx.send("Nice live photo!");
});
await bot.api.sendLivePhoto({ chat_id, live_photo: "/path/to/live.mov" });Guest messages
Bot API 10.0 introduces guest messages — a new guest_message update where users interact with your bot without a regular chat. GramIO maps it to MessageContext, exposes Message.guestQueryId / guestBotCallerUser / guestBotCallerChat, adds MessageContext.answerGuestQuery(), and User.supportsGuestQueries(). The framework-level bot.guestQuery(...) shorthand lands in gramio v0.10 (below).
Polls gain media
Polls and their options can now carry media. Poll gains media, explanationMedia, membersOnly, and countryCodes; PollOption gains media. sendPoll accepts per-option media, and correctOptionId is now correctOptionIds (an array) per the API change.
Reactions you can delete, and react-permissions
NodeMixingainsdeleteReactionanddeleteAllReactions.ChatPermissions.canReactToMessagesandChatMember.canReactToMessages()expose the new react-permission bit.ChatMemberControlMixinaddsgetUserPersonalChatMessages,getManagedBotAccessSettings, andsetManagedBotAccessSettings; newBotAccessSettings/SentGuestMessagestructures back them.
@gramio/files v0.5.0 & @gramio/format v0.8.0 regenerated
@gramio/files switched its generator to @gramio/schema-parser (fetching the live schema instead of a vendored JSON) and regenerated MEDIA_METHODS — picking up sendLivePhoto, sendPoll, setMyProfilePhoto, plus extra cover/photo/live-photo fields on sendVideo, sendMediaGroup, sendPaidMedia, and editMessageMedia. @gramio/format regenerated its mutator for sendLivePhoto, answerGuestQuery, and the new explanation_media / per-option media formattables. Both bumped their @gramio/types peer to ^10.0.0.
gramio v0.10.0 — Guest Queries, CallbackData Inline Results, Updates Fix
Bot API 10.0 support
gramio v0.10.0 wires up the Bot API 10.0 dependency line (@gramio/types ^10, @gramio/contexts ^0.7, @gramio/files ^0.5, @gramio/format ^0.8, @gramio/test ^0.7) and adds guest_message to the allowed_updates filter.
bot.guestQuery() — handle guest messages
A new shorthand mirroring inlineQuery: it accepts a string / RegExp / predicate trigger (or none, to match any guest message), captures args, and supports macro-options. Reply with ctx.answerGuestQuery(result), where result is a single InlineQueryResult:
import { InlineQueryResult, InputMessageContent } from "gramio";
bot.guestQuery(async (ctx) => {
// a user reached the bot via a guest message
await ctx.answerGuestQuery(
InlineQueryResult.article("1", "Hi!", InputMessageContent.text("Hello, guest!")),
);
});
// or filter on the guest query text
bot.guestQuery(/^help/i, (ctx) =>
ctx.answerGuestQuery(
InlineQueryResult.article("help", "Help", InputMessageContent.text("How can I help?")),
),
);It's deliberately separate from command/hears/startParameter — guest messages have different reply semantics (answerGuestQuery, not ctx.send/reply). See the new guestQuery trigger page.
chosenInlineResult accepts a CallbackData schema
bot.chosenInlineResult(schema, handler) now filters on result_id and unpacks into a typed ctx.queryData, exactly like callbackQuery(schema, …):
import { CallbackData } from "gramio";
const card = new CallbackData("card").number("id");
bot.inlineQuery(/cards/, (ctx) =>
ctx.answer([
InlineQueryResult.article(card.pack({ id: 42 }), "Card #42", /* … */),
]),
);
bot.chosenInlineResult(card, (ctx) => {
ctx.queryData.id; // ✅ typed as number
});String / RegExp / predicate triggers still match against query as before.
No-trigger bot.inlineQuery(handler) overload
bot.inlineQuery(handler) (no trigger) now matches any inline query — handy for the auth-redirect pattern where you answer with an empty result set plus a login button.
Plugin authors: subclass-overlay helpers re-exported
WithDerives, WithEventDerive, WithDecorate, WithExtend, and DeriveHandler are now re-exported from gramio directly, so plugin authors building Composer-derived classes (like Scene) can import them without reaching into @gramio/composer.
Fix: no lost updates when stopping mid-batch
When bot.stop() flipped isStarted to false while a getUpdates batch was in flight, the offset was advanced locally (confirming the batch on Telegram's side) while the batch was dropped — silently losing those updates. v0.10 abandons such a batch without advancing the offset, so Telegram re-delivers it on the next start.
Fix: typed bots assignable to webhookHandler
Bots with derives/plugins/macros couldn't be passed to webhookHandler without as any because their generics sat in contravariant positions. The parameter widened to AnyBot, so the cast is gone.
@gramio/scenes v0.7 — Scenes Become Composers
The biggest scenes release ever. Scene now extends EventComposer, so the full bot-level DSL — .use/.on/.derive/.decorate/.guard/.command/.callbackQuery/.hears/… — is available directly on every scene. On top of that foundation come three big DX wins.
Builder steps — each step is its own sub-composer
The new recommended way to define a step: pass a builder callback that receives a per-step composer with .enter / .exit / .fallback / .message lifecycle hooks plus the full event surface (.on / .command / .callbackQuery / .hears):
import { Scene } from "@gramio/scenes";
const checkout = new Scene("checkout")
.step("ask-name", (c) =>
c
.message("What's your name?") // sugar for .enter(ctx => ctx.send(...))
.on("message", (ctx) => ctx.scene.update({ name: ctx.text })),
)
.step("confirm", (c) =>
c
.enter((ctx) => ctx.send(`${ctx.scene.state.name}, confirm? (yes/no)`))
.hears("yes", (ctx) => ctx.scene.exit())
.fallback((ctx) => ctx.send("Please answer yes or no")),
);State auto-inferred from ctx.scene.update()
Builder steps thread the shape you pass to ctx.scene.update({...}) into ctx.scene.state for every later step — no .state<T>() declaration needed:
new Scene("signup")
.step("ask", (c) =>
c.on("message", (ctx) => ctx.scene.update({ name: ctx.text! })),
)
.step("greet", (c) =>
c.enter((ctx) => {
ctx.scene.state.name; // ✅ inferred as string
}),
);Reusable step modules — scene.extend(otherScene)
Define a nameless Scene as a reusable block of steps and .extend() it into any named scene. Named-step collisions throw; numeric steps are renumbered automatically:
// A reusable confirmation module — cannot be entered directly
const confirm = new Scene().step("confirm", (c) =>
c
.enter((ctx) => ctx.send("Are you sure?"))
.callbackQuery("yes", (ctx) => ctx.scene.step.next())
.callbackQuery("no", (ctx) => ctx.scene.exit()),
);
const order = new Scene("order")
.step("review", (c) => c.enter((ctx) => ctx.send("Review your order")))
.extend(confirm) // ← pulls in the "confirm" step
.step("done", (c) => c.enter((ctx) => ctx.send("Order placed!")));onExit lifecycle hook + derive visible in onEnter
Symmetric to onEnter, the new onExit fires when a user leaves a scene (via exit(), exitSub(), or reenter()) before storage is torn down — perfect for cleanup or a "thanks for completing" message. And scene-level .derive() results are now visible inside onEnter, so you can load data and react to it on entry in one chain:
const checkout = new Scene("checkout")
.derive(async (ctx) => ({ user: await db.users.find(ctx.from!.id) }))
.onEnter((ctx) => analytics.track("checkout_start", { user: ctx.user }))
.onExit((ctx) => ctx.send("Thanks for stopping by!"))
.step("review", (c) => c.message("Looks good?").on("message", confirm));The classic event-filter step form —
.step("message", handler)and.step(["message", "callback_query"], handler)— is fully supported and works alongside builder steps in the same scene. Reach for builder steps in new code for the cleaner per-step lifecycle and automatic state inference. See the scenes guide for both forms side by side.
v0.7.1 — onEnter-consumed derives run exactly once again
A quick follow-up patch: 0.7.0 could run a scene-level .derive() twice on the entry update when its result was consumed inside onEnter (once so onEnter could see it, once in the dispatch chain). @gramio/scenes 0.7.1 restores exactly-once-per-update execution — important if your derive has side effects (counters, spans, DB writes). Upgrade straight to 0.7.1.
@gramio/onboarding v0.2.0 — Typed build()
createOnboarding({ id: "welcome" }).….build() previously returned a plain Plugin, erasing the derive shape — so ctx.onboarding.welcome needed module augmentation or a cast at every call site. v0.2 threads the flow Id through the whole builder, so bot.extend(...) now widens ctx.onboarding.welcome automatically:
import { Bot } from "gramio";
import { createOnboarding } from "@gramio/onboarding";
const bot = new Bot(process.env.BOT_TOKEN!).extend(
createOnboarding({ id: "welcome" })
.step("hi", { text: "Hi!" })
.step("done", { text: "All set!" })
.build(),
);
bot.command("start", (ctx) => {
ctx.onboarding.welcome.start(); // ✅ typed, no augmentation needed
return ctx.send("Let's go!");
});No runtime changes — type-level only — but consumers that relied on the old plain-Plugin return will pick up stricter inference.
Documentation & Skills
- New
guestQuerytrigger page documenting the Bot API 10 guest-message flow. - Scenes docs restructured to lead with the builder-step API while keeping the event-filter form documented as an equal alternative.
- New deep-links reference in the AI skills — a single source of truth for every
t.me/<bot>?…link family (?start=,?startapp=,?startgroup=, payload encoding,admin=tokens) so downstream agents route each link correctly. - Telegram method/type reference pages updated to Bot API 10.0, plus a twoslash + Vite build pass across ~200 snippets.