# zod-compiler: build-time Zod validation with no runtime codegen

> gajus/zod-compiler compiles Zod 4.5 schemas into pre-generated validators through Vite, webpack, esbuild, Rollup, a CLI, or a Node register hook. It claims up to 44x faster validation, and the design trade-offs are worth reading before you adopt it.

**gajus/zod-compiler** — Compile Zod schemas into zero-overhead validation functions at build time. Works with Vite, webpack, esbuild, Rollup, etc

- Repository: https://github.com/gajus/zod-compiler
- Stars: 812 · Forks: 12
- Language: TypeScript
- License: BSD-3-Clause
- Published: 2026-09-17 · Updated: 2026-09-17 · Language: en
- Canonical page: https://hysenlabs.com/projects/gajus-zod-compiler

## What zod-compiler solves, and who it is for

Zod validates by interpreting a schema object on every parse. That is fine for a config file read once at boot and less fine for a request body validated on every call. Zod's own answer is z.compile(), which generates a validator at runtime with new Function(). zod-compiler takes the other route: it generates the validator at build time, so the production bundle ships validators and runtime helpers rather than a compiler. The README states the compiler is not in the runtime bundle, and that Zod's z.compile() path ships roughly 7 KB gzipped of compiler according to Zod.

The audience is teams that already have Zod schemas and do not want to rewrite them. The README says no code changes are required in automatic mode, and that methods are installed on the original schema object, so .shape, ._zod, Standard Schema, instanceof and z.toJSONSchema keep working. The project also states it has been tested in large projects with tens of thousands of Zod schemas. That is the author's claim, not an independent measurement, and the repository does not publish the corpus behind it.

## How compilation works: AOT rewriting versus runtime JIT

In automatic mode the build plugin detects every exported Zod schema and compiles it. Your source file stays pure Zod; the plugin rewrites the module so the exported binding carries compiled methods. That is why the README can promise no imports from zod-compiler in your source. The CLI does the same work without a bundler, writing a generated file you import yourself.

The version coupling is the sharpest constraint in the design. Compiled output reproduces Zod 4.5 semantics exactly, including code-point string lengths, symbol-keyed shapes and tuple issue order, so it does not match earlier 4.x releases. An older Zod is refused with an explicit error, and the build plugin and jit() warn once and leave the schemas as plain Zod rather than emitting validators that disagree with the installed version. If you are on Zod 4.0 through 4.4, the README points you at zod-compiler 1.x.

Two of the five modes are not AOT. jit() runs the same pipeline in-process and needs new Function, as does Zod's own object fast-path. The Node.js register hook, available on Node.js 22.15+, registers live Zod objects behind exported schema bindings and generates validators in-process on first use. The README is explicit that this is runtime JIT instrumentation, not the AOT source rewriting the build plugins perform, and that it adds no cache beyond Node's own module cache.

## Installing zod-compiler and compiling a first schema

The README does not give a package manager install line, but package.json declares a bin entry named zod-compiler pointing at ./dist/cli/index.js, and the CLI examples are written with npx. A single-file generation run looks like this:

```bash
npx zod-compiler generate src/schemas.ts -o src/schemas.compiled.ts
```

You should get a generated module containing the compiled validators for the exported schemas in that file. The same command accepts a directory and an output directory, and a --watch flag. Two flags change what is emitted: --schemas explicit restricts work to compile() calls and skips plain exports, and --emit bag produces a minimal methods-only output. A third, --emit compact, keeps only the fast path and delegates cold error production to Zod, which the README describes as roughly 70% smaller.

For a bundler, the plugin is the entry point. The README's Vite example is:

```typescript
import zodCompiler from "zod-compiler/vite";

export default defineConfig({
  plugins: [zodCompiler()],
});
```

With that in place, a schema file such as an object of name, email and role needs no wrapper. If you would rather opt in per schema, import compile from zod-compiler and wrap the schema; the README notes compile() and auto mode coexist, and that pairing with schemas: "explicit" makes compile() the only path, so plain schema files are not executed at build time.

The register hook path skips the bundler entirely:

```bash
node --import zod-compiler/register src/server.js
```

The README states this preload handles ESM imports, CommonJS require(), and Node's native TypeScript formats, and can chain with a TypeScript runner via a second --import tsx. Settings for it come from zod-compiler.json in the working directory, with keys including include, exclude, schemas, eager, output and hoist. Note that output: "bag" is unavailable in this mode, because a load hook cannot safely replace already-linked ESM export bindings.

## The cost of jit(): module load and new Function

jit() is the mode that fits a test runner or a tsx script where no plugin fires. It is also the mode with the clearest bill attached. The README puts the import at roughly 570 KB of codegen and acorn, about 10 ms of module load. Compilation itself is lazy and costs 0.1 to 0.3 ms on a schema's first parse, unless you pass { eager: true } to compile up front or use jitAll(namespace) on a whole module.

That shape suits a long-lived process and not much else. The README says so directly: use the build plugin for a CLI, a cold serverless handler or a browser, and have libraries ship plain Zod so the application can decide. If you ignore that and import zod-compiler/jit from a library's public entry, every consumer pays the 10 ms and the bundle weight whether or not they wanted compiled validators.

The second cost is the CSP one. jit() needs new Function, and so does the register hook. The README notes that z.config({ jitless: true }) and a CSP that blocks eval both leave a working plain-Zod schema, which is the graceful degradation you want, but it also means you get no speedup in those environments. The build plugins and the CLI are the paths that support a strict CSP without 'unsafe-eval', because nothing is generated at runtime.

