OrvalOrval

Output

Output configuration options

target

Type: String

Output path for generated files.

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

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

client

Type: String | Function Default: 'axios-functions' Options: angular, angular-query, axios, axios-functions, react-query, solid-start, solid-query, svelte-query, vue-query, pinia-colada, swr, zod, effect, hono, fetch, mcp

See the Pinia Colada guide for generated Vue queries, mutations, query keys, and options.

orval.config.ts
export default defineConfig({
  petstore: {
    output: {
      client: 'react-query',
    },
  },
});

You can also provide a function to create a custom client generator.

axios client

The axios client generates a factory function with an optional axios instance parameter for dependency injection:

import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios';

export const getPetsApi = (axiosInstance: AxiosInstance = axios) => ({
  listPets: (params?: ListPetsParams) =>
    axiosInstance.get<Pet[]>('/pets', { params }),
});

You can inject your own axios instance for testing or custom configuration:

const customAxios = axios.create({ baseURL: 'https://api.example.com' });
const api = getPetsApi(customAxios);

The generated factory also exposes a get<Operation>Url helper for each operation that does not use a custom mutator:

const url = api.getListPetsUrl({ limit: 10 });

The helper returns the generated OpenAPI route with its query parameters serialized by Axios. It uses the injected instance's Axios serialization configuration, while ignoring that instance's runtime baseURL, default request params, request options, and interceptors. The same helpers are emitted for axios-functions and Axios-backed query clients.

Helpers are named get<Operation>Url (for example, getListPetsUrl) and return a string. They accept the operation's path and query parameters, but not Axios request options. Operations using a custom mutator do not emit a helper because the mutator owns the runtime URL and serialization behavior. Fetch output already has its own URL helper and is unchanged by this feature.

httpClient

Type: 'fetch' | 'axios' | 'angular' Default: 'fetch'

HTTP transport used by compatible generated clients. Use fetch or axios for query clients, and angular for angular and angular-query output.

orval.config.ts
export default defineConfig({
  petstore: {
    output: {
      client: 'swr',
      httpClient: 'axios',
    },
  },
});

schemas

Type: String | Object | false Default: Same as target

Output path for generated model types. Set to false to disable separate schema file output.

String form

orval.config.ts
export default defineConfig({
  petstore: {
    output: {
      schemas: './api/model',
    },
  },
});

Object form

orval.config.ts
export default defineConfig({
  petstore: {
    output: {
      schemas: {
        path: './api/model',
        type: 'typescript', // 'typescript' | 'zod'
        routes: {
          default: 'models',
          enum: 'types',
        },
      },
    },
  },
});
PropertyTypeDescription
pathstringFilesystem path for schema output. A directory by default; in mode: 'single' it may point directly at the schema module file
typestring'typescript' (default) or 'zod' — optional
importPathstringOptional package import specifier (see below)
splitByTagsbooleanOrganize schemas into per-tag subdirectories (default false, see below)
routesobjectRoute enums separately from other schemas
mode'split' | 'single'Schema file layout: one file per schema, or one module (default 'split', see below)

routes.default is required and route values are relative directories under schemas.path. routes.enum is optional; without it, enums use the default route. Empty, absolute, parent-escaping, or equivalent route values are rejected. Route comparison is case-insensitive so configurations remain portable to case-insensitive file systems. Routes are supported for TypeScript and Zod schemas; enums use routes.enum and all other schemas use routes.default. Routes can be combined with splitByTags. They remain incompatible with operationSchemas, mocks, and factory methods.

When different schema names produce the same generated file under the selected naming convention, equivalent definitions are emitted once and imports are redirected to the canonical schema. Different definitions are rejected with a configuration error instead of being silently overwritten.

With indexFiles: true, Orval writes a barrel for each route that contains schemas and a root schemas/index.ts barrel. With indexFiles: false, generated imports point directly to the routed schema files.

For example, with routes: { default: 'models', enum: 'types' }:

schemas/
├── models/
│   ├── index.ts
│   └── pet.ts
├── types/
│   ├── index.ts
│   └── petStatus.ts
└── index.ts

When splitByTags is also enabled, tag directories are nested under their route. Schemas used by multiple tags (or by no operation) are placed under <route>/shared, for example schemas/models/pets, schemas/models/shared, and schemas/types/pets.

Single-file schemas

schemas is written independently of mode, which only arranges the client files. By default every schema gets its own file under schemas.path, even with mode: 'split'. Set schemas.mode: 'single' to write all of them to one module instead:

output: {
  target: './api/client.ts',
  mode: 'split',
  // Every model in ./api/model/index.ts
  schemas: { path: './api/model', mode: 'single' },
}

path may also name the module itself, e.g. schemas: { path: './api/model/api.schemas.ts', mode: 'single' }. A directory path writes <schemas.path>/index with schemaFileExtension. No additional barrel is generated, regardless of indexFiles. The same restrictions as for Zod below apply.

Zod

Set schemas.mode: 'single' to write component and operation schemas into <schemas.path>/index.zod.ts. The API client stays in its own output file and imports the schemas and their derived TypeScript types from this module.

output: {
  target: './api/client.ts',
  client: 'react-query',
  httpClient: 'fetch',
  schemas: { path: './api/model', type: 'zod', mode: 'single' },
}

path names a directory by default, so the module is written as <schemas.path>/index.zod.ts. In mode: 'single', path may also point directly at the output file, e.g. schemas: { path: './api/model.zod.ts', type: 'zod', mode: 'single' }; a directory path keeps the index.zod.ts behavior. The filename uses schemaFileExtension (default .zod.ts). No additional barrel is generated, regardless of indexFiles. importPath, when provided, must resolve to the single module. This mode cannot be combined with splitByTags, routes, operationSchemas, mock generators, or factoryMethods. Omitting schemas.mode preserves the existing split-file schema output. output.mode independently controls how API client files are organized.

importPath

When importPath is set, generated client files import schema types from that package specifier instead of computing a relative filesystem path:

orval.config.ts
export default defineConfig({
  petstore: {
    output: {
      target: './libs/client/angular/src/lib/endpoints',
      schemas: {
        path: './libs/client/models/src/lib',
        type: 'typescript',
        importPath: '@acme/client/models',
      },
    },
  },
});
// Without importPath — computed relative path:
import type { Pet } from '../models/pet';

// With importPath: '@acme/client/models':
import type { Pet } from '@acme/client/models';

Schemas are still written to the filesystem path — only the generated import statements change.

Requirements when using importPath:

  • The target package must export the types at the specified import path.
  • With indexFiles: true (recommended), all types are imported from the single importPath (e.g., @acme/models).
  • With indexFiles: false, each schema is imported individually (e.g., @acme/models/pet). The package must support these subpath exports. For Zod schemas (type: 'zod') the per-file suffix is .zod, so the package must also expose ./pet.zod (e.g., @acme/models/pet.zod).
  • If using faker schema factories (mock: { generators: [{ type: 'faker', schemas: true }] }), the package must also export ./index.faker. When the package can't expose a sub-path (e.g. importPath resolves to a single barrel file via tsconfig path mappings), set schemasImportPath on the faker generator to point faker factories at a separate import path.
  • If using factory methods (factoryMethods), each schema is imported individually regardless of indexFiles.
  • When importPath is set, the relative-path computation in factoryMethods.outputDirectory is bypassed: factories resolve imports against the package specifier rather than the on-disk factory output directory.
  • Config normalization rejects invalid importPath values (empty, whitespace, relative, or absolute paths) before generation runs — see Validation of importPath.

Validation of importPath

During config normalization, orval rejects the following importPath values with a clear error message before generation runs:

  • Empty string.
  • Strings that are empty after trimming whitespace (e.g. " "), or that contain leading/trailing whitespace around a valid-looking specifier.
  • Relative specifiers starting with ./ or ../ (e.g. ./models, ../models).
  • Absolute paths — POSIX (starting with /, e.g. /abs/models) or Windows (drive-letter like C:\models, or UNC like \\server\share\models).

splitByTags

When splitByTags is true, schemas are organized into per-tag subdirectories instead of a single flat directory. Schemas referenced by only one tag go in that tag's directory; schemas referenced by multiple tags (or not referenced by any operation) go in shared. If an operation has multiple tags, Orval uses its first tag, matching the existing Orval routing behavior. Works with any output.mode (single, split, tags, tags-split).

orval.config.ts
export default defineConfig({
  petstore: {
    output: {
      mode: 'tags-split',
      schemas: {
        path: './api/model',
        splitByTags: true,
      },
    },
  },
});

Result:

api/model/
├── shared/                ← schemas used by 2+ tags (or unreferenced)
│   ├── error.ts
│   └── pagination.ts
├── pets/                  ← schemas only used by "pets" operations
│   ├── pet.ts
│   ├── createPetsBody.ts
│   ├── listPetsParams.ts
│   └── index.ts
└── index.ts               ← root barrel re-exporting shared/ + tag dirs

Cross-tag imports from within a tag subdirectory resolve to the parent:

// pets/pet.ts
import type { Error } from '../shared/error';

Requirements:

  • Works with any output.mode (single, split, tags, tags-split).
  • Incompatible with schemas.mode: 'single', which writes all Zod schemas into one file instead of per-tag directories.
  • Can be combined with schemas.routes; tag directories are nested under the selected route, while shared schemas are nested under <route>/shared.
  • Incompatible with operationSchemas — operation-derived types are placed within their tag directories automatically.
  • Schema-to-tag mapping is transitive: if Pet imports Dog which imports Dachshund, all three land in the same directory.

operationSchemas

Type: String

Separate path for operation-derived types (params, bodies, responses).

orval.config.ts
export default defineConfig({
  petstore: {
    output: {
      schemas: './api/model',
      operationSchemas: './api/model/params',
    },
  },
});

fileExtension

Type: String Default: .ts

Customize file extension for generated files:

orval.config.ts
export default defineConfig({
  petstore: {
    output: {
      mode: 'split',
      target: './gen/endpoints',
      schemas: './gen/model',
      fileExtension: '.gen.ts',
    },
  },
});

Result:

src/gen/
├── endpoints
│   └── swaggerPetstore.gen.ts
└── model
    ├── listPetsParams.ts
    └── pets.ts

schemaFileExtension

Type: String Default: '.zod.ts' when generating Zod schemas (schemas: { type: 'zod' } or client: 'zod' + generateReusableSchemas), otherwise the same as fileExtension.

Override the file extension for schema artifacts only — without affecting the global fileExtension (which still drives client output, mock files, etc.). It applies to TypeScript and Zod schema files alike. Without schemas, the schemas file written next to the target keeps its .schemas marker, so target: './api.ts' with schemaFileExtension: '.types.ts' produces api.schemas.types.ts. Useful when you want client files at one extension and schema files at another:

orval.config.ts
export default defineConfig({
  petstore: {
    output: {
      mode: 'split',
      client: 'zod',
      target: './gen/endpoints',
      schemas: './gen/model',
      fileExtension: '.ts',
      // Keep client files at .ts but emit reusable Zod schemas as .zod.ts:
      schemaFileExtension: '.zod.ts',
      override: { zod: { generateReusableSchemas: true } },
    },
  },
});

namingConvention

Type: 'camelCase' | 'PascalCase' | 'snake_case' | 'kebab-case' Default: 'camelCase'

Naming convention for generated files:

orval.config.ts
export default defineConfig({
  petstore: {
    output: {
      namingConvention: 'PascalCase',
      mode: 'split',
      target: './gen/endpoints',
    },
  },
});

workspace

Type: String

Base folder for all generated files. Creates an index.ts with exports:

orval.config.ts
export default defineConfig({
  petstore: {
    output: {
      workspace: 'src/',
      target: './petstore.ts',
    },
  },
});

mode

Type: 'single' | 'split' | 'tags' | 'tags-split' | 'tags-operations' | 'tags-operations-split' Default: 'single'

single

Everything in one file.

split

Separate files for implementation, schemas, and mocks:

my-app/src/
├── petstore.schemas.ts
├── petstore.msw.ts
└── petstore.ts

tags

One file per OpenAPI tag:

my-app/src/
├── pets.ts
└── petstore.schemas.ts

tags-split

Folder per tag with split files:

my-app/src/
├── petstore.schemas.ts
└── pets/
    ├── petstore.msw.ts
    └── petstore.ts

When schemas is configured, models go to a dedicated directory with per-file output. With indexFiles enabled (the default), a barrel index.ts re-exports all schemas so service files import from a single path. With indexFiles: false, each schema is imported individually (e.g., ../models/pet). All schemas — domain types, generic wrappers, and bound aliases — live in this single shared directory and are never duplicated per tag:

my-app/src/
├── models/
│   ├── index.ts
│   ├── pet.ts
│   ├── listResponse.ts
│   ├── pagination.ts
│   └── userListResponse.ts
├── pets/
│   └── pets.ts              ← imports from ../models
└── users/
    └── users.ts             ← imports from ../models

To organize schemas into per-tag subdirectories instead of a flat directory, use splitByTags:

my-app/src/
├── models/
│   ├── pagination.ts          ← shared schemas at root
│   ├── error.ts
│   ├── pets/
│   │   ├── pet.ts
│   │   ├── listPetsParams.ts
│   │   └── index.ts
│   ├── users/
│   │   ├── user.ts
│   │   ├── listUsersParams.ts
│   │   └── index.ts
│   └── index.ts               ← root barrel
├── pets/
│   └── pets.ts
└── users/
    └── users.ts

tags-operations

Generate one implementation file per operation inside a directory for each OpenAPI tag. When indexFiles is enabled (the default), a per-tag index.ts re-exports the operation files:

my-app/src/
└── pets/
    ├── get-pet.ts
    ├── list-pets.ts
    └── index.ts

The operation file contains that operation's types and runtime implementation. With indexFiles: false, import the operation files directly. This mode is supported for the react-query, svelte-query, vue-query, swr, and fetch clients.

tags-operations-split

Generate one implementation file and one .schemas.ts file per operation, nested under the operation's tag directory. When indexFiles is enabled (the default), a per-tag index.ts re-exports both files:

my-app/src/
└── pets/
    ├── get-pet.ts
    ├── get-pet.schemas.ts
    ├── list-pets.ts
    ├── list-pets.schemas.ts
    └── index.ts

This mode uses the same supported clients as tags-operations. With indexFiles: false, import the operation and schema files directly. Component schemas referenced by an operation are emitted into that operation's schema file; schemas configured through output.schemas continue to use that output location.

baseUrl

Type: String | Object Default: ''

orval.config.ts
export default defineConfig({
  petstore: {
    output: {
      baseUrl: 'https://api.example.com',
    },
  },
});

For the angular client, prefer override.angular.baseUrl when you need the base URL resolved through Angular's dependency injection (for example, per-API gateway routing or TestBed overrides) instead of baked into every generated route string. baseUrl and override.angular.baseUrl are mutually exclusive on the same output.

runtime

Type: String

Embed a JavaScript expression into generated request URLs so the same build can call different hosts at runtime (for example with Docker images and environment variables). The value is emitted inside template literals in generated clients; only use trusted expressions from your configuration. JavaScript expression used inside generated template literals for the request base URL. Set this to the expression only (for example process.env.API_BASE_URL), not including `${...}`; Orval wraps it for you.

orval.config.ts
export default defineConfig({
  petstore: {
    output: {
      baseUrl: {
        runtime: 'process.env.API_BASE_URL',
      },
    },
  },
});

imports

Type: GeneratorImport[]

