Node.js 26 is scheduled to become the Active LTS line on October 28, 2026, and it's the last release line under the old twice-a-year model. If you run Node 24 or 22 in production, 26 is the next version worth moving to. The upgrade is mostly painless, but a handful of APIs that warned for years are now gone. If you're coming from 22, you also inherit everything Node 25 removed, and since odd-numbered releases see little production use, that's the part people miss.
The dates that matter
The official release schedule is the authoritative record, and right now it says:
| Line | Status on 2026-10-21 | Next change | End of life |
|---|---|---|---|
| Node 22 (Jod) | Maintenance LTS | none | 2027-04-30 |
| Node 24 (Krypton) | Maintenance LTS since 2026-10-20 | none | 2028-04-30 |
| Node 26 | Current (26.12.0, released 2026-10-14) | Active LTS on 2026-10-28 | 2029-04-30 |
Node 24 left Active LTS yesterday, so from here on it gets security and critical fixes only. Node 22 has six months left. If you're on 22, don't stop at 24. Going straight to 26 buys you an extra year of support for the same migration effort.
Node 27 changes the model. The project's Evolving the Node.js Release Schedule announcement says that starting with 27.x there will be one major release per year, every release becomes LTS, and the odd/even split goes away. Each line gets a six-month Alpha (October to March, semver-major changes allowed), six months as Current, then 30 months of LTS. The schedule file already lists a 27 alpha starting on October 28. If you only ever upgrade to LTS versions, the post's own summary is that "little changes beyond version numbering." Library authors are asked to put the Alpha builds in CI, because by the time a release hits LTS it's too late to report a bug before users see it.
What breaks moving to Node 26
These are the removals and new runtime deprecations from the Node.js 26.0.0 release notes that real application code is likely to hit. Each entry's history comes from the deprecations reference.
response.writeHeader()is gone (DEP0063). It was never documented as a supported API, but older code and some frameworks call it. UsewriteHead().require('_stream_readable')and the other_stream_*modules are removed (DEP0193). Import fromnode:streaminstead. This one usually lives in an old dependency, not your code.- Decrypting AES-GCM with a tag shorter than 16 bytes now requires an explicit
authTagLength(DEP0182, end-of-life in 26). - The
--experimental-transform-typesflag is removed. module.register()now prints a runtime deprecation warning (DEP0205). The replacement is module.registerHooks(), whose hooks are synchronous and run in-thread. It's been available since 23.5.0 and 22.15.0, so you can migrate before you upgrade.- Passing a
CryptoKeytonode:cryptofunctions such ascreateHmac()orcreateCipheriv()is runtime-deprecated (DEP0203), and the docs say it "will throw an error in a future version."
Native addons need rebuilding or new prebuilt binaries: 26.0.0 bumps NODE_MODULE_VERSION to 147, so check every package with a compiled component, private ones especially. The release also moves undici, the HTTP client implementation inside Node, to 8.0.2. That's a major version of its own, so retest code that makes outbound HTTP calls.
What you inherit from Node 25 if you skip it
The Node.js 25.0.0 release notes carried a bigger cleanup than 26 did, and a team moving from 22 to 26 gets all of it at once:
- Corepack is no longer distributed with Node. CI scripts that run
corepack enableto get pnpm or Yarn will fail until you install Corepack separately or install the package manager directly. fs.F_OK,fs.R_OK,fs.W_OKandfs.X_OKare removed (DEP0176). Read them fromfs.constants.SlowBufferis removed (DEP0030). UseBuffer.allocUnsafeSlow(size).- Web Storage is enabled by default, so
localStorageandsessionStorageexist as globals. Node 26 then changedlocalStorageto returnundefinedwhen no storage file is configured. Code that checkstypeof localStorageto decide whether it's running in a browser should switch to a more specific check.
Node 24 users have already been warned about the fs constants and SlowBuffer, since both became runtime deprecations in 24.0.0. Node 22 printed nothing for either. If your logs show deprecation warnings on 24, treat every one as a hard error on 26.
Find the problems before you upgrade
The cheapest check is to run your test suite on your current version with --throw-deprecation, which turns every deprecation warning into an exception. Here's DEP0182 on Node 23.9.0, where it's a runtime deprecation that Node 26 makes fatal:
import { createCipheriv, createDecipheriv, randomBytes } from 'node:crypto';
const key = randomBytes(32);
const iv = randomBytes(12);
const cipher = createCipheriv('aes-256-gcm', key, iv, { authTagLength: 8 });
const data = Buffer.concat([cipher.update('hello'), cipher.final()]);
const tag = cipher.getAuthTag(); // 8 bytes
// No authTagLength here: the 8-byte tag is accepted only with a warning
const decipher = createDecipheriv('aes-256-gcm', key, iv);
decipher.setAuthTag(tag);
console.log(Buffer.concat([decipher.update(data), decipher.final()]).toString());
$ node --throw-deprecation gcm.mjs
hello
node:internal/process/warning:176
throw warning;
^
DeprecationWarning: Using AES-GCM authentication tags of less than 128 bits without specifying the authTagLength option when initializing decryption is deprecated.
at Decipheriv.setAuthTag (node:internal/crypto/cipher:211:22)
Notice that hello printed first. The warning is emitted asynchronously, after the decrypt has already returned, so a test that only checks return values can pass and still crash the process a moment later. Run the whole suite this way, not just one file. Passing { authTagLength: 8 } to createDecipheriv() makes the same script exit cleanly.
Deprecation flags only catch code paths your tests actually run, and on Node 22 they can't catch anything Node 22 never warned about. For those, a dumb text scan is worth the two minutes. This script flags the patterns above in source files and package.json scripts:
// node26-audit.mjs - flag code that breaks or warns on Node.js 26
// usage: node node26-audit.mjs
import { readdir, readFile } from 'node:fs/promises';
import { join, relative } from 'node:path';
const CHECKS = [
{ re: /\.writeHeader\s*\(/, msg: 'removed in 26 (DEP0063): use res.writeHead()' },
{ re: /['"](node:)?_stream_(wrap|readable|writable|duplex|transform|passthrough)['"]/,
msg: 'removed in 26 (DEP0193): import from node:stream' },
{ re: /\bSlowBuffer\b/, msg: 'removed in 25: use Buffer.allocUnsafeSlow()' },
{ re: /\bfs\.(F_OK|R_OK|W_OK|X_OK)\b/, msg: 'removed in 25: use fs.constants.*' },
{ re: /\bregister\s*\(\s*['"][^'"]+['"]\s*,\s*(import\.meta\.url|pathToFileURL)/,
msg: 'runtime-deprecated in 26 (DEP0205): move to module.registerHooks()' },
{ re: /--experimental-transform-types/, msg: 'flag removed in 26' },
{ re: /\bcorepack\b/, msg: 'Corepack is no longer bundled since 25: install it yourself' },
{ re: /createDecipheriv\s*\(\s*['"]aes-\d+-gcm['"]/,
msg: 'check authTagLength: short GCM tags without it fail in 26 (DEP0182)' },
];
const EXT = /\.(c|m)?(j|t)sx?$/;
const SKIP = new Set(['node_modules', '.git', 'dist', 'build']);
async function* walk(dir) {
for (const entry of await readdir(dir, { withFileTypes: true })) {
if (SKIP.has(entry.name)) continue;
const path = join(dir, entry.name);
if (entry.isDirectory()) yield* walk(path);
else if (EXT.test(entry.name) || entry.name === 'package.json') yield path;
}
}
const root = process.argv[2] ?? '.';
let hits = 0;
for await (const file of walk(root)) {
const lines = (await readFile(file, 'utf8')).split('\n');
lines.forEach((line, i) => {
for (const { re, msg } of CHECKS) {
if (re.test(line)) {
hits++;
console.log(`${relative(root, file)}:${i + 1} ${msg}`);
}
}
});
}
console.log(`${hits} finding(s) under ${root} (audited with Node ${process.version})`);
process.exitCode = hits ? 1 : 0;
It only uses node:fs/promises and node:path, so it runs on whatever version you have now. We ran it against a small sample project with a writeHeader() call, an fs.R_OK check, a _stream_transform require, a module.register() loader and a package.json that calls corepack enable and --experimental-transform-types:
$ node node26-audit.mjs sample
loader.mjs:3 runtime-deprecated in 26 (DEP0205): move to module.registerHooks()
package.json:4 Corepack is no longer bundled since 25: install it yourself
package.json:5 flag removed in 26
server.js:3 removed in 26 (DEP0193): import from node:stream
server.js:6 removed in 25: use fs.constants.*
server.js:7 removed in 26 (DEP0063): use res.writeHead()
6 finding(s) under sample (audited with Node v23.9.0)
It exits non-zero when it finds something, so it can sit in CI until the migration is done. It deliberately skips node_modules. For dependencies, the useful signal is the deprecation warnings when your tests run, plus a quick look at whether each package's latest release lists Node 26 in its engines field. For the fs constants and SlowBuffer, the deprecations page lists official codemods (npx codemod@latest @nodejs/fs-access-mode-constants and @nodejs/slow-buffer-to-buffer-alloc-unsafe-slow) that rewrite the calls for you.
What you get for the trouble
The headline in 26.0.0 is that the Temporal date/time API is enabled by default, so you can stop pulling in a date library just to get time zones and calendar arithmetic right. V8 moves to 14.6, which adds Map.prototype.getOrInsert() and getOrInsertComputed() (plus the WeakMap versions) and Iterator.concat(). None of that is a reason to rush. The reason to move in the next few months is the support calendar: 26 is supported until April 2029, and 22 runs out in April 2027.
One caveat about how this article was checked. Both scripts ran on Node 23.9.0, and Node 26 itself wasn't run, so the Node 26 behaviour described here comes from the release notes and the deprecations reference linked above. Before you switch production, run your own suite on 26.12.0 or the first LTS-tagged 26.x release.
No comment for this article.