Zod
The zod format generates Zod schema definitions for runtime validation alongside your OpenAPI types.
Basic Example
OpenAPI Schema:
yaml
components:
schemas:
User:
type: object
required:
- id
- email
properties:
id:
type: string
description: Unique identifier
email:
type: string
format: email
name:
type: string
age:
type: integer
minimum: 0
maximum: 150Generated Zod:
typescript
// Generated by an automated build step. Do not edit.
import * as z from "zod";
/** Unique identifier */
export const userSchema = z.object({
id: z.string(),
email: z.string(),
name: z.string().optional(),
age: z.number().optional()
});Configuration
jsonc
{
"outputs": [
{
"path": "./src/schemas/api.ts",
"format": "zod",
"contents": ["api:*"],
"options": {
"inferTypes": true
}
}
]
}Options
inferTypes
Type: boolean
Default: false
Generates TypeScript type aliases using z.infer alongside the schemas.
Without inferTypes:
typescript
export const userSchema = z.object({
id: z.string(),
name: z.string()
});With inferTypes: true:
typescript
export const userSchema = z.object({
id: z.string(),
name: z.string()
});
export type User = z.infer<typeof userSchema>;This gives you both runtime validation and TypeScript types from a single source.
Type Mapping
| OpenAPI Type | OpenAPI Format | Zod Schema |
|---|---|---|
string | - | z.string() |
string | email | z.string() |
string | uri | z.string() |
string | uuid | z.string() |
string | date | z.string() |
string | date-time | z.string() |
integer | - | z.number() |
number | - | z.number() |
boolean | - | z.boolean() |
array | - | z.array(...) |
object | - | z.object({...}) |
object | (no properties) | z.record(z.unknown()) |
String Constraints
OpenAPI string constraints map to Zod validations:
yaml
properties:
email:
type: string
minLength: 5
maxLength: 255
pattern: "^[a-z]+$"typescript
export const userSchema = z.object({
email: z.string().min(5).max(255).regex(/^[a-z]+$/)
});| OpenAPI | Zod |
|---|---|
minLength | .min() |
maxLength | .max() |
pattern | .regex() |
Enums
String enums generate z.enum():
yaml
UserRole:
type: string
enum: [admin, user, guest]typescript
export const userRoleSchema = z.enum(["admin", "user", "guest"]);With inferTypes: true:
typescript
export const userRoleSchema = z.enum(["admin", "user", "guest"]);
export type UserRole = z.infer<typeof userRoleSchema>;
// type UserRole = "admin" | "user" | "guest"Nullable Fields
Nullable fields use z.nullable():
yaml
properties:
nickname:
type: string
nullable: truetypescript
export const UserSchema = z.object({
nickname: z.string().nullable().optional()
});References
Schema references become Zod schema references:
yaml
properties:
author:
$ref: '#/components/schemas/User'typescript
export const PostSchema = z.object({
author: UserSchema
});Composition
allOf (Intersection)
yaml
allOf:
- $ref: '#/components/schemas/BaseEntity'
- type: object
properties:
name:
type: stringtypescript
export const ProductSchema = BaseEntitySchema.extend({
name: z.string()
});oneOf / anyOf (Union)
yaml
oneOf:
- $ref: '#/components/schemas/Dog'
- $ref: '#/components/schemas/Cat'typescript
export const PetSchema = z.union([DogSchema, CatSchema]);Namespaces
Group schemas into namespaces:
jsonc
{
"contents": [
{
"namespace": "Schemas",
"contents": ["api:*"]
}
]
}typescript
export const userSchema = z.object({
id: z.string(),
name: z.string()
});
export namespace Schemas {
export type User = z.infer<typeof userSchema>;
}Usage Examples
Validating API Responses
typescript
import { userSchema } from './schemas/api';
async function fetchUser(id: string) {
const response = await fetch(`/api/users/${id}`);
const data = await response.json();
// Validates and returns typed data
return userSchema.parse(data);
}Safe Parsing
typescript
const result = userSchema.safeParse(data);
if (result.success) {
// result.data is typed as User
console.log(result.data.name);
} else {
// result.error contains validation errors
console.error(result.error.issues);
}Form Validation
typescript
import { createUserRequestSchema } from './schemas/api';
function validateForm(formData: FormData) {
return createUserRequestSchema.safeParse({
name: formData.get('name'),
email: formData.get('email')
});
}Best Practices
Schema Naming
Schemas are named with a Schema suffix and a camel case name:
User→userSchemaCreateUserRequest→createUserRequestSchema
Error Messages
Add custom error messages in your application code:
typescript
const UserSchema = z.object({
email: z.string().email({ message: "Invalid email address" })
});Transformations
Add transformations after importing:
typescript
import { userSchema } from './schemas/api';
const userSchemaWithDefaults = userSchema.transform(user => ({
...user,
createdAt: user.createdAt ?? new Date()
}));