UnFFI
Runtimes

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 msg: string
const sum = ..(3, 4)
const sum: number

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 createRequire from node:module
  • Deno: loads via createRequire from node:module (requires --allow-ffi and a local node_modules/ directory. See Deno NAPI docs)
  • Bun: delegates to Bun.dlopen which natively supports .node files

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.

On this page