{"id":50779349,"url":"https://github.com/gajus/zod-compiler","last_synced_at":"2026-06-16T06:01:12.638Z","repository":{"id":364100743,"uuid":"1264568215","full_name":"gajus/zod-compiler","owner":"gajus","description":"Compile Zod schemas into zero-overhead validation functions at build time. Works with Vite, webpack, esbuild, Rollup, etc","archived":false,"fork":false,"pushed_at":"2026-06-15T04:07:57.000Z","size":590,"stargazers_count":233,"open_issues_count":0,"forks_count":6,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-06-15T05:25:03.059Z","etag":null,"topics":["zod"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/gajus.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"github":"gajus"}},"created_at":"2026-06-10T02:05:05.000Z","updated_at":"2026-06-15T05:13:31.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/gajus/zod-compiler","commit_stats":null,"previous_names":["gajus/zod-compiler"],"tags_count":19,"template":false,"template_full_name":null,"purl":"pkg:github/gajus/zod-compiler","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gajus%2Fzod-compiler","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gajus%2Fzod-compiler/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gajus%2Fzod-compiler/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gajus%2Fzod-compiler/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/gajus","download_url":"https://codeload.github.com/gajus/zod-compiler/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gajus%2Fzod-compiler/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34393302,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-16T02:00:06.860Z","response_time":126,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["zod"],"created_at":"2026-06-12T02:01:26.993Z","updated_at":"2026-06-16T06:01:12.602Z","avatar_url":"https://github.com/gajus.png","language":"TypeScript","funding_links":["https://github.com/sponsors/gajus"],"categories":["TypeScript","Plugins","Other"],"sub_categories":["Unplugin"],"readme":"# zod-compiler\n\n**Compile Zod schemas into zero-overhead validation functions at build time.**\n\nKeep your existing Zod schemas. Get **2-75x faster** validation. No code changes required.\n\n- [What Gets Compiled](#what-gets-compiled)\n- [Schema Hoisting](#schema-hoisting)\n- [Benchmark](#benchmark)\n\n\u003e [!NOTE]\n\u003e zod-compiler has been tested to work in large projects with tens of thousands of Zod schemas.\n\n## Usage\n\nThere are three ways to use zod-compiler. Choose the one that fits your project.\n\n### 1. Automatic Mode (Default)\n\nThe plugin automatically detects and compiles all exported Zod schemas at build time. No wrappers, no imports from `zod-compiler` in your source code.\n\n**vite.config.ts:**\n\n```typescript\nimport zodCompiler from \"zod-compiler/vite\";\n\nexport default defineConfig({\n  plugins: [zodCompiler()],\n});\n```\n\n**Your schema file stays pure Zod:**\n\n```typescript\n// src/schemas.ts\nimport { z } from \"zod\";\n\nexport const CreateUserSchema = z.object({\n  name: z.string().min(1).max(100),\n  email: z.email(),\n  age: z.number().int().min(0).max(150),\n  role: z.enum([\"admin\", \"editor\", \"viewer\"]),\n});\n\nexport const UpdateUserSchema = z.object({\n  name: z.string().min(1).max(100).optional(),\n  email: z.email().optional(),\n});\n\nexport const ListUsersSchema = z.object({\n  page: z.number().int().min(1).optional().default(1),\n  limit: z.number().int().min(1).max(100).optional().default(20),\n});\n```\n\n**Use them as usual:**\n\n```typescript\nconst user = CreateUserSchema.parse(data); // throws on failure\nconst result = CreateUserSchema.safeParse(data); // { success, data/error }\n```\n\n**Zero-allocation type guard — `.is()`:** compiled schemas also expose an `.is(input): input is T` boolean guard. For the common case (objects, primitives, arrays, enums with no `coerce`/`default`/`catch`/`transform`) this _is_ the compiled fast-check — one boolean expression, no `SafeParseResult`, no issues array — the cheapest possible \"does this match?\" check, on par with typia's `is\u003cT\u003e()` and a clean replacement for `schema.safeParse(x).success`:\n\n```typescript\nif (CreateUserSchema.is(data)) {\n  data.email; // narrowed to the schema's output type\n}\nconst valid = items.filter((x) =\u003e CreateUserSchema.is(x));\n```\n\nSchemas without a total fast path fall back to `safeParse(input).success` (still correct). The guard is also available on `compile()`-wrapped schemas (Zod's runtime fallback before the build).\n\nAt build time, the plugin:\n\n1. Finds every file with `import ... from \"zod\"` (skips type-only imports)\n2. Statically pre-filters: files whose exports provably can't be schemas (functions, components, constants) are skipped without ever being executed\n3. Executes the remaining candidates and detects exported Zod schemas\n4. Compiles each schema into an optimized validator\n5. Replaces the export with a tree-shakeable IIFE that preserves the full Zod API\n\n**What \"preserves the full Zod API\" means:** The optimized `parse`/`safeParse`/`parseAsync`/`safeParseAsync` methods (plus the `.is()` guard) are installed directly on the original schema object, which is exported as-is. Identity is preserved, so `._zod`, `.shape`, Standard Schema (`~standard`), `instanceof`, `.meta()` / `z.globalRegistry`, and `z.toJSONSchema()` all still work. Libraries that accept Zod schemas (tRPC, Hono, React Hook Form) work without changes.\n\n### 2. compile() (Explicit)\n\nIf you prefer explicit opt-in, wrap specific schemas with `compile()`:\n\n```typescript\nimport { z } from \"zod\";\nimport { compile } from \"zod-compiler\";\n\nconst UserSchema = z.object({\n  name: z.string().min(3),\n  email: z.email(),\n});\n\nexport const validateUser = compile(UserSchema);\n\n// In dev: falls back to Zod's runtime validation\n// After build: uses AOT-compiled optimized code\nvalidateUser.parse(data);\nvalidateUser.safeParse(data);\n```\n\n`compile()` and auto mode coexist — `compile()` schemas are detected first, then every remaining plain Zod export is picked up. To make `compile()` the _only_ path (no automatic detection, no build-time execution of plain schema files), pair it with `schemas: \"explicit\"` in the plugin options.\n\n### 3. CLI (No Bundler)\n\nGenerate optimized validation files from the command line:\n\n```bash\n# Single file\nnpx zod-compiler generate src/schemas.ts -o src/schemas.compiled.ts\n\n# Directory\nnpx zod-compiler generate src/ -o src/compiled/\n\n# Watch mode\nnpx zod-compiler generate src/ --watch\n\n# Only compile() calls (skip plain exports); minimal methods-only output\nnpx zod-compiler generate src/ --schemas explicit --emit bag\n```\n\n## Build Plugin\n\n### Supported Build Tools\n\n| Build Tool | Import                                            |\n| ---------- | ------------------------------------------------- |\n| Vite       | `import zodCompiler from \"zod-compiler/vite\"`     |\n| webpack    | `import zodCompiler from \"zod-compiler/webpack\"`  |\n| esbuild    | `import zodCompiler from \"zod-compiler/esbuild\"`  |\n| Rollup     | `import zodCompiler from \"zod-compiler/rollup\"`   |\n| Rolldown   | `import zodCompiler from \"zod-compiler/rolldown\"` |\n| rspack     | `import zodCompiler from \"zod-compiler/rspack\"`   |\n| Bun        | `import zodCompiler from \"zod-compiler/bun\"`      |\n| Farm       | `import zodCompiler from \"zod-compiler/farm\"`     |\n\n### Options\n\n| Option    | Type                          | Default         | Description                                                                                                                                                                                                                            |\n| --------- | ----------------------------- | --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `schemas` | `\"auto\" \\| \"explicit\"`        | `\"auto\"`        | How schemas are found. `\"auto\"`: every exported Zod schema compiles (also enables compiling hoisted in-function schemas). `\"explicit\"`: only `compile()`-wrapped schemas; only files importing zod-compiler execute at build time      |\n| `include` | `string[]`                    | —               | Only process files matching these path globs (picomatch, matched anywhere in the path; plain substrings work too)                                                                                                                      |\n| `exclude` | `string[]`                    | —               | Skip files matching these path globs (same matching rules as `include`)                                                                                                                                                                |\n| `output`  | `\"schema\" \\| \"bag\"`           | `\"schema\"`      | What a compiled export evaluates to. `\"schema\"`: the original Zod schema with compiled methods installed (full API preserved). `\"bag\"`: a minimal methods-only object — smaller bundles, breaks Zod-schema consumers                   |\n| `verbose` | `boolean`                     | `false`         | Log per-schema compilation status during build                                                                                                                                                                                         |\n| `hoist`   | `boolean`                     | `true`          | Hoist Zod schemas defined inside function bodies to module scope so they're constructed once instead of per call (babel-plugin-zod-hoist equivalent). Only expressions built purely from imports and literals are hoisted              |\n| `apply`   | `\"build\" \\| \"serve\" \\| \"all\"` | builds + Vitest | **Vite only**: when the plugin runs. By default, production builds and test runs are compiled (so tests exercise what ships); plain dev servers use the Zod fallback. `\"all\"` also compiles the dev server; `\"build\"` also skips tests |\n| `cache`   | `boolean \\| string`           | `true`          | Persistent transform cache (`node_modules/.cache/zod-compiler`, or a custom directory). Skips discovery + codegen across processes when nothing changed; entries self-validate against dependency content hashes                       |\n\n```typescript\nzodCompiler({\n  include: [\"src/schemas\"],\n  verbose: true,\n});\n```\n\n\u003e **Note:** Vitest is detected automatically (via the `VITEST` env var), so\n\u003e tests compile and exercise the same validators that ship to production —\n\u003e including their performance. Pass `apply: \"build\"` if you want tests to use\n\u003e the plain Zod fallback instead.\n\n### Schema Hoisting\n\nSchemas defined inside functions are rebuilt on every call — a hidden cost in\nReact components, request handlers, and helpers. With `hoist` (on by default),\nthe plugin moves them to module scope:\n\n```typescript\n// before\nfunction getSchema() {\n  return z.object({ name: z.string() }); // rebuilt per call\n}\n\n// after (build output)\nconst _zh_94b7f5c1 = z.object({ name: z.string() });\nfunction getSchema() {\n  return _zh_94b7f5c1; // built once per module\n}\n```\n\nHoisting is conservative: only expressions built purely from **imported\nbindings and literals** move. Anything referencing local variables,\nmodule-level bindings, `this`, or eagerly-evaluated globals (`new Date()`,\n`Math.random()`) stays where it is — though safe globals inside callbacks\n(`refine((v) =\u003e Number.isFinite(v))`) are fine, since callbacks run per parse\nregardless. Inline `.parse(...)` calls are peeled so evaluation stays at the\ncall site (`z.string().parse(x)` → `_zh_….parse(x)`), names that are ever\nshadowed (`function f(z) {...}`) disqualify hoists referencing them, and\nidentical schemas dedupe to a single binding.\n\nCombinator chains on imported schemas also qualify: bases matching\n`schemaNamePattern` (default `/ZodSchema$/`) or chains containing an inline\n`z.*` reference (`Base.extend({ a: z.string() })`). Configure via\n`hoist: { schemaNamePattern: /Shape$/ }` (string and `null` accepted).\n\n#### Hoisted schemas compile too (auto mode)\n\nThe most common shape this rescues is a schema that never leaves a function —\na [slonik](https://github.com/gajus/slonik) query, a tRPC input, a handler-local\nvalidator. It is not exported, so export scanning alone would never see it:\n\n```typescript\nimport { pool, sql } from \"./db.js\";\nimport { z } from \"zod\";\n\nconst getUser = (id: number) =\u003e {\n  return pool.one(\n    sql.type(\n      z.object({\n        id: z.number(),\n        name: z.string(),\n      }),\n    )`SELECT id, name FROM users WHERE id = ${id}`,\n  );\n};\n```\n\nIn auto mode (the default), the build output is (verbatim, lightly trimmed):\n\n```typescript\nimport { __zcFin, __zcFinD, __zcIT, __zcMkv } from \"virtual:zod-compiler/runtime\";\nconst _zh_6c9cb1a3 = /* @__PURE__ */ (() =\u003e {\n  function __fc_0(input) {\n    return (\n      typeof input === \"object\" \u0026\u0026\n      input !== null \u0026\u0026\n      !Array.isArray(input) \u0026\u0026\n      Number.isFinite(input[\"id\"]) \u0026\u0026\n      typeof input[\"name\"] === \"string\"\n    );\n  }\n  function __sw_2(input) {\n    var _e = [];\n    /* error-collecting walk — runs only when .error is read */ return _e;\n  }\n  function safeParse__zh_6c9cb1a3(input) {\n    if (__fc_0(input)) {\n      return { success: true, data: input };\n    }\n    return __zcFinD(__sw_2, input);\n  }\n  return __zcMkv(\n    safeParse__zh_6c9cb1a3,\n    z.object({\n      id: z.number(),\n      name: z.string(),\n    }),\n    __fc_0,\n  );\n})();\nimport { pool, sql } from \"./db.js\";\nimport { z } from \"zod\";\n\nconst getUser = (id: number) =\u003e {\n  return pool.one(sql.type(_zh_6c9cb1a3)`SELECT id, name FROM users WHERE id = ${id}`);\n};\n```\n\nReading it bottom-up:\n\n- **The real Zod schema is still constructed** (once, at module load) and is the\n  object `_zh_6c9cb1a3` resolves to — `__zcMkv` installs the compiled\n  `parse`/`safeParse`/`parseAsync`/`safeParseAsync` as own properties on it and\n  returns it. `sql.type()` receives a genuine Zod schema (identity, `.shape`,\n  `._zod`, Standard Schema all intact) whose `safeParse` happens to be compiled.\n- **`__fc_0` is the Fast Path**: when slonik validates each row, a valid row\n  costs one boolean chain — no per-node traversal, no allocations beyond the\n  result object.\n- **`__sw_2` + `__zcFinD` are the failure path**: an invalid row returns\n  `{success: false}` immediately; the full error walk runs lazily only if\n  `.error` is actually read.\n- The `sql.type(...)` call itself stays at the call site (it closes over `id`\n  via the tagged template) — only its schema argument was hoisted and compiled.\n\nMeasured on this exact pattern: schema construction + validation drops from\n~16,700ns to ~14ns per call — construction amortizes to module load, and\nper-row validation rides the Fast Path. With `schemas: \"explicit\"` the same file\nstill gets the plain hoist (construction once instead of per call); the\ncompiled IIFE requires auto mode (the default) because the schema is anonymous.\n\n### Bundle Size \u0026 Cross-File Dedup\n\nGenerated validators share a small runtime helper layer (`__zcMkv` validator\nwrapper, issue factories like `__zcTS`/`__zcIT`, and well-known regexes for\n`email`, `uuid`, `cuid`, `ipv4`, etc.).\n\nOn every supported bundler the plugin imports these helpers from a single\nplugin-provided runtime module — `virtual:zod-compiler/runtime` on Vite,\nRollup, Rolldown, esbuild, Farm, and Bun, or the bare-specifier alias\n`__zod-compiler-runtime__` on webpack and rspack (which reject the `virtual:`\nURI scheme) — so the bundler emits a single bundle-wide copy regardless of how\nmany files reference them.\n\nThe result: a 5-file project with 10 schemas all using `z.email()` and\n`z.uuid()` produces a bundle where each shared regex appears exactly **once**.\nSet `output: \"bag\"` to additionally drop the original Zod schema reference\nwhen you don't need `instanceof` / `.shape` access on the compiled output.\n\n**Structural dedup within a file.** Beyond the shared runtime layer, schemas in\nthe same file that contain a structurally identical sub-tree — a reused\n`Address`, a `Money` pair, an exported schema also embedded in another — emit\nthat shape's error-collecting walk **once** as a shared function and call it\nfrom every occurrence. Only the cold error path is shared (it's 60–80% of the\ngenerated bytes); the zero-allocation fast path stays fully inlined, so valid\ninput runs exactly as fast as before. On a realistic schema set where\n`User`/`Company`/`Order`/`Invoice` reuse `Address`/`Money`/`Contact`, generated\noutput drops **~50% raw / ~34% gzipped** with no change to validation behavior.\n\n### Auto Mode: Side Effects Warning\n\nIn auto mode (the default), the plugin executes files to inspect their exports. A static pre-filter skips files whose exports provably can't be schemas without executing them — but if a file has schema-shaped exports AND side effects (starts a server, connects to a database), those side effects run at build time.\n\n**Fix:** Use `include` to limit which files are scanned:\n\n```typescript\nzodCompiler({\n  include: [\"src/schemas\", \"src/validators\"],\n});\n```\n\n### schemas: \"auto\" vs \"explicit\"\n\n|                              | `\"auto\"` (default)                                         | `\"explicit\"` + compile()                    |\n| ---------------------------- | ---------------------------------------------------------- | ------------------------------------------- |\n| Source code changes          | None                                                       | Wrap each schema                            |\n| `zod-compiler` import needed | No                                                         | Yes                                         |\n| What gets compiled           | All exported Zod schemas                                   | Only wrapped schemas                        |\n| Build-time file execution    | Zod-importing files that may export schemas (pre-filtered) | Files with `import ... from \"zod-compiler\"` |\n| Best for                     | New projects, framework integration                        | Gradual adoption, selective optimization    |\n\n### Large projects and CI\n\nDiscovery executes each schema file — and transitively its first-party import\ngraph — inside the bundler's single-threaded process. In a repository where\nschema files pull in thousands of modules, the **first cold run** is the\nexpensive part: subsequent runs hit the persistent cache and skip discovery\nentirely. On saturated CI hosts a cold discovery of a huge graph can stall the\nbundler's event loop long enough to trip test timeouts (the plugin warns when\na single file's discovery exceeds 5s). Three levers, in order of impact:\n\n**1. Persist the cache across CI runs.** The cache directory is small\n(dependency snapshots are content-addressed and shared between entries) and\nentries self-validate against dependency content hashes — restoring a stale\ncache can only cause recompiles, never stale output:\n\n```yaml\n# GitHub Actions\n- uses: actions/cache@v4\n  with:\n    path: node_modules/.cache/zod-compiler\n    key: zod-compiler-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}\n    restore-keys: zod-compiler-${{ runner.os }}-\n```\n\n**2. Scope what gets discovered.** `include` limits discovery to your schema\ndirectories. If test startup latency matters more than test-time validator\nperformance, run hoist-only in Vitest and compile only real builds:\n\n```typescript\n// vitest.config.ts — hoisting still applies; validation uses plain Zod\nzodCompiler({ schemas: \"explicit\" });\n\n// vite.config.ts (build)\nzodCompiler({ include: [\"src/schemas\"] });\n```\n\n**3. Measure before tuning.** `ZOD_COMPILER_TIMING=1` prints per-phase wall\ntime (hoist / static-filter / discover / compile) on exit, so you can see\nwhether discovery or codegen dominates and which files pay it.\n\n## Framework Examples\n\n### tRPC\n\n```typescript\n// src/schemas.ts\nimport { z } from \"zod\";\n\nexport const CreateUserSchema = z.object({\n  name: z.string().min(1).max(100),\n  email: z.email(),\n  age: z.number().int().min(0).max(150),\n});\n\n// src/router.ts\nimport { CreateUserSchema } from \"./schemas\";\n\nexport const appRouter = t.router({\n  createUser: t.procedure.input(CreateUserSchema).mutation(({ input }) =\u003e createUser(input)),\n});\n```\n\nIn auto mode (the default), `CreateUserSchema` is compiled at build time. The tRPC router uses the optimized version automatically. No `.input(compile(CreateUserSchema))` needed.\n\n### Hono\n\n```typescript\nimport { Hono } from \"hono\";\nimport { zValidator } from \"@hono/zod-validator\";\nimport { UserSchema } from \"./schemas\";\n\nconst app = new Hono();\n\napp.post(\"/users\", zValidator(\"json\", UserSchema), (c) =\u003e {\n  const user = c.req.valid(\"json\");\n  return c.json(user);\n});\n```\n\n### React Hook Form\n\n```typescript\nimport { useForm } from \"react-hook-form\";\nimport { zodResolver } from \"@hookform/resolvers/zod\";\nimport { UserSchema } from \"./schemas\";\n\nfunction UserForm() {\n  const form = useForm({\n    resolver: zodResolver(UserSchema),\n  });\n  // ...\n}\n```\n\n### Any Standard Schema Consumer\n\nCompiled schemas are the original Zod schema objects with optimized parse methods installed, so they still implement [Standard Schema](https://standardschema.dev). Any library that accepts Standard Schema validators works automatically.\n\n## Schema Diagnostics\n\nAnalyze your schemas before compiling — check coverage, Fast Path eligibility, and get actionable hints:\n\n```bash\nnpx zod-compiler check src/schemas.ts\n```\n\nOutput:\n\n```\nsrc/schemas.ts\n\n  CreateUserSchema — 100% compiled (4/4 nodes) | Fast Path: eligible\n    └─ ✓ object\n       ├─ ✓ string .name\n       ├─ ✓ string .email\n       ├─ ✓ number .age\n       └─ ✓ enum .role\n\n  OrderSchema — 67% compiled (2/3 nodes) | Fast Path: ineligible (fallback (transform))\n    └─ ✓ object\n       ├─ ✓ string .id\n       └─ ✓ object .metadata\n          ├─ ✓ string .metadata.region\n          └─ ✗ fallback .metadata.audit (transform)\n                hint: Extract transform into a separate post-processing step\n\n    Fallbacks:\n      ✗ .metadata.audit — transform\n        Extract transform into a separate post-processing step\n```\n\n### CI Integration\n\n```bash\n# JSON output\nnpx zod-compiler check src/schemas.ts --json\n\n# Fail if any schema below 80% coverage\nnpx zod-compiler check src/schemas.ts --json --fail-under 80\n```\n\n| Flag                 | Description                             |\n| -------------------- | --------------------------------------- |\n| `--json`             | Structured JSON output                  |\n| `--fail-under \u003cpct\u003e` | Exit code 1 if coverage below threshold |\n| `--no-color`         | Disable colored output                  |\n\n## What Gets Compiled\n\n### Fully Compiled (2-75x faster)\n\n`string`, `number`, `bigint`, `boolean`, `null`, `undefined`, `any`, `unknown`, `literal`, `enum`, `stringbool`, `date`, `file`, `object`, `strictObject` / `.strict()`, `looseObject`, `array`, `tuple`, `record`, `set`, `map`, `union`, `discriminatedUnion`, `intersection`, `pipe` (non-transform), `optional`, `nullable`, `readonly`, `default`, `catch`, `coerce`, `templateLiteral`, `symbol`, `void`, `nan`, `never`, `lazy` (self-recursive), `transform` / `refine` (zero-capture — see below)\n\nAll standard Zod checks are supported: `min`, `max`, `length`, `email`, `url`, `uuid`, `regex`, `int`, `positive`, `negative`, `multipleOf`, `int32`, `uint32`, `float32`, `float64`, `includes`, `startsWith`, `endsWith`, and more.\n\n### Falls Back to Zod (Still Works, Not Faster)\n\nThese contain JavaScript callbacks that cannot be reproduced in generated code:\n\n| Type                                 | Why                                                           | Alternative                                   |\n| ------------------------------------ | ------------------------------------------------------------- | --------------------------------------------- |\n| `transform` / `refine` with captures | Callback captures outer variables (or is async / takes `ctx`) | Use zero-capture callbacks or built-in checks |\n| `superRefine`                        | Callback needs `ctx` for issue collection                     | Use `refine` or built-in checks               |\n| `custom`                             | Arbitrary validation logic                                    | —                                             |\n| `preprocess`                         | Input preprocessing function                                  | Use `z.coerce` when possible                  |\n| `lazy` (non-recursive)               | Cannot resolve inner type                                     | Use self-referencing lazy for recursion       |\n| `.catchall(schema)`                  | Unknown keys validated against a value schema                 | `strictObject` and `looseObject` both compile |\n\n**Zero-capture effects compile:** a `transform`/`refine` callback that takes a\nsingle argument and references only its own parameters, locals, and safe\nglobals (`Math`, `Number`, `JSON`, …) is extracted via `fn.toString()` and\ninlined into the generated validator. `z.string().transform((s) =\u003e s.trim())`\ncompiles; `z.string().transform((s) =\u003e s + suffix)` falls back (it captures\n`suffix`).\n\n**Partial fallback:** If an object has 10 properties and 1 uses `transform`, the other 9 are still compiled. Only the `transform` property falls back to Zod.\n\n**Tip:** Run `npx zod-compiler check` to see exactly which parts of your schemas are compiled and which fall back.\n\n## Benchmark\n\n5-way comparison: **Zod v3** vs **Zod v4** vs **zod-compiler** vs **[Typia](https://typia.io/)** vs **[AJV](https://ajv.js.org/)**\n\n| Scenario                                          | Zod v3 | Zod v4 | **zod-compiler** | Typia | AJV   | vs Zod v4 |\n| ------------------------------------------------- | ------ | ------ | ---------------- | ----- | ----- | --------- |\n| simple string                                     | 13.3M  | 14.4M  | **16.2M**        | 17.7M | 17.8M | 1.1x      |\n| string (min/max)                                  | 13.0M  | 8.0M   | **17.2M**        | 18.1M | 16.3M | 2.2x      |\n| number (int+positive)                             | 11.5M  | 7.8M   | **15.7M**        | 16.4M | 16.7M | 2.0x      |\n| enum                                              | 11.3M  | 12.3M  | **16.9M**        | 17.2M | 17.6M | 1.4x      |\n| bigint (min/max)                                  | 11.8M  | 7.9M   | **15.7M**        | —     | —     | 2.0x      |\n| tuple [string, int, bool]                         | 6.0M   | 6.5M   | **17.0M**        | 16.2M | 16.5M | 2.6x      |\n| record\\\u003cstring, number\\\u003e                          | 3.3M   | 2.8M   | **8.5M**         | 11.5M | 15.1M | 3.0x      |\n| set\\\u003cstring\\\u003e (5 items)                           | 3.7M   | 2.3M   | **15.2M**        | —     | —     | 6.7x      |\n| set\\\u003cstring\\\u003e (20 items)                          | 1.3M   | 695K   | **12.1M**        | —     | —     | **17x**   |\n| map\\\u003cstring, number\\\u003e (5 entries)                 | 2.1M   | 1.4M   | **13.1M**        | —     | —     | 9.6x      |\n| map\\\u003cstring, number\\\u003e (20 entries)                | 652K   | 361K   | **8.6M**         | —     | —     | **24x**   |\n| pipe (non-transform)                              | 8.8M   | 5.9M   | **16.1M**        | —     | —     | 2.7x      |\n| discriminatedUnion (3 variants)                   | 3.3M   | 4.0M   | **16.1M**        | 15.8M | 8.0M  | 4.0x      |\n| discriminatedUnion (8 variants, rotating)         | 2.7M   | 3.5M   | **9.6M**         | —     | —     | 2.7x      |\n| plain union of 8 tagged objects (auto-discrim.)   | 368K   | 655K   | **8.6M**         | —     | —     | **13x**   |\n| strict object (DB row)                            | 1.8M   | 3.2M   | **7.3M**         | —     | —     | 2.3x      |\n| medium object (valid)                             | 2.0M   | 2.4M   | **10.3M**        | 11.4M | 7.7M  | 4.3x      |\n| medium object (invalid)                           | 536K   | 80K    | **15.5M**        | 2.9M  | 7.9M  | **194x**  |\n| large object (10 items)                           | 123K   | 174K   | **8.0M**         | 5.9M  | 1.3M  | **46x**   |\n| large object (100 items)                          | 13K    | 19K    | **1.4M**         | 1.3M  | 127K  | **73x**   |\n| recursive tree (7 nodes)                          | 547K   | 2.0M   | **11.8M**        | 11.7M | 4.7M  | 5.8x      |\n| recursive tree (121 nodes)                        | 32K    | 142K   | **2.3M**         | 1.9M  | 356K  | **16x**   |\n| deeply nested object (243 leaves)                 | 11K    | 19K    | **1.2M**         | 1.0M  | 122K  | **64x**   |\n| event log (combined)                              | 382K   | 618K   | **5.8M**         | —     | —     | 9.4x      |\n| object with transform (zero-capture)              | 1.2M   | 1.9M   | **6.1M**         | —     | —     | 3.3x      |\n| array 10 × transform (zero-capture)               | 129K   | 220K   | **3.4M**         | —     | —     | **15x**   |\n| array 50 × transform (zero-capture)               | 26K    | 44K    | **821K**         | —     | —     | **19x**   |\n| object with captured transform (partial fallback) | 1.4M   | 6.4M   | **6.2M**         | —     | —     | 1.0x      |\n\n_ops/s, higher is better. \"—\" = not supported by the library. Measured with `vitest bench` on Apple M4 Max (zod 4.3.6, zod v3 3.23.8, typia 12, ajv 8)._\n\nPerformance scales with schema complexity. Nested objects and arrays see the biggest gains because zod-compiler eliminates per-node traversal overhead. Deeply nested schemas (the 243-leaf dashboard row) stay fast because oversized fast-check functions are split into smaller boolean helpers, each kept within V8's optimizing-compiler budget. `discriminatedUnion` uses O(1) `switch` dispatch instead of Zod's sequential trial, and each case validates only its variant's distinctive fields — the object type-guard and the discriminator are checked once before dispatch, never re-checked inside the matched case (a redundancy the engine only elides on unions small enough to inline, so large unions get a measured ~1.5x on the fast check). A **plain `z.union`** of objects that all pin a shared key to disjoint literals is auto-detected and lowered to the same switch dispatch — so an untagged union written without `discriminatedUnion` still validates in O(1) (13x faster than Zod here), as long as it has enough options to outweigh the switch's setup cost; below that it keeps the fully-inlined `||`-chain. The invalid-input row is large because failed `safeParse` defers error materialization until `.error` is read. Zero-capture `transform`/`refine` callbacks are compiled (3-19x); schemas with captured callbacks fall back per-field and roughly match Zod.\n\n`parse()` (throwing API) rides a zero-allocation fast path: medium object 2.3M → 9.7M ops/s (4.1x), large object (100 items) 17K → 1.4M ops/s (79x).\n\n```bash\npnpm benchmark   # run locally\n```\n\n### Performance Architecture\n\nFor eligible schemas, zod-compiler generates a **two-phase validator**:\n\n1. **Fast Path** — A single `\u0026\u0026` expression chain that validates the entire input with zero allocations. Valid input returns immediately.\n2. **Slow Path** — Error-collecting validation that only runs when the Fast Path fails.\n\nAdditional optimizations: check ordering (cheap checks first), pre-compiled regex, Set-based enum lookups, small enum inlining (`===` for up to 5 values), discriminated-union cases that skip the now-redundant object-guard and discriminator re-check after `switch` dispatch, and auto-discrimination of plain `z.union`s of tagged objects into the same switch dispatch.\n\nRun `npx zod-compiler check --json` to see which schemas qualify for Fast Path.\n\n## Development\n\n```bash\npnpm install\npnpm test\npnpm benchmark\npnpm lint\n```\n\n## Acknowledgements\n\nzod-compiler started as a fork of [zod-aot](https://github.com/wakita181009/zod-aot) by [@wakita181009](https://github.com/wakita181009).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgajus%2Fzod-compiler","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgajus%2Fzod-compiler","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgajus%2Fzod-compiler/lists"}