Zod schema generator

Generate Zod schemas from OpenAPI schema

@skmtc/gen-zod

OpenAPI to Zod schema generator for Skmtc.

Supported Features

  • Primitive types: string, number, integer, boolean, void, unknown
  • Complex types: object, array, union (oneOf/anyOf), references ($ref)
  • Modifiers: nullable (.nullable()), optional (.optional())
  • Enums: Single values (z.literal()) and multiple values (z.enum())
  • Integer validation: Uses z.number().int() for integer types
  • Objects: Nested objects, properties with special characters
  • Additional properties: Record types (z.record()), mixed with regular properties using .and()
  • Arrays: Typed arrays, nested arrays, arrays of objects
  • Unions: Simple unions and discriminated unions (z.discriminatedUnion())
  • References: Schema references including recursive types with z.lazy()
  • Name transformations: kebab-case and snake_case to PascalCase

Getting started

Install Skmtc

deno install -g -A --unstable-worker-options jsr:@skmtc/cli -n skmtc -f

Skmtc runs on Deno. You can install it using

  • curl -fsSL https://deno.land/install.sh | sh on MacOS/Linux
  • irm https://deno.land/install.ps1 | iex on Windows

Create project and generate artifacts using TUI

skmtc

Create project and generate artifacts using CLI

# Create project skmtc init <project name> # Install Zod generator skmtc install @skmtc/gen-zod <project name> # Bundle generator code skmtc bundle <project name> # Generate artifacts from OpenAPI schema skmtc generate <project name> <path or url to openapi schema>

Usage Examples

Basic Primitive Types

Input (OpenAPI Schema)Zod Schema
{ "components": { "schemas": { "User": { "type": "object", "properties": { "id": { "type": "string" }, "age": { "type": "number" }, "score": { "type": "integer" }, "isActive": { "type": "boolean" } }, "required": ["id", "age", "score", "isActive"] } } } }
import { z } from "zod"; export const user = z.object({ id: z.string(), age: z.number(), score: z.number().int(), isActive: z.boolean(), });

Optional Properties

Properties not in the required array become optional:

Input (OpenAPI Schema)Zod Schema
{ "Profile": { "type": "object", "properties": { "username": { "type": "string" }, "bio": { "type": "string" }, "website": { "type": "string" } }, "required": ["username"] } }
export const profile = z.object({ username: z.string(), bio: z.string().optional(), website: z.string().optional(), });

Enums and Literals

Single enum values become literals, multiple values become z.enum:

Input (OpenAPI Schema)Zod Schema
{ "Status": { "type": "string", "enum": ["active", "inactive", "pending"] }, "Role": { "type": "string", "enum": ["admin"] } }
export const status = z.enum(["active", "inactive", "pending"]); export const role = z.literal("admin");

Arrays

Input (OpenAPI Schema)Zod Schema
{ "Tags": { "type": "array", "items": { "type": "string" } }, "Matrix": { "type": "array", "items": { "type": "array", "items": { "type": "number" } } } }
export const tags = z.array(z.string()); export const matrix = z.array(z.array(z.number()));

Nested Objects

Input (OpenAPI Schema)Zod Schema
{ "Company": { "type": "object", "properties": { "name": { "type": "string" }, "address": { "type": "object", "properties": { "street": { "type": "string" }, "city": { "type": "string" } }, "required": ["street", "city"] } }, "required": ["name", "address"] } }
export const company = z.object({ name: z.string(), address: z.object({ street: z.string(), city: z.string(), }), });

Nullable Types

Input (OpenAPI Schema)Zod Schema
{ "Article": { "type": "object", "properties": { "title": { "type": "string" }, "publishedAt": { "type": "string", "nullable": true } }, "required": ["title", "publishedAt"] } }
export const article = z.object({ title: z.string(), publishedAt: z.string().nullable(), });

Union Types

Input (OpenAPI Schema)Zod Schema
{ "StringOrNumber": { "anyOf": [ { "type": "string" }, { "type": "number" } ] }, "Pet": { "oneOf": [ { "type": "object", "properties": { "type": { "type": "string", "enum": ["cat"] }, "meow": { "type": "boolean" } }, "required": ["type", "meow"] }, { "type": "object", "properties": { "type": { "type": "string", "enum": ["dog"] }, "bark": { "type": "boolean" } }, "required": ["type", "bark"] } ] } }
export const stringOrNumber = z.union([z.string(), z.number()]); export const pet = z.discriminatedUnion("type", [ z.object({ type: z.literal("cat"), meow: z.boolean(), }), z.object({ type: z.literal("dog"), bark: z.boolean(), }), ]);

Record Types (Additional Properties)

Input (OpenAPI Schema)Zod Schema
{ "Metadata": { "type": "object", "additionalProperties": { "type": "string" } }, "Config": { "type": "object", "properties": { "id": { "type": "string" } }, "required": ["id"], "additionalProperties": { "type": "number" } } }
export const metadata = z.record(z.string(), z.string()); export const config = z.object({ id: z.string() }).and( z.record(z.string(), z.number()), );

References and Recursive Types

Input (OpenAPI Schema)Zod Schema
{ "Category": { "type": "object", "properties": { "name": { "type": "string" }, "parent": { "$ref": "#/components/schemas/Category" } }, "required": ["name"] } }
export const category = z.object({ name: z.string(), parent: z.lazy(() => Category).optional(), });

Type Name Transformations

Schema names are automatically converted to PascalCase:

Input (OpenAPI Schema)Zod Schema
{ "user-profile": { "type": "string" }, "api_response": { "type": "number" }, "MyType": { "type": "boolean" } }
export const userProfile = z.string(); export const apiResponse = z.number(); export const myType = z.boolean();

Testing

Run tests for this generator:

cd gen-zod && deno task test

Or with coverage:

deno task test:coverage

Generate HTML coverage report:

deno task coverage:html

Support

License

MIT.

@skmtc/gen-zod

Coverage