· via dev.to (home feed)
Node.js 26.9 enables node:ffi by default, no experimental flag required
Node.js 26.9.0 ships the node:ffi module enabled by default, letting JavaScript call native C libraries without compiled addons. Early benchmarks show it slightly behind N-API addons on per-call overhead.

Node.js 26.9.0, released on 16 September, carries a one-line changelog entry with outsized consequences: node:ffi, the built-in module for invoking native C functions without writing a compiled addon, is now available without any launch flags. A hands-on writeup published on dev.to examined what the switch means in practice, and the short version is that "just call a C library" in Node got easier — with some sharp edges attached.
The flag flip
Through 26.8.2, touching node:ffi required starting Node with --experimental-ffi. On 26.9.0 the module loads with no flags at all, though it still prints an ExperimentalWarning noting the API may change. The old enabling flag is now redundant, and the only way back to previous behaviour is --no-experimental-ffi, under which importing node:ffi fails with an unknown built-in module error.
That has a practical implication for version pins: code that imports node:ffi used to fail loudly on installations without the flag, and on 26.9.0 it simply runs. Anyone with strict Node version ranges in CI should be aware the failure mode changed underneath them.
What a call costs
According to the dev.to benchmarks, which timed five million calls to a trivial add function, plain JavaScript took roughly 2.2 to 3.0 nanoseconds per call, an equivalent N-API addon 34.1 to 35.8, and node:ffi 37.5 to 38.1. FFI lands 7 to 8 percent behind the compiled addon, and both are around fifteen times the cost of staying in JavaScript — the toll of converting values each time control crosses the JS/native boundary.
The takeaway is not that FFI is slow in absolute terms but that it does not beat the thing it is meant to replace on speed. Its argument is convenience: no node-gyp, no compiler toolchain baked into deploy images, and no recompilation whenever Node's ABI shifts.
Where FFI actually wins
Per-call overhead dominates trivial functions, so the same writeup re-ran the comparison as a bulk operation: summing ten million float64 values by handing the native function a pointer to the underlying buffer. FFI finished in 13.8 to 14.0 milliseconds against 16.0 to 18.4 for a plain JavaScript loop, a 15 to 20 percent advantage, because one call carries all the data instead of spreading fixed overhead across millions of invocations.
At the other extreme, a single fib(75) computed in C took 0.09 milliseconds versus 0.10 in JavaScript — effectively a tie, since V8's JIT compiles a simple loop about as well as gcc -O2. FFI's fixed cost only pays for itself at high call counts or large data volumes; a one-off hop into native code usually is not that case.
What it validates, and what it doesn't
Node's documentation labels the module unsafe, and the dev.to testing found the protection is selective. Argument shape is checked: passing a Number where a uint64 is expected throws a TypeError demanding a BigInt rather than coercing, and calling a two-argument function with one argument is rejected. But declaring an int64-returning function as returning int32 produced a silently truncated wrong value with no error or warning — the kind of bug that sails through review because nothing about it looks wrong. Worse, handing a function a valid pointer paired with an inflated length caused an immediate segfault with exit code 139, the sort of inconsistency a refactor can introduce when a buffer changes size but a call site does not. In short: counts and scalar types are validated; pointer bounds and return-type declarations are not.
Permissions and concurrency
The Permission Model gates FFI separately. Under --permission, calls are denied until --allow-ffi is passed, and Node attaches a security warning that the flag must be used with extreme caution because it could invalidate the permission model — native code, once loaded, is bound by none of the other restrictions. Closed library handles fail cleanly with a "Library is closed" error, and the newer using syntax closes them automatically at scope exit. FFI calls also ran from worker threads without special setup, with throughput scaling from roughly 7.1 million calls per second on one worker to about 23 million on four, indicating the calls parallelise rather than serialise through a global lock.
Why it matters
Default-on FFI removes the biggest logistical barrier to calling native code from Node — the build chain — which matters for slim deployment images and for packages that want to ship without prebuilt binaries. But the module remains experimental, and its failure modes are the nasty kind: silent numeric truncation and segfaults rather than exceptions, plus a documented ability to punch through the Permission Model. Teams should treat it as a convenience for bulk, well-tested native calls — not a performance upgrade over existing addons, and not something to enable casually in security-sensitive processes.
- #node-js
- #ffi
- #native-modules
- #javascript
- #benchmarks