Zod's product is largely its types — z.infer is the reason the library exists — so the TypeScript setup is unusually deliberate.
The compiler pin
The root package.json declares "typescript": "~5.5.4": a tilde range, not a caret, so the repo builds with 5.5.x only. Zod's type-level code (recursive inference, template-literal types, the $replace machinery in registries) sits close to compiler limits, and behavior differences between TypeScript minor versions can change what compiles and how fast. The dedicated workspace packages/tsc (@zod/tsc-perftest) exists precisely to benchmark compiler performance of Zod-heavy code.
The shared compiler options are strict across the board:
{
"compilerOptions": {
"target": "es2020",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"strict": true,
"exactOptionalPropertyTypes": true,
"noUnusedLocals": true,
"noUnusedParameters": true
}
}
exactOptionalPropertyTypes matters more here than in most repos: Zod 4 distinguishes "key absent" from "key present with undefined" (see optional vs exactOptional in the API), and the compiler flag enforces the same distinction in the library's own types.
zshy: one source tree, dual output
The build command is a single line — zshy --project tsconfig.build.json — and zshy (a build tool by Zod's author, in devDependencies at ^0.7.2) reads its configuration from a zshy field in the package manifest:
"zshy": {
"exports": {
"./package.json": "./package.json",
".": "./src/index.ts",
"./mini": "./src/mini/index.ts",
"./locales": "./src/locales/index.ts",
"./v3": "./src/v3/index.ts",
"./v4": "./src/v4/index.ts",
"./v4-mini": "./src/v4-mini/index.ts",
"./v4/mini": "./src/v4/mini/index.ts",
"./v4/core": "./src/v4/core/index.ts",
"./v4/locales": "./src/v4/locales/index.ts",
"./v4/locales/*": "./src/v4/locales/*"
},
"conditions": {
"@zod/source": "src"
}
}
From that map zshy emits, next to each entry point, .js/.cjs plus .d.cts/.d.mts declarations — the generated exports field in the same file shows the result, with import, require, and types conditions per subpath. Output lands in the package root ("outDir": "." in tsconfig.build.json), which is why files whitelists **/*.js, **/*.cjs, **/*.d.cts, and also src — the original TypeScript sources ship in the tarball.
The @zod/source condition
The "conditions": { "@zod/source": "src" } line generates an extra branch in every export: when a tool resolves with the @zod/source condition, import "zod" lands on packages/zod/src/index.ts instead of built output. The whole repo runs on it:
"dev": "tsx --conditions @zod/source",
"bench": "tsx --conditions @zod/source packages/bench/index.ts",
and the Vitest config sets resolve.conditions: ["@zod/source", "default"]. The practical consequence for contributors: there is no build step in the edit-test loop, ever. The separate packages/integration workspace covers the other side, testing against actual build output (test:built vs test:source).
Runtime code discipline
Two source-level conventions support the toolchain. Nearly every exported constructor is annotated /*@__PURE__*/ (and factories @__NO_SIDE_EFFECTS__) so bundlers can eliminate unused schemas — this is what makes zod/mini meaningfully tree-shakable, and packages/treeshake verifies it. And pnpm check:circular runs madge over packages/zod/src excluding v4/core and v3, keeping the API layers free of import cycles.