Optional. When runtime references a symbol from another module, list the imports Orval should emit into generated clients. Paths are relative to the generated file, same idea as mutator imports. The runtime expression must be valid where the generated code runs (after those imports).

Use a default import:

orval.config.ts
export default defineConfig({
  petstore: {
    output: {
      baseUrl: {
        runtime: 'apiBase',
        imports: [{ name: 'apiBase', importPath: '../config/api' }],
      },
    },
  },
});

Or a named export used as an object (for example import { env } from '../../env' and env.API_BASE_URL in application code) — set runtime to that property access and import the object under name:

orval.config.ts
export default defineConfig({
  petstore: {
    output: {
      baseUrl: {
        runtime: 'env.API_BASE_URL',
        imports: [{ name: 'env', importPath: '../../env' }],
      },
    },
  },
});

Adjust importPath so it resolves from the generated client file to your module (the example assumes the client is nested deeper than env.ts).

getBaseUrlFromSpecification

Type: Boolean

Read the base URL from the OpenAPI servers field instead of a fixed string. When true, Orval resolves it from the spec’s servers entry (optionally with variables and index below).

orval.config.ts
export default defineConfig({
  petstore: {
    output: {
      baseUrl: {
        getBaseUrlFromSpecification: true,
        variables: {
          environment: 'api.dev',
        },
      },
    },
  },
});

variables

Type: Record<string, string>

Values for variables used in server URL templates from the OpenAPI servers field.

index

Type: Number

Which servers entry to use (0-based) when multiple URLs are defined:

orval.config.ts
export default defineConfig({
  petstore: {
    output: {
      baseUrl: {
        getBaseUrlFromSpecification: true,
        index: 1, // Use second server URL
      },
    },
  },
});

mock

Type: Boolean | Object | Function Default: false

Configures one or more mock generators. The shorthand mock: true enables both MSW and Faker mock files with default options:

orval.config.ts
export default defineConfig({
  petstore: {
    output: {
      mock: true,
    },
  },
});

Each entry in mock.generators produces its own file (<filename>.msw.ts, <filename>.faker.ts, ...), unless inline: true keeps the code in the implementation file. Set mock: false (or omit it) to disable mock generation entirely.

Mocks Options

orval.config.ts
export default defineConfig({
  petstore: {
    output: {
      mock: {
        indexMockFiles: true,
        generators: [
          {
            type: 'msw',
            delay: 1000,
            useExamples: false,
            generateEachHttpStatus: false,
            baseUrl: '/api',
            locale: 'en',
          },
          {
            type: 'faker',
            useExamples: false,
          },
        ],
      },
    },
  },
});
OptionTypeDefaultDescription
indexMockFilesBooleanfalseIn split and tags-split modes, emit one root-level index.<ext>.ts file per generator entry that re-exports the mocks (e.g. index.msw.ts, index.faker.ts). In tags-split it re-exports the per-tag mocks; in split it re-exports the single mock file. Useful to keep mocks (e.g. MSW) in a dedicated barrel that production/model barrels never import.
pathStringundefinedShared output directory for all mock files. Per-generator path values override this. In single and tags mode this only changes where the de-inlined mock files land; see inline to keep mock code in the implementation file instead. Ignored on function-form generators, which always fall back to the shared path.
inlineBooleanfalseIn single and tags mode, mock code is written to separate .msw.ts / .faker.ts files next to the implementation file(s) by default, matching split and tags-split. Set to true to append mock code to the implementation file instead, matching the layout Orval used before this option existed. No effect in split or tags-split, which always write separate mock files.
generatorsArray<MockOptions | Function>[]One entry per output mock file. Each entry can be an object (MockOptions) or a custom ClientMockBuilder function.
orval.config.ts
export default defineConfig({
  petstore: {
    output: {
      mock: {
        path: './src/api/mocks',
        generators: [
          { type: 'msw', path: './src/api/mocks/msw' },
          { type: 'faker' },
        ],
      },
    },
  },
});

MSW generator (type: 'msw')

OptionTypeDefaultDescription
type'msw'requiredDiscriminator for MSW handler generation.
pathStringundefinedOutput directory for this generator's mock files. Overrides the shared mock.path when set. In single and tags mode this only changes where the de-inlined mock file lands; see mock.inline to keep mock code in the implementation file instead.
operationResponsesBooleantrueEmit get<Op>ResponseMock factories in the MSW output. Set to false to generate handlers only, response fallbacks become undefined. No effect when a Faker generator also emits the factories, the handlers then import them from the .faker file. Honored in split and tags-split modes.
delayNumber | Function | falsefalseResponse delay in ms.
delayFunctionLazyExecuteBooleanfalseExecute delay function at runtime instead of at build time.
baseUrlString''Base URL for the generated MSW handlers.
useExamplesBooleanfalseUse OpenAPI examples to seed response values.
generateEachHttpStatusBooleanfalseGenerate response factories for every documented status code.
localeString'en'Faker.js locale.
preferredContentTypeStringundefinedPreferred content type when an operation lists more than one.

Faker generator (type: 'faker')

The Faker generator emits the same get<Op>ResponseMock factories MSW would emit, but without any msw dependency or HTTP handler code. Useful for tests or stories that only need fake response data.

In split and tags-split modes, configuring Faker alongside MSW moves the get<Op>ResponseMock factories to the .faker file. The .msw file only contains the handlers and imports (and re-exports) the factories instead of duplicating them. If the Faker generator is configured with operationResponses: false it emits no factories, so there is nothing to move and the .msw file keeps them inline.

OptionTypeDefaultDescription
type'faker'requiredDiscriminator for Faker-only output.
pathStringundefinedOutput directory for this generator's mock files. Overrides the shared mock.path when set. In single and tags mode this only changes where the de-inlined mock file lands; see mock.inline to keep mock code in the implementation file instead.
schemasBooleanfalseEmit a consolidated mock factory file (get<SchemaName>Mock) for every entry under components/schemas.
schemasImportPathStringundefinedPackage specifier for importing the schema-level faker factories emitted by schemas: true (e.g. @acme/models/fakers). When set, used verbatim instead of appending /index.faker to schemas.importPath — useful when the production barrel can't expose a sub-path export. Requires schemas: true, plus schemas.importPath or schemasPath.
schemasPathStringundefinedOutput directory for the schema-level factories file (index.faker.ts) emitted by schemas: true. Defaults to the schemas directory. Set it to keep @faker-js/faker out of a schemas package, for example ./src/mocks/faker/schemas. Operation mocks import the factories through a relative path, or through schemasImportPath when set. Inside the generator's path, the faker barrel re-exports them. Requires schemas: true and output.schemas. With indexMockFiles: true it can't be the generator's own path, since the faker barrel is also called index.faker.ts.
importPathStringundefinedPackage specifier for the faker directory's barrel, for example @acme/mocks/faker. MSW files then import the get<Op>ResponseMock factories from it instead of through a relative path into the faker directory. Use it when MSW and Faker live in different packages. split and tags-split modes only. Requires mock.indexMockFiles: true, and the specifier must resolve to the generated index.faker.ts barrel.
operationResponsesBooleantrueEmit per-operation response mock factories (the historical behavior). Set to false together with schemas: true to get only the consolidated schema factories.
useExamplesBooleanfalseUse OpenAPI examples to seed response values.
generateEachHttpStatusBooleanfalseGenerate response factories for every documented status code.
localeString'en'Faker.js locale.
preferredContentTypeStringundefinedPreferred content type when an operation lists more than one.
arrayItemsBooleanfalseEmit reusable mock factories for object-like array item schemas in operation responses.

schemasImportPath

Only applies when schemas: true is set on the same faker generator (requires schemas: true, plus schemas.importPath or schemasPath). When schemas.importPath resolves to a single barrel file (e.g. via tsconfig path mappings), appending /index.faker produces an unresolvable sub-path. schemasImportPath lets you point faker factories at a separate import path so you can expose them through a dedicated barrel:

orval.config.ts
export default defineConfig({
  petstore: {
    output: {
      target: './libs/client/sdk/generated',
      schemas: {
        path: './libs/data-layer/sdk/generated',
        importPath: '@acme/data-layer/sdk',
      },
      mock: {
        path: './libs/client/sdk/mocks',
        generators: [
          {
            type: 'faker',
            schemas: true,
            schemasImportPath: '@acme/data-layer/sdk/fakers',
          },
        ],
      },
    },
  },
});
// Without schemasImportPath (default — joins importPath with /index.faker):
import { getPetMock } from '@acme/data-layer/sdk/index.faker'; // may not resolve

// With schemasImportPath: '@acme/data-layer/sdk/fakers':
import { getPetMock } from '@acme/data-layer/sdk/fakers';

Artifact groups

One config run can split its output into four groups, each in its own directory with its own barrel:

GroupDirectoryBarrelRuntime imports
Clienttargetindex.ts (indexFiles)Your HTTP client, e.g. @angular/*
Schemasschemas.pathindex.tsNone, or zod with type: 'zod'
MSWMSW generator pathindex.msw.ts (indexMockFiles)msw, plus the Faker group
FakerFaker generator pathindex.faker.ts (indexMockFiles)@faker-js/faker

Each barrel re-exports only its own group, so a Node test that imports the MSW barrel never loads the Angular client, and the app never loads msw or @faker-js/faker. Because every group comes from one run, operation names, type names and overrides stay the same across groups.

Use split or tags-split mode. In single and tags mode, mock files are written next to the implementation unless mock.path is set, and mock.inline puts them back into the implementation file.

When the groups are separate packages, give each one a package specifier so generated files never import across packages with a relative path:

  • schemas.importPath: client, MSW and Faker files import schema types from it.
  • Faker importPath: MSW files import the response factories from it.
  • Faker schemasPath: writes the schemas: true factories into the Faker group instead of the schemas directory, which keeps the schemas package free of @faker-js/faker.
  • Faker schemasImportPath: the specifier operation mocks use for those factories, when a relative path won't do.

MSW files re-export the response factories they import, so get<Op>ResponseMock is also reachable from the MSW group. When schemasPath sits inside the Faker path and indexMockFiles is on, index.faker.ts also re-exports the get<Schema>Mock factories. It skips any factory whose name an operation mock already exports, such as getListPetsResponseMock for a ListPetsResponse schema; import that one from the factories file directly.

orval.config.ts
export default defineConfig({
  petstore: {
    input: { target: './petstore.yaml' },
    output: {
      mode: 'tags-split',
      client: 'angular',
      target: './src/generated/angular', // client group
      indexFiles: true, // -> src/generated/angular/index.ts
      schemas: {
        path: './src/generated/schemas',
        importPath: '@acme/models',
      },
      mock: {
        indexMockFiles: true, // -> index.msw.ts, index.faker.ts
        generators: [
          { type: 'msw', path: './src/generated/msw' },
          {
            type: 'faker',
            path: './src/generated/faker',
            importPath: '@acme/mocks/faker',
            schemas: true,
            schemasPath: './src/generated/faker/schemas',
          },
        ],
      },
    },
  },
});
src/generated/
├── angular/  index.ts         client services, imports @angular/*
├── schemas/  index.ts         types, no runtime imports
├── msw/      index.msw.ts     handlers, imports msw and @acme/mocks/faker
└── faker/    index.faker.ts   factories, imports @faker-js/faker
    └── schemas/index.faker.ts get<Schema>Mock factories

Orval writes the groups and their barrels. Nx project files, ng-package.json and package.json exports maps are up to your project. samples/angular-app generates all four groups from one config (petstoreArtifactGroups) and has a Node test that loads the MSW barrel without loading Angular.

indexFiles

Type: Boolean Default: true

Generate index.ts files for schemas.

tagsSplitDeduplication

Type: Boolean Default: false

In tags-split mode, when tagsSplitDeduplication is enabled and workspace is not set, shared infrastructure types (e.g. HTTPStatusCode* emitted by the fetch client) can be extracted into a single common-types.ts file. A shared-types file is generated only when there are shared types to extract; indexFiles independently controls whether a root index.ts barrel is generated.

Deduplication and indexFiles control separate behaviors:

  • Shared types that would otherwise be duplicated across per-tag files are collected and written once to [commonTypesFileName].ts
  • Each per-tag file imports shared types from the common file instead of declaring them inline
  • When indexFiles: true, a barrel index.ts is generated with named re-exports for public shared types plus export * re-exports for each per-tag implementation file
orval.config.ts
export default defineConfig({
  petstore: {
    output: {
      mode: 'tags-split',
      target: './src/api/endpoints.ts',
      schemas: './src/api/model',
      client: 'fetch',
      indexFiles: true,
      tagsSplitDeduplication: true,
    },
  },
});

Resulting structure:

src/api/
├── common-types.ts           ← shared types extracted once
├── index.ts                  ← barrel with named + wildcard re-exports
├── pets/
│   └── pets.ts               ← import type { ... } from '../common-types'
└── health/
    └── health.ts             ← import type { ... } from '../common-types'

When tagsSplitDeduplication is disabled (default), shared types are inlined per tag. Shared-type extraction is only available when tagsSplitDeduplication is enabled and workspace is not set. With that condition satisfied, the shared types file can still be generated when indexFiles is false, but the root barrel is not generated.

Shared-type extraction is suppressed when workspace is set; the workspace barrel handles aggregation instead.

commonTypesFileName

Type: String Default: 'common-types'

The file name (without extension) used for the shared types file when tagsSplitDeduplication is enabled.

orval.config.ts
export default defineConfig({
  petstore: {
    output: {
      mode: 'tags-split',
      indexFiles: true,
      tagsSplitDeduplication: true,
      commonTypesFileName: 'shared',  // generates shared.ts
    },
  },
});

docs

Type: Boolean | Object Default: false

Generate API docs using TypeDoc.

TypeDoc is an optional peer dependency, so install it alongside the markdown plugin (and any other TypeDoc plugin you configure) before enabling this option:

npm install typedoc typedoc-plugin-markdown -D
orval.config.ts
export default defineConfig({
  petstore: {
    output: {
      docs: true,
      // or with config
      docs: {
        configPath: './typedoc.config.mjs',
      },
    },
  },
});

clean

Type: Boolean | String[] Default: false

Remove files left over from previous runs before regenerating. What is removed depends on whether Orval owns the directory.

target and schemas are wiped. Every file in them is removed (.d.ts files are preserved) — not only files produced by Orval, but also any other file that happens to live there. These are the directories Orval asks you to keep hand-written files out of, so it takes them as its own.

Configured mock directories are pruned. Set mock.path, or a mock generator's own path, to give mock files their own directory. You frequently keep hand-written code in that directory too — MSW's browser.ts and server.ts, fixtures, or a barrel. Orval removes only the files that it could have written there. The patterns are **/*.msw<ext> and **/*.faker<ext>. <ext> is your fileExtension, or one of the usual source extensions: .ts, .tsx, .mts, .cts, .js, .jsx, .mjs, .cjs. Orval matches those extensions as well as your own. Thus a change from .ts to .js does not strand the mock files of the earlier runs.

The same rule applies when the mock directory is inside target or schemas. The wipe does not go into a mock directory. Orval prunes that directory instead.

Orval keeps all the files that these patterns do not match. But it also removes every empty directory below each directory that it cleans. This includes your own empty directories.

Without a mock path there is no separate mock directory: mock output lands beside the implementation files and is covered by the target rule above.

Two limits apply to the prune patterns. A hand-written file with a name such as handlers.msw.ts or fixtures.faker.ts is removed, because Orval cannot tell it from its own output. And a compound fileExtension such as .gen.ts is matched only while you keep it configured. After you change it, remove the files of the earlier extension by hand.

When set to a String[], the array entries are extra glob patterns appended to the deletion list for the target and schemas directories. They are not applied to mock directories — a positive glob there could delete hand-written files Orval never produced. Use negated globs (prefixed with !) to preserve specific files from removal.

orval.config.ts
export default defineConfig({
  petstore: {
    output: {
      // preserve `important.ts` when wiping `target` / `schemas`
      clean: ['!**/important.ts'],
    },
  },
});

