Skip to content

A simple package

This tutorial creates a napi-rs v3 addon, calls it from Node.js, and prepares the project for CI. Complete the prerequisites first.

Create the project

Choose a package name you control. A scope is strongly recommended because the release workflow creates one npm package per target.

The following command uses the Yarn template and supplies the non-interactive defaults for everything else:

sh
npx @napi-rs/cli new cool \
  --name @your-scope/cool \
  --no-interactive

To use the pnpm template instead:

sh
pnpm dlx @napi-rs/cli new cool \
  --name @your-scope/cool \
  --package-manager pnpm \
  --no-interactive

Replace @your-scope before running the command. If you want to choose the Node-API level, targets, license, and CI workflow interactively, omit --no-interactive.

INFO

napi new supports the maintained Yarn and pnpm templates. It does not generate a generic template that can be switched to an arbitrary package manager afterward.

Understand the generated project

Enter the project and install its dependencies:

sh
cd cool
yarn install

Use pnpm install for the pnpm template. The files you will work with first are:

text
.
├── .github/workflows/CI.yml
├── Cargo.toml
├── build.rs
├── package.json
├── src/lib.rs
└── __test__/index.spec.ts
  • src/lib.rs is the Rust addon source.
  • Cargo.toml declares a cdylib and the napi-rs v3 crates.
  • build.rs calls the napi-rs build setup and must remain at the crate root.
  • package.json contains the CLI scripts and napi packaging config.
  • .github/workflows/CI.yml builds and tests the target rows retained from the template.

npm/ is not present yet. The publish job creates its per-platform package directories with napi create-npm-dirs after the platform builds finish.

The generated Rust source exports a small function:

src/lib.rs
rust
#![deny(clippy::all)]

use napi_derive::napi;

#[napi]
pub fn plus_100(input: u32) -> u32 {
  input + 100
}

The macro exposes the Rust function as the JavaScript function plus100 and writes the corresponding TypeScript declaration during the build.

Build the addon

Run the template's release build for your current platform:

sh
yarn build
sh
pnpm build

The script invokes napi build --platform --release and produces files like:

text
cool.darwin-arm64.node
index.js
index.d.ts

The exact .node suffix follows your current OS, architecture, and ABI. A Linux glibc build, for example, uses linux-x64-gnu. Use yarn build:debug when you want a debug build.

The generated declaration contains:

index.d.ts
ts
export declare function plus100(input: number): number

Call the native function from Node.js:

sh
node -e "const { plus100 } = require('./index.js'); console.log(plus100(42))"

The output is:

text
142

Change and test the Rust API

Add another exported function to src/lib.rs:

src/lib.rs
rust
#[napi]
pub fn multiply(left: i32, right: i32) -> i32 {
  left * right
}

Rebuild, then verify both the runtime export and generated types:

sh
yarn build
node -e "const { multiply } = require('./index.js'); console.log(multiply(6, 7))"

Add a matching AVA assertion to __test__/index.spec.ts:

**test/index.spec.ts**

ts
import test from 'ava'

import { multiply } from '../index'

test('multiply in native code', (t) => {
  t.is(multiply(6, 7), 42)
})

Run the tests:

sh
yarn test

Prepare the repository

Before pushing the generated workflow, update package.json:

  • Set name to a package and scope you can publish.
  • Set repository to the final GitHub repository. npm provenance checks this metadata, so do not leave the template repository URL in place.
  • Review license, description, keywords, homepage, and bugs.
  • Review napi.targets. It controls package creation and publishing, but each target still needs an actual CI build job.

If the name or binary name changes later, use the CLI so Cargo, package config, CI, and generated binding names stay aligned:

sh
yarn napi rename \
  --name @your-scope/cool \
  --binary-name cool \
  --repository https://github.com/your-name/cool.git

Then create and push the repository:

sh
git init
git add .
git commit -m "Create napi-rs package"
git branch -M main
git remote add origin git@github.com:your-name/cool.git
git push -u origin main

Prepare npm and GitHub Actions

The generated workflow publishes through npm and creates a GitHub release. For its token-based setup:

  1. Create the npm scope and package access you intend to use.
  2. Create an npm automation token that can publish the root and every per-platform package.
  3. Add it as the NPM_TOKEN Actions secret.
  4. Keep the workflow's contents: write permission for GitHub releases and id-token: write permission for npm provenance.
  5. Run the ordinary CI path successfully before attempting a release.

WARNING

Publishing is not atomic. napi pre-publish updates package metadata, publishes platform packages, and can create or update a GitHub release before npm publishes the root package. Do not use it as a trial command with real credentials.

Read Release native packages for the complete preflight, release, and recovery procedure. The exact side effects and flags are documented under napi pre-publish.

Where to go next

Last updated on
LongYinan