Type conversion
Contents
- Zig to JS
- JS to Zig
- Structs
- Enums
- Tuples
- Optionals and null
- Custom conversion
- Buffers and typed arrays
The bridge auto-converts every supported Zig type to and from a JS value. The two endpoints are:
env.toJs(value)for Zig to JS. Used implicitly on every return value, callback argument, and field assignment.val.to(env, T)for JS to Zig. Used implicitly on every JS argument that is bound to a typed parameter.
You usually never call them directly. They are listed here so you know what is happening underneath, and so you can call them yourself when working with napi.Val directly.
Zig to JS
JS to Zig
Type mismatches throw a JS TypeError with the actual JS type:
A BigInt that does not fit the target Zig int throws RangeError: bigint out of range for .... To handle lossy values yourself instead of erroring, take a napi.Val parameter and call getBigIntI64 / getBigIntU64.
Number-backed integers are strict: fractional values, NaN, infinities, negative values for unsigned types, and values outside the destination range throw RangeError. The bridge never applies JavaScript's 32-bit wrapping or truncation rules.
Untyped comptime_int constants are emitted as JavaScript numbers only within Number.MIN_SAFE_INTEGER...Number.MAX_SAFE_INTEGER; larger values are rejected at compile time instead of being rounded. Give a larger constant an explicit i64 or u64 type when it should be exposed as a JavaScript BigInt. A Zig slice longer than JavaScript's maximum array length fails with RangeError before any narrowing conversion.
Structs
Struct fields are matched by camelCase own-property name. Inherited values are ignored, so a polluted prototype cannot satisfy a required field or override a Zig default. Zig default values are used when the JS object omits a property:
This makes it cheap to add a field: bump the Zig struct, give it a default, no JS callers break.
Automatic struct input must be a JavaScript object or function. null and primitive values are rejected even for struct {}. An own property whose value is undefined follows the same missing-field path as an omitted property.
If every field has a default, a function parameter of that struct type may be omitted or passed as undefined; both forms construct .{} and apply the Zig defaults. Quoted Zig field names are supported and are escaped correctly in generated declarations. Field names must remain unique after snake_case-to-camelCase conversion: ambiguous shapes such as read_file plus readFile are rejected at compile time instead of overwriting data.
Enums
Enums map to and from strings.
Both the snake_case and camelCase form of every variant is accepted on the way in. The way out is always camelCase.
Quoted Zig enum tags preserve their exact quote, backslash, and Unicode content in JavaScript and generated TypeScript string literals. Enum tags must remain unique after camelCase conversion; ambiguous tags are rejected at compile time because they could not round-trip losslessly.
Tuples
A Zig anonymous tuple maps to a JS array, and vice versa.
Tuple input must be a real JavaScript Array with exactly one element per tuple field. Fixed arrays follow the same exact-length rule; slices accept any Array length. Array-like objects and extra elements are rejected instead of being silently accepted or ignored.
Optionals and null
?T is T | null on the JS side. null on the Zig side becomes null in JS. undefined on the JS side is treated as null when converting to ?T.
Custom conversion
For types the converter cannot handle (unions, opaque handles, tagged shapes), define toJs and fromJs on the type. They take priority over the default field-by-field walk.
Both methods are looked up by name. Defining only one is fine; the auto-converter handles the other direction.
When using generated declarations, add a TypeScript override to a custom-converted struct if you want a precise public type:
_dts is inserted as a trusted, bidirectional TypeScript representation. Use it only when fromJs accepts that representation and toJs produces it. Without _dts, a struct that declares toJs or fromJs is generated as unknown rather than incorrectly exposing its storage fields as the JavaScript representation. If a one-way custom converter differs from the automatic representation in the other direction, keep the generated type unknown or provide a handwritten declaration file with distinct parameter and return types.
Array, slice, and tuple conversion also uses own properties for every index. A hole cannot be filled by an inherited numeric property; an explicit own undefined is still converted normally according to the element type.
Buffers and typed arrays
Buffers are intentionally not in the auto-conversion table. The converter cannot know whether you want a copy, a borrowed slice, or a typed array view. Build them explicitly:
env.createBuffer(len)returns{ .val, .data }(a JS Buffer plus a writable[]u8).env.createArrayBuffer(len)does the same forArrayBuffer.val.getBufferData(env)returns a[]u8into the existing memory.
See the Env reference for the full list.