npm node-telegram-bot-api 2.4.0

one hour ago

Bot API 10.4 (November 9, 2026)

Widgets

  • Added sendWidget, sendSharedWidget, answerWidgetQuery, editMessageWidgetData, and editSharedWidgetData.
  • Added Widget and widget on Message and ExternalReplyInfo.
  • Added WidgetQuery/SharedWidgetQuery and the widget_query/shared_widget_query update types, plus WidgetQueryType (WidgetQueryTypeData, WidgetQueryTypeSignData, WidgetQueryTypeSentTransaction) and WidgetQueryWallet.
  • Added InputWidgetMessageContent and InputSharedWidgetMessageContent.
  • ctx.chat/ctx.from resolve for widget queries (shared_widget_query has no chat).
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_id in the ephemeral-capable send methods and receiver_user_id in EphemeralMessageParameters are now optional, so callback_query_id alone 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_id is a type error unless it is an ephemeral reply to a callback query (callback_query_id without receiver_user_id). The rule is the exported ChatOrCallbackReply type.
    • EphemeralMessageParameters must have receiver_user_id or callback_query_id. The callback-reply form is the exported EphemeralCallbackReply type.
    • Omit and Pick merge the members of these param types, so a wrapper such as Omit<SendMessageParams, "parse_mode"> loses the rule and its spread is rejected. Use the exported DistributiveOmit<SendMessageParams, "parse_mode">. Omit<SendMessageParams, "chat_id"> plus your own chat_id works as before. interface X extends SendMessageParams no longer compiles; use a type intersection.
  • ctx.reply on an update without a chat (e.g. a callback query from an inline message) sends when other makes it an ephemeral reply to the callback query, instead of throwing.
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_texts to TextQuote and quote_rich_texts to ReplyParameters.
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, and ChatFullInfo.ton_wallet_card.
  • Added TonWalletTransfer and Message.ton_wallet_transfer.
  • Renamed StoryAreaPosition to AreaPosition, following the Bot API. StoryAreaPosition stays as a deprecated alias.

Chat share themes

  • Added ChatShareTheme (ChatShareThemeEmoji, ChatShareThemeUniqueGift), ChatFullInfo.share_theme, and EmojiType (EmojiTypeEmoji, EmojiTypeCustomEmoji).
  • Added ChatShareThemeBackdrop, ChatShareThemeBackdrops, and getChatShareThemeBackdrops.

Gifts

  • Added getUniqueGift and deleteBusinessAccountGift.

General

  • Added RichTextTonAddress (with RichTextBuilder.tonAddress()) and the ton_address/bank_card_number entity types (with EntityType.TonAddress / EntityType.BankCardNumber). EntityType also gains the previously missing DateTime.
  • Added custom_description_parse_mode and custom_description_entities to verifyUser and verifyChat.
  • Added PaidMessagesWaived and Message.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.
// 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: v2.3.0...v2.4.0

Don't miss a new node-telegram-bot-api release

NewReleases is sending notifications on new releases.