Skip to content

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: 150

Generated 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 TypeOpenAPI FormatZod Schema
string-z.string()
stringemailz.string()
stringuriz.string()
stringuuidz.string()
stringdatez.string()
stringdate-timez.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]+$/)
});
OpenAPIZod
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: true
typescript
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: string
typescript
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:

  • UseruserSchema
  • CreateUserRequestcreateUserRequestSchema

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()
}));

See Also

Released under the MIT License.