uniffi-bindgen-node-js generates ESM Node packages for UniFFI cdylibs. Point it at a built Rust dynamic library and it writes a package with JavaScript, TypeScript declarations, a native loader, and the runtime helpers needed to call the library through koffi.
Contributor setup, tests, and coverage live in DEVELOPMENT.md.
Each generate run writes one npm package containing:
- a public JavaScript API for your UniFFI namespace
- matching
.d.tsfiles - a low-level FFI module
- runtime helpers used by the generated bindings
- a
package.jsonthat declareskoffias the runtime dependency
Generated packages are ESM-only and do not require a TypeScript build step.
Install the CLI from crates.io:
cargo install uniffi-bindgen-node-jsIf you want the current repository checkout instead:
cargo install --path .Or run it without installing:
cargo run -- --help- Build your UniFFI crate as a
cdylib.
cargo build --release -p your-crate- Generate a Node package from the built library.
uniffi-bindgen-node-js generate \
path/to/your/built/library \
--crate-name your-crate \
--out-dir ./generated/your-packageUse the built library file for your platform:
- macOS:
libyour_crate.dylib - Linux:
libyour_crate.so - Windows:
your_crate.dll
- Install the generated package dependencies.
cd ./generated/your-package
npm installYou can then publish that directory as a package or install it into another app with npm install ./generated/your-package.
- Consume the generated package from Node.
import { greet } from "./generated/your-package/index.js";
console.log(greet("world"));--crate-name accepts either the Cargo package name (your-crate) or the underscored library crate name (your_crate).
uniffi-bindgen-node-js generate [OPTIONS] --crate-name <CRATE_NAME> --out-dir <OUT_DIR> <LIB_SOURCE>Required inputs:
LIB_SOURCE: path to a built.so,.dylib, or.dll--crate-name: Cargo package name or library crate name--out-dir: directory where the npm package will be written
Optional flags:
--package-name <name>: npm package name to write intopackage.json--cdylib-name <name>: native library basename used by the generated loader--node-engine <range>: value written topackage.jsonengines.node--lib-path-literal <path>: hard-coded default path for the native library--bundled-prebuilds: resolve the default library fromprebuilds/<target>/...--manual-load: export explicitload()andunload()helpers instead of auto-loading on import--config-override KEY=VALUE: override supported[bindings.node]settings from the CLI
By default, the generated package expects a single native library for the current build and places it next to the generated JavaScript files:
your-package/
index.js
your_namespace.js
your_namespace-ffi.js
libyour_crate.dylib
If you pass --bundled-prebuilds, the generated loader looks for platform-specific libraries under prebuilds/<target>/:
your-package/
index.js
your_namespace.js
your_namespace-ffi.js
prebuilds/
darwin-arm64/libyour_crate.dylib
linux-x64-gnu/libyour_crate.so
win32-x64/your_crate.dll
This mode defines the lookup contract only. You still need to build and stage the per-target native libraries yourself.
Linux bundled targets include a libc suffix:
-gnuwhen Node reports glibc at runtime-muslotherwise
Without --manual-load, the generated package loads the native library during import.
With --manual-load, the top-level package exports load() and unload() so you can choose the library path explicitly:
uniffi-bindgen-node-js generate \
path/to/your/built/library \
--crate-name your-crate \
--out-dir ./generated/your-package \
--manual-loadimport { load, unload, greet } from "./generated/your-package/index.js";
load("./path/to/your/native/library");
console.log(greet("world"));
unload();You can store Node generator settings in uniffi.toml:
[bindings.node]
package_name = "your-package"
cdylib_name = "your_crate"
node_engine = ">=16"
bundled_prebuilds = true
manual_load = falseDefaults:
package_name: UniFFI namespacecdylib_name: UniFFIcdylibname from generation settingsnode_engine:>=16lib_path_literal: unsetbundled_prebuilds:falsemanual_load:false
CLI flags apply after config-file settings.
bundled_prebuilds = true cannot be combined with lib_path_literal.
- First-class target: UniFFI
0.29.5 - Generated output: ESM-only
- Default Node engine range:
>=16
The current generator supports:
- top-level functions
- objects, constructors, and synchronous methods
- async methods using the UniFFI Rust-future polling path
- records
- flat enums
- tagged enums
- UniFFI error enums as JavaScript error classes
Option<T>Vec<T>HashMap<K, V>bytesasUint8Array- callback interfaces, including async methods
Public API conventions:
Option<T>becomesT | undefinedVec<T>becomesArray<T>HashMap<K, V>becomesMap<K, V>bytesbecomesUint8Array- Node
Bufferinputs also work forbytes - 64-bit integers use
bigint timestampbecomesDatewith millisecond precisiondurationbecomesnumberas a non-negative integer millisecond count- records become plain JavaScript objects
- objects become JavaScript classes backed by UniFFI handles
The generator does not yet support:
- UniFFI custom types
- UniFFI external types
- automatic multi-target package assembly