Node 24's full changelog runs past sixty entries between the initial release and its LTS promotion, most of it internal, dependency bumps, or edge-case fixes that don't touch how application code gets written. Here's the subset that's actually worth knowing, filtered down from a changelog most developers reasonably skip reading in full.
What Does Node's Permission Model Do?
node --permission --allow-fs-read="/app/data" --allow-net server.js
Node's experimental permission model, first introduced years earlier, stabilized in the Node 24 line, letting you run a script with explicit, enforced restrictions on filesystem access, network access, and child process spawning. A script started with --permission and without --allow-fs-write throws a runtime error the moment it attempts a write, rather than a script's actual capabilities being defined only by what the surrounding OS user account happens to allow.
// Check permissions at runtime, useful for a library that wants to
// behave differently when it detects restricted permissions
import { permission } from 'node:process';
if (!permission.has('fs.write')) {
console.warn('Running with read-only filesystem access');
}
This matters most for anything running third-party or AI-generated code with reduced trust, a plugin system, a code execution sandbox, or a CI step running a dependency's install script. Previously, that isolation required a container or a separate OS user; Node's own permission model provides a lighter-weight boundary for cases where full container isolation is overkill.
Previously, sandboxing untrusted code required a container or a separate OS user. Node's own permission model now provides a lighter-weight boundary for cases where full container isolation is overkill.
How Does require() Handle ESM Interop Now?
// This now works directly in more cases without a dynamic import() workaround
const { someExport } = require('some-esm-only-package');
require() can now load most ESM-only packages synchronously from CommonJS files, support that landed across the Node 22 to 24 line. That closes a friction point going back years: a CommonJS project needing one ESM-only dependency had to reach for await import() at the top level, or convert the whole file to ESM just to use a single package.
It doesn't apply universally. A package that uses top-level await internally is genuinely asynchronous and still needs the dynamic import path, because there is no way to make that synchronous without blocking the loop. Node throws a clear ERR_REQUIRE_ASYNC_MODULE rather than failing quietly, so you find out immediately.
For everything else, and the straightforward ESM package is the common case, the interop just works. If your codebase carries a // eslint-disable next to a dynamic import that exists only to pull in one dependency, that comment can probably go.
How Has Native TypeScript Support Improved?
node src/index.ts # runs directly, no ts-node/tsx, no build step
Native TypeScript execution improved mainly in source maps: stack traces now point at the original .ts line rather than the stripped output. The underlying type stripping arrived in Node 22.6 and stabilized in 23.6; Node 24 refines the developer experience around it rather than adding new capability.
Of everything in this release, this is the change with the most direct effect on daily work. Running a script or a CLI tool straight against .ts source, with no compile step and no ts-node dependency, removes an entire class of tooling from small projects.
The limit is worth stating plainly, because it surprises people: Node strips types, it does not check them. Nothing validates your annotations at runtime, so tsc --noEmit stays in CI regardless. Anything needing real transformation rather than erasure, which in practice means enums and decorators, still needs a compiler.
What's in the Changelog but Not Worth Changing Code For?
- V8 engine version bumps: newer JavaScript syntax support and JIT performance improvements apply automatically. No code change needed, no action required beyond upgrading.
- npm version bump bundled with Node: relevant if you rely on the bundled npm specifically, most projects using a separate package manager (pnpm, Yarn) aren't affected.
- Internal dependency updates (OpenSSL, ICU, etc.): security and correctness fixes that matter for the runtime's own safety, not something application code interacts with directly.
- Deprecation warnings for older APIs: worth noting if your codebase uses the specifically-flagged deprecated API, otherwise safe to ignore until the next major version actually removes it.
The distinction that matters is between changes you adopt and changes that adopt you. Roughly four of the sixty-odd entries in a typical Node changelog need a decision from you; the rest arrive with the binary and improve things without asking. Reading the changelog as a to-do list is how upgrades turn into week-long projects.
None of the four items above belongs in a migration plan. If a V8 bump breaks something, that is a bug report rather than a task, and it will be fixed upstream faster than you can work around it. The two features from this release that do need a decision are the permission model and, for TypeScript projects, whether to drop the loader dependency. Both are reversible in a single commit, which is the useful thing to know before scheduling either.
Which Changes Need Code and Which Are Free?
| Feature | Requires code changes? | Why it matters |
|---|---|---|
Permission model (--permission) | Yes, opt-in flags | Sandboxes fs/network access for untrusted or AI-generated code |
require() ESM interop | No, works automatically for common cases | Removes need for dynamic import() workarounds |
| Native TypeScript execution | No, run .ts files directly | No ts-node/tsx, better source-mapped stack traces |
| V8 engine bumps | No | New JS syntax and JIT gains apply automatically |
| Bundled npm version | No, unless using bundled npm directly | Irrelevant if using pnpm/Yarn |
| Deprecation warnings | Only if using the flagged API | Safe to ignore until next major removes it |
Only the first row of that table costs you anything, and reading it top to bottom is the fastest way to size an upgrade: five of six features arrive with the binary and need no decision from anyone. Only the first row costs you anything. The permission model is opt-in by design, because turning it on by default would break every application that reads a config file, so it is the one feature here that needs a deliberate decision and some testing.
Plan an upgrade in that order:
- Bump the runtime and run the existing test suite unchanged. Rows two through six should need nothing from you.
- Delete the workarounds the release made unnecessary: dynamic imports that only existed for interop,
ts-nodefrom scripts that just run a file. - Adopt the permission model last, on one service, with flags scoped as tightly as the app tolerates. Expect two or three rounds of widening the paths before the app starts cleanly; that iteration is the point of the exercise rather than a sign it isn't working.
Two of the three features worth adopting cost nothing to try, which is a good argument for doing the upgrade in one sitting rather than scheduling it. Three terms get mixed up in Node upgrade threads and mean different things:
- Type stripping removes annotations without checking them. That is what Node does natively.
- Transpilation rewrites syntax into something the runtime understands, which is what enums and decorators still require.
- Type checking validates the annotations. Node never does this, and
tsc --noEmitremains your only source of truth.
How Do You Check Compatibility Before Upgrading?
npx npm-check-updates --target minor # check which deps have Node 24-tested versions
node --version # confirm the target version
Check the engines field across your dependency tree for anything capping at an older Node major, then run the full test suite against Node 24 in CI before merging the version bump. Native modules are the usual holdout, since they compile against a specific Node ABI and need a release built for it. better-sqlite3, sharp, and anything wrapping a C library fall in this group, and a stale one shows up as an install-time build failure rather than a runtime error, which is at least a fast signal. Check them first rather than last, because a missing prebuilt binary can block the upgrade for weeks while everything else is ready. Most compatibility breaks in a Node major upgrade come from a native-module dependency (anything requiring node-gyp compilation) not yet supporting the new ABI, not from your own application code.
Conclusion
Reading a Node release's full changelog isn't necessary to stay current, most of what changes in any given major version is internal or transparent. The permission model reaching stability, the continued easing of CommonJS/ESM interop friction, and native TypeScript execution maturing further are the three changes in Node 24 actually worth adjusting a workflow around; the rest arrives automatically the moment you upgrade.