## Where zod-compiler is the wrong tool

Schemas constructed at runtime are the obvious failure case. Automatic mode works by detecting exported Zod schemas at build time, so a schema assembled from a database table, a remote config, or a loop has nothing for the plugin to find. jit() can handle a schema you build in code, but it brings the 10 ms import and the new Function requirement with it. If your schema set is genuinely dynamic, Zod's z.compile() may be the better fit despite shipping the compiler, because it does not require a build step to see the schema.

The second case is a library author. The README's own guidance is that libraries should ship plain Zod and let the app decide. If you compile inside a published package, you have coupled every consumer to Zod 4.5 semantics and to your build configuration. That coupling is invisible from the outside until someone on Zod 4.4 installs the package and the plugin refuses the version.

Third, the version gate is a hard boundary, not a warning you can ignore. Staying on zod-compiler 1.x for Zod 4.0 to 4.4 is the documented path, which means two maintenance lines to track if your dependency graph spans both. The README does not document rollback, so if a compiled validator disagrees with your expectations in production, the documented fallback is the version gate and the jitless config, not a runtime switch that reverts a single schema.

## zod-compiler against Zod's z.compile() and plain Zod

The README's own comparison table is the most useful thing to read before choosing. Zod's z.compile() generates optimized JavaScript at runtime with new Function(), either when you call it or on the first parse, and the README reports roughly 9x in Zod's headline example. zod-compiler reports up to 44x, and up to 46x on rejected input, because the code generation happened before the process started. Those numbers come from the project's benchmark directory and are not independently reproduced here.

The meaningful difference is not the multiplier. It is cold start and CSP. Runtime compilation pays for code generation at startup or first use; a pre-generated validator does not. And a strict CSP without 'unsafe-eval' makes runtime compilation unavailable outright, while the build-time path keeps working. If neither cold start nor CSP is a constraint for you, plain Zod with no compiler at all is a defensible choice: it has no version coupling to a codegen tool, no build step, and no generated file to keep in sync.

The middle ground is --emit compact, which keeps the compiled fast path but delegates cold error production back to Zod. That is a real trade: you get the speedup on the happy path and on rejected input you pay Zod's error construction cost, in exchange for roughly 70% smaller output. If your service logs validation errors on every bad request, measure that path before assuming compact is free.

## Maintenance, licence and upgrade cost

The last push to the repository was on 2026-09-15, the same day v2.0.5 was released, with v2.0.4 on 2026-09-09 and v2.0.3 on 2026-09-02. Three releases in two weeks is a fast cadence, and it is also the thing to plan around: a codegen tool that tracks Zod's semantics closely will need to move when Zod moves. The README already documents one such break, the 4.5 semantics change that split zod-compiler 1.x from 2.x.

The repository is not archived, and it is a pnpm workspace with apps/, benchmarks/, src/, tests/ and a knip.json config, so the test and benchmark surface is present in the tree. The README does not publish a support window or a compatibility matrix beyond the Zod >= 4.5 requirement and the 1.x fallback.

The licence is BSD-3-Clause, declared in package.json and shipped as a LICENSE file in the published tarball. That is a permissive licence, but it is not the same as MIT: BSD-3-Clause adds a clause restricting use of the copyright holder's name for endorsement. If your organisation maintains an allowlist of permissive licences, check that BSD-3-Clause is on it before you wire the plugin into a production build. This is a description of the licence text, not legal advice.

## Conclusion

Adopt zod-compiler if you are on Zod 4.5 or newer, your schemas are exported from modules a bundler or the CLI can see, and you want validators generated before your process starts so a strict CSP without 'unsafe-eval' still works. Skip it if you are pinned to Zod 4.0 to 4.4 (the plugin warns once and leaves schemas as plain Zod, and 1.x is the line for those versions), if your schemas are built dynamically at runtime, or if you are shipping a library that should let the consuming app decide. Before committing, run the CLI over your schema directory with --emit bag and confirm the generated file type-checks against your existing types, then confirm your build pipeline actually loads the plugin in production mode rather than only in dev.

## FAQ

### What is zod-compiler used for?

It compiles Zod schemas into pre-generated validation functions at build time, through a bundler plugin, a CLI, or a Node register hook. The README states the compiled output ships without the compiler in the runtime bundle, and that no source changes are required in automatic mode.

### Can zod-compiler be used with TypeScript?

Yes. The package is written in TypeScript and ships type declarations, and the README's examples are TypeScript files. The Node register hook also handles Node's native TypeScript formats, and can chain with a TypeScript runner via a second --import tsx.

### Does zod-compiler work well with React?

The README does not describe a React integration. It covers Vite, webpack, esbuild and Rollup plugins, a CLI, jit() and a Node register hook, so a React app built with one of those bundlers is the documented path.

## Sources

- [gajus/zod-compiler on GitHub](https://github.com/gajus/zod-compiler)
- [Issues](https://github.com/gajus/zod-compiler/issues)
- [License: BSD-3-Clause](https://github.com/gajus/zod-compiler/blob/main/LICENSE)
- [README](https://github.com/gajus/zod-compiler/blob/main/README.md)
- [Releases](https://github.com/gajus/zod-compiler/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/gajus-zod-compiler
