Migrating from NAPI-RS v2 to v3
This guide is for maintainers of an existing NAPI-RS v2 project who want to
upgrade to v3. It covers the package.json config changes, the rewritten
CLI, and the Rust API changes, in the order you should apply them.
For the background and the design goals behind these changes, see the Announcing NAPI-RS v3 blog post.
Prerequisites
Before you start:
- Node.js
^20.17.0 || ^22.13.0 || >=23.5.0for the new@napi-rs/cli. Node.js 22.13+ or 24+ is recommended. This is a build-time requirement only; it does not change the runtime support of the addon you ship. See Support and compatibility. - Rust 1.88 or newer.
- A clean Git working tree, so you can review every change the migration makes.
TL;DR: breaking changes
| Area | v2 | v3 |
|---|---|---|
| Config | napi.name |
napi.binaryName |
| Config | napi.triples.defaults + napi.triples.additional |
Flat napi.targets array |
| Config | napi.package.name |
napi.packageName (the old nested field is not read by v3) |
| CLI | napi build --cargo-cwd ./crates/napi |
napi build --manifest-path ./crates/napi/Cargo.toml |
| CLI | napi build --cargo-flags="--locked" |
napi build -- --locked |
| CLI | napi create-npm-dir |
napi create-npm-dirs |
| CLI | napi universal |
napi universalize |
| Rust | JsObject, JsFunction, JsBuffer, Ref, … |
New owned/scoped types; old types moved behind the compat-mode feature |
| Rust | napi::module_init |
napi_derive::module_init |
| Rust | #[module_exports] (compat-mode in napi-derive) |
#[napi(module_exports)] |
| Rust | ThreadsafeFunction with ref-count lifecycle |
Rewritten ownership-based ThreadsafeFunction |
Step 1: Update the dependencies
Update the Rust crates and the CLI:
[dependencies]
napi = "3"
napi-derive = "3"
[build-dependencies]
napi-build = "3"
npm install --save-dev @napi-rs/cli@latest
Then run a first build and let the compiler list every API that changed:
napi build
Step 2: Update the napi config
The napi configuration in package.json has changed. The name field is
now binaryName:
{
"name": "@napi-rs/package-template",
"version": "1.0.0",
"napi": {
- "name": "my-package",
+ "binaryName": "my-package",
}
}
The triples config has been removed. Set a flat targets array instead.
What used to be triples.defaults was:
"x86_64-unknown-linux-gnu",
"x86_64-pc-windows-msvc",
"x86_64-apple-darwin"
In v3 you list every target explicitly, including the former defaults:
{
"name": "@napi-rs/package-template",
"version": "1.0.0",
"napi": {
- "triples": {
- "defaults": true,
- "additional": [
- "aarch64-apple-darwin",
- "x86_64-unknown-linux-musl",
- "aarch64-unknown-linux-musl"
- ]
- }
+ "targets": [
+ "x86_64-unknown-linux-gnu",
+ "x86_64-pc-windows-msvc",
+ "x86_64-apple-darwin",
+ "aarch64-apple-darwin",
+ "x86_64-unknown-linux-musl",
+ "aarch64-unknown-linux-musl"
+ ]
}
}
The v3 CLI still reads napi.name and napi.triples for compatibility, but
new projects should not use them. The old nested napi.package.name field is
not read at all: move that value to napi.packageName yourself. See
NAPI Config for the full field reference.
Step 3: Update CLI usage
The CLI has been rewritten. The breaking changes that affect existing scripts and CI jobs are:
--cargo-cwd removed
Use --manifest-path to point at the Cargo.toml of the crate:
- napi build --cargo-cwd ./crates/napi
+ napi build --manifest-path ./crates/napi/Cargo.toml
In v2, path flags of napi build were resolved relative to
--cargo-cwd || process.cwd(). In v3, they are resolved relative to
--cwd || process.cwd().
--cargo-flags removed
Flags after -- are now passed through to the cargo build command:
- napi build --cargo-flags="--locked"
+ napi build -- --locked
The --locked flag is passed to cargo build, resulting in
cargo build --locked.
create-npm-dir renamed to create-npm-dirs
See create-npm-dirs for more details.
Besides the renamed command and flags, it is no longer recommended to commit
the npm/* directories. Create them in CI with napi create-npm-dirs
instead, like this:
https://github.com/napi-rs/package-template/blob/main/.github/workflows/CI.yml
napi universal renamed to napi universalize
See universalize for more details.
Step 4: Update the Rust code
Some JsValues are now behind the compat-mode feature flag
The full list of these values is:
JsObjectJsFunctionJsNullJsBooleanJsUndefinedJsBufferJsBufferViewJsArrayBufferJsArrayBufferViewJsTypedArrayJsBigintRef
These APIs are not safe; see Lifetime in V3 for more details.
Migrate these APIs to the new APIs. See Values,
Function, Reference,
and TypedArray for more details. If you cannot
migrate yet, enable the compat-mode feature flag as an escape hatch (see
below).
ThreadsafeFunction
ThreadsafeFunction has been totally
rewritten. See ThreadsafeFunction in V3
for the background, and the new
ThreadsafeFunction API doc for
day-to-day usage.
napi::module_init moved to napi_derive::module_init
This is due to breaking changes in the upstream
ctor crate:
- #[napi::module_init]
+ #[napi_derive::module_init]
fn init() {
// ...
}
New #[napi(module_exports)]
This replaces the #[module_exports] macro from compat-mode. You can now
drop the compat-mode feature from napi-derive and use only the modern
#[napi] macros. See module_exports
for the accepted signature.
Step 5: The compat-mode escape hatch
If a full migration is not possible right now, enable the compat-mode
feature to keep using the deprecated types while you migrate incrementally:
[dependencies]
napi = { version = "3", features = ["compat-mode"] }
This is not recommended as a permanent state: the types behind compat-mode
have known safety issues, and the feature exists only to make the upgrade
path incremental.
FAQ
Do I have to migrate everything at once?
No. Enable compat-mode, upgrade the config and the CLI first, then migrate
one API at a time until you can remove the feature flag.
Where did napi build --cargo-flags go?
Everything after -- is forwarded to cargo build. Run napi build -- --locked instead of napi build --cargo-flags="--locked".
Does v3 change which Node.js versions my addon supports?
The new CLI requires Node.js ^20.17.0 || ^22.13.0 || >=23.5.0 at build
time. The runtime support of the addon you produce is a separate question; it
depends on the Node-API level you compile against. See Support and
compatibility.
My build fails after removing compat-mode. What now?
Read the compiler errors from the top: each one points at a deprecated type and its modern replacement. The concept pages Values, Function, Reference, and TypedArray show the v3 equivalent of every removed API.