For example, to keep a committed swagger.json next to the generated output:

orval.config.ts
export default defineConfig({
  petstore: {
    output: {
      target: './src/generated',
      clean: ['!**/swagger.json'],
    },
  },
});

clean removes the entire contents of target and schemas, not just generated files. Do not point either directly at a package or library entrypoint root that holds files you need to keep (package.json, ng-package.json, public-api.ts, etc.). Place generated output in a dedicated subdirectory such as ./generated/ so those files are never touched.

Keep hand-written files (mutators, transformers, app code) outside the target and schemas directories for the same reason. You can share a configured mock directory with hand-written code, because Orval prunes that directory and does not wipe it. This stays true when the mock directory is inside target or schemas. But keep to the two limits above: do not give a hand-written file a mock file name, and do not rely on Orval to keep your empty directories.

A directory configured by more than one project is cleaned by each of them before that project writes, so the last project to run wins and the earlier project's output is gone. This applies to target, schemas, and mock directories alike. Give each project its own output directories.

formatter

Type: 'prettier' | 'biome' | 'oxfmt' | undefined Default: undefined

Format generated files with the specified formatter. Only one formatter can be used at a time.

orval.config.ts
export default defineConfig({
  petstore: {
    output: {
      formatter: 'prettier',
    },
  },
});

headers

Type: Boolean Default: false

Generate typed parameters for the HTTP request headers an operation declares in the specification. When disabled, header parameters are omitted from the generated function signatures.

This is unrelated to override.header, which controls the comment block written at the top of each generated file.

orval.config.ts
export default defineConfig({
  petstore: {
    output: {
      headers: true,
    },
  },
});

tsconfig

Type: String | Object

Custom TypeScript configuration path or inline config. When omitted, Orval looks for the nearest tsconfig.json (or jsconfig.json).

orval.config.ts
export default defineConfig({
  petstore: {
    output: {
      tsconfig: './tsconfig.json',
      // or inline
      tsconfig: {
        compilerOptions: {
          moduleResolution: 'NodeNext',
        },
      },
    },
  },
});

Orval reads a small number of compilerOptions to shape the emitted imports — module and moduleResolution (whether generated relative imports carry a .js extension), allowImportingTsExtensions, allowSyntheticDefaultImports and esModuleInterop (default vs namespace import form), baseUrl, and exactOptionalPropertyTypes.

target is not one of them, and it does not need to be set for Orval's sake. In particular, a mutator is bundled internally only so its exported function can be inspected for how many parameters it takes; that bundle is discarded and never reaches your generated client, so it is parsed as modern JavaScript regardless of the target your project compiles to. A mutator may therefore use import.meta, top-level await, or any other syntax your own toolchain supports.

packageJson

Type: String

Path to your package.json (usually auto-detected).


override

transformer

Type: String | Function

Transform the generated output:

orval.config.ts
export default defineConfig({
  petstore: {
    output: {
      override: {
        transformer: 'src/yourfunction.js',
      },
    },
  },
});

mutator

Type: String | Object

Custom HTTP client implementation:

orval.config.ts
export default defineConfig({
  petstore: {
    output: {
      override: {
        mutator: {
          path: './api/mutator/custom-instance.ts',
          name: 'customInstance',
        },
      },
    },
  },
});

Example implementation:

custom-instance.ts
import Axios, { AxiosRequestConfig } from 'axios';

export const AXIOS_INSTANCE = Axios.create({ baseURL: '' });

export const customInstance = <T>(config: AxiosRequestConfig): Promise<T> => {
  return AXIOS_INSTANCE({ ...config }).then(({ data }) => data);
};

export type ErrorType<Error> = AxiosError<Error>;
export type BodyType<BodyData> = BodyData;

inferred

Type: Boolean

Default: false

When true, the generated fetch functions omit the async keyword and the : Promise<T> return type annotation. The return type is inferred from the custom mutator, allowing it to return sync values, Effect-style promises, or any other wrapper type.

orval.config.ts
export default defineConfig({
  petstore: {
    output: {
      client: 'fetch',
      override: {
        mutator: {
          path: './api/mutator/custom-instance.ts',
          name: 'customInstance',
          inferred: true,
        },
      },
    },
  },
});

title

Type: String | Function

Customize the API service title (only for axios and angular clients):

orval.config.ts
export default defineConfig({
  petstore: {
    output: {
      override: {
        title: (title) => `${title}Api`,
      },
    },
  },
});

namingConvention (property keys)

Type: Object

Change naming convention for property keys (not files):

orval.config.ts
export default defineConfig({
  petstore: {
    output: {
      override: {
        namingConvention: {
          enum: 'PascalCase', // camelCase, PascalCase, snake_case, kebab-case
          properties: 'camelCase', // schema property names
        },
      },
    },
  },
});

properties renames the keys of schema objects, e.g. widget_id becomes widgetId with camelCase. Keys that would collide after conversion (such as first_name next to firstName) keep their original name.

  • TypeScript models use the converted names. required is still matched against the original spec key, and form-data serialization writes the original key on the wire.
  • Zod schemas (the zod client and schemas: { type: 'zod' }) still validate the original spec keys, then a .transform() moves each value to its converted name. zod.input (the exported X type) is the wire shape and zod.output (XOutput) has the converted names. With fetch runtimeValidation, responses therefore come back with converted keys while request bodies are sent with the spec's keys.

A renamed Zod object is no longer a ZodObject: .shape, .extend() and friends are not available on it, and it cannot be converted to JSON Schema. Zod v3 only accepts ZodObject options in discriminatedUnion, so there a union whose branches are renamed is emitted as a plain zod.union. A wire-shaped object default is emitted as .prefault() on Zod v4.

Type: Boolean | Function Default: the built-in header function

Customize or disable the comment block written at the top of each generated file. This is unrelated to output.headers, which controls HTTP request header parameters.

Pass false to omit the comment block, or a function to replace it. Passing true produces the same output as omitting the option, since any value that is neither false nor a function falls back to the built-in header.

The callback receives the OpenAPI Info Object as info. title and version are required by the OpenAPI specification. summary, description, termsOfService, contact, and license are optional, so check them before using them. Specification extensions such as x-* may also be present. Return either one string, which is used verbatim, or an array of strings, which is rendered one line per string.

orval.config.ts
export default defineConfig({
  petstore: {
    output: {
      override: {
        header: (info) => [
          `Generated by Orval`,
          `Do not edit manually.`,
          ...(info.title ? [info.title] : []),
        ],
      },
    },
  },
});

override.query

TanStack Query options:

orval.config.ts
export default defineConfig({
  petstore: {
    output: {
      override: {
        query: {
          useQuery: true,
          useSuspenseQuery: true,
          useMutation: true,
          useInfinite: true,
          useSuspenseInfiniteQuery: true,
          useInfiniteQueryParam: 'nextId',
          usePrefetch: true,
          useInvalidate: true,
          useSetQueryData: true,
          useGetQueryData: true,
          signal: true,
          runtimeValidation: true,
          options: {
            staleTime: 10000,
          },
        },
      },
    },
  },
});

useQuery

Type: Boolean

Default: true for GET operations; false otherwise.

Generate useQuery hooks. When set explicitly, applies to all operations regardless of HTTP verb — setting useQuery: true routes POST, PUT, PATCH, and DELETE operations to useQuery hooks as well. This is useful for APIs that use POST for read-style endpoints (e.g. complex search bodies, GraphQL-style single-endpoint APIs).

Cache keys for non-GET operations are automatically namespaced by HTTP verb to avoid collisions with GET operations on the same path (e.g. ['POST', '/pets', body]).

Set to false to suppress useQuery hook generation; pair with useMutation: true (default for non-GET) if you want the request to be wired up as a Mutation instead.

useSuspenseQuery

Type: Boolean

Default: unset — opt-in.

Generate useSuspenseQuery hooks. When set globally, this only applies to GET operations; per-operation overrides (override.operations.<id>.query.useSuspenseQuery) bypass that restriction for individual operations.

useMutation

Type: Boolean

Default: true for non-GET operations; false otherwise.

Generate useMutation hooks. When set explicitly, applies to all operations regardless of HTTP verb — setting useMutation: true (globally or via override.operations.<id>.query.useMutation) routes a GET operation to a useMutation hook as well. This is useful for GET endpoints that you want to trigger imperatively rather than on render.

Set to false to suppress Mutation hook generation; pair with useQuery: true if you want non-GET operations to be generated as Query hooks instead. When both useQuery and useMutation resolve to true for the same operation, the Mutation hook wins for GET and the Query hook wins for non-GET.

useInfinite

Type: Boolean

Default: unset — opt-in.

Generate useInfiniteQuery hooks. When set globally, this only applies to GET operations; per-operation overrides (override.operations.<id>.query.useInfinite) bypass that restriction for individual operations.

useSuspenseInfiniteQuery

Type: Boolean

Default: unset — opt-in.

Generate useSuspenseInfiniteQuery hooks. When set globally, this only applies to GET operations; per-operation overrides (override.operations.<id>.query.useSuspenseInfiniteQuery) bypass that restriction for individual operations.

useInfiniteQueryParam

Type: String | String[]

Query parameter name for infinite queries. An operation only gets an infinite hook when it declares the configured parameter. To page through a property of the request body instead, set useInfiniteQueryParamLocation to 'body'.

Pass an array when a single spec paginates in more than one way. The names are candidates in priority order and are resolved per operation: the first one the operation actually declares becomes its page parameter, and an operation matching none of them gets no infinite hook.

An override.operations entry replaces the global value entirely, so a single operation can opt into a different candidate list.

orval.config.ts
export default defineConfig({
  petstore: {
    output: {
      override: {
        query: {
          useInfinite: true,
          useInfiniteQueryParam: ['page', 'cursor'],
        },
        operations: { 
          listBets: {
            useInfinite: true,
            useInfiniteQueryParam: "cursor.marker",
          }
        }
      },
    },
  },
});

useInfiniteQueryParamLocation

Type: 'query' | 'body'

Default: 'query'

Where the useInfiniteQueryParam page param lives. With 'query' it must be one of the operation's URL query parameters. With 'body' it is a property of the JSON request body instead, which suits POST endpoints that paginate through a filter body. The infinite query then spreads pageParam into the body, and TPageParam is typed from the body schema (ElementFilter['offset']).

The candidates are matched against the body schema's properties. An operation without a JSON body, or whose body declares none of the candidates, gets no infinite hook. If the operation opted in explicitly, orval also logs a warning. When the body schema doesn't list its properties directly (an allOf, for example), the first candidate is used as configured.

orval.config.ts
export default defineConfig({
  petstore: {
    output: {
      override: {
        operations: {
          searchPageableElementsByFilter: {
            query: {
              useQuery: true,
              useInfinite: true,
              useInfiniteQueryParam: 'offset',
              useInfiniteQueryParamLocation: 'body',
            },
          },
        },
      },
    },
  },
});

The generated queryFn then passes the page param through the body:

const queryFn = ({ signal, pageParam }) =>
  searchPageableElementsByFilter(
    { ...elementFilter, offset: pageParam ?? elementFilter?.['offset'] },
    { signal },
  );

usePrefetch

Type: Boolean

Generate prefetch functions for SSR.

useInvalidate

Type: Boolean

Generate query invalidation helpers.

useSetQueryData

Type: Boolean

Generate type-safe helpers that update cached query data via setQueriesData.

The helpers take a trailing $exactMatch argument that defaults to true, so only the cache entry whose key matches exactly is updated. Query params and body arguments are widened to accept undefined: passing undefined together with $exactMatch: false updates every cached entry that shares the same path.

useGetQueryData

Type: Boolean

Generate type-safe getQueryData helpers.

useSkipToken

Type: Boolean Default: false

Hold a query whose params are not resolved yet with skipToken instead of the generated enabled guard.

Unlike enabled, this also covers refetch(): no request is sent with an unresolved param, and the query rejects with Missing queryFn instead (TanStack also logs a console error in development). It leaves enabled free for the caller too — the caller's ...queryOptions is spread last, so their own enabled would otherwise replace the generated param check.

React Query v5 only. Suspense queries are unaffected — TanStack excludes SkipToken from their queryFn.

mutationInvalidates

Type: Array

Automatically invalidate or reset queries on mutation success (Angular Query, React Query, Svelte Query & Vue Query v5):

orval.config.ts
export default defineConfig({
  petstore: {
    output: {
      override: {
        query: {
          useInvalidate: true,
          mutationInvalidates: [
            {
              onMutations: ['createPets'],
              invalidates: ['listPets'],
            },
            {
              onMutations: ['deletePet', 'updatePet'],
              invalidates: [
                'listPets',
                { query: 'showPetById', params: ['petId'], invalidationMode: 'reset' },
                { query: 'adminPets', file: './admin' },
              ],
            },
          ],
        },
      },
    },
  },
});

Each entry in params is either a variable reference (string) or a literal value ({ literal: string }):

SyntaxGenerated code
params: ['petId']getShowPetByIdQueryKey(variables.petId)
params: [{ literal: '@me' }]getShowPetByIdQueryKey('@me')

Use { literal: "..." } for fixed values like "@me" that are not taken from mutation variables:

orval.config.ts
mutationInvalidates: [
  {
    onMutations: ['updateProfile'],
    invalidates: [
      { query: 'getProfile', params: [{ literal: '@me' }] },
    ],
  },
],

When a user provides their own onSuccess callback, both the auto-invalidation and the user callback run — the generated onSuccess composes them together. To opt out of auto-invalidation at runtime, pass skipInvalidation: true:

// Default: invalidation + user callback both run
const deletePet = injectDeletePet({
  mutation: { onSuccess: () => showToast('Deleted!') },
});

// Skip auto-invalidation and handle it manually
const deletePet = injectDeletePet({
  mutation: {
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: getListPetsQueryKey() });
    },
  },
  skipInvalidation: true,
});

Operation names

Both fields name operations by their generated name — the camelCased, sanitized form of the operationId, or whatever override.operationName returns. An operationId of PostNotes is generated as postNotes:

orval.config.ts
mutationInvalidates: [
  {
    onMutations: ['postNotes'], // not 'PostNotes'
    invalidates: ['getNotes'],
  },
],

invalidates resolves its target through the query key function name, so the document's own casing happens to work there too. onMutations is matched exactly, so it does not.

orval warns when either field names an operation the output does not generate, since the rule is otherwise ignored without a trace:

⚠️  mutationInvalidates references an unknown operation 'PostNotes' in
onMutations. No operation with that name is generated for this output, so the
rule is ignored — did you mean 'postNotes'?

An operation excluded by an input filter or by useDeprecatedOperations: false is not generated either, so naming one warns for the same reason.

signal

Type: Boolean

Include abort signal in queries.

queryKey / queryOptions / mutationOptions

Type: String | Object

Custom query/mutation key or options functions.

