Skip to content

Move to node-telegram-bot-api v2.0.0 — major update, analysis and migration guide required before implementation #501

Description

@windkh

node-telegram-bot-api@2.0.0 is out (release notes). We are on ^1.2.0 (1.2.0 installed). This issue tracks the move.

This is not a dependency bump. The upstream release describes itself as "a from-scratch redesign with no backward compatibility with the v1 TelegramBot surface". Treat it as a rewrite of our transport layer, planned and executed in steps — not as a Dependabot PR to merge.

Requirements

  • Major update. Ships as a new major of this package (V20.0.0) with an ADR recording the decision, per the workflow rule that no major lands without one.
  • Migration guide required. MIGRATION.md gets a "Migrating to V20.0.0" section, in the same shape as the V18 entry. Whatever cannot be shimmed must be spelled out with before/after examples.
  • Analyse breaking changes before any implementation. Produce the full list of what v2 changes for us — not a copy of the upstream notes — before a line is written. See the checklist below.
  • Check which node features become superfluous. v2 absorbs work we currently do ourselves. Anything it now does natively should be deleted, not ported.
  • Plan the rebuild in discrete steps. Each step independently reviewable, with the suite green at every step. No single "port to v2" commit.
  • Review the existing issues and update or close them where v2 changes the premise. First pass below.

What v2 changes upstream

From the release notes:

Area v1 v2
Class TelegramBot Bot
Method calls positional — bot.sendMessage(chatId, text, opts) single params object — bot.api.sendMessage({ chat_id, text, … })
Update handling event emitter — bot.on('message') koa-style middleware
Text matching bot.onText() bot.hears()
Reply hooks bot.onReplyToMessage() middleware reading ctx.message.reply_to_message
Webhook bot.setWebHook() positional bot.api.setWebhook({})
Polling state bot.isPolling() bot.isRunning()
Errors EFATAL split into NetworkError and TimeoutError
Uploads streams/buffers InputFile + fromPath(), streaming without full buffering
Keyboards JSON strings plain typed objects
Module format CJS dual ESM + CJS, Node-specific utilities under node-telegram-bot-api/node
Engine Node >= 18 (we are already at >= 22.19, so no constraint)

New surface worth noting: rich messages (InputRichMessageMedia, InputRichBlock), ephemeral message methods, communities, subscription updates.

Analysis to do first (before implementation)

Breaking-change impact per file. Rough size of the blast radius, by matches on the affected APIs:

File Hits What lives there
telegrambot/nodes/bot-node.js 33 bot construction, polling/webhook lifecycle, listener wiring, restart logic
telegrambot/nodes/out-node.js 6 the send path
telegrambot/lib/undici-pool.js 3 per-bot dispatcher via request.fetchOptions
telegrambot/nodes/control-node.js 2 setWebHook / deleteWebHook
telegrambot/nodes/reply-node.js 2 reply handling

Questions the analysis has to answer:

  • Does the per-bot dispatcher survive? ADR 0011 documents that we hand an undici Agent to Node's built-in fetch through request.fetchOptions. Does v2 still expose a transport hook of that shape? This decides whether SOCKS proxy support and the Connection to TG break after weeks #442 keep-alive defence survive the port — if v2 drops the hook, that is a blocker, not a detail. Check before anything else.
  • Does v2 bring its own HTTP stack, and does that interact with the undici-major problem in ADR 0011? If v2 uses its own fetch, the ignore rule on undici majors may become liftable — or moot.
  • What happens to the error taxonomy? EFATAL splitting into NetworkError / TimeoutError touches telegrambot/lib/error-chain.js and the status/error surfacing in the nodes. msg.error shapes are user-visible.
  • Is the middleware model compatible with per-node listeners? ADR 0005 tracks listener handles so a redeploy detaches cleanly. Middleware is a chain, not an emitter — the removal story has to be re-established, or redeploys leak handlers.
  • Does the per-chat queue still make sense? ADR 0003 and ADR 0008 exist because of v1 behaviour. Verify against v2 rather than porting them blind.
  • Node-RED constraint: we are CommonJS. v2 is dual-format, so confirm the CJS entry point exposes everything we need, including the node-telegram-bot-api/node subpath.

Candidates for deletion rather than porting

Things we built because v1 lacked them. Each needs a yes/no answer, and a no means the code goes:

  • telegrambot/lib/legacy-options.js — the V18 compatibility shim rewriting deprecated msg.payload.options fields. With v2's params objects the mapping changes completely. Decide: carry it forward (rewritten), or drop it as part of the major and document the removal in MIGRATION.md.
  • Keyboard/markup building — v2 returns plain typed objects instead of JSON strings. Whatever we serialise by hand may be redundant.
  • Upload handlingInputFile / fromPath() and streaming uploads may replace buffering we do ourselves.
  • The callApi escape hatch — its reason for existing is methods the library did not expose. Re-evaluate against v2's bot.api.* surface, which appears to be complete and uniform.
  • Bot API 10.2 features we currently reach only through callApi (ephemeral messages, rich messages, communities, subscriptions) — v2 exposes them natively.

Existing issues to re-check

Required by this issue; first pass only, each still needs a real review:

  • #459 Tier 1, #460 Tier 2, #461 Tier 3, #462 Tier 4 — all four are "implement Bot API method X" issues from 2026-06-17, written against the v1 surface. v2's uniform bot.api.* namespace likely changes both the effort and the tiering: some may collapse into "already reachable", others may need re-scoping. Do not implement any of them against v1 before this decision is made — that work would be thrown away.
  • #498 — npm-bundled advisories. Unrelated to v2; leave open.

Suggested step plan

Refine after the analysis; the point is that no step is a big-bang.

  1. Spike, throwaway. Build one Bot, send one message, run one update through middleware. Answers the dispatcher and CJS questions cheaply.
  2. Write the analysis into an ADR (breaking changes for us, what dies, what survives).
  3. Isolate the transport. Put the v1 surface behind a thin internal seam so nodes stop calling the library directly.
  4. Port the seam to v2 behind the same interface, with the suite green.
  5. Port node by node — out, reply, control, event, receiver — one commit each.
  6. Delete what v2 made redundant (the list above), each removal its own commit.
  7. MIGRATION.md + ADR + major bump, then release.

Steps 1–2 are the gate: if the spike says the dispatcher hook is gone, this stops and gets re-planned rather than continuing.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions