Last updated Oct 14, 2026

Conversion Model

Exported functions normally do not call wrapper constructors manually. The module registration layer converts JavaScript arguments into Zig types, calls the Zig function, then converts the return value back into JavaScript.

Function Parameters

The first parameter may be napi.Env. It is injected by the wrapper and is not part of the JavaScript signature.

pub fn make(env: napi.Env, name: []const u8) napi.Object {
    _ = env;
    _ = name;
}

All other parameters are read from JavaScript arguments in order. Missing arguments are passed as undefined before conversion.

Supported Input Shapes

Zig type JavaScript input
bool boolean
integer and float types except u64 / i64 number
u64, i64, napi.BigInt bigint
[]u8, []const u8, [N]u8 UTF-8 string input
[]u16, []const u16, [N]u16 UTF-16 string input
?T T, null, or undefined
struct object with matching fields
tuple struct array with positional entries
array, slice, std.ArrayList(T) JavaScript Array, or numeric TypedArray when T is supported
enum numeric enum value
enum with pub const napi_string_enum = true string enum name
union(enum) first variant whose range, members, or fields fit
napi.NapiValue raw JavaScript value
napi.Function, *napi.Function, napi.FunctionRef JavaScript function
napi.ThreadSafeFunction pointer JavaScript function promoted to a TSFN
napi.Buffer Node Buffer
napi.ArrayBuffer ArrayBuffer
napi.TypedArray(T) and aliases matching TypedArray
napi.DataView DataView
napi.External(T) zig-napi external value tagged with T
napi.AbortSignal AbortSignal-like object

Use napi.Buffer or napi.ArrayBuffer when the JavaScript input should be treated as binary data instead of a string.

Conversion Hooks

Most wrapper types expose from_raw(env, raw) for manually wrapping an existing napi_value. Types that can be accepted as exported function parameters also expose from_napi_value; that hook is what the generated wrapper calls during automatic argument conversion.

Regular addon code usually only needs from_raw for low-level N-API interop. Prefer typed function parameters for normal exports so cleanup and error mapping stay in the generated conversion layer.

Napi.from_napi_value*, NapiValue.As, Object.Get/GetNamed/Has, Array.Get and string copy/length APIs now return error unions. Migrate manual callers with try or explicit catch. Binary wrappers expose tryFromRaw and tryAsSlice for checked construction/access; generated inputs use checked paths.

Supported Return Shapes

The return conversion supports ordinary Zig values and wrapper values.

Zig return JavaScript output
void undefined
napi.Owned(T) same as T; release native ownership after conversion
bool, numbers, strings primitive JavaScript values
i128 / u128 bigint
?T T or undefined
struct object
tuple, array, slice, std.ArrayList(T) array
enum numeric or string enum value
union(enum) payload of the active field
napi.Result(T) payload or thrown JavaScript error
Zig error union !T payload or thrown JavaScript error
napi.Async(T, runtime) Promise<T>
napi.AsyncWithEvents(T, Event, runtime) Promise<T> plus optional event callback
napi.Promise Promise
napi.Function JavaScript function
napi.Class(T) / napi.ClassWithoutInit(T) JavaScript class constructor
napi.External(T) branded external object

Allocation And Cleanup

Conversions that allocate Zig memory use napi.globalAllocator(). String, array, slice, object, function, external, and async conversions are cleaned up by the conversion layer when the wrapper owns the temporary value.

For addon-wide allocator control, export pub const napi_allocator from the addon root. For narrow tests or scoped operations, use setOperationAllocator and resetOperationAllocator.

A root allocator is reached from every thread of the addon: a worker executes Execute while the JavaScript thread converts arguments. On WebAssembly std.heap.page_allocator is the break allocator, whose free lists are one unsynchronized global that emnapi's worker threads share with the JavaScript thread; a custom allocator (a counting wrapper, for example) must therefore take its pages from napi.safePageAllocator(), which is that allocator behind one module-global lock (and is std.heap.page_allocator itself on native targets).

Converted arguments are call-scoped owned copies. Plain return values are borrowed and are not freed; return native heap allocations in napi.Owned(T). Async captures clone native inputs and dispose owned results explicitly. A custom deinit must release the allocations its value actually owns; it must not free borrowed literals or instance-retained constructor arguments. See Ownership.

Conversion Transactions

Argument conversion is a transaction. Some conversions do not copy data, they create JavaScript resources: napi.Reference(T), napi.ObjectRef and napi.FunctionRef create a strong reference, and a *napi.ThreadSafeFunction(...) parameter creates an active thread-safe function. Those resources are recorded while the arguments are converted and released again when the call is rejected, including a failure inside a nested struct or array, before the native body runs.

The transaction is committed at the moment the native body is entered. From then on the converted values belong to the body, so a successful call never releases a reference the body stored or aborts a TSFN another thread is using. Releasing them is the body's job: Unref()/Delete() for a reference, the final release()/abort() for a TSFN. See Ownership and Functions.

Manual conversions (Napi.from_napi_value*, NapiValue.As(T)) are transactions too:

  • inside an automatic argument conversion they join that conversion, so a failure anywhere still releases everything the outer conversion created,
  • anywhere else - a standalone conversion, or a manual conversion inside a native body whose own transaction is already committed - they run in their own transaction. It is committed when the conversion succeeds, which hands the created resources to the caller, and it releases them if the conversion fails halfway instead of leaking what it had already created.

One consequence of the ordering: a custom deinit of a converted value (a struct that releases its own native memory) runs after the resource rollback, during the ordinary native cleanup. It must only release native memory it owns - never a reference or TSFN the conversion created, which the transaction releases itself and the body owns after the commit.

TypeScript Output

generateTypeDefinition follows the same conversion model. It emits interfaces for object-like structs, tuples for tuple structs, const enum for Zig enums, union types for union(enum), ExternalObject<T> for napi.External(T), and an AbortSignal interface when an API references napi.AbortSignal.