Determinate Nix: Wasm in Expressions and Flake Schemas
Determinate Nix: Wasm in Expressions and Flake Schemas
Goal: Use two Determinate Nix features that ordinary Nix lacks (or lacks in usable form): builtins.wasm to call WebAssembly from the evaluator, and flake schemas so nix flake show / nix flake check understand your custom outputs. Neither replaces overlays, flakes, or modules — they extend evaluation and flake UX. Keep this chapter next to flake layout and checks; do not confuse it with the light overlays chapter.
Version note: the book tracks NixOS 26.05 (“Yarara”) and Nix 2.35+ (re-verified 2026-10-09: 26.05 remains the current stable branch). builtins.wasm ships in Determinate Nix ≥ 3.17.0 behind extra-experimental-features = wasm-builtin. Flake schemas are documented in the Determinate Nix 3.23.1 reference manual; stock Nix understands a built-in schema set, while Determinate adds the extensible schemas / exportedSchemas protocol. This box does not run Determinate Nix, so listings below are checked against upstream docs and the public examples — run them on a Determinate host to confirm.
Mental model
┌─ Nix expression ─────────────────────────────────────────────┐
│ builtins.wasm { path = ./fn.wasm; function = "fib"; } 33 │
│ │ │
│ ▼ Wasmtime (pure host interface; fresh instance) │
│ fib.wasm ──► Nix value (int / list / attrset / …) │
└──────────────────────────────────────────────────────────────┘
┌─ flake outputs ──────────────────────────────────────────────┐
│ packages / devShells / nixosConfigurations / YOUR OUTPUTS │
│ │ │
│ ▼ schemas.<outputName> = { version = 1; inventory…} │
│ nix flake show | check enumerates & validates generically │
└──────────────────────────────────────────────────────────────┘
| Piece | What it means |
|---|---|
builtins.wasm |
Primop: load a .wasm module, call one export, pass one Nix value in, get one Nix value out |
wasm-builtin |
Experimental feature flag required before the primop exists |
| Fresh Wasm instance per call | No ambient mutable state across calls — preserves evaluation purity |
schemas output |
Attrset of schema defs for this flake’s outputs |
inventory |
Function: flake output → tree of nodes (children / leaf what / evalChecks / …) |
roles |
Declare which CLI verbs apply (nix-build, nix-run, …) without hard-coding output names |
| Built-in fallback | If a flake has no schemas, Nix uses Determinate’s default schema set for known outputs |
When to reach for Wasm. Parsing formats Nix lacks (YAML), heavy pure computation, or tree walks that would otherwise force IFD. Prefer a real derivation when the work belongs in the build plan; prefer Wasm when you need the answer during evaluation without IFD.
When to reach for schemas. Custom flake outputs (myTeam.services, lab topologies, internal bundlers) that nix flake show should list and nix flake check should validate — without teaching every tool a new special case.
Worked examples
Case 1: Enable and probe builtins.wasm
On a machine with Determinate Nix 3.17+:
# /etc/nix/nix.custom.conf (or equivalent Determinate drop-in)
# extra-experimental-features = wasm-builtinnix eval --extra-experimental-features wasm-builtin --expr 'builtins.wasm'«primop wasm»
Call shape:
builtins.wasm {
path = ./nix_wasm_plugin_fib.wasm; # or builtins.fetchurl / flake `file` input
function = "fib";
} 33
# → 5702887Public examples live in DeterminateSystems/nix-wasm-rust (Fibonacci, Mandelbrot, YAML, …). A documented smoke test:
nix build github:DeterminateSystems/nix-wasm-rust
nix eval --extra-experimental-features wasm-builtin --impure --raw --expr \
'builtins.wasm { path = ./result/nix_wasm_plugin_mandelbrot.wasm; function = "mandelbrot"; } { width = 60; }'Performance intuition from Determinate’s measurements: naive fib 40 in Nix ~tens of seconds and multi-GB RSS; the Wasm port returns in well under a second with tens of MB. Per-call overhead is still higher than a native Nix function call — batch work inside one Wasm invocation.
Case 2: YAML without IFD (sketch)
Rust side (via nix-wasm-rust host crate) exports fromYAML; Nix side becomes:
# yaml.nix — evaluation-time YAML → Nix values (requires wasm-builtin)
{ wasmModule }:
let
fromYAML = text:
builtins.wasm {
path = wasmModule;
function = "fromYAML";
} text;
in
fromYAML ''
service:
replicas: 3
image: ghcr.io/example/app:1.2.3
''Compared to IFD (runCommand + builtins.fromJSON), this keeps nix flake show / eval-only CI from spawning builders just to read config. Compared to a C++ plugin, the .wasm can live in-tree or as a file flake input — flakes stay self-contained.
Case 3: A minimal custom flake schema
From the Determinate 3.23.1 manual — teach nix flake show about nixosConfigurations-shaped output without special-casing in the CLI:
# flake.nix — schemas output (excerpt)
{
outputs = { self, ... }: {
nixosConfigurations.edge = { /* … */ };
schemas.nixosConfigurations = {
version = 1;
doc = ''
The `nixosConfigurations` flake output defines NixOS system configurations.
'';
inventory = output: {
children = builtins.mapAttrs (configName: machine: {
what = "NixOS configuration";
derivationAttrPath = [ "config" "system" "build" "toplevel" ];
}) output;
};
};
};
}Leaf nodes may also set evalChecks (attrset of booleans for nix flake check), shortDescription, forSystems, isLegacy, and isFlakeCheck. Non-leaves nest via children.
Case 4: Roles — teach nix build about a custom output
schemas.labImages = {
version = 1;
doc = "Disk images for the teaching lab.";
roles.nix-build = { }; # nix build / nix shell / nix develop may consume these
inventory = output: {
children = builtins.mapAttrs (_: drv: {
what = "lab disk image";
derivationAttrPath = [ ]; # leaf is the derivation itself
}) output;
};
};Stock roles include nix-build, nix-run, nix-develop, nix-fmt, nix-bundler, nix-search, nix-template. Tools may define more (for example a future nixos-configuration role instead of hard-coding nixosConfigurations).
Case 5: Exporting schemas for other flakes
Determinate’s later schema revision distinguishes schemas you consume locally from schemas you export for others to import (exportedSchemas). Prefer documenting shared schemas in one flake and importing them, rather than copy-pasting inventory functions into every repo. Check the current flake schemas protocol and the “interface change” blog post before pinning a team convention — the attribute names moved once already.
The trap
- Expecting
builtins.wasmon upstream Nix. As of this writing it is Determinate-first (with an upstream PR in flight). Flakes that require it are not portable to stocknixwithout a feature flag and binary that provide the primop. - Using Wasm for impure work. No RNG, no ambient FS writes: that is the point. If you punch host functions that read the ambient machine, you break evaluation reproducibility.
- Chatty per-item Wasm calls. Instance setup dominates tiny calls. Parse a whole file once; do not call Wasm once per list element in a tight eval loop.
- IFD-building the
.wasmon every eval. Fetch or vendor the module; compiling it via IFD reintroduces the cost Wasm was meant to avoid. - Schemas that lie. An
inventorythat always returnsevalChecks = { ok = true; }makesnix flake checkgreen while hiding broken outputs. Put real predicates inevalChecks. - Confusing schemas with overlays. Overlays rewrite
pkgs. Schemas only describe flake outputs for tooling. Different layer (see the overlays chapter for the fixed-point story).
The boring rule
On Determinate Nix: enable wasm-builtin only when evaluation-time pure compute beats IFD; vendor or file-input the .wasm; keep flakes that must stay upstream-Nix-portable free of builtins.wasm. Add a schemas (and, when sharing, exportedSchemas) entry for every custom output you expect humans and CI to show/check, with an honest inventory.
Try this
- On a Determinate host,
nix eval --extra-experimental-features wasm-builtin --expr 'builtins.wasm'and run the Mandelbrot example fromnix-wasm-rust. - Add a
schemasstub for one custom output in a lab flake; comparenix flake showbefore and after. - Time an IFD YAML parse vs a Wasm
fromYAMLon a multi-thousand-line file; keep the winner, delete the loser.
Sources
- WebAssembly | Determinate Nix docs (Wasm since 3.17.0; docs updated 2026-09-21)
- A new chapter for the Nix language, courtesy of WebAssembly — Eelco Dolstra, 2026-03-05
- Flake Schemas — Determinate Nix 3.23.1 Reference Manual
- Extensible flake outputs with flake schemas; An interface change for flake schemas
- NixOS 26.05 release notes / channel (stable as of 2026-10-09)