NAPI (`.node` addons)
Loading native Node-API addons (.node files) via unffi's unified interface.
unffi can load .node native addons alongside regular shared libraries. When the path ends in .node, unffi automatically switches to the NAPI adapter — no manual routing needed.
NAPI addons are compiled native modules (from node-gyp, node-addon-api, napi-rs, etc.) that export JavaScript functions directly, rather than C ABI symbols.
Usage
import { , } from 'unffi'
const = await ('./my-addon.node', {
: { : [], : . },
: { : [., .], : . },
})
const msg = ..()const sum = ..(3, 4)The schema defines which exports to surface and their TypeScript types. At runtime, NAPI addon functions already accept and return JavaScript values — the type annotations serve for type safety, not marshalling.
Automatic routing
dlopen detects .node paths automatically:
- Node.js: loads via
createRequirefromnode:module - Deno: loads via
createRequirefromnode:module(requires--allow-ffiand a localnode_modules/directory. See Deno NAPI docs) - Bun: delegates to
Bun.dlopenwhich natively supports.nodefiles
You can also import the adapter directly:
import { dlopen, t } from 'unffi/napi'Loading .node addons compiled with napi-rs
import { , } from 'unffi'
// addon built with napi-rs exports { hello, add }
await using = await ('./native-addon.node', {
: { : [], : . },
: { : [., .], : . },
})Lifecycle
NAPI addons cannot be unloaded from the process. The close() method and Symbol.dispose are provided for interface consistency but are no-ops — double-close is safe.
Unlike shared libraries (.so/.dylib), NAPI addons register their exports during require() — the schema acts as a contract rather than an ABI definition.