A machine pouring type-safe TypeScript code from an OpenAPI blueprint, with a large red Maine Coon cat resting nearby.

Orval: Pour Yourself a Type-Safe API Client Straight From OpenAPI

The Orange Cat
The Orange Cat

If you have ever built a React frontend against a REST API, you know the ritual. You hand-write a fetch or axios call for each endpoint. You hand-write TypeScript interfaces for every request and response. You hand-write the TanStack Query hooks that wrap those calls, complete with query keys and invalidation logic. And then you hand-write mock data so your tests and local dev don't grind to a halt waiting on a live backend. It's a lot of typing, and worst of all, every single line of it silently drifts out of sync the moment the backend changes a field.

Orval deletes that entire category of work. It's a code generation CLI that reads any valid OpenAPI v3 or Swagger v2 specification, in YAML or JSON, and emits ready-to-import, fully type-safe TypeScript: client functions, models for every schema, framework-specific data-fetching hooks, and optional mocks. The spec becomes your single source of truth, and regenerating gives you compile-time errors anywhere your code no longer matches the contract. The name is a beer reference (note the cheerful 🍺 in its tagline), and the whole tool has that same "pour it out and enjoy" energy.

Why Reach for the Bottle

Orval is more than a types generator. Here's what it brings to the table:

  • Type-safe clients from any OpenAPI v3 or Swagger v2 spec, sourced from a local file or a remote URL.
  • Multiple output clients: react-query, swr, vue-query, svelte-query, solid-query, angular, plus plain axios, fetch, zod validators, hono server stubs, effect, and even an mcp server target.
  • Generated TanStack Query hooks: queries, mutations, infinite queries, suspense queries, query-key factories, and invalidation helpers.
  • MSW + Faker mocks auto-generated from your schemas and examples, so frontend work never blocks on a live backend.
  • Custom mutators to swap in your own axios or fetch instance for auth, base URLs, and interceptors.
  • Per-operation overrides to fine-tune naming, query options, or mock data on a single endpoint.
  • Flexible output modes (single, split, tags, tags-split) plus a clean step and a Prettier or Biome formatter.
  • Watch mode that regenerates whenever your spec changes.

In short: end-to-end type safety across a polyglot stack, without a shared monorepo or a TypeScript-only backend.

Getting It Into Your Pantry

Orval lives as a dev dependency, since it only runs at build time.

npm install orval -D

Or with yarn:

yarn add orval --dev

The First Pour

There are two ways to run Orval. The quick one is straight from the CLI with flags:

orval --input ./petstore.yaml --output ./src/petstore.ts

That works for a one-off, but the recommended approach is a config file. Orval auto-discovers orval.config.ts, .js, or .mjs in your project root, and the defineConfig helper gives you full type hints while you write it.

// orval.config.ts
import { defineConfig } from 'orval';

export default defineConfig({
  petstore: {
    input: './petstore.yaml',
    output: './src/petstore.ts',
  },
});

Wire up a script and you're done:

{
  "scripts": {
    "generate:api": "orval"
  }
}
npm run generate:api

The input can be a local path or a remote URL pointing at a live spec, which is handy when your backend publishes its OpenAPI document at an endpoint. Run the command and Orval writes out everything described by the spec.

Turning a Spec Into Living Hooks

This is Orval's headline trick. Set client: 'react-query' and a flat spec becomes a folder of TanStack Query hooks. Here's a richer config that groups output by OpenAPI tag, puts schemas in their own directory, and emits mocks alongside the client:

// orval.config.ts
import { defineConfig } from 'orval';

export default defineConfig({
  petstore: {
    output: {
      mode: 'tags-split',         // group generated files by OpenAPI tag
      target: 'src/api/petstore.ts',
      schemas: 'src/api/model',   // TS interfaces land here
      client: 'react-query',
      httpClient: 'axios',        // or 'fetch'
      mock: true,                 // also emit MSW mocks
    },
    input: { target: './petstore.yaml' },
  },
});

For each operation, Orval generates three things: the raw request function, a query-key factory, and a fully wired hook.

// the raw request fn
export const showPetById = (
  petId: string,
  options?: AxiosRequestConfig,
): Promise<AxiosResponse<Pet>> => {
  return axios.get(`/pets/${petId}`, options);
};

// the query-key factory
export const getShowPetByIdQueryKey = (petId: string) => [`/pets/${petId}`];

// the ready-to-use hook
export const useShowPetById = <
  TData = Awaited<ReturnType<typeof showPetById>>,
  TError = Error,
>(
  petId: string,
  options?: {
    query?: UseQueryOptions<TData, TError>;
    axios?: AxiosRequestConfig;
  },
) => {
  // wires queryKey + queryFn into useQuery
};

Inside a component, you just import the hook and use it like any other TanStack Query call. Every field on data is typed straight from the spec.

