napi
Build Node.js extensions in Dart. Export functions and classes, generate an npm
package, and call it from JavaScript or TypeScript. Choose Dart Wasm or a native
Node-API .node addon; both run your application code in Dart.
Requirements
- Dart 3.13.5.
- Node.js 24.19.0 or 26.11.1.
- Native builds: macOS 14 or later and Linux with glibc 2.28 or later on arm64
or x64, or Windows 10 / Server 2016 or later on x64. The runtime downloads
automatically on first use. Linux needs
unzipto extract that download. Application builds do not need a C/C++ toolchain or Dart SDK sources. See native builds for offline builds.
Wasm is the default target. It depends on experimental Dart compiler and interop
APIs and is tied to Dart 3.13.5. The browser ESM entry
runs in Chrome 155 without Node.js or a bundler. Resource policies and JsArray
handles require Node. Bun and other browsers have not yet been verified.
Build a package
For a ready-to-build project, download the starter, extract it, and follow its README. It includes a Counter, a typed build script and a JavaScript example. Change the package name and backend in the script.
To set up a project manually, follow the steps below.
Create a Dart package and add napi:
dart create -t package dart_counter
cd dart_counter
dart pub add napi
Write lib/counter.dart:
import 'package:napi/napi.dart';
@NAPI()
class Counter {
Counter([this.value = 0]);
int value;
int increment([int amount = 1]) => value += amount;
}
Create a build script, for example tool/build.dart:
import 'dart:io';
import 'package:napi/build.dart';
final package = PackageBuild(
library: File('lib/counter.dart').absolute.uri,
output: Directory('npm-package'),
name: '@scope/counter',
version: '0.0.0',
packagesPath: File('.dart_tool/package_config.json').absolute.path,
);
Future<void> main(List<String> args) => package.run(args);
Build and install the generated package in the same directory:
dart run tool/build.dart
npm init -y
npm install ./npm-package
Create example.mjs and run it with node example.mjs:
import {Counter} from '@scope/counter';
const counter = new Counter();
console.log(counter.increment()); // 1
console.log(counter.increment(2)); // 3
The package initializes when imported. TypeScript declarations are generated
alongside the bindings. To use the package in another Node project, run
npm pack ./npm-package and install the resulting tarball there.
The script can have any name or location; relative paths use your working
directory. For a native package, run dart run tool/build.dart --target native,
or set target: BuildTarget.native in the declaration. See
native builds for runtime caching and offline builds, and
packaging for script options, dual-target packages and custom
build workflows. Dual-backend packages can opt into
Wasm fallback for
root ESM imports on unsupported native platforms. CommonJS stays native-only;
explicit native and Wasm entries keep separate Dart state.
Learn more
- Export functions and classes
- Parameters and defaults · Numbers and strings
- Classes · Plain objects
- Lists, Maps and records · Shared JS collections
- Generic payloads · Concrete generic aliases
- Promises · Callbacks · Streams
- Binary data · Shared bytes
- Resource cleanup · Cancellation
Contributing
See CONTRIBUTING.md to run the tests.
License
MIT. See LICENSE. Generated packages include Dart's DART_LICENSE;
keep the included license notices when distributing them.