• Node.js
  • Releases
  • Testing

Node.js 26 becomes LTS on October 28: an upgrade checklist for SaaS backends and extension builds

Node.js 26 becomes LTS on October 28, 2026. What breaks when you jump from Node 24, from Corepack and localStorage to native addons, plus a checklist.

Author
Extenify Team
Published
Reading time
12 min read
Long aisle between rows of black server racks in a data center, with green status lights and an overhead cable tray

On October 28, 2026, Node.js 26 is promoted to Long Term Support. A week earlier, on October 20, Node.js 24 leaves Active LTS and moves into maintenance. If your CI uses lts/*, your Docker base image tracks node:lts, or your hosting platform follows the newest LTS by default, your builds will start running on Node 26 at the end of this month whether you planned the upgrade or not.

For most teams that only install LTS versions, this is a bigger jump than the version number suggests. Going from Node 24 to Node 26 also brings in everything that changed in Node 25, which LTS-only teams never ran. That is where the surprises live: Corepack is gone, localStorage behaves differently, native addons need new binaries, and a TypeScript flag some tools relied on no longer exists.

This guide is for people who ship a SaaS backend, a browser extension, or both. The extension itself runs in the browser, but its bundler, test runner, release scripts and the small backend behind it (license checks, telemetry, an analytics relay) all run on Node. Here is what changes, what the community is already tripping over, and a checklist to get through it in an afternoon.

Key takeaways

  • Node.js 26 becomes LTS on October 28, 2026. Node 24 enters maintenance on October 20 and is supported until April 30, 2028, so there is no need to rush, but lts/* will switch on its own.
  • Moving from Node 24 to 26 includes the Node 25 changes too. The most common breakages are the missing Corepack binary, the built-in localStorage shadowing test environments, and native addons without Node 26 binaries.
  • --experimental-transform-types was removed. Node 26 only strips erasable TypeScript syntax, so enums and parameter properties need a build step or a rewrite.
  • Node 26 is the last release on the old schedule. From Node 27 onward there is one major release a year, and every release becomes LTS.

The Node.js release calendar for the next six months

These dates come from the official Node.js release schedule and the March 2026 announcement Evolving the Node.js Release Schedule.

Version Status today Next milestone End of life
Node.js 20 End of life None, upgrade now April 30, 2026
Node.js 22 Maintenance LTS Security fixes only April 30, 2027
Node.js 24 Active LTS Maintenance from October 20, 2026 April 30, 2028
Node.js 26 Current Active LTS from October 28, 2026 April 30, 2029
Node.js 27 Not released Alpha channel from October 28, 2026 April 30, 2030

Two things stand out. First, anything still on Node 20 is already unsupported and should move straight to 24 or 26. Second, Node 26 is the final release under the old "even versions become LTS" model. From Node 27, the project ships one major version a year in April, promotes it to LTS in October, and drops the odd/even split. In the announcement's words: "If you already only upgrade to LTS versions, little changes beyond version numbering."

Hosting platforms follow the same calendar. AWS, for example, opened a public preview of the nodejs26.x Lambda runtime in August and tied general availability to the Active LTS date. AWS also warns that the preview runtime is not for production workloads and has slower cold starts, so wait for GA before you move production functions.

Why "lts/*" will upgrade you on October 28

The fastest way to end up on Node 26 without meaning to is a floating version alias. A GitHub issue opened on September 30 describes the problem well: release builds used lts/* while pull request checks were pinned to Node 24, so after October 28 "every desktop release and the server tarball would be built with a Node version that no PR check runs." The same issue notes that the project's tests already showed dozens of failures on Node 26 because of the localStorage change covered below.

Pick one source of truth for the Node version and make every workflow read it:

# .node-version (read by setup-node, fnm, mise and most hosts)
24
# .github/workflows/ci.yml
- uses: actions/setup-node@v7
  with:
    node-version-file: .node-version

Then add a second CI job that runs the same suite on Node 26. When it is green for a week, change the file to 26 in one commit. That is the whole upgrade strategy: pinned by default, tested ahead, switched deliberately.

Breaking change 1: Corepack is no longer included

If your project uses pnpm or Yarn through Corepack, this is the change most likely to fail your first Node 26 build. The Node.js Technical Steering Committee voted in March 2025 to stop distributing Corepack, and the change landed in Node 25 as "build: stop distributing Corepack". Node 24 still includes it, which is why many LTS-only teams are meeting it now.

Typical symptoms:

  • corepack: command not found in CI or a Dockerfile.
  • A devcontainer script that runs corepack enable, fails quietly, and leaves dependencies uninstalled. One scaffold project issue from September is titled "Node 26 drops corepack, and the CI snippet's pnpm bootstrap stalls five minutes".
  • A hosting build image picking up an older standalone Corepack that cannot run the pnpm version in your packageManager field. A Netlify support thread from September 18 reported exactly this with pnpm 12 on Node 26, and Netlify resolved it on its side by installing pnpm with mise instead of Corepack.

You have two clean options:

# Option A: keep Corepack, installed as a normal package
npm install -g corepack
corepack enable

# Option B: install the package manager directly, at the pinned version
npm install -g pnpm@10

Whichever you choose, keep the packageManager field in package.json as the single place the version is written down, and make sure your Dockerfile and CI read the same value instead of hard-coding their own.

Breaking change 2: localStorage exists in Node, and it shadows your test DOM

Node 25 turned the Web Storage API on by default. In Node 26, reading the global localStorage without passing --localstorage-file returns undefined and prints a one-time warning: "localStorage is not available because --localstorage-file was not provided." Meanwhile sessionStorage is a fully working in-memory store. The change history in the Node.js globals documentation says this access throws a DOMException, but the shipped Node 26 code returns undefined with a warning, and that is what the bug reports below describe. Write code that copes with either.

This breaks two kinds of code that extension and SaaS developers write all the time.

Tests that run in jsdom or happy-dom. Node's own global can take precedence over the one the test environment installs. A color-tools issue from September 27 reports 403 of 1,190 tests failing with "Cannot read properties of undefined (reading 'getItem')". The fix used there, and in several similar reports, is to turn Node's implementation off for the test run:

{
  "scripts": {
    "test": "NODE_OPTIONS=--no-experimental-webstorage vitest run"
  }
}

Shared code that guesses its environment. Extension projects often share a settings or cache module between the popup, the options page, a service worker and a server. Guards like typeof sessionStorage !== 'undefined' or 'localStorage' in globalThis no longer prove you are in a browser. Check for something only a page has, and treat storage access as fallible:

export function pageStorage(): Storage | undefined {
  // Manifest V3 service workers and Node both lack a document.
  if (typeof document === 'undefined') return undefined;
  try {
    return globalThis.localStorage ?? undefined;
  } catch {
    return undefined;
  }
}

In an extension's background service worker, persistent settings belong in chrome.storage anyway, since there is no localStorage there either.

Breaking change 3: native addons need Node 26 builds

Every Node major bumps the ABI version that compiled addons are built against. Node 24 uses NODE_MODULE_VERSION 137, and Node 26 uses 147. A package that ships prebuilt binaries only for older versions falls back to compiling from source, and V8 14.6 has removed C++ APIs that older addon code still calls.

The most common example in recent GitHub issues is better-sqlite3. A report from August 19 shows version 11.10.0 printing "No prebuilt binaries found" on Node 26.7.0 and then failing to compile with errors like "no member named 'GetIsolate' in 'v8::Context'". The fix there was upgrading to better-sqlite3 12.11.1, which publishes binaries for Node 26.

Before you switch, list what you depend on:

node -p process.versions.modules   # 147 on Node 26
npm ls --all | grep -Ei "sqlite|sharp|bcrypt|canvas|argon2|re2"

Upgrade each one to a release that supports Node 26, then run a clean install on Node 26 rather than reusing a node_modules folder built on 24. If a dependency is only there for a small SQLite cache or a local analytics store, consider the built-in node:sqlite module. It has no native install step, and it reached "release candidate" stability in Node 25.7, as noted in the node:sqlite documentation.

Breaking change 4: TypeScript runs natively, but only erasable syntax

Running .ts files directly in Node is now marked stable in the Node 26 TypeScript documentation, which is handy for build scripts, release tooling and small backends. But Node 26 also removed --experimental-transform-types, the flag that handled syntax which needs code generation. Anything that still passes it now exits immediately, and code that uses that syntax throws ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX.

The unsupported list is short: enum declarations, namespaces with runtime code, constructor parameter properties, import aliases, and decorators. Node also ignores tsconfig.json, so path aliases do not resolve, and it refuses to strip .ts files inside node_modules.

If you want your scripts to run on plain Node, let the compiler enforce the rules with erasableSyntaxOnly, which the Node docs recommend alongside verbatimModuleSyntax:

{
  "compilerOptions": {
    "noEmit": true,
    "target": "esnext",
    "module": "nodenext",
    "rewriteRelativeImportExtensions": true,
    "erasableSyntaxOnly": true,
    "verbatimModuleSyntax": true
  }
}

Enums are usually the only real rewrite, and a const object covers the same use:

export const Plan = { Free: 'free', Pro: 'pro', Team: 'team' } as const;
export type Plan = (typeof Plan)[keyof typeof Plan];

Your extension bundle is not affected by any of this, because Vite, esbuild or webpack compile TypeScript before the browser sees it. The change only matters for code you run with node file.ts.

Smaller removals that break old scripts

Release and packaging scripts tend to be the oldest code in an extension repo, and they are where these show up:

  • fs.F_OK, fs.R_OK, fs.W_OK and fs.X_OK were removed in Node 25. Use fs.constants.F_OK and friends.
  • The legacy _stream_readable, _stream_writable and related internal modules are gone, and so is http.Server.prototype.writeHeader(). Use node:stream and writeHead().
  • module.register() is runtime-deprecated as DEP0205. Loaders should move to the synchronous module.registerHooks().
  • Short AES-GCM authentication tags are no longer accepted unless you pass authTagLength explicitly (DEP0182 reached end of life). Check this if your backend encrypts license keys or tokens with a truncated tag.

A quick search finds most of them:

grep -rnE "fs\.(F|R|W|X)_OK|_stream_(readable|writable|duplex|transform|passthrough)|writeHeader\(|module\.register\(" \
  src scripts --include=*.{js,mjs,cjs,ts}

Then run your test suite once with NODE_OPTIONS=--trace-deprecation on Node 26 to catch the runtime deprecations a search misses.

What you get in return

The upgrade is not only cleanup. A few Node 26 additions are directly useful for analytics and SaaS code:

  • Temporal is on by default. Bucketing events by a user's local day, or computing "days since install" across daylight saving changes, no longer needs a date library. Temporal.Now.zonedDateTimeISO('Europe/Berlin').toPlainDate().toString() gives you a stable day key.
  • Map.prototype.getOrInsertComputed() removes the usual check-then-set dance when grouping: byDay.getOrInsertComputed(day, () => []).push(event).
  • Iterator.concat() lazily chains several iterables without building an intermediate array, which helps when you stream large exports.

One caution for extension developers: MDN's compatibility data lists Temporal in Chrome 144, Edge and Firefox 139, but Safari only ships it in preview. If shared code uses Temporal and you also publish a Safari extension, keep a polyfill or a fallback in the browser build.

A Node.js 26 upgrade checklist

  1. Write the current version to .node-version and make every CI job, Dockerfile and devcontainer read it. Remove lts/* from release workflows.
  2. Add a Node 26 CI job next to the Node 24 one and leave it running for a week.
  3. Replace corepack enable with an explicit install of Corepack or of your package manager.
  4. Add --no-experimental-webstorage to test scripts that use jsdom or happy-dom, and fix shared code that uses storage as a browser check.
  5. List native dependencies, upgrade each to a Node 26-compatible release, and do a clean install on Node 26.
  6. Remove --experimental-transform-types, turn on erasableSyntaxOnly, and rewrite enums used by scripts that Node runs directly.
  7. Search for the removed APIs above and run once with --trace-deprecation.
  8. Check your host's Node 26 support date before switching production, especially serverless runtimes.
  9. Switch .node-version to 26 in a single commit, after October 28.

Watch the user-facing signal after you switch

A toolchain upgrade can change your extension's output without any change to your own code: a new bundler default, a different minifier, a polyfill that is no longer included. Those problems reach users through the stores, often on one browser first. Treat the first release built on Node 26 like any other risky release and follow the steps in what to watch in the week after an extension release.

The same goes for the backend. If you run a relay for Google Analytics 4 in a Manifest V3 extension or a daily usage ping for retention and uninstall tracking, compare event volume for a few days before and after the switch. A silent drop is easier to spot in a chart than in logs. And since browsers now ship every two weeks, avoid stacking a Node upgrade on top of a browser release week when you can.

Extenify sends daily alerts when an extension's rating, reviews or installs move on Chrome, Edge or Firefox, so a regression from a toolchain change shows up the next morning instead of the next month. Setting up store change alerts your team will actually read covers which alerts to route to whoever owns the build.

Frequently asked questions

When does Node.js 26 become LTS?

Node.js 26 is promoted to Active LTS on October 28, 2026, enters maintenance on October 20, 2027, and reaches end of life on April 30, 2029, according to the official Node.js release schedule.

Should I upgrade from Node 24 to Node 26 right away?

There is no deadline. Node 24 keeps receiving security fixes until April 30, 2028. Upgrade when your Node 26 test job is green and your host supports Node 26 in production. What you should do now is stop floating on lts/*, so the switch happens when you choose.

Why does "corepack enable" fail on Node 26?

Corepack has not been included with Node.js since version 25, after a Technical Steering Committee vote. Install it with npm install -g corepack, or install pnpm or Yarn directly at the version in your packageManager field.

Why is localStorage undefined in my tests on Node 26?

Node 26 defines its own localStorage global, which returns undefined unless you pass --localstorage-file. In jsdom or happy-dom test runs it can shadow the environment's storage. Run tests with NODE_OPTIONS=--no-experimental-webstorage to turn Node's implementation off.

Can Node 26 run TypeScript enums?

Not natively. Node 26 strips type annotations but does not transform syntax that generates code, such as enums and parameter properties, and the --experimental-transform-types flag was removed. Compile with tsc or a bundler, use a loader such as tsx, or replace the enum with a const object.

What changes after Node.js 26?

Node 26 is the last release on the twice-a-year schedule. Starting with Node 27, there is one major release each April, it becomes LTS in October, and every release gets LTS support, for 36 months in total.

Summary

Node.js 26 becomes LTS on October 28, and for teams that skip odd-numbered releases it bundles two years of changes into one upgrade. The work is predictable: pin the version so lts/* cannot move it for you, install your package manager without Corepack, keep Node's localStorage out of your test DOM, refresh native addons, and keep scripts that Node runs directly to erasable TypeScript. Test on Node 26 in parallel, switch in one commit when it is green, and watch your store ratings and telemetry for a few days after the first release built on it.

Image credits

Put every store in one dashboard tonight.

Sign in with Google, paste one store link, and your unified view is live in under a minute. Free forever for one extension, and you never touch your extension's code.

Track my extension, free