When a queryOptions or mutationOptions mutator declares a third parameter, orval passes operation identity so the mutator can branch on it (for example, to attach per-operation metadata or invalidate by operationId). The exact shape differs between the two:

  • queryOptions mutator — { url, operationId, operationName }
  • mutationOptions mutator — { operationId, operationName } (the url is supplied in the second parameter)

Each option takes a mutator, so point it at a file and an exported name:

orval.config.ts
export default defineConfig({
  petstore: {
    output: {
      client: 'react-query',
      override: {
        query: {
          queryKey: {
            path: './src/mutators/custom-query-key.ts',
            name: 'customQueryKey',
          },
          queryOptions: {
            path: './src/mutators/custom-query-options.ts',
            name: 'customQueryOptions',
          },
          mutationOptions: {
            path: './src/mutators/custom-mutation.ts',
            name: 'useCustomMutation',
          },
        },
      },
    },
  },
});

queryKey replaces the generated key factory. It receives the operation's query properties and a context carrying the url:

src/mutators/custom-query-key.ts
export function customQueryKey(
  // The properties dictionary varies per operation (params, petId, ...), so
  // type it broadly if one mutator serves every endpoint.
  properties: Record<string, unknown>,
  context: { url: string },
) {
  return ['tenant-abc', context.url, properties] as const;
}

queryOptions wraps the options object passed to the generated hook. The third parameter is the operation identity described above:

src/mutators/custom-query-options.ts
import type { QueryKey } from '@tanstack/react-query';

export function customQueryOptions<T extends { queryKey: QueryKey }>(
  options: T,
  _queryProperties: Record<string, unknown>,
  operation: { url: string; operationId: string; operationName: string },
): T & { queryKey: QueryKey } {
  return {
    ...options,
    queryKey: ['operation', operation.operationId, ...options.queryKey],
  };
}

mutationOptions works the same way for mutations, which is where branching on operationId is most useful.

The mutator runs where the options object is built, inside the hook body, so side effects belong in a callback rather than in the mutator itself. Calling invalidateQueries() directly would fire on every render instead of after the mutation succeeds:

src/mutators/custom-mutation.ts
import { type UseMutationOptions, useQueryClient } from '@tanstack/react-query';

export const useCustomMutation = <TData, TError, TVariables, TContext>(
  options: UseMutationOptions<TData, TError, TVariables, TContext>,
  _: { url: string },
  operation: { operationId: string; operationName: string },
) => {
  const queryClient = useQueryClient();
  if (operation.operationId !== 'deletePetById') return options;

  return {
    ...options,
    onSuccess: (...args: Parameters<NonNullable<typeof options.onSuccess>>) => {
      queryClient.invalidateQueries({ queryKey: ['/pets'] });
      // Keep whatever the caller already passed.
      return options.onSuccess?.(...args);
    },
  };
};

Because the mutator receives operationId, one file can serve every operation and branch where the behaviour needs to differ, which is the usual alternative to a per-operation configuration option.

Controlling the use / get prefix on options factories

Set useHooks: false on a queryOptions or mutationOptions mutator to generate a get<Operation>QueryOptions or get<Operation>MutationOptions factory instead of the default use-prefixed factory. This is useful when the mutator does not call hooks and the factory is consumed outside a React component, for example in a router loader or with prefetchQuery / ensureQueryData.

src/mutators/custom-query-options.ts
export function customQueryOptions<T extends { queryKey: QueryKey }>(
  options: T,
): T {
  return options;
}
orval.config.ts
export default defineConfig({
  petstore: {
    output: {
      client: 'react-query',
      override: {
        query: {
          queryOptions: {
            path: './src/mutators/custom-query-options.ts',
            name: 'customQueryOptions',
            useHooks: false,
          },
          mutationOptions: {
            path: './src/mutators/custom-mutation-options.ts',
            name: 'customMutationOptions',
            useHooks: false,
          },
        },
      },
    },
  },
});

With this configuration, Orval generates getPetstoreQueryOptions(...) and getPetstoreMutationOptions(...) instead of their use-prefixed variants. The query options factory can then be used safely with prefetchQuery / ensureQueryData:

export async function loader(queryClient: QueryClient) {
  return queryClient.ensureQueryData(getPetstoreQueryOptions());
}

The use/get prefix only affects the name of the exported options factory. The hook itself is still exported as use<Operation> and must be called from a React component.

shouldExportMutatorHooks

Type: Boolean Default: true

Export mutator hooks.

shouldExportKeys

Type: Boolean Default: true

Export the generated cache key getters. This covers query keys and mutation keys, the latter emitted next to the mutation options factory:

endpoints.ts
export const getShowPetByIdQueryKey = (petId: string) => {
  return ['pets', petId] as const;
};

export const getCreatePetsMutationKey = () => ['createPets'] as const;

shouldFilterQueryKey

Type: Boolean Default: false

Add .filter(q => q !== undefined) to the query key. If false, as const is added instead. The filter can be adjusted with the queryKeyFilter option

When shouldFilterQueryKey is true:

endpoints.ts
export const getShowPetByIdQueryKey = (petId: string) => {
  return ['pets', petId].filter(q => q !== undefined);
};

When shouldFilterQueryKey is false:

endpoints.ts
export const getShowPetByIdQueryKey = (petId: string) => {
  return ['pets', petId] as const;
};

queryKeyFilter

Type: String Default: 'q => q !== undefined'

Adjusts how the queryKey is filtered, when shouldFilterQueryKey is true. Default is 'q => q !== undefined', which will result in it ending up beeing

endpoints.ts
.filter(q => q !== undefined)

One option could be to only make it filter out all falsy keys:

orval.config.ts
  shouldFilterQueryKey: true,
  queryKeyFilter: 'Boolean'

which would result in the generated code being

endpoints.ts
.filter(Boolean)

shouldSplitQueryKey

Type: Boolean Default: false

Generate query keys as arrays instead of strings.

useOperationIdAsQueryKey

Type: Boolean Default: false

Use operation ID instead of route path for query keys.

version

Type: Number Default: Detected from package.json

Force a specific version for generated hooks.

runtimeValidation

Type: boolean | { strategy: 'throw' | 'both' } Default: false

Enable Zod runtime validation for angular-query responses. Requires schemas: { type: 'zod' }. When enabled, responses are validated in the RxJS pipeline according to the configured strategy (Schema.parse() for the default throw). Skipped for primitive types and custom mutators.

This option only drives the Angular HttpClient pipeline that angular-query generates. react-query, vue-query, svelte-query and solid-query issue their requests through the fetch client, so they enable validation through override.fetch.runtimeValidation with httpClient: 'fetch' instead — as does swr.

A validated response is typed with the schema's zod.output alias (e.g. PetsOutput) instead of the input-typed schema name, since that is what the parse returns at runtime — relevant when the schema transforms values through coerce, useDates, defaults or .transform(). Operations using a custom mutator keep the schema (input) type: the mutator issues the request itself, so the generated parse never runs there.

The boolean form is preserved for backward compatibility: true ≡ { strategy: 'throw' }. With { strategy: 'both' } an invalid response is first logged through console.error('[orval] <operation> response validation failed', error) with the raw ZodError, then re-thrown — so the failure still surfaces through the client's native error channel while giving production visibility into contract drift.


override.swr

SWR options:

orval.config.ts
export default defineConfig({
  petstore: {
    output: {
      override: {
        swr: {
          useInfinite: true,
          useSuspense: true,
          generateErrorTypes: false,
          swrOptions: {
            dedupingInterval: 10000,
          },
          swrMutationOptions: {
            revalidate: true,
          },
          swrInfiniteOptions: {
            initialSize: 10,
          },
        },
      },
    },
  },
});

useInfinite

Type: Boolean

Generate useSWRInfinite hooks.

useSWRMutationForGet

Type: Boolean

Generate useSWRMutation for GET requests.

useSuspense

Type: Boolean Default: false

Generate Suspense-compatible hooks.

generateErrorTypes

Type: Boolean Default: false

Generate custom error type aliases.

swrOptions / swrMutationOptions / swrInfiniteOptions

Type: Object

Override SWR hook options.


override.zod

Zod schema generation options:

For inline array responses, item schemas using allOf, oneOf, or anyOf are also emitted as operation item schemas. Array items that are component $refs continue to use the component schema writer. See the Zod guide for an example.

orval.config.ts
export default defineConfig({
  petstore: {
    output: {
      override: {
        zod: {
          variant: 'mini',
          version: 4,
          strict: {
            response: true,
            query: true,
            param: true,
            header: true,
            body: true,
          },
          coerce: {
            query: ['string', 'number', 'boolean'],
          },
          generate: {
            param: true,
            body: true,
            response: true,
            query: true,
            header: true,
          },
          generateEachHttpStatus: true,
          useBrandedTypes: true,
          generateReusableSchemas: true,
          generateDiscriminatedUnion: true,
        },
      },
    },
  },
});

variant

Type: 'classic' | 'mini' — defaults to 'classic'

Select the generated Zod API style.

ValueOutput
'mini'Import from zod/mini and emit Zod Mini's functional/check-based API.
'classic'Import from zod and emit the regular chainable Zod API.

'classic' is the default to avoid changing existing projects. Prefer 'mini' when startup time, memory usage, or bundle size matter.

Zod Mini requires Zod 4 output. If variant: 'mini' is used with version: 3, or with version: 'auto' resolving to Zod 3, Orval throws instead of generating invalid output.

version

Type: 3 | 4 | 'auto' — defaults to 'auto'

Pin the Zod major version that generated output targets, so generation is deterministic instead of inferred from the installed zod package.

ValueOutput
4Always emit Zod 4 syntax (z.strictObject, z.iso.datetime(), .meta(), …).
3Always emit Zod 3-compatible syntax (.strict(), z.string().datetime(), …).
'auto'Infer from the resolved zod version; fall back to Zod 4 when none is detected.

Unlike most override.zod options, version is output-wide and cannot be set per operation or tag. See the Zod guide for details.

strict

Type: Object

Enable strict mode for schemas.

coerce

Type: Object

Enable coercion for specified types.

generate

Type: Object

Control which schemas are generated.

preprocess

Type: Object

Add preprocess functions to schemas.

params

Type: Mutator

Inject a Zod params argument (e.g. { error: ... }) into every generated validator. The referenced function is called once per validator at schema construction time and receives codegen-time context (operation, location, schema name, field path, validator name). Whatever it returns is passed as the trailing argument of the call.

Useful for i18n error keys, branded error messages, or any field-aware customisation that Zod's global error map cannot disambiguate on its own (because issue.path does not carry operation/schema identity).

orval.config.ts
export default defineConfig({
  petstore: {
    output: {
      override: {
        zod: {
          params: { path: './zod-params.ts', name: 'zodParams' },
        },
      },
    },
  },
});
zod-params.ts
import type { ZodParamsContext } from 'orval';
import { i18n } from './i18n';

export const zodParams = (ctx: ZodParamsContext) => ({
  error: (issue: { input: unknown; path: PropertyKey[] }) =>
    i18n.t(
      `errors.${ctx.schemaName}.${ctx.fieldPath.join('.')}.${ctx.validator}`,
      { value: issue.input },
    ),
});

The 'schema' location is used for shared component schemas emitted under generateReusableSchemas. Component schemas have no single owning operation, so operationId is the empty string in that case — branch on ctx.location === 'schema' if your error keys need to fall back to a schema-only namespace.

Generated output (excerpt):

import { zodParams } from './zod-params';

export const CreateUserBody = zod.object({
  email: zod
    .string(zodParams({ operationId: 'createUser', location: 'body', schemaName: 'CreateUserBody', fieldPath: ['email'], validator: 'string' }))
    .email(zodParams({ operationId: 'createUser', location: 'body', schemaName: 'CreateUserBody', fieldPath: ['email'], validator: 'email' })),
});

Injection scope:

  • Applied to base types (string, number, boolean, bigint, date, integer), constraints (min, max, gt, lt, multipleOf, regex, length), formats (email, url, uuid, hostname, datetime, time), and literal, enum, instanceof, stringFormat.
  • Skipped on modifiers (optional, nullable, nullish, default, describe) and structural calls (object, array, tuple, union, rest, passthrough, strict).
  • fieldPath only includes object property names, mirroring Zod's own issue.path. Array indices and tuple positions are not appended — the inner element of { tags: array<string> } and a top-level tags: string both see fieldPath: ['tags']. Use the validator field to distinguish a container ('array', 'tuple') from its element ('string', 'number').

For static messages, return an object with a string error: return { error: 'My message' }. The function may return undefined to fall back to Zod defaults for a specific call.

The { error } shape is Zod v4-only — on v3 it is silently ignored and the default message is used. If your project supports both Zod v3 and v4, return { message: 'My message' } instead, which works on both.

dateTimeOptions / timeOptions

Type: Object

Default (dateTimeOptions): { offset: true }

Configure Zod datetime/time validation options. dateTimeOptions defaults to { offset: true } so generated schemas accept RFC3339 timestamps with timezone offsets (e.g. 2026-03-27T12:00:00+01:00) — matching the OpenAPI format: date-time contract. Pass an explicit object to override (e.g. { offset: false } or { offset: true, precision: 3 }).

useBrandedTypes

Type: boolean

Default: false

Append .brand() to generated Zod schemas using the schema name as the brand identifier. For array request/response bodies, only the top-level array wrapper schema is branded — the exported *Item helper schema is not branded.

generateReusableSchemas

Type: boolean

Default: false

Emit one reusable Zod schema per OpenAPI #/components/schemas/* $ref instead of inlining. The exported name is the last $ref segment with namingConvention applied. Other schemas and operation files reference the export by name (cycles are wrapped in zod.lazy(() => Name) only on the edges that close them).

Behavior:

  • When schemas: is configured (string or { type: 'zod' }) and client: 'zod' is set, the schemas directory holds reusable Zod schemas instead of TypeScript types. The schema files default to a .zod.ts extension; use schemaFileExtension to override it independently from the global fileExtension if you keep both TS types and reusable Zod schemas in the same directory.
  • Operation files import the named exports — pure-$ref body/response wrappers (e.g. PetCreateBody) are skipped so consumers import the component schema directly.
  • $ref siblings: nullable, default, description chain onto the named ref (e.g. Pet.nullable().describe(...)). properties, example, and other non-chainable siblings fall back to inlining at that one site.
  • The namingConvention must produce valid JavaScript identifiers (camelCase, PascalCase, or snake_case). kebab-case is rejected with a clear error because it would emit dashed exports.
  • Trade-off — readOnly on shared component schemas: without this flag, request bodies generated from $ref schemas strip readOnly: true properties so they don't appear in input validators. With this flag on, request and response endpoints share the same exported schema, so readOnly properties remain in body validators too. Either avoid readOnly on component schemas you share between requests and responses, or split into separate request/response schemas in the OpenAPI source.

generateMeta

Type: boolean

Default: false

Attach registry metadata to generated component schemas via .meta() (zod v4 only): id is the schema name, plus description and deprecated when the OpenAPI schema provides them.

// override: { zod: { generateMeta: true } }
export const Pet = zod
  .object({ name: zod.string() })
  .meta({ id: 'Pet', description: 'A pet in the store', deprecated: true });

Behavior:

  • Applies only to component schemas emitted as named exports (schemas: { type: 'zod' }, or client: 'zod' + generateReusableSchemas). Operation wrapper schemas are left untouched, so registry ids stay unique.
  • id is always emitted; description/deprecated only when present. Property-level descriptions still use .describe().
  • zod v3 has no .meta() — the option is a no-op there, and descriptions continue to emit via .describe().
  • The registry id makes z.toJSONSchema() reference the schema as #/$defs/<id>, round-tripping the component structure.

