build.zig (addLib)

Contents

napi.addLib is the only build.zig API you need. It registers your addon as a Zig artifact, applies the right linker flags for each OS, sets up the .d.ts install, and (with -Dnpm=true) builds the cross-compile graph and generates the npm package skeleton.

You can call it more than once in the same build.zig to ship multiple addons from one repo. Each call writes its own zig-out/lib/<name>.node and its own npm/<name>/ tree, and the CLI (napikit build, napikit build --release, napikit bump, napikit publish, napikit npm-init) picks up every one. Every call needs a distinct .name. Per-platform package names are derived from .scope plus the platform suffix, so two addons may share a scope only when their configured platform sets do not overlap; a duplicate generated identity fails while configuring the build graph.

const std = @import("std");
const napi = @import("napi");

pub fn build(b: *std.Build) void {
    const target = b.standardTargetOptions(.{});
    const optimize = b.standardOptimizeOption(.{});

    napi.addLib(b, b.dependency("napi", .{}), .{
        .name = "my-addon",
        .root = b.path("src/lib.zig"),
        .target = target,
        .optimize = optimize,
        .npm = .{
            .scope = "@myscope",
        },
    });
}

LibOptions

OptionRequiredTypeDescription
.nameYes[]const u8Portable unscoped npm name. Used for the .node binary and npm package.
.rootYesLazyPathPath to the root Zig source file (src/lib.zig).
.targetYesResolvedTargetBuild target. Use b.standardTargetOptions.
.optimizeYesOptimizeModeOptimization mode. Use b.standardOptimizeOption.
.importsNo[]const ImportAdditional Zig modules to import.
.npmNo?NpmConfignpm package config. Required for cross-compile + publish.
.host_exeNo[]const u8Windows host binary (default "node.exe", use "electron.exe", etc.).

Import

pub const Import = struct {
    name: []const u8,
    module: *std.Build.Module,
};

Pass via .imports = &.{ .{ .name = "parser", .module = parser } }. See Project layout: where to put your code.

NpmConfig

OptionDefaultDescription
.scoperequirednpm scope (e.g. "@myscope").
.description""Package description.
.license"MIT"License identifier.
.repository""Git repository ("owner/repo" shorthand or a full URL). See below.
.dts.none.{ .file = path }, .auto, or .none. See TypeScript.
.platformsPlatform.defaultsCross-compilation targets.

.repository

npm requires a repository field on every published package for provenance to verify against the source tree, otherwise napikit publish fails in CI with "package must specify a repository". An addon ships as the main package plus one binding per platform (twelve package.json files with the default platform set), and setting .repository once in build.zig writes the field into every one of them on each release build, so you never have to keep them in sync by hand.

Two accepted forms:

.repository = "OWNER/REPO",                              // your consumer repository
.repository = "git+https://github.com/OWNER/REPO.git",   // equivalent explicit URL

Replace OWNER/REPO with the GitHub repository that contains the consumer addon, not the @teakit/napi source repository. The shorthand expands to git+https://github.com/owner/repo.git. It must contain exactly one slash, a valid GitHub owner, and a non-empty repository name; extra path segments, ./.., whitespace, and control characters are rejected. Full URLs may use git+https, git+ssh, ssh, git, http, or https; the git@host:path SCP form is also accepted. Each form must contain a DNS/IPv4-style host with valid labels and a non-empty repository path (IPv6 literals are intentionally not accepted)—transport-looking but incomplete values such as git+x, https://?, or git@host fail during build configuration. Non-GitHub URLs are usable only when you do not use the default GitHub trusted-publishing/provenance flow; napikit npm-init --repo and provenance publishing require the actual consumer GitHub OWNER/REPO. If .repository is empty, no field is emitted and any value already in the existing package.json is preserved.

.name follows npm's lowercase unscoped syntax and also has to be safe on every supported filesystem. Reserved names (node_modules, favicon.ico), trailing dots, and Windows device basenames such as con, con.txt, com1, and lpt9 fail before any artifact is written. A scope may use a value such as @con because the on-disk directory includes the leading @, but it may not end in a dot.

When .npm is present, its package name, scope, repository, and platform list are validated for ordinary development builds as well as release builds. Invalid publication metadata therefore cannot leave a seemingly usable local binary that only fails later during packaging.

Dts

pub const Dts = union(enum) {
    none,
    auto,
    file: std.Build.LazyPath,
};

See TypeScript declarations for what each mode produces.

With .none, the generated main package has no types field and does not list or contain index.d.ts. If a previously typed release package switches to .none, the release reconciler removes that build-owned declaration without deleting unrelated files.

Platform

A tagged enum of every supported (OS, architecture, libc) tuple. Used in .npm.platforms. Defaults are exposed as Platform.defaults:

Platform valueOSArchlibc
.linux_x64_gnuLinuxx64glibc
.linux_x64_muslLinuxx64musl
.linux_arm64_gnuLinuxarm64glibc
.linux_arm64_muslLinuxarm64musl
.linux_arm_gnuLinuxarmglibc
.linux_arm_muslLinuxarmmusl
.macos_x64macOSx64n/a
.macos_arm64macOSarm64n/a
.windows_x64Windowsx64n/a
.windows_arm64Windowsarm64n/a
.freebsd_x64FreeBSDx64n/a

Override the default set with:

.platforms = &.{ .linux_x64_gnu, .macos_arm64 },

What addLib does

  1. Creates a Zig module from .root and adds the napi import.
  2. Adds any .imports to the module.
  3. Builds a dynamic library named <name>.node.
  4. Configures linker flags per OS:
    • macOS: linker_allow_shlib_undefined = true.
    • Linux/FreeBSD: links libc, restricts exports to N-API entry points via exports.ld.
    • Windows: generates an import library from node_api.def so the linker resolves N-API symbols against the host .exe at runtime.
  5. Drops red zone, unwind tables, and unreferenced sections (smaller binaries, no surprise symbols).
  6. If .npm is set, installs the .d.ts next to the binary in the right format.
  7. If both .npm is set and -Dnpm=true is passed, generates the full cross-compile graph and the npm/ package tree.

napikit build --release is zig build -Dnpm=true --release=fast by default, with the selected optimization mode applied to every entry in .npm.platforms. --optimize=safe and --optimize=small select ReleaseSafe and ReleaseSmall for every generated binding. You can run the matching Zig command directly if you prefer.

Two build options narrow that loop, set for you by the matching CLI flags:

Build optionCLI flagEffect
-Dnpm-only=a,b--only a,bOnly run the npm release for addons whose .name is listed.
-Dnpm-host=true--currentCross-compile only the host platform, provided it is present in .platforms.

Under -Dnpm-host the main package.json still lists every configured platform in optionalDependencies, so a later full build stays complete. If the host is absent from .platforms, the build fails rather than creating an undeclared binding. See Building a subset.