| Name | Modified | Size | Downloads / Week |
|---|---|---|---|
| Parent folder | |||
| 2.4.0 source code.tar.gz | 2026-11-09 | 322.0 kB | |
| 2.4.0 source code.zip | 2026-11-09 | 381.4 kB | |
| README.md | 2026-11-09 | 5.6 kB | |
| Totals: 3 Items | 708.9 kB | 0 | |
Bot API 10.4 (November 9, 2026)
Widgets
- Added
sendWidget,sendSharedWidget,answerWidgetQuery,editMessageWidgetData, andeditSharedWidgetData. - Added
WidgetandwidgetonMessageandExternalReplyInfo. - Added
WidgetQuery/SharedWidgetQueryand thewidget_query/shared_widget_queryupdate types, plusWidgetQueryType(WidgetQueryTypeData,WidgetQueryTypeSignData,WidgetQueryTypeSentTransaction) andWidgetQueryWallet. - Added
InputWidgetMessageContentandInputSharedWidgetMessageContent. -
ctx.chat/ctx.fromresolve for widget queries (shared_widget_queryhas no chat).:::ts await bot.api.sendWidget({ chat_id: userId, // widgets work in private chats allow_queries: true, html:
<button id="b">Tap me</button> <script> b.onclick = () => TelegramWidget.request("tap").then((a) => (b.textContent = a.result)); </script>, });bot.on("widget_query", async (ctx) => { if (!("widget_query" in ctx.update)) return; const q = ctx.update.widget_query; await ctx.api.answerWidgetQuery({ widget_query_id: q.id, data:
hi ${ctx.from?.first_name}}); });
Ephemeral Messages
chat_idin the ephemeral-capable send methods andreceiver_user_idinEphemeralMessageParametersare now optional, socallback_query_idalone sends an ephemeral reply to a callback query (in any chat except channel direct-messages chats).- Types are stricter than the "Optional" columns in the Bot API docs, to catch at compile time what Telegram rejects at runtime:
- A call to one of these methods without
chat_idis a type error unless it is an ephemeral reply to a callback query (callback_query_idwithoutreceiver_user_id). The rule is the exportedChatOrCallbackReplytype. EphemeralMessageParametersmust havereceiver_user_idorcallback_query_id. The callback-reply form is the exportedEphemeralCallbackReplytype.OmitandPickmerge the members of these param types, so a wrapper such asOmit<SendMessageParams, "parse_mode">loses the rule and its spread is rejected. Use the exportedDistributiveOmit<SendMessageParams, "parse_mode">.Omit<SendMessageParams, "chat_id">plus your ownchat_idworks as before.interface X extends SendMessageParamsno longer compiles; use atypeintersection.-
ctx.replyon an update without a chat (e.g. a callback query from an inline message) sends whenothermakes it an ephemeral reply to the callback query, instead of throwing.:::ts bot.on("callback_query", async (ctx) => { const q = ctx.callbackQuery; if (!q) return; // No chat_id: shown only to the user who tapped, marked "Only visible to you" await ctx.api.sendMessage({ text: "Only you can see this", ephemeral_message_parameters: { callback_query_id: q.id }, }); await ctx.answerCallbackQuery(); // still required after an ephemeral reply });
await bot.api.sendMessage({ text: "hi" }); // ✗ type error: chat_id is required here
// Wrapping the param types: use DistributiveOmit, not Omit import type { DistributiveOmit, SendMessageParams } from "node-telegram-bot-api";
const sendHtml = (p: DistributiveOmit<SendMessageParams, "parse_mode">) => bot.api.sendMessage({ ...p, parse_mode: "HTML" });
Quotes from rich messages
-
Added
rich_textstoTextQuoteandquote_rich_textstoReplyParameters.:::ts await bot.api.sendMessage({ chat_id: chatId, text: "Agreed!", reply_parameters: { message_id: richMessageId, quote_rich_texts: ["the exact sentence being quoted"], }, });
TON wallet
- Added
TonWalletCard,TonWalletCardSticker,TonNft,TonNftAttribute, andChatFullInfo.ton_wallet_card. - Added
TonWalletTransferandMessage.ton_wallet_transfer. - Renamed
StoryAreaPositiontoAreaPosition, following the Bot API.StoryAreaPositionstays as a deprecated alias.
Chat share themes
- Added
ChatShareTheme(ChatShareThemeEmoji,ChatShareThemeUniqueGift),ChatFullInfo.share_theme, andEmojiType(EmojiTypeEmoji,EmojiTypeCustomEmoji). - Added
ChatShareThemeBackdrop,ChatShareThemeBackdrops, andgetChatShareThemeBackdrops.
Gifts
- Added
getUniqueGiftanddeleteBusinessAccountGift.
General
- Added
RichTextTonAddress(withRichTextBuilder.tonAddress()) and theton_address/bank_card_numberentity types (withEntityType.TonAddress/EntityType.BankCardNumber).EntityTypealso gains the previously missingDateTime. - Added
custom_description_parse_modeandcustom_description_entitiestoverifyUserandverifyChat. - Added
PaidMessagesWaivedandMessage.paid_messages_waived.
Rate limiting
rateLimit: { global, perChat }now takes the global and the per-chat token together, once both are available. Before, a request queued behind a busy chat spent its global token while it waited, so a chat backlog could later push sends over the global cap (#1390). Neither token is spent while a request waits on the other, and an aborted wait spends neither.-
Added
TokenBucket.takeAll(buckets, signal?), which takes one token from each bucket once all of them have one.:::ts // At most 30 requests/s overall and 1 request/s per chat, both checked at send time const bot = new Bot(TOKEN, { rateLimit: { global: 30, perChat: 1 } });
Full Changelog: https://github.com/yagop/node-telegram-bot-api/compare/v2.3.0...v2.4.0