Output
Output configuration options
target
Type: String
Output path for generated files.
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.
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.
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
export default defineConfig({
petstore: {
output: {
schemas: './api/model',
},
},
});Object form
export default defineConfig({
petstore: {
output: {
schemas: {
path: './api/model',
type: 'typescript', // 'typescript' | 'zod'
routes: {
default: 'models',
enum: 'types',
},
},
},
},
});| Property | Type | Description |
|---|---|---|
path | string | Filesystem path for schema output. A directory by default; in mode: 'single' it may point directly at the schema module file |
type | string | 'typescript' (default) or 'zod' — optional |
importPath | string | Optional package import specifier (see below) |
splitByTags | boolean | Organize schemas into per-tag subdirectories (default false, see below) |
routes | object | Route 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.tsWhen 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:
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 singleimportPath(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.importPathresolves to a single barrel file via tsconfig path mappings), setschemasImportPathon the faker generator to point faker factories at a separate import path. - If using factory methods (
factoryMethods), each schema is imported individually regardless ofindexFiles. - When
importPathis set, the relative-path computation infactoryMethods.outputDirectoryis bypassed: factories resolve imports against the package specifier rather than the on-disk factory output directory. - Config normalization rejects invalid
importPathvalues (empty, whitespace, relative, or absolute paths) before generation runs — see Validation ofimportPath.
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 likeC:\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).
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 dirsCross-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
PetimportsDogwhich importsDachshund, all three land in the same directory.
operationSchemas
Type: String
Separate path for operation-derived types (params, bodies, responses).
export default defineConfig({
petstore: {
output: {
schemas: './api/model',
operationSchemas: './api/model/params',
},
},
});fileExtension
Type: String
Default: .ts
Customize file extension for generated files:
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.tsschemaFileExtension
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:
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:
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:
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.tstags
One file per OpenAPI tag:
my-app/src/
├── pets.ts
└── petstore.schemas.tstags-split
Folder per tag with split files:
my-app/src/
├── petstore.schemas.ts
└── pets/
├── petstore.msw.ts
└── petstore.tsWhen 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 ../modelsTo 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.tstags-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.tsThe 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.tsThis 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: ''
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.
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:
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:
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).
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:
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:
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
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,
},
],
},
},
},
});| Option | Type | Default | Description |
|---|---|---|---|
indexMockFiles | Boolean | false | In 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. |
path | String | undefined | Shared 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. |
inline | Boolean | false | In 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. |
generators | Array<MockOptions | Function> | [] | One entry per output mock file. Each entry can be an object (MockOptions) or a custom ClientMockBuilder function. |
export default defineConfig({
petstore: {
output: {
mock: {
path: './src/api/mocks',
generators: [
{ type: 'msw', path: './src/api/mocks/msw' },
{ type: 'faker' },
],
},
},
},
});MSW generator (type: 'msw')
| Option | Type | Default | Description |
|---|---|---|---|
type | 'msw' | required | Discriminator for MSW handler generation. |
path | String | undefined | Output 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. |
operationResponses | Boolean | true | Emit 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. |
delay | Number | Function | false | false | Response delay in ms. |
delayFunctionLazyExecute | Boolean | false | Execute delay function at runtime instead of at build time. |
baseUrl | String | '' | Base URL for the generated MSW handlers. |
useExamples | Boolean | false | Use OpenAPI examples to seed response values. |
generateEachHttpStatus | Boolean | false | Generate response factories for every documented status code. |
locale | String | 'en' | Faker.js locale. |
preferredContentType | String | undefined | Preferred 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.
| Option | Type | Default | Description |
|---|---|---|---|
type | 'faker' | required | Discriminator for Faker-only output. |
path | String | undefined | Output 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. |
schemas | Boolean | false | Emit a consolidated mock factory file (get<SchemaName>Mock) for every entry under components/schemas. |
schemasImportPath | String | undefined | Package 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. |
schemasPath | String | undefined | Output 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. |
importPath | String | undefined | Package 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. |
operationResponses | Boolean | true | Emit per-operation response mock factories (the historical behavior). Set to false together with schemas: true to get only the consolidated schema factories. |
useExamples | Boolean | false | Use OpenAPI examples to seed response values. |
generateEachHttpStatus | Boolean | false | Generate response factories for every documented status code. |
locale | String | 'en' | Faker.js locale. |
preferredContentType | String | undefined | Preferred content type when an operation lists more than one. |
arrayItems | Boolean | false | Emit 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:
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:
| Group | Directory | Barrel | Runtime imports |
|---|---|---|---|
| Client | target | index.ts (indexFiles) | Your HTTP client, e.g. @angular/* |
| Schemas | schemas.path | index.ts | None, or zod with type: 'zod' |
| MSW | MSW generator path | index.msw.ts (indexMockFiles) | msw, plus the Faker group |
| Faker | Faker generator path | index.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 theschemas: truefactories 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.
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 factoriesOrval 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 barrelindex.tsis generated with named re-exports for public shared types plusexport *re-exports for each per-tag implementation file
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.
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 -Dexport 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.
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:
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.
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.
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).
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:
export default defineConfig({
petstore: {
output: {
override: {
transformer: 'src/yourfunction.js',
},
},
},
});mutator
Type: String | Object
Custom HTTP client implementation:
export default defineConfig({
petstore: {
output: {
override: {
mutator: {
path: './api/mutator/custom-instance.ts',
name: 'customInstance',
},
},
},
},
});Example implementation:
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.
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):
export default defineConfig({
petstore: {
output: {
override: {
title: (title) => `${title}Api`,
},
},
},
});namingConvention (property keys)
Type: Object
Change naming convention for property keys (not files):
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.
requiredis still matched against the original spec key, and form-data serialization writes the original key on the wire. - Zod schemas (the
zodclient andschemas: { type: 'zod' }) still validate the original spec keys, then a.transform()moves each value to its converted name.zod.input(the exportedXtype) is the wire shape andzod.output(XOutput) has the converted names. WithfetchruntimeValidation, 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.
header
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.
export default defineConfig({
petstore: {
output: {
override: {
header: (info) => [
`Generated by Orval`,
`Do not edit manually.`,
...(info.title ? [info.title] : []),
],
},
},
},
});override.query
TanStack Query options:
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.
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.
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):
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 }):
| Syntax | Generated 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:
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:
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:
queryOptionsmutator —{ url, operationId, operationName }mutationOptionsmutator —{ operationId, operationName }(theurlis supplied in the second parameter)
Each option takes a mutator, so point it at a file and an exported name:
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:
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:
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:
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.
export function customQueryOptions<T extends { queryKey: QueryKey }>(
options: T,
): T {
return options;
}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:
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:
export const getShowPetByIdQueryKey = (petId: string) => {
return ['pets', petId].filter(q => q !== undefined);
};When shouldFilterQueryKey is false:
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
.filter(q => q !== undefined)One option could be to only make it filter out all falsy keys:
shouldFilterQueryKey: true,
queryKeyFilter: 'Boolean'which would result in the generated code being
.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:
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.
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.
| Value | Output |
|---|---|
'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.
| Value | Output |
|---|---|
4 | Always emit Zod 4 syntax (z.strictObject, z.iso.datetime(), .meta(), …). |
3 | Always 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).
export default defineConfig({
petstore: {
output: {
override: {
zod: {
params: { path: './zod-params.ts', name: 'zodParams' },
},
},
},
},
});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), andliteral,enum,instanceof,stringFormat. - Skipped on modifiers (
optional,nullable,nullish,default,describe) and structural calls (object,array,tuple,union,rest,passthrough,strict). fieldPathonly includes object property names, mirroring Zod's ownissue.path. Array indices and tuple positions are not appended — the inner element of{ tags: array<string> }and a top-leveltags: stringboth seefieldPath: ['tags']. Use thevalidatorfield 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' }) andclient: 'zod'is set, the schemas directory holds reusable Zod schemas instead of TypeScript types. The schema files default to a.zod.tsextension; useschemaFileExtensionto override it independently from the globalfileExtensionif you keep both TS types and reusable Zod schemas in the same directory. - Operation files import the named exports — pure-
$refbody/response wrappers (e.g.PetCreateBody) are skipped so consumers import the component schema directly. $refsiblings:nullable,default,descriptionchain 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
namingConventionmust produce valid JavaScript identifiers (camelCase,PascalCase, orsnake_case).kebab-caseis rejected with a clear error because it would emit dashed exports. - Trade-off —
readOnlyon shared component schemas: without this flag, request bodies generated from$refschemas stripreadOnly: trueproperties so they don't appear in input validators. With this flag on, request and response endpoints share the same exported schema, soreadOnlyproperties remain in body validators too. Either avoidreadOnlyon 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' }, orclient: 'zod'+generateReusableSchemas). Operation wrapper schemas are left untouched, so registryids stay unique. idis always emitted;description/deprecatedonly 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
idmakesz.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 plainzod.union([...])rather than emitting code that throws at construction. - Inheritance (
allOf). Branches composed withallOfare 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). WithgenerateReusableSchemas, a branch that references anallOfschema 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
minivariant.
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
minivariants.
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, andResponse— including the Hono*.zod.tsoutput, which goes through the same generator. Component schemas emitted undergenerateReusableSchemasalready carry this pair unconditionally and are unaffected by this flag. - For an array request/response body, both the
*Itemschema and the array wrapper get their own pair. - Uses
zod.input, notz.infer(an alias ofzod.output): request-side schemas commonly carry.default(),coerce, or.transform(), where the pre-parse (input) and post-parse (output) shapes differ. - With
useBrandedTypes, the cleanzod.inputalias stays unbranded —.brand()only affects the output type — so the brand shows up on the...Outputalias. - 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, anduseBrandedTypes(see Scoping options per operation or tag).
override.effect
Effect schema generation options:
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:
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 AngularHttpClienthttpResource: generate signal-first retrieval functions using AngularhttpResourceboth: keepHttpClientservice methods and emit retrieval resources in a sibling*.resource.tsfile
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
HttpClientservices, eligible JSON body responses are validated against the schema in the RxJS pipeline. - For generated
httpResourcefunctions, eligible JSON resources are validated against the schema via the resource'sparseoption.
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'sstyle/explode—form+explode: true(the OpenAPI default) spreads the object's properties as top-level query params,form+explode: falsejoins them into a single comma-separated value, anddeepObjectemits bracketedname[prop]keys.legacy: restore the pre-#3705 behavior of silently dropping object-typed query params from the generated request.
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.
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
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 identifier | Derivation |
|---|---|
<API_ID>_SERVER_URL | Embedded fallback URL constant |
<API_ID>_BASE_URL_RESOLVER | InjectionToken for the runtime resolver hook |
<API_ID>_BASE_URL | InjectionToken for the composed, normalized base URL |
<Api>BaseUrlResolverContext | Resolver context type ({ apiId, serverUrl }) |
<Api>BaseUrlResolver | Resolver 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`. Removeoutput.baseUrlfrom the output; the token's fallback already reads the specification'sserversfield, and a runtime override belongs in a provided resolver. - Set on a non-
angularclient — logs a warning and is otherwise ignored. - Set under
override.operations[...].angularoroverride.tags[...].angular— logs a warning and is ignored.baseUrlis an output-level concern configured once viaoverride.angular.baseUrl, not per operation or tag.
override.hono
Hono server options:
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 thezValidator(...)arguments, and append handlers for new operations. Your custom imports, middleware, handler bodies, and top-level helpers are preserved. Requires the optionaltypescriptpeer dependency (see note below); if it is absent, smart falls back toskipwith a warning.skip— leave an existing handler file byte-for-byte unchanged. New operations still get fresh files (insplitmode).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:
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:
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.
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:
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:
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.
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.
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.
| Value | Output |
|---|---|
repeat | ?tags=a&tags=b |
brackets | ?tags[]=a&tags[]=b |
comma | ?tags=a%2Cb |
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.
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:
| Client | Config key | Status | Notes |
|---|---|---|---|
angular | override.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-query | override.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 |
fetch | override.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 mutator | varies | ⚠️ | 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:
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:
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:
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:
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:
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:
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:
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 viaNonReadonly<T>. This is the safest default for most OpenAPI specifications becausereadOnlyproperties 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.
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.
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 },
},
},
},
});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
readOnlyproperties: Always omitted from the factory output, as they would be dropped by the server. - Optional
writeOnlyproperties: Always included in the factory output (even ifincludeOptionalPropertyis set tofalse).
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.tswith factory method. By default it is placed next to schema file.single-split: Aggregates all factory methods into a singlefactoryMethods.tsfile.
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
| Pattern | Description |
|---|---|
| Generic template emission | Schemas with $defs entries that have $dynamicAnchor but no $ref are emitted as TypeScript generic interfaces (e.g., interface PaginatedResponse<itemType>). |
| Type alias binding | Schemas 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 $dynamicAnchor | Recursive schemas where $dynamicRef resolves to the declaring schema itself (e.g., tree nodes). |
allOf bound aliases | Schemas 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:
components:
schemas:
PaginatedResponse:
$defs:
itemType:
$dynamicAnchor: itemType
not: {}
type: object
properties:
items:
type: array
items:
$dynamicRef: '#itemType'
total:
type: integerThen 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:
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: stringGenerated 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.schemasis generated once with a single dynamic scope. If the same named component is referenced by multiple endpoints that each provide different$defsbindings, only one binding applies. The common pattern — putting$defsbindings on inline response schemas — works correctly. $dynamicRefvalues targeting external documents (e.g.,other.json#anchor) fall back tounknown.- Inline
$defsentries without$refthat have$dynamicAnchorare treated as generic type parameters, not concrete bindings.
contentType
Filter content types:
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) => { ... }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.
export default defineConfig({
petstore: {
output: {
override: {
formData: {
path: './api/mutator/custom-form-data-fn.ts',
name: 'customFormDataFn',
// default: true
},
},
},
},
});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:
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:
export default defineConfig({
petstore: {
output: {
override: {
formUrlEncoded: {
path: './api/mutator/custom-form-url-encoded-fn.ts',
name: 'customFormUrlEncodedFn',
},
},
},
},
});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.
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 ?):
// 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
paramsSerializeris not defined.
Use qs library for parameter serialization:
export default defineConfig({
petstore: {
output: {
override: {
paramsSerializerOptions: {
qs: {
arrayFormat: 'repeat',
},
},
},
},
},
});paramsFilter
Type: String | Object
Note: Only valid for the
angularclient, orangular-querywhenhttpClient: '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.
export default defineConfig({
petstore: {
output: {
override: {
paramsFilter: {
path: './api/mutator/custom-params-filter-fn.ts',
name: 'customParamsFilterFn',
},
},
},
},
});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.
export default defineConfig({
petstore: {
output: {
override: {
useDates: true,
},
},
},
});Important: You must provide an Axios converter to convert serialized date strings to
Dateobjects. 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.
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
fetchclient withuseDates: true, query parameters of type Date are stringified usingtoISOString().
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-queryandsolid-querywithhttpClient: 'axios'; client: 'fetch', plusreact-query,vue-query,svelte-query,solid-query,swrandpinia-coladaunderhttpClient: '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.
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:
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;
}