generateDiscriminatedUnion

Type: boolean

Default: false

Emit a oneOf/anyOf that carries an OpenAPI discriminator as zod.discriminatedUnion(key, [...]) instead of a plain zod.union([...]). A discriminated union picks the branch by its discriminator value first, so validation errors point at the offending field (type.name) instead of collapsing into a single "no union member matched" at the union root.

// override: { zod: { generateDiscriminatedUnion: true } }
export const Pet = zod.discriminatedUnion('petType', [
  zod.object({ petType: zod.literal('cat'), meows: zod.boolean() }),
  zod.object({ petType: zod.literal('dog'), barks: zod.boolean() }),
]);

Behavior:

  • Opt-in. Left false, unions are emitted exactly as before, so existing output is unchanged.
  • Safe fallback. A discriminated union is emitted only when every branch can be represented as an object carrying a literal (const/enum) discriminator. If any branch is a non-object, a nested union, or lacks a literal discriminator, generation falls back to a plain zod.union([...]) rather than emitting code that throws at construction.
  • Inheritance (allOf). Branches composed with allOf are flattened into a single object so they remain valid discriminated-union options — this is the case that previously forced the feature to be reverted (#2085). With generateReusableSchemas, a branch that references an allOf schema stays a plain union (the referenced schema can't be guaranteed to be an object from the reference alone).
  • Works with both Zod v3 (>= 3.20) and v4, and with the mini variant.

exactOptional

Type: boolean

Default: false

Emit optional object properties with .exactOptional() (classic) / zod.exactOptional() (mini) instead of .optional(), so consumers compiling with exactOptionalPropertyTypes infer { x?: T } rather than { x?: T | undefined }.

// override: { zod: { exactOptional: true } }
export const Pet = zod.object({ name: zod.string().exactOptional() });

Behavior:

  • Opt-in. Left false, optional properties emit .optional() as before, so existing output is unchanged.
  • zod v4 only. zod v3 has no .exactOptional(), so the option is a no-op there and .optional() is emitted.
  • Applies to optional properties in both the classic and mini variants.

generateCompanionTypes

Type: boolean

Default: false

Follow every generated per-operation export const with a zod.input/zod.output type alias pair, so consumers can import a TypeScript type instead of writing one by hand.

// override: { zod: { generateCompanionTypes: true } }
export const CreatePetsBody = zod.object({ name: zod.string() });

export type CreatePetsBody = zod.input<typeof CreatePetsBody>;
export type CreatePetsBodyOutput = zod.output<typeof CreatePetsBody>;

Behavior:

  • Opt-in. Left false, output is unchanged.
  • Applies to per-operation schemas — Params, QueryParams, Header, Body, and Response — including the Hono *.zod.ts output, which goes through the same generator. Component schemas emitted under generateReusableSchemas already carry this pair unconditionally and are unaffected by this flag.
  • For an array request/response body, both the *Item schema and the array wrapper get their own pair.
  • Uses zod.input, not z.infer (an alias of zod.output): request-side schemas commonly carry .default(), coerce, or .transform(), where the pre-parse (input) and post-parse (output) shapes differ.
  • With useBrandedTypes, the clean zod.input alias stays unbranded — .brand() only affects the output type — so the brand shows up on the ...Output alias.
  • Helper value consts (...Max, ...Min, ...RegExp, ...Default) are plain values, not schemas, and never get a companion pair.
  • Supported as a per-operation or per-tag override alongside strict, generate, coerce, preprocess, params, and useBrandedTypes (see Scoping options per operation or tag).

override.effect

Effect schema generation options:

orval.config.ts
export default defineConfig({
  petstore: {
    output: {
      override: {
        effect: {
          strict: {
            response: true,
            query: true,
            param: true,
            header: true,
            body: true,
          },
          generate: {
            param: true,
            body: true,
            response: true,
            query: true,
            header: true,
          },
          generateEachHttpStatus: true,
          useBrandedTypes: true,
        },
      },
    },
  },
});

strict

Type: Object

Enable strict mode for schemas.

generate

Type: Object

Control which schemas are generated.

useBrandedTypes

Type: boolean

Default: false

Append S.brand() to generated Effect schemas using the schema name as the brand identifier. For array request/response bodies, only the top-level array wrapper schema is branded.

exactOptional

Type: boolean

Default: false

Emit optional Struct properties with S.optionalWith(schema, { exact: true }) instead of S.optional(schema), so consumers compiling with exactOptionalPropertyTypes infer { x?: T } rather than { x?: T | undefined }.


override.angular

Angular client options:

orval.config.ts
export default defineConfig({
  petstore: {
    output: {
      override: {
        angular: {
          provideIn: 'root', // 'root' | 'any' | '' | false
          retrievalClient: 'httpClient',
          runtimeValidation: true,
          httpResource: {
            debugName: 'getPetByIdResource',
          },
        },
      },
    },
  },
});

override.angular is reserved for Angular generator settings such as retrieval mode, DI scope, and runtime validation. Angular-only request pipeline overrides like override.paramsFilter still live on override so they can also be applied consistently via override.operations[...] and override.tags[...], alongside mutator and paramsSerializer.

provideIn

Type: 'root' | 'any' | boolean Default: 'root'

Controls the Angular @Injectable({ providedIn }) scope for generated service classes.

provideIn affects generated service classes only. httpResource functions are plain exports, not injectables.

retrievalClient

Type: 'httpClient' | 'httpResource' | 'both' Default: 'httpClient'

Controls how retrieval-style Angular operations are generated.

  • httpClient: keep retrievals as injectable services backed by Angular HttpClient
  • httpResource: generate signal-first retrieval functions using Angular httpResource
  • both: keep HttpClient service methods and emit retrieval resources in a sibling *.resource.ts file

Mutation-style operations still use generated HttpClient service methods by default unless a per-operation override changes the classification.

client

Type: 'httpClient' | 'httpResource' | 'both'

Backward-compatible alias for retrievalClient. Prefer override.angular.retrievalClient in new configs to make the retrieval-only scope clearer.

runtimeValidation

Type: boolean | { strategy?: 'throw' | 'both'; requestBodies?: boolean } Default: false

Enable Zod runtime validation for Angular output. Requires schemas: { type: 'zod' }. This option is opt-in for backward compatibility.

  • For generated HttpClient services, eligible JSON body responses are validated against the schema in the RxJS pipeline.
  • For generated httpResource functions, eligible JSON resources are validated against the schema via the resource's parse option.

The exact mechanism depends on the strategy: throw parses directly (e.g. Schema.parse()), while both validates through a safe-parse wrapper so it can log before re-throwing.

The boolean form is preserved for backward compatibility: true ≡ { strategy: 'throw' }. With { strategy: 'both' } an invalid response is first logged through console.error('[orval] <operation> response validation failed', error) with the raw ZodError, then re-thrown — so the failure still surfaces through the client's native error channel (RxJS error / resource.error() signal) while giving production visibility into contract drift.

Validation is skipped for primitive types, non-JSON responses, and custom mutator paths that bypass the generated validation flow. For Angular HttpClient, observe: 'events' | 'response' responses are still validated by cloning the response and validating its JSON body.

requestBodies

Type: boolean Default: false

Parse JSON request bodies with the generated request schema before the request goes out. Like any object form of runtimeValidation, this also turns response validation on; there is no request-only mode. Without a strategy, it uses throw.

override: {
  angular: {
    runtimeValidation: {
      strategy: 'throw',
      requestBodies: true,
    },
  },
}

A generated HttpClient method then parses its body inside defer(). If the body is invalid, the method sends no request and the returned observable errors with the ZodError. strategy applies here too, so both logs [orval] <operation> request body validation failed before re-throwing.

The parameter keeps the schema's input type. The value sent is the parsed output, so schema defaults and transforms apply: a status field with a default is filled in when the caller leaves it out.

Form data, url-encoded, binary and text bodies are sent unchanged, as are operations with a custom mutator. An omitted optional body is sent as-is. This applies to generated HttpClient methods, including the mutation methods kept under retrievalClient: 'httpResource' | 'both'. httpResource retrievals don't parse their request bodies.

queryObjectSerialization

Type: 'spec' | 'legacy' Default: 'spec'

Controls how query parameters whose declared schema is a plain object are serialized when no paramsSerializer/paramsFilter is configured for the operation. See the Object query parameters guide for the full explanation and a worked example.

  • spec (default): honor the OpenAPI parameter's style/explode — form + explode: true (the OpenAPI default) spreads the object's properties as top-level query params, form + explode: false joins them into a single comma-separated value, and deepObject emits bracketed name[prop] keys.
  • legacy: restore the pre-#3705 behavior of silently dropping object-typed query params from the generated request.
orval.config.ts
export default defineConfig({
  petstore: {
    output: {
      override: {
        angular: {
          queryObjectSerialization: 'legacy',
        },
      },
    },
  },
});

Like retrievalClient/runtimeValidation, this can be set globally, per-tag (override.tags[...].angular), or per-operation (override.operations[...].angular). It has no effect when a paramsSerializer or paramsFilter is configured — those remain in full control of the raw value. See issue #3705.

httpResource

Type: Object

Options forwarded into generated httpResource calls.

orval.config.ts
export default defineConfig({
  petstore: {
    output: {
      override: {
        angular: {
          retrievalClient: 'httpResource',
          httpResource: {
            defaultValue: { id: 'fallback' },
            debugName: 'getPetByIdResource',
            injector: 'inject(Injector)',
            equal: '(a, b) => a?.id === b?.id',
          },
        },
      },
    },
  },
});

defaultValue

Type: unknown

Initial value exposed while the resource is idle/loading. When configured, generated overloads return HttpResourceRef<T> instead of HttpResourceRef<T | undefined>.

debugName

Type: String

Name shown in Angular DevTools.

injector

Type: String

Raw expression passed to HttpResourceOptions.injector.

equal

Type: String

Raw expression passed to HttpResourceOptions.equal.

baseUrl

Type: Object

orval.config.ts
export default defineConfig({
  petstore: {
    output: {
      override: {
        angular: {
          baseUrl: {
            apiId: 'petstore',
          },
        },
      },
    },
  },
});

Opt-in: compose this output's runtime base URL through Angular dependency injection (an InjectionToken) instead of baking a static prefix into every generated route string. See the Angular guide for the full precedence chain, the generated artifacts, and a multi-API gateway-routing example.

angular-client only. Setting baseUrl on any other client logs a warning and has no effect.

apiId

Type: String (required)

Explicit, stable identifier for this API. Must match /^[A-Za-z][A-Za-z0-9_-]*$/; Orval throws a config-time error otherwise. apiId is never derived from the specification's info.title or the target file name — it drives every generated identifier, so it needs to stay stable across regenerations:

Generated identifierDerivation
<API_ID>_SERVER_URLEmbedded fallback URL constant
<API_ID>_BASE_URL_RESOLVERInjectionToken for the runtime resolver hook
<API_ID>_BASE_URLInjectionToken for the composed, normalized base URL
<Api>BaseUrlResolverContextResolver context type ({ apiId, serverUrl })
<Api>BaseUrlResolverResolver function type
provide<Api>BaseUrl(baseUrl)Directly provides the base URL, bypassing the resolver
provide<Api>BaseUrlResolver(resolver)Provides a custom resolver

<API_ID> is apiId upper-snake-cased (e.g. petstore → PETSTORE); <Api> is apiId PascalCased (e.g. petstore → Petstore).

index

Type: Number Default: 0

Which entry of the specification's servers array to embed as the default fallback URL, same semantics as baseUrl.index on the top-level baseUrl option.

variables

Type: Record<string, string>

Values for any {variable} placeholders in the selected server URL.

Error and warning behavior

  • Missing/invalid apiId — throws `override.angular.baseUrl.apiId` must be a non-empty string matching /^[A-Za-z][A-Za-z0-9_-]*$/ at config-normalization time.
  • Combined with output.baseUrl — throws: `override.angular.baseUrl` cannot be combined with the top-level `output.baseUrl`. Remove output.baseUrl from the output; the token's fallback already reads the specification's servers field, and a runtime override belongs in a provided resolver.
  • Set on a non-angular client — logs a warning and is otherwise ignored.
  • Set under override.operations[...].angular or override.tags[...].angular — logs a warning and is ignored. baseUrl is an output-level concern configured once via override.angular.baseUrl, not per operation or tag.

override.hono

Hono server options:

orval.config.ts
export default defineConfig({
  petstore: {
    output: {
      override: {
        hono: {
          handlers: 'src/handlers',
          handlerGenerationStrategy: 'smart',
          validatorOutputPath: 'src/validator.ts',
          compositeRoute: 'src/routes.ts',
        },
      },
    },
  },
});

handlers

Type: String

Changes output path for Hono handlers.

handlerGenerationStrategy

Type: 'smart' | 'skip' | 'full'

Default: 'smart'

Controls how an existing handler file is treated when you re-run orval. A file that does not exist yet is always generated fresh.

  • smart (default) — non-destructively reconcile only the parts orval owns: its own imports (names, module paths, casing) and the zValidator(...) arguments, and append handlers for new operations. Your custom imports, middleware, handler bodies, and top-level helpers are preserved. Requires the optional typescript peer dependency (see note below); if it is absent, smart falls back to skip with a warning.
  • skip — leave an existing handler file byte-for-byte unchanged. New operations still get fresh files (in split mode).
  • full — rebuild the file header, imports, and validator chain from the spec, splicing back only each handler body. Destructive: custom imports, middleware, and top-level helpers are dropped. Use only if you keep handlers minimal and want maximal sync with the spec.

smart and full use the TypeScript compiler API to parse existing handler files. typescript is an optional peer dependency — virtually every orval project already has it, so nothing extra is installed.

If output.clean is enabled and the handlers directory lives under the output target directory, handler files are deleted before generation runs, which defeats smart/skip preservation. Disable clean (or scope it) when relying on handler preservation.

validatorOutputPath

Type: String

Changes the validator output path.

compositeRoute

Type: String

Generate a combined routes file.


override.mcp

MCP server options:

orval.config.ts
export default defineConfig({
  petstore: {
    output: {
      override: {
        mcp: {
          server: {
            path: './custom-server.ts',
            name: 'customServer',
          },
          handler: {
            path: './custom-handler.ts',
            name: 'customHandler',
          },
        },
      },
    },
  },
});

server

Type: String | Object

Custom server function to use instead of the default StdioServerTransport. When set, the generated server.ts calls the function with createMcpServer.

Example implementation using @hono/mcp:

custom-server.ts
import type {
  McpServer,
  RegisteredTool,
} from '@modelcontextprotocol/sdk/server/mcp.js';
import { StreamableHTTPTransport } from '@hono/mcp';
import { Hono } from 'hono';

export const customServer = (
  createMcpServer: (options?: RequestInit) => {
    server: McpServer;
    tools: Record<string, RegisteredTool>;
  },
) => {
  const app = new Hono();
  const { server } = createMcpServer();
  const transport = new StreamableHTTPTransport();

  app.all('/mcp', async (c) => {
    if (!server.isConnected()) {
      await server.connect(transport);
    }
    return transport.handleRequest(c);
  });

  Bun.serve({ fetch: app.fetch, port: Number(process.env.PORT ?? 3000) });
};

handler

Type: Object

Custom handler function that replaces the default response shaping and error mapping of every generated handler. When set, each handler binds the tool arguments to a fetcher and calls your function with it, the tool call context, and a toStructuredContent function built in server.ts for that operation. It validates the body with the response schema and returns the safeParse result: on success data is the body as is for a plain object (fields not in the spec dropped), { result: body } for any other response, or undefined when the tool declares no outputSchema; on failure error.message describes the mismatch. The return value is used as the tool result as is.

custom-handler.ts
import type { RequestHandlerExtra } from '@modelcontextprotocol/sdk/shared/protocol.js';
import type {
  CallToolResult,
  ServerNotification,
  ServerRequest,
} from '@modelcontextprotocol/sdk/types.js';

export const customHandler = async (
  fetcher: (
    overrides?: RequestInit,
  ) => Promise<{ status: number; data: unknown; headers: Headers }>,
  ctx: RequestHandlerExtra<ServerRequest, ServerNotification>,
  toStructuredContent: (
    data: unknown,
  ) =>
    | { success: true; data: Record<string, unknown> | undefined }
    | { success: false; error: { message: string } },
): Promise<CallToolResult> => {
  const res = await fetcher();

  if (res.status >= 400) {
    return {
      content: [{ type: 'text', text: JSON.stringify(res.data ?? null) }],
      isError: true,
    };
  }

  const text = JSON.stringify(res.data ?? null);
  const result = toStructuredContent(res.data);

  return result.success
    ? { content: [{ type: 'text', text }], structuredContent: result.data }
    : {
        content: [
          { type: 'text', text },
          { type: 'text', text: result.error.message },
        ],
        isError: true,
      };
};

See the MCP guide for details on fetcher and ctx.


override.axios

Axios client options:

orval.config.ts
export default defineConfig({
  petstore: {
    output: {
      override: {
        axios: {
          includeHttpResponseReturnType: true,
        },
      },
    },
  },
});

includeHttpResponseReturnType

Type: Boolean Default: false

Include the HTTP status in the return type and correlate it with the response data type. For example, an operation with a JSON 200 response and an empty 204 response returns a union that narrows data to the JSON body or void based on status.

The built-in Axios client normalizes spec-declared empty responses to undefined. With a custom mutator, the option changes the generated type but not the runtime value because the mutator issues the request itself. The mutator must return a complete Axios response with the same status-correlated shape.


override.fetch

Fetch client options:

orval.config.ts
export default defineConfig({
  petstore: {
    output: {
      override: {
        fetch: {
          includeHttpResponseReturnType: false,
          forceSuccessResponse: true,
        },
      },
    },
  },
});

includeHttpResponseReturnType

Type: Boolean Default: true

Include HTTP status in return type. Set to false to return data directly. When an exact response status and a matching wildcard are both declared, the exact status takes precedence in the generated type. For example, 200 and 2XX produce status: 200 and status: Exclude<HTTPStatusCode2xx, 200>.

forceSuccessResponse

Type: Boolean Default: false

Throw on error responses instead of returning them.

includeHttpErrorResponse

Type: Boolean Default: false

Pass operation-specific error response information to a custom fetch mutator. Requires includeHttpResponseReturnType: true, forceSuccessResponse: true, and a non-hook mutator accepting three arguments.

orval.config.ts
export default defineConfig({
  api: {
    input: './openapi.yaml',
    output: {
      target: './api.ts',
      client: 'react-query',
      httpClient: 'fetch',
      override: {
        mutator: { path: './mutator.ts', name: 'customFetch' },
        fetch: {
          includeHttpResponseReturnType: true,
          forceSuccessResponse: true,
          includeHttpErrorResponse: true,
        },
      },
    },
  },
});

For Query clients, a mutator exporting ErrorType<T> receives the generated error response union as T, preserving the relationship between status and data for both queries and mutations:

// With this option enabled:
type GetItemQueryError = ErrorType<GetItemResponseError>;
// Without it, T remains the union of error response bodies.

Operations with no declared error responses pass never to ErrorType, so the mutator can still add transport or unexpected-response errors to its error type.

At runtime, the generated function passes the declared error statuses and media types in a separate third argument:

customFetch(url, options, {
  errorResponses: [
    { status: 404, contentType: 'application/json' },
    { status: 422, contentType: 'application/json' },
  ],
});

The mutator's third parameter can use { errorResponses: readonly { status: number; contentType: string }[] }. The list respects override.contentType and is empty when no errors are declared. Responses without a body use an empty contentType. The second argument remains the request options, including schema when includeZodSchemaInArguments is enabled.

The mutator owns parsing, validation, and error handling. Matching the supplied status and media type does not validate the response body. Named and default mutator exports are supported; hook mutators are not. Only explicit error status codes are supported among the responses retained by override.contentType. A retained default or range such as 4XX causes generation to fail. Responses excluded by the filter are omitted from both the generated error types and metadata and do not prevent generation.

serializeResponseHeaders

Type: Boolean Default: false

Return response headers as a plain Record<string, string> instead of a Headers instance. Enable it when a response is cached across a serialization boundary — a Headers instance in dehydrate() state makes a Next.js Server Component fail with Only plain objects can be passed to Client Components from Server Components. Requires includeHttpResponseReturnType (the default).

Header names are lowercased and repeated headers are joined with , . set-cookie is dropped, because a dehydrated cache is embedded in the RSC payload and upstream session cookies must not travel with it. Only headers is converted: blob responses still return a Blob, and application/x-ndjson responses still return the raw Response under stream.

With a custom mutator the option changes the generated type but not the runtime value, because the mutator issues the request itself. The mutator must return headers that already match the declared shape — as it must for includeHttpResponseReturnType today.

orval.config.ts
export default defineConfig({
  petstore: {
    output: {
      client: 'react-query',
      httpClient: 'fetch',
      override: {
        fetch: {
          serializeResponseHeaders: true,
        },
      },
    },
  },
});

jsonReviver

Type: String | Object

Custom JSON reviver function (useful for date parsing).

useDatesTransform does not replace or disable this reviver. With both enabled the two run in sequence: the fetch client still passes the reviver to JSON.parse, and the generated deserialize<OperationName>Response then runs over the result. The reviver sees every string in the body; the deserializer touches only the schema-declared format: date/date-time fields.

runtimeValidation

Type: boolean | { strategy: 'throw' | 'both' } Default: false

Enable Zod runtime validation for fetch client responses. Requires schemas: { type: 'zod' }. When enabled, JSON responses are validated according to the configured strategy (Schema.parse() for the default throw) before being returned.

This covers every client whose request function is the fetch one: client: 'fetch', and react-query, vue-query, svelte-query, solid-query, swr and pinia-colada under httpClient: 'fetch'. Skipped for primitive response types (number, string, boolean) and for ndjson streams, neither of which has a generated schema to parse against.

The boolean form is preserved for backward compatibility: true ≡ { strategy: 'throw' }. With { strategy: 'both' } an invalid response is first logged through console.error('[orval] <operation> response validation failed', error) with the raw ZodError, then re-thrown (rejecting the returned promise) — giving production visibility into contract drift while still failing fast.

A validated response is typed with the schema's zod.output alias (e.g. PetsOutput) instead of the input-typed schema name, since that is what the parse returns at runtime — relevant when the schema transforms values through coerce, useDates, defaults or .transform(). Operations using a custom mutator keep the schema (input) type: the mutator issues the request itself, so the generated parse never runs there.

arrayFormat

Type: 'repeat' | 'brackets' | 'comma'

Controls how array query parameters are serialized when the OpenAPI spec does not explicitly set explode on a parameter. The spec's own explode property always takes precedence.

ValueOutput
repeat?tags=a&tags=b
brackets?tags[]=a&tags[]=b
comma?tags=a%2Cb
orval.config.ts
export default defineConfig({
  petstore: {
    output: {
      client: 'fetch',
      override: {
        fetch: {
          arrayFormat: 'repeat',
        },
      },
    },
  },
});

For full control over serialization (including custom encoding, nested objects, etc.) use override.paramsSerializer instead.

useRuntimeFetcher

Type: Boolean Default: false

Allow injecting a custom fetch function at runtime. When enabled, generated request functions accept an optional fetchFn parameter and call (fetchFn ?? fetch)(...) instead of the global fetch(...). For query client hooks, a fetcher field is added to the options type. Has no effect on operations that use a custom mutator.

orval.config.ts
export default defineConfig({
  petstore: {
    output: {
      client: 'react-query', // also works with 'fetch', 'vue-query', 'svelte-query'
      httpClient: 'fetch',
      override: {
        fetch: {
          useRuntimeFetcher: true,
        },
      },
    },
  },
});

Generated output example

// Request function — gains a fetchFn parameter
export const listPets = async (
  params: ListPetsParams,
  options?: RequestInit,
  fetchFn?: typeof globalThis.fetch,
): Promise<listPetsResponse> => {
  const res = await (fetchFn ?? fetch)(getListPetsUrl(params), {
    ...options,
    method: 'GET',
  });
  // ...
};

// Query hook — gains a fetcher field in options
export const useListPets = (
  params: ListPetsParams,
  options?: {
    query?: UseQueryOptions<...>;
    fetch?: RequestInit;
    fetcher?: typeof globalThis.fetch;
  },
) => { ... };

Usage — SSR with request-scoped fetch

// SvelteKit — +page.ts
export const load = async ({ fetch }) => {
  const queryClient = new QueryClient();
  await prefetchListPetsQuery(queryClient, params, { fetcher: fetch });
  return { queryClient };
};

Runtime validation support matrix

runtimeValidation support differs by client family and mutator usage:

ClientConfig keyStatusNotes
angularoverride.angular.runtimeValidation✅Validates JSON body responses via Schema.parse(), including observe: 'events'/'response'; types them as the schema's zod.output alias; skips primitive/void and custom mutator paths. requestBodies also parses JSON request bodies before sending
angular-queryoverride.query.runtimeValidation✅Validates eligible responses via Schema.parse() in RxJS pipeline; types them as the schema's zod.output alias; skips primitive/void and custom mutator paths
fetchoverride.fetch.runtimeValidation✅Validates JSON responses via Schema.parse(); types them as the schema's zod.output alias; skips primitive/void, ndjson and custom mutator paths
any client with custom mutatorvaries⚠️Runtime validation may be bypassed depending on mutator path and signature (see #2858). For the fetch client, override.includeZodSchemaInArguments passes the schema to the mutator so it can validate the response itself

Runtime validation is disabled by default (false). When enabled, all three supporting clients accept the object form { strategy: 'throw' | 'both' }, and true is shorthand for { strategy: 'throw' }. The throw strategy parses and throws; both additionally console.errors the raw ZodError before re-throwing, for production observability without giving up fail-fast behaviour.


override.mock

Mock generation overrides:

orval.config.ts
export default defineConfig({
  petstore: {
    output: {
      override: {
        mock: {
          properties: {
            '/tag|name/': 'jon',
            email: () => faker.internet.email(),
          },
          schemas: {
            Apple: {
              properties: {
                color: () => faker.helpers.arrayElement(['red', 'green']),
              },
            },
          },
          format: {
            email: () => faker.internet.email(),
            iban: () => faker.finance.iban(),
          },
          required: true,
          nonNullable: true,
          delay: 500,
          arrayMin: 1,
          arrayMax: 10,
          stringMin: 10,
          stringMax: 20,
          numberMin: 0,
          numberMax: 100,
        },
      },
    },
  },
});

properties

Override mock values per property path or regex. Applies to every schema that has a matching property.

schemas

Scope property overrides to a named schema, so the same property name can mock differently per schema (e.g. color on Apple vs. Car). Keyed by schema name; each entry holds a properties map using the same matching rules as properties (bare name, /regex/, exact #.path). Takes precedence over the global properties overrides. See the Faker guide for a worked example.

format

Provide custom generators for OpenAPI format values.

required

Type: Boolean

Make all properties required in mocks.

nonNullable

Type: Boolean Default: false

When true, nullable properties are generated without faker.helpers.arrayElement([value, null]). For OpenAPI 3.1 null-union array items (type: ['string', 'null']), this also skips null inside .map() callbacks. Optional properties may still be omitted via arrayElement([value, undefined]) unless required is also true. You can still pass null at runtime through the factory's overrideResponse argument.

exactOptional

Type: Boolean Default: false

When true, an optional property is left out of the mock object half the time instead of being set to undefined. Use it when your project compiles with exactOptionalPropertyTypes, which rejects a present key holding undefined.

// default
barksPerMinute: faker.helpers.arrayElement([faker.number.int(), undefined]),

// exactOptional: true
...(faker.datatype.boolean() ? { barksPerMinute: faker.number.int() } : {}),

Optional nullable properties are unchanged: they still pick between the value and null, because null is a value, not an absence. Setting nonNullable as well drops that null, and those properties are then spread in the same way.

This changes runtime shape as well as types: in checks and Object.keys now see the key missing. To keep every optional property present instead, use required: true.

delay

Type: Number | Function | false Default: false

Response delay in milliseconds. Set to false to remove delay.

fractionDigits

Type: Number Default: 2

Number of decimal places for floating-point numbers.

Array/String/Number Min/Max

Control generated data bounds (arrayMin, arrayMax, stringMin, stringMax, numberMin, numberMax).

useExamples

Type: Boolean Default: false

Use example property from OpenAPI specification for mock generation.

baseUrl

Type: String

Base URL for mock handlers.


override.operations

Override by operation ID:

orval.config.ts
export default defineConfig({
  petstore: {
    output: {
      override: {
        operations: {
          listPets: {
            mutator: 'src/response-type.js',
            query: {
              useQuery: true,
              useInfinite: false,
            },
            mock: {
              data: () => ({ id: 1, name: 'Buddy' }),
            },
          },
        },
      },
    },
  },
});

override.tags

Override by OpenAPI tag (same options as operations).


override.operationName

Type: Function

Custom function to override generated operation names.

The callback receives (operation, route, verb), where operation is the OpenAPI Operation Object, route is the operation's route string, and verb is Orval's HTTP verb value. Return a string to set both the method name and the type-name base, or return [methodName, typeNameBase] to control them independently.

Return string to control both the method name and the type-name base together:

orval.config.ts
export default defineConfig({
  petstore: {
    output: {
      override: {
        operationName: (operation, route, verb) => {
          return `custom_${operation.operationId}`;
        },
      },
    },
  },
});

Return [methodName, typeNameBase] to decouple method names from type-identifier names. This is useful for gateway-aggregated specs where multiple services share the same REST patterns (GET /products, GET /orders) — bare method names are safe per-tag (each service class scopes them), but type names (*Params, *Body, *Error, *Result) need to be globally unique to avoid barrel-level collisions with tags-split + splitByTags + indexFiles:

orval.config.ts
import { pascal } from '@orval/core';

export default defineConfig({
  api: {
    output: {
      mode: 'tags-split',
      schemas: { path: './model', splitByTags: true },
      override: {
        operationName: (_operation, route, verb) => {
          const segments = route.split('/').filter(Boolean);
          return [
            `${verb}${pascal(segments.slice(2).join('-'))}`,  // getProducts
            `${verb}${pascal(segments.slice(1).join('-'))}`,  // getCatalogProducts
          ];
        },
      },
    },
  },
});

Result:

// catalog/catalog.service.ts
class CatalogService {
  getProducts = (params: GetCatalogProductsParams) => ...;
}

// inventory/inventory.service.ts
class InventoryService {
  getProducts = (params: GetInventoryProductsParams) => ...;
}

The first element controls the function/hook name. The second controls the base for all operation-specific TypeScript type identifiers (*Params, *Body, *Error, *Result, *Accept, zod/hono/effect schema names).


override.components

Add suffixes to generated model names:

orval.config.ts
export default defineConfig({
  petstore: {
    output: {
      override: {
        components: {
          schemas: { suffix: 'DTO' },
          responses: { suffix: 'Response' },
          parameters: { suffix: 'Params' },
          requestBodies: { suffix: 'Bodies' },
        },
      },
    },
  },
});

Add prefixes to generated component names (available for schemas, responses, parameters, and requestBodies). prefix is applied to the component itself; itemPrefix is applied to the element type when the component is an array (valid for components.schemas only). Prefixes are also applied to $ref references, so a Pet schema with prefix: 'I' is referenced as IPet everywhere it appears:

orval.config.ts
export default defineConfig({
  petstore: {
    output: {
      override: {
        components: {
          schemas: { prefix: 'I', itemPrefix: 'I' },
          responses: { prefix: 'I' },
          parameters: { prefix: 'I' },
          requestBodies: { prefix: 'I' },
        },
      },
    },
  },
});

The default suffix is '' for schemas, 'Response' for responses, 'Parameter' for parameters, and 'Body' for request bodies. Set a suffix to '' explicitly to disable it.

For array schemas, schemas.itemSuffix controls the suffix of the generated item type. It defaults to 'Item' and applies only to components.schemas:

orval.config.ts
export default defineConfig({
  petstore: {
    output: {
      override: {
        components: {
          schemas: { itemSuffix: 'Element' },
        },
      },
    },
  },
});

An inline array response can consequently produce an item type such as ListPets200Element. The item suffix names the generated element type; it does not change the component schema's structure.

Type Generation Options

useDates

Type: Boolean Default: false

Convert date/datetime to JavaScript Date objects.

useBigInt

Type: Boolean Default: false

Convert int64/uint64 to BigInt.

useTypeOverInterfaces

Type: Boolean Default: false

Use TypeScript type instead of interface.

useNamedParameters

Type: Boolean Default: false

Use named parameters object instead of positional arguments.

useDeprecatedOperations

Type: Boolean Default: true

Include deprecated operations.

enumGenerationType

Type: 'const' | 'enum' | 'union' Default: 'const'

How to generate enums:

// 'const' (default)
export const Example = { foo: 'foo', bar: 'bar' } as const;
export type Example = (typeof Example)[keyof typeof Example];

// 'enum'
export enum Example { foo = 'foo', bar = 'bar' }

// 'union'
export type Example = 'foo' | 'bar';

Zod output ignores this option for enums that carry x-enumNames or x-enumDescriptions metadata, which always emit the z.enum({ ... }) object form. See the enums guide.

'enum' emits a TypeScript enum declaration, which type stripping cannot remove. If you compile with TypeScript 5.8+ erasableSyntaxOnly — for example under Node's native TypeScript support or any type-stripping build step — generated 'enum' output fails to compile. Use 'const' (default) or 'union' instead in that environment.

aliasCombinedTypes

Type: Boolean Default: false

Create intermediate type aliases for anyOf/oneOf/allOf.

suppressReadonlyModifier

Type: Boolean Default: false

Suppress readonly modifier on properties.

preserveReadonlyRequestBodies

Type: 'strip' | 'preserve' Default: 'strip'

Controls how Orval treats readonly properties when a schema is reused as a request body.

  • strip (recommended): removes readonly modifiers from generated request-body types via NonReadonly<T>. This is the safest default for most OpenAPI specifications because readOnly properties are response-oriented.
  • preserve: keeps readonly modifiers on generated request-body types. Use this only when your request DTOs are intentionally immutable and you want that immutability reflected in the generated TypeScript types.
orval.config.ts
export default defineConfig({
  petstore: {
    output: {
      override: {
        preserveReadonlyRequestBodies: 'strip',
      },
    },
  },
});

Tip

Prefer separate request and response schemas when your API semantics differ. This option is mainly useful when a single schema is reused for both request and response payloads.

This setting applies to request bodies regardless of the generated Angular style (HttpClient or httpResource). httpResource still sends request payloads, so the same request-body guidance applies.

useNullForOptional

Type: Boolean Default: false

Type optional properties as T | null instead of just T. Useful for JSON:API compatibility where null explicitly indicates "no value".

// Default (false)
export interface Pet {
  id: number;
  name?: string;
  tag?: string;
}

// With useNullForOptional: true
export interface Pet {
  id: number;
  name?: string | null;
  tag?: string | null;
}

includeZodSchemaInArguments

Type: Boolean Default: false

Pass the zod schema of the response to the custom mutator as an extra schema option, so the mutator can validate the response itself.

A custom mutator issues the request on its own, so the Schema.parse() call Orval generates for runtimeValidation never runs (see #2858). Enabling this option hands the schema to the mutator instead.

Supported by the fetch client and by the query clients using httpClient: 'fetch'. Requires schemas: { type: 'zod' } and override.fetch.runtimeValidation: true. The schema is omitted for operations without a validatable response — primitive or void responses, and application/x-ndjson streams.

orval.config.ts
export default defineConfig({
  petstore: {
    output: {
      client: 'fetch',
      schemas: { path: 'src/gen/model', type: 'zod' },
      override: {
        includeZodSchemaInArguments: true,
        mutator: { path: './src/custom-fetch.ts', name: 'customFetch' },
        fetch: { runtimeValidation: true },
      },
    },
  },
});
src/custom-fetch.ts
import type { ZodType } from 'zod';

export const customFetch = async <T>(
  url: string,
  options: RequestInit & { schema?: ZodType<T> },
): Promise<T> => {
  const { schema, ...init } = options;
  const response = await fetch(url, init);
  const data = await response.json();

  return schema ? schema.parse(data) : (data as T);
};

schema.parse() throws a ZodError on a mismatch. Use safeParse() in the mutator if you would rather handle validation failures yourself.

factoryMethods

Type: Object Default: { generate: false }

Generate factory methods for DTOs (Data Transfer Objects) initialized with safe default values. Useful for testing and initializing empty state. Functionality handles OpenAPI readOnly and writeOnly flags to generate appropriate payload structures:

  • Required properties: Always included in the factory output, regardless of their visibility flags.
  • Optional readOnly properties: Always omitted from the factory output, as they would be dropped by the server.
  • Optional writeOnly properties: Always included in the factory output (even if includeOptionalProperty is set to false).
orval.config.ts
export default defineConfig({
  petstore: {
    output: {
      factoryMethods: {
        functionNamePrefix: 'create',
        mode: 'split',
        includeOptionalProperty: true,
        outputDirectory: `#output.workspace.schemas`,
      },
    },
  },
});

functionNamePrefix

Type: String Default: 'create'

Prefix for the generated factory function names.

mode

Type: 'single' | 'split' | 'single-split' Default: 'split'

Where to generate the factory methods:

  • single: Appends the factory function to the schema file.
  • split: Creates a {schema}.factory.ts with factory method. By default it is placed next to schema file.
  • single-split: Aggregates all factory methods into a single factoryMethods.ts file.

includeOptionalProperty

Type: boolean Default: true

Determines whether optional schema properties are included in the default factory output.

outputDirectory

Type: String Default: #output.workspace.schemas

Defaults to the value configured in #output.workspace.schemas. Determines where factory methods will be generated (can be used to generated methods away from schema directory). Takes effect only when used mode is split or single-split.

Other Options

allParamsOptional

Type: Boolean Default: false

Let callers leave parameters unresolved.

Path parameters keep their position in the signature but widen to accept a missing value, and the query/header parameters object becomes optional:

// allParamsOptional: false
export const getPet = async (petId: string, params: GetPetParams, ...)

// allParamsOptional: true
export const getPet = async (petId: string | undefined | null, params?: GetPetParams, ...)

Properties inside the parameters object keep whatever the document says — a required: true query parameter stays required within GetPetParams.

This is useful with the TanStack Query clients, where a hook is often called before its id is known and the generated enabled guard (or useSkipToken) already holds the request back until then.

urlEncodeParameters

Type: Boolean Default: false

Wrap each path parameter with encodeURIComponent(String(...)) in generated URL helpers. This option only affects path parameters; query parameters are typically encoded by the underlying client (URLSearchParams, axios, etc.).

Path parameters are stringified via String(value) before encoding, so array (style: simple|matrix|label) and object path parameters are not serialized according to their OpenAPI style — they fall back to the default String(value) representation.

optionsParamRequired

Type: Boolean Default: false

Make the options parameter required. Since the options parameter appears last in the parameter-set, any preceding parameters will also be required.

propertySortOrder

Type: 'Alphabetical' | 'Specification' Default: 'Specification'

How to sort properties in generated types.

$dynamicRef / $dynamicAnchor support

Orval automatically resolves JSON Schema 2020-12 $dynamicRef / $dynamicAnchor keywords in OpenAPI 3.1 specs. No configuration is needed.

Supported patterns

PatternDescription
Generic template emissionSchemas with $defs entries that have $dynamicAnchor but no $ref are emitted as TypeScript generic interfaces (e.g., interface PaginatedResponse<itemType>).
Type alias bindingSchemas that $ref a generic template and bind $defs entries with $dynamicAnchor + $ref are emitted as type aliases (e.g., type UserListResponse = PaginatedResponse<User>).
Self-referential $dynamicAnchorRecursive schemas where $dynamicRef resolves to the declaring schema itself (e.g., tree nodes).
allOf bound aliasesSchemas that combine a generic template reference with additional properties via allOf emit intersection types (e.g., type X = Template<Args> & { extra }).

Generic template example

Define a reusable generic schema with an unbound $dynamicAnchor in $defs:

OpenAPI 3.1 spec
components:
  schemas:
    PaginatedResponse:
      $defs:
        itemType:
          $dynamicAnchor: itemType
          not: {}
      type: object
      properties:
        items:
          type: array
          items:
            $dynamicRef: '#itemType'
        total:
          type: integer

Then bind it to concrete types:

    UserListResponse:
      $defs:
        itemType:
          $dynamicAnchor: itemType
          $ref: '#/components/schemas/User'
      $ref: '#/components/schemas/PaginatedResponse'

    OrderListResponse:
      $defs:
        itemType:
          $dynamicAnchor: itemType
          $ref: '#/components/schemas/Order'
      $ref: '#/components/schemas/PaginatedResponse'

Generated TypeScript:

export interface PaginatedResponse<itemType> {
  items: itemType[];
  total: number;
}

export type UserListResponse = PaginatedResponse<User>;
export type OrderListResponse = PaginatedResponse<Order>;

The generic parameter name (itemType) comes from the $dynamicAnchor value. The type alias name (UserListResponse) comes from the schema key in components.schemas. Endpoints that reference a bound alias use the alias name directly (e.g., Promise<AxiosResponse<UserListResponse>>).

Self-referential $dynamicAnchor example

When a schema declares $dynamicAnchor and uses $dynamicRef with the same anchor, the type resolves to itself:

OpenAPI 3.1 spec
components:
  schemas:
    BaseCategory:
      $dynamicAnchor: category
      type: object
      properties:
        id:
          type: string
        children:
          type: array
          items:
            $dynamicRef: '#category'

    LocalizedCategory:
      $dynamicAnchor: category
      allOf:
        - $ref: '#/components/schemas/BaseCategory'
        - type: object
          properties:
            displayName:
              type: string

Generated TypeScript:

export interface BaseCategory {
  id?: string;
  children?: BaseCategory[];
}

export interface LocalizedCategory {
  id?: string;
  children?: LocalizedCategory[];
  displayName?: string;
}

Each schema's $dynamicRef: '#category' resolves to its own type because it declares $dynamicAnchor: category.

Output layout with $dynamicRef generics

Generic templates, bound aliases, and their type arguments are all emitted as individual model files in the same shared location — they are never duplicated per tag. In tags and tags-split mode, every tag's service file imports from a single shared schema source.

With schemas configured and tags-split mode (indexFiles shown at its default, true):

models/
├── index.ts                      ← barrel re-exports everything (indexFiles: true)
├── apiEnvelopeTemplate.ts        ← generic template (ApiEnvelopeTemplate<T>)
├── paginatedTemplate.ts          ← generic template (PaginatedTemplate<T>)
├── pet.ts                        ← domain type
├── owner.ts                      ← domain type
├── paginatedPetItems.ts          ← bound alias (PaginatedTemplate<Pet>)
└── paginatedOwnerItems.ts        ← bound alias (PaginatedTemplate<Owner>)
pets/
└── pets.ts                       ← import { ... } from '../models'
owners/
└── owners.ts                     ← import { ... } from '../models'

With indexFiles: false, no barrel is generated and service files import each schema individually (e.g., import type { Pet } from '../models/pet').

Without a dedicated schemas directory, all schemas go into a single petstore.schemas.ts file at the output root.

When using input.filters.tags to filter endpoints, schemas referenced exclusively through $dynamicAnchor + $ref bindings in inline response $defs are automatically discovered and included in the output — no manual filters.schemas configuration is needed.

Limitations

  • Each schema in components.schemas is generated once with a single dynamic scope. If the same named component is referenced by multiple endpoints that each provide different $defs bindings, only one binding applies. The common pattern — putting $defs bindings on inline response schemas — works correctly.
  • $dynamicRef values targeting external documents (e.g., other.json#anchor) fall back to unknown.
  • Inline $defs entries without $ref that have $dynamicAnchor are treated as generic type parameters, not concrete bindings.

contentType

Filter content types:

orval.config.ts
export default defineConfig({
  petstore: {
    output: {
      override: {
        contentType: {
          include: ['application/json'],
          exclude: ['application/xml'],
        },
      },
    },
  },
});

splitByContentType

Type: Boolean Default: false

When an endpoint's requestBody supports multiple content types (e.g. application/json and multipart/form-data), generate a separate function for each content type instead of combining them into a single function with a union type parameter.

Each generated function is suffixed with the content type name (e.g. WithJson, WithFormData).

// Default (false) — single function with union body
updateProfile(body: FormDataType | JsonType) => { ... }

// With splitByContentType: true — separate function per content type
updateProfileWithFormData(body: FormDataType) => { ... }
updateProfileWithJson(body: JsonType) => { ... }
orval.config.ts
export default defineConfig({
  petstore: {
    output: {
      override: {
        splitByContentType: true,
      },
    },
  },
});

If the endpoint only has a single content type, no suffix is added and the behavior is the same as the default.

formData

Type: Boolean | String | Object

Customize form data generation. If an object is provided, specify path, name, and optionally default: true for default export.

orval.config.ts
export default defineConfig({
  petstore: {
    output: {
      override: {
        formData: {
          path: './api/mutator/custom-form-data-fn.ts',
          name: 'customFormDataFn',
          // default: true
        },
      },
    },
  },
});
custom-form-data-fn.ts
export const customFormDataFn = <Body>(body: Body): FormData => {
  const formData = new FormData();
  // Custom implementation
  Object.entries(body as Record<string, any>).forEach(([key, value]) => {
    if (value !== undefined) {
      formData.append(key, value);
    }
  });
  return formData;
};

arrayHandling

Type: 'serialize' | 'serialize-with-brackets' | 'explode' Default: 'serialize'

Specifies how FormData handles arrays:

orval.config.ts
export default defineConfig({
  petstore: {
    output: {
      override: {
        formData: {
          arrayHandling: 'serialize-with-brackets',
        },
      },
    },
  },
});
  • serialize: formData.append('items', JSON.stringify(value))
  • serialize-with-brackets: formData.append('items[]', JSON.stringify(value))
  • explode: Expands nested objects with indexed keys

formUrlEncoded

Type: Boolean | String | Object

Customize form URL encoded data generation:

orval.config.ts
export default defineConfig({
  petstore: {
    output: {
      override: {
        formUrlEncoded: {
          path: './api/mutator/custom-form-url-encoded-fn.ts',
          name: 'customFormUrlEncodedFn',
        },
      },
    },
  },
});
custom-form-url-encoded-fn.ts
export const customFormUrlEncodedFn = <Body>(body: Body): URLSearchParams => {
  const params = new URLSearchParams();
  Object.entries(body as Record<string, any>).forEach(([key, value]) => {
    if (value !== undefined) {
      params.append(key, String(value));
    }
  });
  return params;
};

paramsSerializer

Type: String | Object

Note: Valid for Axios, Angular, and the fetch client.

Custom parameter serializer for query parameters. When set, the generated URL helper delegates query string building entirely to this function instead of using the built-in logic.

orval.config.ts
export default defineConfig({
  petstore: {
    output: {
      override: {
        paramsSerializer: {
          path: './api/mutator/custom-params-serializer-fn.ts',
          name: 'customParamsSerializerFn',
        },
      },
    },
  },
});

For Axios and Angular the function receives the params object and can return any value Axios/Angular accepts. For the fetch client it must return a string (the raw query string without the leading ?):

custom-params-serializer-fn.ts
// Axios / Angular
export const customParamsSerializerFn = (
  params: Record<string, any>,
): string => {
  return Object.entries(params)
    .filter(([_, v]) => v !== undefined)
    .map(([k, v]) => `${k}=${encodeURIComponent(v)}`)
    .join('&');
};

// fetch client — must return a string
export const customParamsSerializer = (
  params: Record<string, unknown> | undefined,
): string =>
  new URLSearchParams(
    Object.entries(params ?? {})
      .filter(([_, v]) => v !== undefined)
      .flatMap(([k, v]) =>
        Array.isArray(v)
          ? v.map((item) => [k, String(item)])
          : [[k, String(v)]],
      ),
  ).toString();

paramsSerializerOptions

Type: Object

Note: Only valid when using Axios or Angular. Only used when paramsSerializer is not defined.

Use qs library for parameter serialization:

orval.config.ts
export default defineConfig({
  petstore: {
    output: {
      override: {
        paramsSerializerOptions: {
          qs: {
            arrayFormat: 'repeat',
          },
        },
      },
    },
  },
});

paramsFilter

Type: String | Object

Note: Only valid for the angular client, or angular-query when httpClient: 'angular'.

Replaces the built-in query-parameter filter that the Angular client applies before handing params to HttpParams. When set, orval does not strip null/undefined or non-primitive values for you — your function returns exactly the object HttpParams (or a configured paramsSerializer) receives.

This option intentionally lives at override.paramsFilter rather than override.angular.paramsFilter so the same request-shaping override can be used globally or narrowed per operation/tag, just like mutator and paramsSerializer.

When a paramsSerializer is configured, orval already preserves schema-declared object and array-of-object params so the serializer can handle them; without a serializer those params are dropped. See the Angular guide. Use paramsFilter when you need the raw object without a serializer, or for any control that the schema cannot express.

orval.config.ts
export default defineConfig({
  petstore: {
    output: {
      override: {
        paramsFilter: {
          path: './api/mutator/custom-params-filter-fn.ts',
          name: 'customParamsFilterFn',
        },
      },
    },
  },
});
custom-params-filter-fn.ts
export const customParamsFilterFn = (
  params: Record<string, unknown>,
): Record<string, unknown> => {
  const result: Record<string, unknown> = {};
  for (const [key, value] of Object.entries(params)) {
    if (value !== undefined) {
      result[key] = value;
    }
  }
  return result;
};

useDates

Type: Boolean Default: false

Convert OpenAPI date or datetime to JavaScript Date objects instead of string.

orval.config.ts
export default defineConfig({
  petstore: {
    output: {
      override: {
        useDates: true,
      },
    },
  },
});

Important: You must provide an Axios converter to convert serialized date strings to Date objects. This option only affects the TypeScript definition.

If you also want runtime conversion, prefer useDatesTransform below; the interceptor approach traverses every response and can convert date-looking strings that are not schema dates.

axios-instance.ts
import axios from 'axios';

const client = axios.create({ baseURL: '' });

client.interceptors.response.use((originalResponse) => {
  handleDates(originalResponse.data);
  return originalResponse;
});

export default client;

const isoDateFormat =
  /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d*)?(?:[-+]\d{2}:?\d{2}|Z)?$/;

function isIsoDateString(value: any): boolean {
  return value && typeof value === 'string' && isoDateFormat.test(value);
}

export function handleDates(body: any) {
  if (body === null || body === undefined || typeof body !== 'object')
    return body;

  for (const key of Object.keys(body)) {
    const value = body[key];
    if (isIsoDateString(value)) {
      body[key] = new Date(value); // default JS conversion
      // body[key] = parseISO(value); // date-fns conversion
      // body[key] = luxon.DateTime.fromISO(value); // Luxon conversion
    } else if (typeof value === 'object') {
      handleDates(value);
    }
  }
}

If using fetch client with useDates: true, query parameters of type Date are stringified using toISOString().

useDatesTransform

Type: Boolean Default: false

Use this property to also convert dates at runtime. While useDates only changes the generated TypeScript types, useDatesTransform additionally generates a small deserialize<OperationName>Response function for every operation whose response schema declares format: date or format: date-time fields, and chains it onto the generated call:

export const getOrderDetails = (orderId: string) => {
  return customInstance<OrderDetails>({ url: `/orders/${orderId}`, method: 'GET' }).then(
    deserializeGetOrderDetailsResponse,
  );
};

That is the axios shape. client: 'fetch' has no promise to chain onto, so the conversion is inlined into the request function, guarded by the declared success status:

export const getOrderDetails = async (
  orderId: string,
  options?: RequestInit,
): Promise<getOrderDetailsResponse> => {
  const res = await fetch(getGetOrderDetailsUrl(orderId), {
    ...options,
    method: 'GET',
  });

  const body = [204, 205, 304].includes(res.status) ? null : await res.text();

  let data: getOrderDetailsResponse['data'] = body ? JSON.parse(body) : {};
  if (body && res.status === 200) {
    data = deserializeGetOrderDetailsResponse(data as OrderDetails);
  }
  return {
    data,
    status: res.status,
    headers: res.headers,
  } as getOrderDetailsResponse;
};

Only schema-declared date fields are converted — no response-wide traversal, no pattern matching on strings — and operations without date fields generate no extra code. Setting useDatesTransform: true implies useDates: true. This removes the need for the axios interceptor shown under useDates.

For request bodies, format: date fields are serialized back to a calendar day. A serialize<OperationName>Request function is generated for every operation whose body has exactly one JSON media type declaring one, and wraps the body at the call site:

export const updateAppointment = (appointment: Appointment) => {
  return customInstance<Appointment>({
    url: `/appointments`,
    method: 'PUT',
    headers: { 'Content-Type': 'application/json' },
    data: serializeUpdateAppointmentRequest(appointment),
  }).then(deserializeUpdateAppointmentResponse);
};

format: date-time fields are left alone — Date.prototype.toJSON() already renders them correctly — and readOnly properties are skipped, since OpenAPI readOnly means the field is response-only; this applies even under preserveReadonlyRequestBodies: 'preserve', so a preserved readOnly format: date field keeps going out as a full datetime. The serializer copies the objects and arrays it touches, so the value you pass in is never modified. A format: date value is treated as its UTC calendar day, the same convention the mock generator uses for that format: construct it with new Date(Date.UTC(year, month, day)) and read it back with getUTCFullYear() / getUTCMonth() / getUTCDate() — a Date built from local parts can land on the previous day at positive UTC offsets.

A oneOf/anyOf with an explicit OpenAPI discriminator mapping is converted by emitting a switch on the discriminator property with one case per mapping key. Any other union whose variants are all object-shaped — each one declaring its own properties — is converted structurally instead: each property a variant declares is guarded by its presence on the value ('key' in value), which is also what lets TypeScript narrow the union. A variant that declares no properties of its own — one composed purely with allOf, one that is itself a oneOf/anyOf, or one declaring an empty properties: {} — fails that test and disqualifies the whole union, not just itself. A variant that declares its own properties and composes with allOf does qualify, but only its own properties are converted: dates it inherits through the allOf are left as strings even though the generated type calls them Date. A property the variants treat differently is skipped, while the rest of the union still converts — because the response direction converts every date format identically, a date-versus-date-time disagreement between variants only affects the request direction. A property one variant marks readOnly while another does not counts as such a disagreement and is skipped too, even though both spell the same Date. A date property named after an Object.prototype member (constructor, toString, valueOf, and so on) is skipped inside a structurally-walked union as well, since 'key' in value is true for every object and would convert the field on payloads of a variant that never declared it. A union that mixes object variants with array or scalar ones is skipped entirely. additionalProperties maps are converted with a loop over Object.keys, unless the schema also declares properties — directly, via allOf, or a sibling oneOf/anyOf — or declares a propertyNames that narrows the keys to an enumerable set (enum or const, inline or via $ref), in which case those take precedence and it is never walked as a map.

Current limitations: responses with multiple success shapes are skipped, and recursive schemas are left untouched entirely — converting only the levels above the cycle would leave deeper dates as strings while the generated types claim Date. A request body with several media types, several JSON media types, or a blob body generates no serializer, since there is no single JSON schema to walk; a schema that is both array- and object-shaped (OAS 3.1 type: ['array', 'object'], or a stray sibling properties on a type: array schema) also generates no serializer, since the two shapes can't be walked together on the request side — the same conflict with a map-valued additionalProperties in place of properties drops both the serializer and the deserializer. Date values in query and path parameters are still serialized as full ISO datetimes, so date-only parameters are not covered, and the mcp client is not transformed.

Wired clients:

  • the axios-based query clients — react-query, vue-query, svelte-query and solid-query with httpClient: 'axios';
  • client: 'fetch', plus react-query, vue-query, svelte-query, solid-query, swr and pinia-colada under httpClient: 'fetch'.

Under fetch, only the declared success status is converted — error bodies are returned untouched. The one exception is a custom mutator combined with includeHttpResponseReturnType: false: the mutator resolves with the body alone, so the status is not visible to the generated code and whatever the mutator resolves with is converted. An inferred custom mutator gets no response conversion at all — its return type is unknown to orval and may not even be a promise, so there's nothing to chain the conversion onto; its request body is still serialized. ndjson and blob responses are not converted either: a stream is handed to the caller unparsed, and a blob has no parsed fields to walk.

useBigInt

Type: Boolean Default: false

Convert OpenAPI int64 and uint64 format to JavaScript BigInt objects instead of number.

orval.config.ts
export default defineConfig({
  petstore: {
    output: {
      override: {
        useBigInt: true,
      },
    },
  },
});

requestOptions

Type: Object | Boolean

Configure or remove request options. Set to false to remove entirely.

jsDoc.filter

Type: Function

Customize JSDoc generation by filtering and transforming schema entries:

orval.config.ts
export default defineConfig({
  petstore: {
    output: {
      override: {
        jsDoc: {
          filter: (schema) => {
            const allowlist = [
              'type', 'format', 'maxLength', 'minLength',
              'description', 'minimum', 'maximum', 'pattern',
              'nullable', 'enum',
            ];
            return Object.entries(schema || {})
              .filter(([key]) => allowlist.includes(key))
              .map(([key, value]) => ({ key, value }))
              .sort((a, b) => a.key.length - b.key.length);
          },
        },
      },
    },
  },
});

Result:

export interface Pet {
  /**
   * @type integer
   * @format int64
   */
  id: number;
  /**
   * @type string
   * @description Name of pet
   */
  name: string;
}

On this page

targetclientaxios clienthttpClientschemasString formObject formSingle-file schemasZodimportPathValidation of importPathsplitByTagsoperationSchemasfileExtensionschemaFileExtensionnamingConventionworkspacemodesinglesplittagstags-splittags-operationstags-operations-splitbaseUrlruntimeimportsgetBaseUrlFromSpecificationvariablesindexmockMocks OptionsMSW generator (type: 'msw')Faker generator (type: 'faker')schemasImportPathArtifact groupsindexFilestagsSplitDeduplicationcommonTypesFileNamedocscleanformatterheaderstsconfigpackageJsonoverridetransformermutatorinferredtitlenamingConvention (property keys)headeroverride.queryuseQueryuseSuspenseQueryuseMutationuseInfiniteuseSuspenseInfiniteQueryuseInfiniteQueryParamuseInfiniteQueryParamLocationusePrefetchuseInvalidateuseSetQueryDatauseGetQueryDatauseSkipTokenmutationInvalidatesOperation namessignalqueryKey / queryOptions / mutationOptionsControlling the use / get prefix on options factoriesshouldExportMutatorHooksshouldExportKeysshouldFilterQueryKeyqueryKeyFiltershouldSplitQueryKeyuseOperationIdAsQueryKeyversionruntimeValidationoverride.swruseInfiniteuseSWRMutationForGetuseSuspensegenerateErrorTypesswrOptions / swrMutationOptions / swrInfiniteOptionsoverride.zodvariantversionstrictcoercegeneratepreprocessparamsdateTimeOptions / timeOptionsuseBrandedTypesgenerateReusableSchemasgenerateMetagenerateDiscriminatedUnionexactOptionalgenerateCompanionTypesoverride.effectstrictgenerateuseBrandedTypesexactOptionaloverride.angularprovideInretrievalClientclientruntimeValidationrequestBodiesqueryObjectSerializationhttpResourcedefaultValuedebugNameinjectorequalbaseUrlapiIdindexvariablesError and warning behavioroverride.honohandlershandlerGenerationStrategyvalidatorOutputPathcompositeRouteoverride.mcpserverhandleroverride.axiosincludeHttpResponseReturnTypeoverride.fetchincludeHttpResponseReturnTypeforceSuccessResponseincludeHttpErrorResponseserializeResponseHeadersjsonReviverruntimeValidationarrayFormatuseRuntimeFetcherRuntime validation support matrixoverride.mockpropertiesschemasformatrequirednonNullableexactOptionaldelayfractionDigitsArray/String/Number Min/MaxuseExamplesbaseUrloverride.operationsoverride.tagsoverride.operationNameoverride.componentsType Generation OptionsuseDatesuseBigIntuseTypeOverInterfacesuseNamedParametersuseDeprecatedOperationsenumGenerationTypealiasCombinedTypessuppressReadonlyModifierpreserveReadonlyRequestBodiesuseNullForOptionalincludeZodSchemaInArgumentsfactoryMethodsfunctionNamePrefixmodeincludeOptionalPropertyoutputDirectoryDefaults to the value configured in #output.workspace.schemas. Determines where factory methods will be generated (can be used to generated methods away from schema directory). Takes effect only when used mode is split or single-split.Other OptionsallParamsOptionalurlEncodeParametersoptionsParamRequiredpropertySortOrder$dynamicRef / $dynamicAnchor supportSupported patternsGeneric template exampleSelf-referential $dynamicAnchor exampleOutput layout with $dynamicRef genericsLimitationscontentTypesplitByContentTypeformDataarrayHandlingformUrlEncodedparamsSerializerparamsSerializerOptionsparamsFilteruseDatesuseDatesTransformuseBigIntrequestOptionsjsDoc.filter