function PetDetail({ petId }: { petId: string }) {
  const { data, isLoading, error } = useShowPetById(petId);
  if (isLoading) return <Spinner />;
  if (error) return <ErrorBanner />;
  return <h1>{data?.data.name}</h1>;
}

One thing worth flagging early: with the default axios client, the response object is wrapped, so you reach the payload via data?.data. If that double .data bothers you, switch to httpClient: 'fetch' or a custom mutator that unwraps it, and the extra hop disappears.

Dialing In Individual Endpoints

The defaults are sensible, but real apps need control. Orval's override.operations block lets you tune a single endpoint without touching the rest. Want a list endpoint to also produce infinite, suspense, and invalidation variants? Opt in per operation:

override: {
  operations: {
    listPets: {
      query: {
        useQuery: true,
        useSuspenseQuery: true,
        useInfinite: true,
        useInfiniteQueryParam: 'limit',
        useSuspenseInfiniteQuery: true,
        useInvalidate: true,
      },
    },
  },
}

From that single block, Orval generates useListPets, useSuspenseListPets, and useInfiniteListPets, plus invalidation helpers, all type-checked against the spec. You get the exact surface area you want for each route instead of a one-size-fits-all hook.

Bringing Your Own HTTP Instance

Generated raw axios calls are fine for a demo, but production code needs auth headers, a configured base URL, and centralized error handling. Orval's custom mutator lets you point every generated request at your own instance:

override: {
  mutator: {
    path: './src/api/mutator/custom-instance.ts',
    name: 'customInstance',
  },
}

Your customInstance function wraps axios or fetch however you like, attaching tokens, setting interceptors, and shaping the return value. Every generated request routes through it, so you configure cross-cutting concerns in exactly one place and the generated code stays untouched.

Mocks On The House

Set mock: true and Orval emits a companion mock file, for example petstore.msw.ts, containing two kinds of helpers: getXxxMock() Faker factories that produce realistic, schema-shaped fake data, and a getXxxMSW() function returning an array of MSW request handlers. Wiring those into a test server takes one line:

import { setupServer } from 'msw/node';
import { getPetstoreMSW } from './api/petstore.msw';

const server = setupServer(...getPetstoreMSW());

The same handlers work in a browser worker for local development, so you can build and demo the entire frontend before the backend is even deployed. And the fake data is fully customizable per operation:

override: {
  operations: {
    showPetById: {
      mock: {
        data: () => ({
          id: faker.number.int({ min: 1, max: 99 }),
          name: faker.person.firstName(),
        }),
      },
    },
  },
  mock: {
    // regex match on property names
    properties: { '/tag|name/': () => faker.person.lastName() },
  },
}

Beyond custom data, the mock controls include delay, useExamples (which reuses the example values from your spec), generateEachHttpStatus, arrayMin and arrayMax, required, and locale. Recent versions can also emit Faker-only factories with no MSW dependency at all, which is perfect for Storybook stories or unit tests that just need a realistic object without an interception layer.

A Few Things To Watch For

Orval is sharp, and like any sharp tool it rewards understanding its edges. The single most important rule: output quality tracks spec quality. A vague, loosely typed OpenAPI document produces loose TypeScript, full of optionals and unknown. The cleaner your spec, the better your generated client, so it's worth investing in the contract itself.

Treat generated files as committed build artifacts, not source you edit by hand. You re-run generation whenever the spec changes, and anything you typed into a generated file gets overwritten. Set clean: true to wipe stale output between runs so deleted endpoints don't linger. Also note that axios is the default HTTP client and pulls axios in as a runtime dependency; if you'd rather ship nothing extra, httpClient: 'fetch' uses the platform's native fetch. Finally, Orval targets MSW v2, so older setups using v1 handler signatures will need an update.

How It Stacks Up

Orval sits in a crowded but distinct corner of the OpenAPI codegen world. If you want types and nothing else, with zero runtime and zero opinion about your HTTP client, openapi-typescript is the lean choice. If you want maximum plugin-driven flexibility and don't mind assembling your own pipeline, Kubb is the modular powerhouse. Orval's pitch is the opposite of minimalism: it's batteries-included. Out of the box, from one spec, you get client code, typed models, framework hooks, query-key factories, invalidation helpers, and mocks, all wired together and all in sync.

The rule of thumb is simple. Types only? Reach for openapi-typescript. Everything generated for you, especially React Query plus mocks? That's Orval's home turf. It's MIT-licensed, extremely actively maintained, and clears over a million weekly downloads, yet it remains underused relative to how much hand-written boilerplate it can vaporize. If your backend already speaks OpenAPI and your frontend already speaks TanStack Query, Orval is the bridge between them that you never have to maintain by hand. Pour yourself a glass. 🍺