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.
Developer setup, tests, and coverage live in DEVELOPMENT.md.
This project is generated by AI with heavy steering from me, a human. See I don't want your PRs anymore for this project's contribution philosophy. Follow the guidance in How can you help instead.
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-jsRelease automation currently publishes plain 0.0.X versions. If you do not want to automatically pull in the latest release, pin the CLI to an explicit version:
cargo install uniffi-bindgen-node-js --version <version>If 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-package--crate-name is optional when the built library exposes exactly one UniFFI component. If the library exposes multiple components, the generator reports the discovered crate names and requires --crate-name.
Use the built library file for your platform:
- macOS:
libyour_crate.dylib - Linux:
libyour_crate.so - Windows:
your_crate.dll
- Copy your built native library into the generated package.
By default, place it next to the generated JavaScript files:
cp path/to/your/built/library ./generated/your-package/If you generated with --bundled-prebuilds, copy it into the expected prebuilds/<target>/ path instead.
Bundled prebuild filenames are canonicalized from the UniFFI cdylib name:
- macOS:
prebuilds/<target>/libyour_crate.dylib - Linux and other Unix targets:
prebuilds/<target>/libyour_crate.so - Windows:
prebuilds/<target>/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] --out-dir <OUT_DIR> <LIB_SOURCE>Required inputs:
LIB_SOURCE: path to a built.so,.dylib, or.dll--out-dir: directory where the npm package will be written
Optional component selection and source resolution:
--crate-name <name>: Cargo package name or library crate name when the library exposes more than one UniFFI component--manifest-path <Cargo.toml>: Cargo.toml hint used when BindgenLoader needs workspace, UDL, oruniffi.tomlresolution help
Optional Node package settings:
--package-name <name>: npm package name to write intopackage.json--node-engine <range>: value written topackage.jsonengines.node--bundled-prebuilds: resolve the packaged native library fromprebuilds/<host-target>/instead of the package root--manual-load: export explicitload()andunload()helpers instead of auto-loading on import
Generated packages are always ESM. The CLI does not offer CommonJS output or legacy native-library path overrides.
By default, the generated loader expects the native library at the package root 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 resolves the runtime target and expects canonical platform filenames 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
You can generate the package once and populate as many prebuilds/<target>/ directories as you need. The bundled loader derives the filename from the runtime platform instead of preserving the filename of the library used during code generation.
Linux bundled targets include a libc suffix:
-gnuwhen Node reports glibc at runtime-muslotherwise
Without --manual-load, the generated package loads the native library at its default packaged path during import.
With --manual-load, the package exports load() and unload() so you control when loading happens. Calling load() with no argument uses the default packaged path; passing load(path) overrides it 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();
console.log(greet("world"));
unload();You can store Node generator settings in uniffi.toml:
[bindings.node]
package_name = "your-package"
node_engine = ">=16"
bundled_prebuilds = true
manual_load = falseDefaults:
package_name: UniFFI namespacenode_engine:>=16bundled_prebuilds:falsemanual_load:false
CLI flags apply after config-file settings.
Generated packages are always ESM. The generator rejects legacy [bindings.node] keys such as cdylib_name, lib_path_literal, module_format, and commonjs with explicit diagnostics.
- First-class target: UniFFI
0.31.x - Generated output: ESM-only
- Default Node engine range:
>=16
The generator currently 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
Unsupported or not-yet-adopted UniFFI surfaces fail generation with an explicit diagnostic rather than producing partial bindings.
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