Drop-in blazingly-fast replacement for Zod with a Rust validation core.
Benchmarks • Behavior contract • Divergences • Dependency rationale
zodrs keeps the Zod v4 TypeScript surface and replaces the validation engine.
Schemas compile to a flat plan arena that a Rust core walks over raw JSON bytes
in one pass. The backends are designed to agree, and the conformance suite
runs four of them against the same corpus, so the engine underneath is an
implementation detail you can switch off.
Correctness is measured against Zod's own v4.4.3 test corpus, vendored into
packages/conformance. Every departure is recorded in
packages/conformance/EXCISIONS.md;
anything failing that was not excised is a bug.
npm install zod-rsRequires Node 20.17 or later. The package embeds a prebuilt Linux x64 GNU
N-API addon and a wasm32-wasip1-threads addon. On any other platform the
loader skips the native tier and uses the embedded WASM addon; if that is
unavailable too, validation falls back to the TypeScript validator.
import { z } from "zod-rs";
const User = z.object({
name: z.string().min(3),
age: z.number().int().positive(),
});
User.parse({ name: "Ada", age: 36 });
// -> { name: "Ada", age: 36 }
const result = User.safeParse({ name: "Al", age: -1 });
result.success; // false
result.error.issues.map((i) => [i.code, i.path]);
// -> [["too_small", ["name"]], ["too_small", ["age"]]]To hand raw bytes straight to the Rust core, skip JSON.parse and call
parseJson:
User.parseJson('{"name":"Ada","age":36}');parseJson returns exactly what parse(JSON.parse(...)) returns. When no Rust
backend is registered it does that internally.
| Import | Contents |
|---|---|
zod-rs |
Classic z surface |
zod-rs/mini |
Tree-shakeable functional surface |
zod-rs/core |
Shared primitives and issue types |
zod-rs/locales |
Error-message locales |
zod-rs/v4, zod-rs/v4-mini, zod-rs/v4/core, zod-rs/v4/locales |
Version-qualified aliases |
Zod's v3 surface is not shipped.
Two independent switches select how validation runs. Both exist so the conformance suite can pin each tier against the corpus; neither is needed in normal use.
| Variable | Values | Effect |
|---|---|---|
ZODRS_LOADER |
unset / native |
Native N-API addon, then WASM, then none |
wasm |
WASM addon only | |
none |
No Rust backend; TypeScript validator only | |
ZODRS_BACKEND |
interpreter |
Forces the tree-walking interpreter over compiled codegen |
The suite runs all four lanes. Each passes 5,679 tests across 435 files.
Measured against Zod v4.4.3 on the native tier, with valid inputs. Each figure
is the median of three full runs. The harness lives in packages/bench; the
comparison set is fixed in packages/bench/gate.mjs.
| Suite | Workload per op | zodrs (ops/s) | zod4 (ops/s) | Ratio |
|---|---|---|---|---|
z.string().parse |
10,000 strings | 34,252 | 1,875 | 18.27x |
z.number().parse |
10,000 numbers | 8,116 | 1,570 | 5.17x |
z.array().parse |
1,000 arrays of 3 strings | 12,217 | 4,188 | 2.92x |
z.union().parse |
1 value, 3-member union | 3,438,230 | 1,495,908 | 2.30x |
z.discriminatedUnion().parse |
100 objects | 87,666 | 39,411 | 2.22x |
z.object().safeParse |
1,000 3-field objects | 15,740 | 7,251 | 2.17x |
z.string().datetime().parse |
10,000 ISO strings | 820 | 441 | 1.86x |
z.object() initialization |
build 1,000 schemas | 12 | 7 | 1.71x |
z.object().parse |
1,000 3-field objects | 19,812 | 17,871 | 1.11x |
safeParseJson |
one 4 KB nested payload | 21,433 | 21,699 | 0.99x |
An op is a whole suite workload, not one value, and the workloads differ. Read each row across its two engine columns; do not compare rows to each other.
The safeParseJson row measures zodrs.safeParseJson(buffer) against
zod4.safeParse(JSON.parse(json)). At 0.99x it is a tie, and it is the one
cell of the release gate that did not pass on this run: the gate requires
every comparison to reach at least 1.00x.
Reproduce the full gate with three runs into distinct directories, then the median-of-three comparison:
SUITES=object,object-safe,string,number,datetime,union,discriminated-union,array,init
for i in 1 2 3; do
BENCH_RESULTS_DIR=/tmp/bench$i pnpm -C packages/bench run run "$SUITES"
done
node packages/bench/gate.mjs /tmp/bench1 /tmp/bench2 /tmp/bench3Numbers above: Node 24.18.0, Linux x64, Xeon Gold 6138, Tinybench 6.1.3. Absolute throughput depends on the machine; rerun the gate on yours.
| Path | Contents |
|---|---|
packages/zodrs |
The published TypeScript package |
packages/conformance |
Vendored Zod v4.4.3 corpus, the correctness oracle |
packages/bench |
Benchmarks and the release performance gate |
crates/zodrs |
Pure Rust validation engine, no napi dependency |
crates/zodrs-node |
N-API and WASI binding layer |
cargo build --workspace
cargo nextest run --workspace
cargo clippy --workspace --all-targets -- -D warnings
cargo fmt --all -- --check
cargo deny check bans licenses sources
pnpm install
pnpm -r build
pnpm -C packages/conformance test
pnpm exec oxlint --config .oxlintrc.json packages/Pre-commit hooks run through lefthook. Install them once:
pnpm exec lefthook installMIT