Skip to content

Migrating from hey-api or orval

This guide is for developers who already have a TypeScript project with an OpenAPI generator set up and want to switch to openapi-zod-ts. It covers the mental model shift, the step-by-step switch, and what to watch for.

Many OpenAPI generators follow a pattern where the generated code depends on a runtime client library that the generator itself publishes. Your code calls into that runtime, and the generated types describe its API. The generator ships a package to dependencies, not just devDependencies.

This toolchain works differently:

  • Generated files are owned code. models.ts, client.ts, client-config.ts, and (optionally) schemas.ts live in your repository. They are plain TypeScript you can read, extend, and commit like any other file.
  • Zero runtime footprint. The generator (openapi-zod-ts) is a devDependency. The generated client.ts uses only native fetch. Nothing is added to your production bundle that your project did not already have.
  • Runtime validation is opt-in and user-owned. If you add input_schema to your config, the generator bootstraps a Zod schemas.ts file once, then never overwrites it. You add error messages and business rules. The generator warns on drift but your schema stays yours.
  • Requests and responses are validated at runtime when Zod integration is enabled: the client calls Schema.parse(body) before sending and Schema.parse(await res.json()) after receiving.
  1. Remove the old generator.

    Uninstall the previous generator and any runtime packages it required:

    Terminal window
    # example: remove hey-api packages
    npm uninstall @hey-api/client-fetch @hey-api/openapi-ts
    # example: remove orval
    npm uninstall orval

    Delete the old generated output directory and any existing generator config files (orval.config.ts, openapi-ts.config.ts, etc.).

  2. Install openapi-zod-ts.

    Terminal window
    npm install -D openapi-zod-ts

    If you also want React Query hooks, add the hooks package:

    Terminal window
    npm install -D @codewithagents/openapi-react-query

    If your old setup relied on MSW or Faker mocks (orval and kubb both ship mock generation), add @codewithagents/openapi-msw. It generates MSW v2 handlers with seeded Faker data from the same spec, so you do not lose mocking when you switch:

    Terminal window
    npm install -D @codewithagents/openapi-msw
  3. Create the config file.

    Create openapi-zod-ts.config.json in your project root:

    {
    "input_openapi": "./openapi.json",
    "output": "./src/api"
    }

    Optional fields for a fuller setup:

    {
    "input_openapi": "./openapi.json",
    "output": "./src/api",
    "input_schema": "./src/api/schemas.ts",
    "baseUrl": "https://api.example.com",
    "server_client": true
    }
    FieldRequiredDescription
    input_openapiYesPath to your OpenAPI spec (JSON or YAML)
    outputYesDirectory to write the generated files
    input_schemaNoPath to a user-owned Zod schema file. Bootstrapped on first run, never overwritten after that.
    baseUrlNoDefault base URL embedded in client-config.ts
    server_clientNoWhen true, also generates server.ts with a createServerClient() factory
  4. Run the generator.

    Terminal window
    npx openapi-zod-ts

    This writes the following files to your output directory:

    FileContents
    models.tsTypeScript interfaces for every schema
    client.tsOne typed async function per API operation
    client-config.tsconfigureClient() and getConfig()
    index.tsBarrel re-export of all three files above
  5. Update your imports.

    Replace imports from the old generator’s runtime with imports from your generated barrel:

    // Before: importing from a runtime package
    import { createClient } from '@hey-api/client-fetch'
    // After: importing from your own generated code
    import { configureClient, listTasks, ApiError } from './src/api'

    Call configureClient once at app startup:

    import { configureClient } from './src/api'
    configureClient({
    baseUrl: 'https://api.example.com',
    token: () => getAccessToken(), // sync or async, called per request
    })
  6. Add a generate script to package.json.

    {
    "scripts": {
    "generate": "openapi-zod-ts"
    }
    }
// orval.config.ts (illustrative, your config may differ)
import { defineConfig } from 'orval'
export default defineConfig({
myApi: {
input: './openapi.json',
output: {
target: './src/api/client.ts',
client: 'react-query',
},
},
})
// package.json dependencies (illustrative)
{
"devDependencies": {
"orval": "^7.x"
},
"dependencies": {
"axios": "^1.x"
}
}

Breaking change: int64 fields are now number, not bigint

Section titled “Breaking change: int64 fields are now number, not bigint”

In v1, fields with format: int64 generated bigint in TypeScript and z.bigint() in Zod schemas. In v2 they generate number and z.number().

The rationale: JSON.stringify throws when a value contains a bigint, and JSON.parse never produces a bigint. This makes bigint unworkable for any fetch-based API client. Precision is therefore limited to Number.MAX_SAFE_INTEGER (2^53-1), and the generated TypeScript type carries an inline comment to make that explicit:

generated/models.ts
export interface Order {
id: number /* int64, precision limited to 2^53-1 */
}

How to fix your schemas.ts: The generator never overwrites schemas.ts, so any z.bigint() you wrote (or that v1 bootstrapped) for an int64 property will produce a TypeScript compile error against the regenerated number types. Find those fields and replace z.bigint() with z.number(), removing any BigInt(...) conversions at call sites.

// Before (v1)
export const OrderSchema = z.object({
id: z.bigint(),
})
// After (v2)
export const OrderSchema = z.object({
id: z.number(),
})

Non-breaking improvements worth regenerating for

Section titled “Non-breaking improvements worth regenerating for”

Re-running the generator after upgrading picks up several typing improvements:

  • Inline object responses typed correctly (#298): response schemas without a $ref are now typed as their actual inline object shape instead of Record<string, unknown>.
  • Integer path params typed number (#300): path parameters with type: integer or type: number are now number in the generated function signature instead of string.
  • Bracket-notation array query params typed correctly (#299): parameters like ids[] are typed number[] or string[] depending on the item schema, instead of always string.
  • Accept: application/json header on all requests: every generated fetch call now sends Accept: application/json automatically.

None of these change the runtime behavior of existing working calls, but regenerating will improve type safety and remove unnecessary casts.

Types and fetch client

Complete configuration reference, generated file details, and advanced usage.

openapi-zod-ts guide

Mock data with MSW

Generate MSW v2 handlers with seeded Faker data from the same spec. The replacement for orval or kubb mock generation.

openapi-msw guide