colinhacks/zod
github.com/colinhacks/zod ↗ · 680 files · 5.2 MB
Zod TypeScript-first schema validation with static type inference by @colinhacks Docs • Discord • 𝕏 • Bluesky Read the docs → What is Zod? Zod is a TypeScript-first validation library.
Overview
Zod is a dependency-free, TypeScript-first schema library that turns declarative runtime validators into statically inferred input and output types for Node.js, browsers, and plain JavaScript consumers. This repository contains the full fluent API, a smaller functional Zod Mini surface, a shared schema runtime, bidirectional codecs, JSON Schema conversion, a legacy v3 compatibility entrypoint, and an optional ahead-of-time compiler. It also includes the zod.dev Next.js documentation site, extensive compatibility and bundle-size test workspaces, benchmarks, and guarded npm/JSR release automation.
System map
The repository is a Nub-managed ESM monorepo whose principal deliverables are the `zod` and `@zod/mini` packages. Zod v4 is layered into a shared core, a fluent classic facade, and a functional mini facade; v3 remains published through a separate compatibility entrypoint. Runtime validation is entirely in-process, while the repository also contains a statically generated Next.js documentation application and maintenance scripts that access GitHub, npm, JSR, and the ISO currency source. Publishing is a manually dispatched, approval-gated GitHub Actions workflow rather than an automatic deployment from `main`.
- The root workspace is private, ESM-only, pinned to Nub 0.8.3, and discovers packages through `packages/*`; root scripts build `zod` and `@zod/mini`, run Vitest, check formatting, linting, circular dependencies, version consistency, comments, and release lockstep.
- The published `zod` package exposes root, mini, compile, locales, v3, v4, v4-mini, v4/mini, v4/core, and locale wildcard subpaths for source, ESM, CommonJS, and declarations.
- The root `zod` entrypoint re-exports the v4 classic facade, while separate entrypoints route `zod/mini` to the v4 mini facade and `zod/v3` to the legacy implementation.
- The v4 runtime boundary is explicit: `core/index.ts` exports constructors, parsers, schemas, checks, registries, compilation, and JSON Schema machinery; classic and mini compose those primitives into distinct public APIs.
- Schema instances are trait-composed objects created through `$constructor`; their internal definitions, parse functions, traits, structural metadata, and deferred initialization live under a non-enumerable `_zod` property.
- Runtime state is process-local rather than database-backed: global configuration and metadata registries are shared through `globalThis`, while parse-cycle state uses WeakMaps keyed by schemas, objects, and parse contexts.
- The documentation application is a Next.js 15 and React 19 workspace using Fumadocs, Tailwind, Vercel Analytics, and Inkeep search/chat; its content is compiled from MDX collections.
- Auxiliary workspaces separate concerns: `packages/bench` benchmarks competing validators, `packages/integration` type-checks Drizzle and AI SDK fixtures, `packages/resolution` verifies built module resolution, and `packages/treeshake` measures bundler behavior.
- The normal build uses zshy to emit the packages and declarations, while package exports select raw TypeScript only under the private `@zod/source` condition used by repository development and tests.
- Production documentation deployment is shaped for Vercel: the install command bootstraps the pinned Nub release, installs the monorepo lockfile, and then runs the repository build.
- Release deployment is manual and approval-gated: the workflow tests, builds, validates package resolution, publishes both packages to npm and JSR with provenance, creates a GitHub release, and verifies the two packages remain version-aligned.
Feature catalog
The primary user-facing product is a schema construction and validation API, offered in fluent classic and smaller functional mini forms. It supports primitive, structured, recursive, transforming, asynchronous, and bidirectional schemas, with both throwing and result-oriented execution APIs. AOT compilation and JSON Schema interoperability extend the runtime beyond ordinary interpretation. The repository's second user-facing surface is zod.dev, which exposes documentation, blog, search, generated social cards, RSS, and machine-readable LLM documentation endpoints.
- Every classic schema exposes `parse`, `safeParse`, async variants, boolean `validate` methods, forward decode methods, backward encode methods, and safe async codec variants.
- The schema catalog covers primitives and special values plus arrays, objects, records, tuples, unions, XOR unions, discriminated unions, intersections, maps, sets, enums, literals, files, promises, functions, lazy recursion, and custom schemas.
- Built-in string validators include email, GUID and versioned UUIDs, URL and HTTP URL, emoji, NanoID, CUID/CUID2, ULID, XID, KSUID, IP and CIDR forms, MAC, Base64, E.164, credit card, IBAN, JWT, hostname, hex, currency codes, and hashes.
- Object schemas support strip-by-default, strict, loose, and catchall modes, plus shape access, `keyof`, extension, picking, omission, partialization, requiredness, and deep-partial helpers.
- Composable wrappers cover refinements, super-refinements, overwrites, optional/exact-optional/nonoptional, nullable/nullish, defaults, prefaults, catch values, arrays, unions, intersections, transforms, pipes, branding, readonly output, and metadata.
- Codecs are bidirectional pipes with explicit decode and encode callbacks; `invertCodec` swaps their input/output schemas and forward/reverse transforms.
- Function schemas can wrap implementations so arguments and return values are parsed synchronously or asynchronously at each call boundary.
- `toJSONSchema` supports single schemas and registries, input/output views, draft 4, draft 7, draft 2020-12, OpenAPI 3.0, cycle and reuse policies, custom processors, overrides, external registries, and configurable handling of unrepresentable types.
- `fromJSONSchema` converts boolean or object JSON Schemas back into Zod, recognizes composition, object, array, string, numeric, metadata, content, and OpenAPI keywords, and resolves local `$defs` or `definitions` references.
- Schemas implement Standard Schema validation and Standard JSON Schema input/output conversion, allowing library-agnostic consumers to infer types, validate values, and request JSON Schema.
- `z.compile(schema)` creates a compiled clone, `withParser` accepts an externally generated parser, and importing `zod/compile` enables lazy global compilation for subsequently constructed schemas.
- Zod Mini reuses the same core schema implementations but exposes a smaller base instance API centered on functional factories and free parsing functions rather than the classic chain-heavy method surface.
Data flow
A schema factory creates an immutable schema instance whose definition and structural capabilities are stored under `_zod`. Parsing sends a mutable payload and shared context through the schema graph; child schemas transform values and append raw issues, while the public boundary either returns a deep output structure or finalizes issues into a Zod error. Optional compilation replaces only the success path and deliberately delegates failure reporting to the interpreter. Separate conversion pipelines translate schema graphs to and from JSON Schema, while the documentation application turns MDX files into pages and text feeds.
- Public factories delegate to core factory functions that instantiate a supplied schema class with a normalized definition, preserving one shared construction mechanism across classic and mini APIs.
- `$ZodType` initializes checks, runs each check's attachment hooks, and selects either the raw parser or a parse-then-check runner; asynchronous checks are rejected from synchronous parse contexts.
- A synchronous public parse creates `{value, issues: []}`, calls `schema._zod.run`, rejects unexpected promises, finalizes raw issues through configured error maps, and otherwise returns the transformed value.
- Object parsing validates each declared string or symbol key through its child schema, builds a fresh result object, prefixes child issue paths, enforces optionality, and then strips, rejects, passes, or validates undeclared keys through the catchall policy.
- Array, map, and set schemas allocate fresh output containers, run element schemas with the same parse context, prefix or nest element issues, and join child promises before returning.
- The `optin` and `optout` structural metadata controls whether a parent may omit a slot, whether a default supplies a replacement, and whether undefined output should omit a property or shorten a tuple.
- Safe parsing keeps failure construction lazy: callers can branch on `success` without immediately finalizing issues or constructing a ZodError, and reading or replacing `error` releases captured raw input state.
- The cycle memoizer detects recursive schema graphs, allocates placeholder containers before descending, caches results per schema/input pair, resolves back-edges to those placeholders, and rejects cycles that pass through transforms.
- Pipes run the input schema first and stop on substantive issues before invoking the output schema; codecs additionally transform between the sides and reverse direction when encoding.
- Compilation walks the schema graph into generated source, hoists runtime constants and user callbacks into closure arguments, constructs a function with the `Function` constructor, and records whether an invalid sentinel is a definitive rejection.
- A compiled schema sends valid inputs directly through its generated parser, but marks the context and invokes the original runtime parser on an invalid sentinel so canonical issue construction remains single-sourced.
- JSON Schema emission traverses schemas into a shared `seen` map, invokes per-schema or registry processors, attaches metadata, extracts reused or cyclic nodes into definitions, applies overrides and deferred rewrites, and JSON-round-trips the final document.
Behavior traces
The most important flows begin at package import, schema construction, and a public parse call. The interpreted and compiled paths intentionally converge on the same runtime fallback for errors, while JSON Schema conversion follows a separate graph traversal. Recursive parsing adds a context-scoped memoization layer without changing the public parse contract. The documentation site has its own static content request path layered on top of Fumadocs and MDX.
- Import trace: `src/index.ts` loads `v4/classic/external.ts`; that module exposes classic schemas, checks, errors, parse functions, core helpers, JSON Schema conversion, locales, ISO validators, and coercion factories.
- Construction trace: `z.object(shape)` creates `ZodObject`; `ZodObject` ensures the default memoizer, initializes `$ZodObject`, initializes `ZodType`, attaches JSON Schema processing, and ultimately lets `$constructor` run deferred initializers and any global post-processor.
- Successful sync parse trace: `schema.parse(data)` calls classic `parse.parse`, which calls core `_parse`, then `schema._zod.run`; an object run allocates a result, runs each child, merges property results, and returns the transformed value when no issues remain.
- Failed sync parse trace: a leaf or container appends raw issues, parents prefix nested paths, `_parse` maps each issue through `finalizeIssue`, constructs the configured error class, restores a useful stack, and throws.
- Safe parse trace: `_safeParse` runs the same schema graph but returns `{success: true, data}` or a failure object whose `error` getter creates and caches the finalized error only when accessed.
- Async trace: `parseAsync` sets `ctx.async`, awaits promise-returning schema runs, then performs the same issue finalization; synchronous entrypoints instead throw `$ZodAsyncError` when a promise escapes.
- Global compile trace: importing `zod/compile` installs `globalConfig.postProcessor`; each later schema receives a one-shot shim; its first eligible parse calls `compile(..., {strict:true})`, replaces itself with the compiled run on success, or permanently restores the interpreter on refusal.
- Compiled failure trace: the generated parser returns `INVALID`; `withParser` sets the fallback flag and invokes the captured original run, preventing nested compiled wrappers from repeatedly executing user callbacks while preserving runtime-generated errors.
- Cycle trace: a recursive container's memoized wrapper creates context state, caches an output placeholder before children parse, returns that placeholder on a repeated input visit, and records final issues after synchronous or asynchronous completion.
- JSON Schema trace: `toJSONSchema` initializes the processor registry, calls `processSchema`, extracts definitions for cycles or reuse, finalizes references and metadata, and returns either one schema document or a registry map with shared definitions.
- Docs request trace: the catch-all route resolves a page through `source.getPage`, derives LLM-friendly markdown with `getLLMText`, renders the compiled MDX body with shared components, and statically enumerates all routes through `source.generateParams`.
Testing signals
Testing is broad and strongly integrated into normal development and CI. Vitest combines runtime assertions and TypeScript type checking, while a second project reruns the entire Zod test corpus with global AOT compilation enabled. Dedicated suites cover runtime semantics, compiler parity, JSON Schema conversion, cyclic data, package resolution, integrations, tree-shaking, and documentation consistency. Performance and randomized compiler fuzzing also exist, although the fuzzer is intentionally not part of deterministic CI.
- The root Vitest configuration discovers every package project plus a dedicated compile-mode project, isolates tests, type-checks `*.test.ts`, and fails tests that write to console.
- Compile mode includes every `packages/zod/src/**/*.test.ts` file, disables redundant type checking, and imports the production `zod/compile` side-effect module before tests.
- The v4 classic suite is organized by schema behavior and includes focused files for objects, strings, codecs, cyclic data, optionality, registries, Standard Schema, JSON Schema, transforms, errors, and ReDoS cases.
- The compiler has direct unit tests plus differential tests that compare success, issues, prototype identity, own-key order, symbols, array holes, frozen state, NaN, negative zero, thrown-error classes, and union branch soundness.
- JSON Schema conversion has large dedicated suites in both directions, while core processors are also exercised by compile mode because they live under the same package project.
- Bundle tests build real fixtures with esbuild, enforce gzip ceilings for classic and mini bundles, and assert that unrelated feature identifiers and regexes are absent after tree-shaking.
- Built-package compatibility is tested separately: resolution tests run Are The Types Wrong and executable CJS/ESM probes, while integration fixtures type-check source and built packages under bundler, NodeNext, and Node16 CommonJS modes.
- CI tests TypeScript 5.5, 6, and latest on Node LTS, builds before tests, runs integration and resolution checks, and separately enforces formatting, linting, comment style, and circular-dependency checks.
- The release workflow repeats build, test, resolution, integration, lint, and circular-dependency checks before gaining publish permissions.
- The seeded compiler fuzzer generates nested schema grammars and compares interpreted and compiled outputs, issues, and union behavior, but its header explicitly states that it is a hunting tool rather than a CI job.
- Documentation-specific automated coverage is narrow: the checked-in docs test validates parity of code-block heights between classic and mini API tabs, while route rendering and external search/chat behavior have no corresponding route-level test file. (inferred)
Risk map
The core library has no authentication, database, or arbitrary filesystem surface, but it processes untrusted values and generates executable validation functions, so correctness and denial-of-service behavior are security-relevant. The code contains explicit mitigations for prototype pollution, cycles, ReDoS-prone formats, CSP restrictions, and duplicated callback execution. The documentation and automation layers introduce the repository's network, filesystem, token, and privileged CI boundaries. The largest maintenance risk is semantic drift among interpreted parsing, object JIT, AOT compilation, JSON Schema generation, classic, and mini surfaces.
- AOT compilation uses the `Function` constructor and is incompatible with strict no-eval CSPs; global mode honors `config({jitless:true})`, while an explicit `z.compile()` call is treated as deliberate opt-in.
- Generated code receives user callbacks as hoisted constants rather than interpolating their source, but those callbacks can throw or return promises and force the compiler to weaken its definitive-rejection guarantee or refuse compilation.
- On invalid compiled inputs, refinements and transforms may execute twice—once in the fast path and once in interpreter fallback—so user callbacks should not rely on exactly-once side effects.
- `z.jwt()` is a structural validator only: it splits three segments, decodes and parses the header, checks `typ` and `alg`, but performs no signature, issuer, audience, expiration, or claims verification.
- Prototype-pollution defenses are load-bearing: object parsing strips undeclared `__proto__`, compiled records recheck transformed keys, and `assignProp` uses `Object.defineProperty` so hostile property names become own data properties.
- Recursive object graphs are supported only through the memoized runtime path; cycles through transforms throw `$ZodCyclicError`, and AOT compilation refuses recursive schema subtrees because generated functions do not receive the parse context.
- `fromJSONSchema` rejects external references and normalizes input with a JSON round-trip; this bounds traversal to JSON data but also means cyclic JavaScript objects, getters, proxies, class identity, and non-JSON values cannot be preserved.
- The docs build sends a server-side `GITHUB_TOKEN` to GitHub GraphQL to obtain ecosystem star counts, retries failures, and intentionally fails production builds if the integration cannot supply data.
- Inkeep search and chat execute in client components using `NEXT_PUBLIC_INKEEP_KEY`; the variable is intentionally browser-visible and must not be treated as a private server credential.
- The Pullfrog workflow combines repository and issue write permissions with a broad set of model-provider secrets, making the third-party agent action a high-blast-radius CI trust boundary.
- Publishing has write access to repository contents and OIDC, but is constrained to a manually dispatched npm environment with required approval, full prepublish checks, package availability polling, and provenance-enabled publishing.
- Shared semantic changes have unusually high blast radius because interpreted containers, optionality rules, object JIT, AOT code generation, JSON Schema processors, classic, and mini must agree on key presence, output construction, and failure behavior. (inferred)
Extension points
Zod is extensible primarily through schema constructors, structural internals, checks, registries, callbacks, and JSON Schema processors rather than a separately loaded plugin system. Classic and mini are examples of alternate facades over the same core factory and trait machinery. Public extension hooks coexist with internal hooks such as the global construction post-processor, so consumers should distinguish supported APIs from implementation seams. The documentation site has an independent content-extension model through Fumadocs collections and MDX component mappings.
- `core.$constructor(name, initializer, proto, params)` is the foundational schema-type extension point: it composes traits, installs prototype members, handles subclasses, runs deferred initialization, and defines trait-based `instanceof` behavior.
- Core factory helpers accept a schema class parameter, allowing classic and mini constructors to share identical definition creation without importing either facade into core.
- New schema behavior is represented by `$ZodTypeDef.type`, a constructor that supplies `_zod.parse`, and structural fields such as `values`, `pattern`, `propValues`, `optin`, and `optout` consumed by shared algorithms.
- Checks are an extension seam: schema definitions carry a check array, each check may run attachment hooks, and classic `check`, `with`, `refine`, `superRefine`, and `overwrite` clone the schema with additional checks.
- Typed metadata registries can be created with `registry()`, attach metadata through `schema.register`, inherit metadata through parent clones, and index schemas by metadata `id` for multi-schema JSON Schema output.
- JSON Schema generation is extensible through a processor table keyed by schema type, per-schema `_zod.processJSONSchema` or `_zod.toJSONSchema` overrides, a global `override` callback, and an `unrepresentable` handler.
- `withParser` is an explicit bridge for build-time or native compilers: a caller may install a synchronous parser returning either a fresh output or the shared `INVALID` sentinel while retaining interpreter fallback.
- `globalConfig.postProcessor` is an internal construction hook used by `zod/compile`; `customError`, `localeError`, `jitless`, and `memoizer` are the supported global behavior knobs.
- User callbacks extend runtime behavior through custom predicates, refinements, transforms, preprocessors, catches, codecs, lazy getters, and function-schema implementations.
- Standard Schema properties provide an ecosystem integration point that does not require consumers to depend on concrete Zod classes.
- Documentation is extended by adding MDX to the docs or blog collection and, when needed, mapping React components into the catch-all docs and blog renderers.
Contributor handoff
Future agents should treat this as a performance-sensitive library with several mutually dependent execution modes, not as a conventional web application. The safest workflow is to inspect the relevant facade, core implementation, and focused tests before making the smallest possible change. Runtime correctness alone is insufficient for changes under `packages/zod/src`; memory and bundle impact are first-class acceptance criteria. Release operations, version changes, GitHub ticket workflows, and new files all have explicit repository-specific constraints.
- Read `AGENTS.md`, the relevant public facade, the corresponding core schema/check implementation, and the focused test file before editing; `CLAUDE.md` is only a symlink to the same instructions.
- Use Nub exclusively: install with `nub install --frozen-lockfile`, run repository TypeScript with `nub`, scripts with `nub run`, and third-party tools with `nub exec --node`.
- Start verification with the smallest relevant Vitest file or test name, then run broader tests; every bug fix or feature must include focused TypeScript test coverage for success, failure, and meaningful edges.
- For compiler changes, run the direct compiler and differential suites plus `nub run test:compile`; compile mode is already part of the full root test command.
- Any non-trivial source change must report runtime, retained-memory, and bundle-size measurements together; core changes affect every distribution, and mini requires a separate bundle-size result.
- When changing constructor or property-placement machinery, preserve descriptors and instance shapes, then run the memory surface and dictionary-mode checks because accessor placement can cause large V8 regressions.
- Do not add schema-type-specific branching to shared parse paths; expose capabilities structurally through internals such as `optin`, `optout`, `values`, `pattern`, or `propValues`.
- Do not widen format validators solely because a specification permits more inputs; repository policy prioritizes observed producer/consumer compatibility, JSON Schema consistency, hot-path cost, and narrow defaults.
- Keep comments short, avoid consecutive prose `//` lines, add no debug logging, and run `nub run check:comments` alongside format and lint checks.
- Do not bump package versions or dispatch the release workflow without explicit maintainer direction; release publication is manual and protected by an npm environment approval.
- For docs changes, remember that production ecosystem builds require `GITHUB_TOKEN`, content lives under `packages/docs/content`, and the build is a Next/Turbopack invocation from the docs workspace.
- Run built-package resolution and integration checks after `nub run build`; source-condition tests cannot prove CommonJS, ESM, declaration, or downstream framework compatibility.
Languages
| Language | Files | Share of source |
|---|---|---|
| TypeScript | 511 | 98% |
| Markdown | 50 | docs/config |
| JSON | 38 | docs/config |
| YAML | 10 | docs/config |
| JavaScript | 7 | 1% |
| CSS | 2 | docs/config |
| HTML | 2 | docs/config |
| Shell | 1 | 0% |
Start reading here
Probable source entrypoint: packages/ — inferred from the directory tree and manifest, not the upstream docs.
Source roots
packages/ (624 files)
Source layout — packages/
| Path | Files |
|---|---|
packages/zod/ | 329 |
Repository structure
| Top-level path | Files |
|---|---|
packages/ | 624 |
scripts/ | 10 |
.github/ | 8 |
.husky/ | 3 |
.claude/ | 3 |
.configs/ | 2 |
wiki/ | 2 |
.devcontainer/ | 1 |
Docs found: AGENTS.mdCLAUDE.mdCONTRIBUTING.mdREADME.md
Package
package.json — , 34 declared dependencies.
@arethetypeswrong/cli@biomejs/biome@seriousme/openapi-schema-validator@types/benchmark@types/semver@web-std/filearktypebenchmarkchalkconsole-table-printeresbuildexeca
CI & config entrypoints
README.md— Repository readmeAGENTS.md— Agent instructionsCLAUDE.md— Claude instructions.github/workflows/release.yml— GitHub Actions workflow.github/workflows/pullfrog.yml— GitHub Actions workflow.github/workflows/test.yml— GitHub Actions workflow.github/workflows/lockstep.yml— GitHub Actions workflow.github/workflows/stale.yml— GitHub Actions workflow.github/workflows/claude-code-review.yml— GitHub Actions workflow.github/workflows/iso-4217.yml— GitHub Actions workflow
Structural health signals
Average score 8.2/10 across 96 analyzed source files; 7 hotspots surfaced.
packages/zod/src/v4/classic/from-json-schema.ts— 944 lines, score 5.7 (Large file, Deep nesting)packages/zod/src/v4/core/schemas.ts— 2559 lines, score 6.2 (Large file, Deep nesting)packages/zod/src/v3/types.ts— 1430 lines, score 6.2 (Large file, Deep nesting)
These are deterministic review leads — size, churn, and structural signals — not proof of a bug in this project.
Recent history
10dda3a3a4bc2026-09-21 — Cleanup bad commits (#6620)94886319faac2026-09-21 — ci: publish to npm with NPM_TOKENb1970f64930e2026-09-21 — 4.6.8c7eb014eaef52026-09-21 — 4.6.784f79a7d4ce82026-09-21 — ci: drop the npm environment approval gate from publish