Zod
Generate Zod schemas from OpenAPI
Generate Zod schemas from your OpenAPI specification for runtime validation.
Configuration
Set the client option to zod:
import { defineConfig } from 'orval';
export default defineConfig({
petstore: {
output: {
client: 'zod',
mode: 'single',
target: './src/api/schemas',
override: {
zod: {
// Prefer Mini for more tree-shakeable generated schemas.
variant: 'mini',
version: 4,
},
},
},
input: {
target: './petstore.yaml',
},
},
});Generated Output
Orval generates a Zod schema for each model in your OpenAPI specification:
export const CreatePetsBody = /*#__PURE__*/ zod.object({
id: /*#__PURE__*/ zod.number(),
name: /*#__PURE__*/ zod.string(),
tag: /*#__PURE__*/ zod.optional(/*#__PURE__*/ zod.string()),
});Tree Shaking
Prefer variant: 'mini' when startup time, memory usage, or bundle size matter. Zod Mini uses top-level helpers, so generated validators can be dropped more reliably when their exports are unused:
// Regular Zod
export const User = zod.object({
email: zod.email().min(5).max(255),
});
// Zod Mini
export const User = /*#__PURE__*/ zod.object({
email: /*#__PURE__*/ zod
.email()
.check(/*#__PURE__*/ zod.minLength(5))
.check(/*#__PURE__*/ zod.maxLength(255)),
});When User is unused, a bundled regular Zod output can still keep the schema initializer as top-level work even if nothing uses it:
// possible bundled regular Zod output (no export const User, but the zod call is still there)
object({
email: email().min(userEmailMin).max(userEmailMax),
});With Zod Mini + pure annotations, the unused schema can disappear entirely:
// possible bundled Zod Mini output
// no User schema code emitted at allIn large generated clients this can remove unused schema initializers from the final bundle. In real builds this can mean hundreds of kilobytes less generated validation code loaded at startup.
Array response items
Inline array response items can use OpenAPI compositions such as allOf,
oneOf, or anyOf. Orval generates a schema for the item type, for example:
export const ListAllOf200Item = zod.object({
id: zod.int().optional(),
}).and(
zod.object({
extra: zod.string().optional(),
}),
);The generated client can then use ListAllOf200Item[] for the response. When
an array item is a component $ref, the component schema writer owns that
schema and the operation does not generate a duplicate item schema.
Zod version
Orval uses Zod 4 as its output baseline (z.strictObject, z.looseObject, z.iso.datetime(), .meta(), …), while projects still on Zod 3 are fully supported. The emitted syntax follows whichever zod major your project resolves.
With the default ('auto'), Orval infers the target from the zod version resolved in your project's package.json. When no zod package can be detected — for example in a fresh or partially-installed workspace — it falls back to Zod 4.
To make generation deterministic, pin the target with override.zod.version:
import { defineConfig } from 'orval';
export default defineConfig({
petstore: {
output: {
client: 'zod',
target: './src/api/schemas',
override: {
zod: {
variant: 'mini',
version: 4, // 3 | 4 | 'auto'
},
},
},
input: {
target: './petstore.yaml',
},
},
});| Value | Output |
|---|---|
4 | Always emit Zod 4 syntax, regardless of the installed zod version. |
3 | Always emit Zod 3-compatible syntax, regardless of the installed zod version. |
'auto' | (default) Infer from the resolved zod version; fall back to Zod 4 when none is detected. |
Pinning the version keeps output stable: the same spec produces the same schemas on every machine and in CI, independent of which zod version happens to be installed.
Zod Mini
Set override.zod.variant to 'mini' to generate schemas against Zod Mini:
export default defineConfig({
petstore: {
output: {
client: 'zod',
override: {
zod: {
variant: 'mini',
version: 4,
},
},
},
},
});Mini output imports from zod/mini and uses Zod Mini's functional/check-based API. It requires Zod 4; version: 3 or version: 'auto' resolving to Zod 3 is rejected.
Prefer Zod Mini when generated schemas are bundled into frontend apps, Cloudflare Workers, or other startup-sensitive runtimes where tree-shaking and bundle size matter. Use regular Zod when those constraints are not important; it has the more familiar chainable API and better autocomplete ergonomics.
One behavior difference: Zod Mini does not load the English locale by default. Configure it once if you want regular Zod's default English messages:
import * as zod from 'zod/mini';
zod.config(zod.locales.en());Scoping options per operation or tag
Most override.zod settings can be applied to part of your API instead of globally, via override.operations[operationId].zod (a single operation) or override.tags[tagName].zod (every operation sharing a tag):
export default defineConfig({
petstore: {
output: {
client: 'zod',
override: {
zod: {
variant: 'mini',
version: 4,
strict: { response: true }, // applies everywhere
},
operations: {
listPets: {
zod: {
coerce: { query: ['number'] }, // only this operation
},
},
},
tags: {
admin: {
zod: {
strict: { body: true }, // every operation tagged "admin"
},
},
},
},
},
},
});Per-operation and per-tag overrides accept the settings that apply to an individual schema: strict, generate, coerce, preprocess, params, useBrandedTypes, and generateCompanionTypes.
Output-wide settings — variant, version, dateTimeOptions, timeOptions, generateEachHttpStatus, generateReusableSchemas, generateMeta, generateDiscriminatedUnion, and exactOptional — only make sense for the whole output and must stay on override.zod. If you place one on an operation or tag it is ignored — the value from override.zod still applies — and Orval prints a build warning:
⚠️ override.operations.listPets.zod only supports strict, generate, coerce, preprocess, params, useBrandedTypes, and generateCompanionTypes. Ignoring unsupported field: zod.version.Usage
Parsing Data
import { CreatePetsBody } from './src/api/schemas';
const pet = { id: 1, name: 'Buddy', tag: 'dog' };
const parsedPet = CreatePetsBody.parse(pet);
// => { id: 1, name: "Buddy", tag: "dog" }Type Inference
import type { z } from 'zod/mini';
import { CreatePetsBody } from './src/api/schemas';
type Pet = z.infer<typeof CreatePetsBody>;
const pet: Pet = { id: 1, name: 'Buddy', tag: 'dog' };Safe Parsing
const result = CreatePetsBody.safeParse(unknownData);
if (result.success) {
console.log(result.data);
} else {
console.error(result.error);
}Combining with HTTP Clients
See the Client with Zod guide for using Zod schemas with React Query, SWR, or other HTTP clients.