# Orval > Orval generates type-safe TypeScript clients from OpenAPI v3 and Swagger v2 specifications. It produces HTTP request functions, React Query/Vue Query/SWR hooks, Angular services, Zod schemas, and MSW mock handlers — all fully typed. Orval takes any valid OpenAPI/Swagger specification (YAML or JSON) and generates production-ready code for your frontend. The default HTTP client uses the Fetch API, but you can configure any client including Axios. Orval supports multiple data-fetching libraries (React Query, Vue Query, Svelte Query, Solid Query, Angular Query, SWR) and frameworks (Angular, SolidStart, Hono). --- # Overview Orval generates **type-safe TypeScript clients** from any valid OpenAPI v3 or Swagger v2 specification, either in YAML or JSON formats. ## Why Orval? Developers often use OpenAPI/Swagger as a contract between frontend and backend. Before Orval, tools like [Swagger Editor](https://editor.swagger.io) or [Swagger Codegen](https://swagger.io/tools/swagger-codegen) did the job. But that's not enough for modern development needs. Enter Orval. ### Main Goals - **Generate TypeScript models** — Full type safety for your API responses and requests - **Generate HTTP request functions** — Ready-to-use functions with proper typing - **Generate mocks with [MSW](https://mswjs.io/)** — Test your frontend without a backend ## Supported Clients The default generated clients use [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API). You can configure any client (e.g. axios) depending on your project needs. - [React Query](https://orval.dev/docs/guides/react-query) - [Vue Query](https://orval.dev/docs/guides/vue-query) - [Svelte Query](https://orval.dev/docs/guides/svelte-query) - [Solid Query](https://orval.dev/docs/guides/solid-query) - [Angular](https://orval.dev/docs/guides/angular) - [SWR](https://orval.dev/docs/guides/swr) ## Getting Started - [Installation](https://orval.dev/docs/installation): Install Orval in your project - [Quick Start](https://orval.dev/docs/quick-start): Generate your first client in seconds - [Configuration](https://orval.dev/docs/reference/configuration): Learn about all configuration options --- # Installation Install Orval as a dev dependency in your project. ## Package Managers ```bash bun add -d orval ``` ```bash npm install orval -D ``` ```bash yarn add orval -D ``` ```bash pnpm add orval -D ``` ## Docker Orval 8+ requires Node.js 22.18 or newer. If your project targets an older Node LTS version, you can run code generation with the official Docker image instead of installing Orval locally. `/app` is only a path inside the container. Your project does not need an `app` folder — mount your project root to any container path and set `-w` to match. ### Basic usage ```bash docker run --rm -v "$(pwd):/app" -w /app ghcr.io/orval-labs/orval ``` ```bash MSYS_NO_PATHCONV=1 docker run --rm -v "$(pwd):/app" -w /app ghcr.io/orval-labs/orval ``` ```bat docker run --rm -v "%cd%:/app" -w /app ghcr.io/orval-labs/orval ``` ```powershell docker run --rm -v "${PWD}:/app" -w /app ghcr.io/orval-labs/orval ``` Pin a specific release with a version tag (for example `8.21.0`): ```bash ghcr.io/orval-labs/orval:8.21.0 ``` ### With a config file ```bash docker run --rm -v "$(pwd):/app" -w /app ghcr.io/orval-labs/orval --config ./orval.config.ts ``` ```bash MSYS_NO_PATHCONV=1 docker run --rm -v "$(pwd):/app" -w /app ghcr.io/orval-labs/orval --config ./orval.config.ts ``` ```bat docker run --rm -v "%cd%:/app" -w /app ghcr.io/orval-labs/orval --config ./orval.config.ts ``` ```powershell docker run --rm -v "${PWD}:/app" -w /app ghcr.io/orval-labs/orval --config ./orval.config.ts ``` On Windows Git Bash, prefix commands with `MSYS_NO_PATHCONV=1` so Docker receives `/app` instead of `C:/Program Files/Git/app`. ### Fetching OpenAPI from localhost Inside a container, `localhost` refers to the container itself, not your machine. If your `orval.config.ts` fetches a spec from a local API (for example `https://localhost:7142/swagger/v1/swagger.json`), replace `localhost` with `host.docker.internal` and pass the URL via an environment variable. | Host OS | Use instead of `localhost` | | --- | --- | | Windows / macOS (Docker Desktop) | `host.docker.internal` | | Linux (Docker Desktop) | `host.docker.internal` | | Linux (native Docker) | `host.docker.internal` with `--add-host=host.docker.internal:host-gateway` | Read the URL from an environment variable in your config: ```ts const inputTarget = process.env.ORVAL_SWAGGER_URL ?? 'https://localhost:7142/swagger/v1/swagger.json'; ``` Set `NODE_TLS_REJECT_UNAUTHORIZED=0` only when your local API uses a self-signed certificate. For CI or offline builds, point Orval at a committed file such as `./src/swagger.json` instead. Replace `ghcr.io/orval-labs/orval` with `orval:local` when testing with a locally built image before the official image is published. ```bash docker run --rm \ -v "$(pwd):/app" \ -w /app \ -e ORVAL_SWAGGER_URL="https://host.docker.internal:7142/swagger/v1/swagger.json" \ -e NODE_TLS_REJECT_UNAUTHORIZED=0 \ ghcr.io/orval-labs/orval \ --config ./orval.config.ts ``` ```bash docker run --rm --add-host=host.docker.internal:host-gateway \ -v "$(pwd):/app" \ -w /app \ -e ORVAL_SWAGGER_URL="https://host.docker.internal:7142/swagger/v1/swagger.json" \ -e NODE_TLS_REJECT_UNAUTHORIZED=0 \ ghcr.io/orval-labs/orval \ --config ./orval.config.ts ``` ```bash MSYS_NO_PATHCONV=1 docker run --rm \ -v "$(pwd):/app" \ -w /app \ -e ORVAL_SWAGGER_URL="https://host.docker.internal:7142/swagger/v1/swagger.json" \ -e NODE_TLS_REJECT_UNAUTHORIZED=0 \ ghcr.io/orval-labs/orval \ --config ./orval.config.ts ``` ```bat cd /d "C:\path\to\your-project" docker run --rm -v "%cd%:/app" -w /app -e ORVAL_SWAGGER_URL=https://host.docker.internal:7142/swagger/v1/swagger.json -e NODE_TLS_REJECT_UNAUTHORIZED=0 ghcr.io/orval-labs/orval --config ./orval.config.ts ``` ```powershell cd "C:\path\to\your-project" docker run --rm -v "${PWD}:/app" -w /app -e ORVAL_SWAGGER_URL="https://host.docker.internal:7142/swagger/v1/swagger.json" -e NODE_TLS_REJECT_UNAUTHORIZED=0 ghcr.io/orval-labs/orval --config ./orval.config.ts ``` ## Global Installation You can also install Orval globally: ```bash bun add -g orval ``` ```bash npm install orval -g ``` ```bash yarn global add orval ``` ```bash pnpm add orval -g ``` ## Verify Installation After installation, verify that Orval is installed correctly: ```bash bunx orval --version ``` ```bash npx orval --version ``` ```bash yarn orval --version ``` ```bash pnpm orval --version ``` ## Next Steps - [Quick Start](https://orval.dev/docs/quick-start): Generate your first API client - [Configuration](https://orval.dev/docs/reference/configuration): Learn about configuration options --- # Quick Start Get started with Orval in just a few minutes. ## Without a Configuration File The simplest way to use Orval is directly from the command line: ```bash orval --input ./petstore.yaml --output ./src/petstore.ts ``` - `--input` accepts any YAML or JSON-formatted OpenAPI specification - `--output` specifies the target path for the generated files ## With a Configuration File For more control, create a configuration file. Orval will automatically look for `orval.config.js`, `orval.config.ts`, or `orval.config.mjs` in your project root. ```bash orval --config ./orval.config.js # or simply orval ``` ### Example Configuration ```ts import { defineConfig } from 'orval'; export default defineConfig({ petstore: { input: './petstore.yaml', output: './src/petstore.ts', }, }); ``` ## Generate React Query Hooks Want to generate React Query hooks instead? Just set the `client` option: ```ts import { defineConfig } from 'orval'; export default defineConfig({ petstore: { input: './petstore.yaml', output: { target: './src/api/petstore.ts', schemas: './src/api/model', client: 'react-query', }, }, }); ``` ## Next Steps - [Basic Configuration](https://orval.dev/docs/guides/basics): Learn the fundamentals - [React Query](https://orval.dev/docs/guides/react-query): Generate React Query hooks - [MSW Mocking](https://orval.dev/docs/guides/msw): Generate API mocks - [Full Configuration](https://orval.dev/docs/reference/configuration/full-example): See all options --- # Basics Start by generating or defining an OpenAPI specification (example [petstore.yaml](https://github.com/orval-labs/orval/blob/master/samples/basic/petstore.yaml)). Then create a file `orval.config.ts` at the root of your project. ## Basic Configuration ```ts import { defineConfig } from 'orval'; export default defineConfig({ petstore: { output: { mode: 'single', target: './src/petstore.ts', schemas: './src/model', client: 'react-query', httpClient: 'fetch', mock: true, }, input: { target: './petstore.yaml', }, }, }); ``` ## Output Options The output options configure how and where you want to write the generated code. | Option | Description | | --- | --- | | `mode` | Specifies how files are generated. Default: `single` (one file with everything) | | `target` | Specifies where the generated file(s) will be written | | `schemas` | Specifies where the models will be written | | `client` | Selects the generated client: `angular`, `angular-query`, `axios`, `axios-functions`, `react-query`, `solid-start`, `solid-query`, `svelte-query`, `vue-query`, `swr`, `zod`, `effect`, `hono`, `fetch`, or `mcp` | | `httpClient` | Selects the request transport: `fetch` (default), `axios`, or `angular`. The available transport depends on the selected `client`. | | `mock` | Generates mocks with MSW. See the [MSW guide](/docs/guides/msw) | `client` and `httpClient` control different parts of the generated output. The `client` selects the generated API style, while `httpClient` selects the request implementation used by compatible clients. For example, the configuration above generates React Query hooks backed by the Fetch API. ## Input Options The input options configure the imported specification and any optional overrides. | Option | Description | | --- | --- | | `target` | Path or URL to the OpenAPI specification file | | `override.transformer` | Transform the specification (e.g., add a parameter to every request) | ## Next Steps - [Configuration Reference](https://orval.dev/docs/reference/configuration): See all available options - [React Query](https://orval.dev/docs/guides/react-query): Generate React Query hooks - [MSW Mocking](https://orval.dev/docs/guides/msw): Generate API mocks --- # Fetch The Fetch API is a native browser API requiring no additional dependencies. It also works in server-side frameworks and edge runtimes like Cloudflare Workers, Vercel Edge, and Deno. ## Configuration Set the `client` option to `fetch`: ```ts import { defineConfig } from 'orval'; export default defineConfig({ petstore: { output: { mode: 'tags-split', target: 'app/gen/petstore.ts', schemas: 'app/gen/models', client: 'fetch', baseUrl: 'http://localhost:3000', mock: true, }, input: { target: './petstore.yaml', }, }, }); ``` ## Runtime base URL For a host that is only known at runtime (for example `process.env.API_BASE_URL` in Node or `import.meta.env.VITE_*` in Vite), use `baseUrl.runtime` instead of a fixed string. The built-in fetch client embeds the expression in generated URL template literals, so you can keep using `override.fetch` options such as `runtimeValidation` and `includeHttpResponseReturnType` without a custom mutator. ```ts baseUrl: { runtime: 'process.env.API_BASE_URL', }, ``` See the [output `baseUrl` reference](/docs/reference/configuration/output#runtime) for `imports` and expressions such as `env.API_BASE_URL` from a shared module (for example `import { env } from '../../env'`). MSW mock host filtering uses the separate [`mock.baseUrl`](/docs/reference/configuration/output#mock-options) option. ## Generated Output Orval generates three things for each endpoint: ### 1. Response Type ```ts export type listPetsResponse = { data: Pets; status: number; }; ``` ### 2. URL Generator ```ts export const getListPetsUrl = (params?: ListPetsParams) => { const normalizedParams = new URLSearchParams(); Object.entries(params || {}).forEach(([key, value]) => { if (value === null) { normalizedParams.append(key, 'null'); } else if (value !== undefined) { normalizedParams.append(key, value.toString()); } }); return `http://localhost:3000/pets?${normalizedParams.toString()}`; }; ``` ### 3. Fetch Function ```ts export const listPets = async ( params?: ListPetsParams, options?: RequestInit, ): Promise => { const res = await fetch(getListPetsUrl(params), { ...options, method: 'GET', }); const data = await res.json(); return { status: res.status, data }; }; ``` ## Custom Fetch Function Add a custom fetch implementation via mutator: ```ts import { defineConfig } from 'orval'; export default defineConfig({ petstore: { output: { // ... override: { mutator: { path: './custom-fetch.ts', name: 'customFetch', }, }, }, // ... }, }); ``` The generated functions will use your custom implementation: ```ts export const listPets = async ( params?: ListPetsParams, options?: RequestInit, ): Promise => { return customFetch>(getListPetsUrl(params), { ...options, method: 'GET', }); }; ``` ## Shared Runtime By default every generated function carries its own request and response handling. With `override.fetch.runtimeImplementationStyle: 'shared'`, that logic is generated once into a shared `runtime.ts` and each operation becomes a thin wrapper around it. Call signatures and the `{ data, status, headers }` response shape are the same as the inline style, and MSW handlers written with `getXxxUrl` keep working. ```ts override: { fetch: { runtimeImplementationStyle: 'shared', // optional, defaults to runtime.ts next to the target runtimeOutputPath: './src/runtime.ts', }, }, ``` ```ts import { execute, serializeQuery } from './runtime'; import type { CallOptions, ResponseOf } from './runtime'; export type listPetsResponse = ResponseOf<{ 200: Pets; default: Error }>; export const getListPetsUrl = (params?: ListPetsParams) => { const stringifiedParams = serializeQuery(params); return stringifiedParams.length > 0 ? `/pets?${stringifiedParams}` : `/pets`; }; export const listPets = (params?: ListPetsParams, options?: CallOptions) => execute({ url: getListPetsUrl(params), method: 'GET', options, }); ``` Several outputs can point at the same `runtimeOutputPath` to share one runtime. A `mutator` is wired through a per-output `client-config.ts` and keeps its existing contract. Keep a shared runtime outside the target directory of any output that uses `clean: true` with the inline style, since `clean` wipes that directory. The per-status type aliases of the inline style (`listPetsResponse200`, ...) are not generated; use `StatusOf['data']` instead. See the [`runtimeImplementationStyle` reference](/docs/reference/configuration/output#runtimeimplementationstyle). ## Full Example See the [Next.js with Fetch example](https://github.com/orval-labs/orval/tree/master/samples/next-app-with-fetch) on GitHub. --- # Fetch Client The Fetch API is the default HTTP client for `swr`, `react-query`, and other query library clients. ## Generated Output When using SWR or TanStack Query, Orval generates fetch-based functions with hooks: ```ts export type listPetsResponse = { data: Pets | BadRequest; status: number; }; export const getListPetsUrl = (params?: ListPetsParams) => { const normalizedParams = new URLSearchParams(); Object.entries(params || {}).forEach(([key, value]) => { if (value === null) { normalizedParams.append(key, 'null'); } else if (value !== undefined) { normalizedParams.append(key, value.toString()); } }); return normalizedParams.size ? `http://localhost:8000/pets?${normalizedParams.toString()}` : `http://localhost:8000/pets`; }; export const listPets = async ( params?: ListPetsParams, options?: RequestInit, ): Promise => { const res = await fetch(getListPetsUrl(params), { ...options, method: 'GET', }); const data: Pets = [204, 205, 304].includes(res.status) || !res.body ? {} : await res.json(); return { status: res.status, data }; }; export const useListPets = >( params?: ListPetsParams, options?: { swr?: SWRConfiguration>, TError>; fetch?: RequestInit; }, ) => { // ... hook implementation }; ``` ## Simplified Return Type By default, the fetch response includes HTTP status, requiring `data.data` access. To return the defined type directly: ```ts import { defineConfig } from 'orval'; export default defineConfig({ petstore: { output: { // ... override: { fetch: { includeHttpResponseReturnType: false, }, }, }, }, }); ``` This changes the return type: ```diff - export type listPetsResponse = { - data: Pets | BadRequest; - status: number; - }; export const listPets = async ( params?: ListPetsParams, options?: RequestInit, - ): Promise => { + ): Promise => { // ... - return { status: res.status, data }; + return data; }; ``` ## Exact and wildcard response statuses When `includeHttpResponseReturnType` is enabled and an operation declares both an exact status and a matching wildcard status, the exact status is removed from the wildcard type. This keeps the generated response union discriminated: ```ts export type getMixedResponsesResponse200 = { data: SuccessDto; status: 200; }; export type getMixedResponsesResponse2xx = { data: GenericSuccessDto; status: Exclude; }; ``` Narrow the generated response by checking `status`; this behavior affects the generated TypeScript types and does not change how the server selects a response. ## Custom Fetch Client Use a mutator for custom fetch implementations: ```ts import { defineConfig } from 'orval'; export default defineConfig({ petstore: { output: { client: 'swr', override: { mutator: { path: './src/mutator.ts', name: 'customFetch', }, }, }, }, }); ``` --- # Custom HTTP Client Create a custom HTTP client using the mutator configuration option. ## Configuration ```ts import { defineConfig } from 'orval'; export default defineConfig({ petstore: { output: { // ... override: { mutator: { path: './api/mutator/custom-instance.ts', name: 'customInstance', }, }, }, }, }); ``` ## Custom Instance Implementation ```ts const baseURL = ''; // use your own URL or environment variable export const customInstance = async ( url: string, { method, params, body, }: { method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'; params?: any; body?: BodyType; responseType?: string; }, ): Promise => { let targetUrl = `${baseURL}${url}`; if (params) { targetUrl += '?' + new URLSearchParams(params); } const response = await fetch(targetUrl, { method, body, }); return response.json(); }; export default customInstance; // Override the return error type for react-query and swr export type ErrorType = AxiosError; // Wrap the body type if needed (e.g., for case transformation) export type BodyType = CamelCase; ``` ## Re-exporting a Mutator The file referenced by `mutator.path` can also re-export the named mutator from another module: ```ts export { customInstance } from '@acme/api-client/mutators'; ``` Named re-exports are treated as valid mutator exports. Because the re-exported implementation is outside the configured file, Orval uses the standard single-request-argument mutator shape for argument detection. Use a local wrapper function with explicit parameters when generated clients need additional request option arguments. ## Angular HTTP Client For Angular, configure a mutator for the HttpClient: ```ts import { defineConfig } from 'orval'; export default defineConfig({ petstore: { output: { // ... override: { mutator: 'src/api/mutator/response-type.ts', }, }, }, }); ``` ```ts import { HttpClient } from '@angular/common/http'; import { Observable } from 'rxjs'; const responseType = ( { url, method, params, data, }: { url: string; method: string; params?: any; data?: any; headers?: any; }, http: HttpClient, ): Observable => http.request(method, url, { params, body: data, responseType: 'json', }); export default responseType; ``` ## Full Examples - [Next.js with Fetch](https://github.com/orval-labs/orval/tree/master/samples/next-app-with-fetch) - [Angular App](https://github.com/orval-labs/orval/tree/master/samples/angular-app) --- # Custom Axios Instance Set up a custom Axios instance with interceptors, base URL, and other configurations. ## Configuration ```ts import { defineConfig } from 'orval'; export default defineConfig({ petstore: { output: { // ... httpClient: 'axios', override: { mutator: { path: './api/mutator/custom-instance.ts', name: 'customInstance', }, }, }, }, }); ``` ## Custom Instance Implementation ```ts import Axios, { AxiosRequestConfig, AxiosError } from 'axios'; export const AXIOS_INSTANCE = Axios.create({ baseURL: '', // use your own URL or environment variable }); // Add a second `options` argument to pass extra options to each query export const customInstance = ( config: AxiosRequestConfig, options?: AxiosRequestConfig, ): Promise => { const promise = AXIOS_INSTANCE({ ...config, ...options, }).then(({ data }) => data); return promise; }; // Override the return error type for react-query and swr export type ErrorType = AxiosError; // Standard body type export type BodyType = BodyData; // Or wrap the body type if processing data before sending // export type BodyType = CamelCase; ``` ## Adding Interceptors ```ts import Axios, { AxiosRequestConfig, AxiosError } from 'axios'; export const AXIOS_INSTANCE = Axios.create({ baseURL: process.env.API_URL, }); // Request interceptor for auth AXIOS_INSTANCE.interceptors.request.use( (config) => { const token = localStorage.getItem('token'); if (token) { config.headers.Authorization = `Bearer ${token}`; } return config; }, (error) => Promise.reject(error), ); // Response interceptor for error handling AXIOS_INSTANCE.interceptors.response.use( (response) => response, (error) => { if (error.response?.status === 401) { // Handle unauthorized window.location.href = '/login'; } return Promise.reject(error); }, ); export const customInstance = ( config: AxiosRequestConfig, options?: AxiosRequestConfig, ): Promise => { return AXIOS_INSTANCE({ ...config, ...options, }).then(({ data }) => data); }; export type ErrorType = AxiosError; export type BodyType = BodyData; ``` --- # Custom Base URL Set a custom base URL for each OpenAPI specification, or use the `servers` field from the spec. ## Configuration ```ts import { defineConfig } from 'orval'; export default defineConfig({ petstore: { output: { target: 'src/petstore.ts', baseUrl: '/api/v2', // or full URL: // baseUrl: 'https://petstore.swagger.io/v2', }, }, }); ``` ## From OpenAPI Specification Use the `servers` field from your OpenAPI spec: ```ts import { defineConfig } from 'orval'; export default defineConfig({ petstore: { output: { target: 'src/petstore.ts', baseUrl: { getBaseUrlFromSpecification: true, variables: { environment: 'api.dev', }, }, }, }, }); ``` ## Runtime base URL To resolve the API base URL when your app runs (for example from environment variables) while using Orval's generated fetch client and built-in `override.fetch` features, set [`baseUrl.runtime`](/docs/reference/configuration/output#runtime) in your output config. This is separate from [`mock.baseUrl`](/docs/reference/configuration/output#mock-options), which only affects generated MSW handlers. ## HTTP Client Configuration ### Axios Set a default baseURL: ```ts axios.defaults.baseURL = ''; ``` Or use an interceptor: ```ts axios.interceptors.request.use((config) => ({ ...config, baseURL: '', })); ``` Or create a [custom Axios instance](/docs/guides/custom-axios): ```ts const AXIOS_INSTANCE = axios.create({ baseURL: '' }); ``` ### Fetch Client Create a custom fetch wrapper: ```ts const getUrl = (contextUrl: string): string => { const url = new URL(contextUrl); const baseUrl = process.env.NODE_ENV === 'production' ? 'productionBaseUrl' : 'http://localhost:3000'; return new URL(`${baseUrl}${url.pathname}${url.search}`).toString(); }; export const customFetch = async ( url: string, options: RequestInit, ): Promise => { const response = await fetch(getUrl(url), options); const data = await response.json(); return { status: response.status, data } as T; }; ``` See the [complete example](https://github.com/orval-labs/orval/blob/master/samples/next-app-with-fetch/custom-fetch.ts). ### Angular HTTP Client Use an interceptor: ```ts import { Injectable } from '@angular/core'; import { HttpEvent, HttpInterceptor, HttpHandler, HttpRequest, } from '@angular/common/http'; import { Observable } from 'rxjs/Observable'; @Injectable() export class APIInterceptor implements HttpInterceptor { intercept( req: HttpRequest, next: HttpHandler, ): Observable> { const apiReq = req.clone({ url: `/${req.url}` }); return next.handle(apiReq); } } ``` Register in your module: ```ts providers: [ { provide: HTTP_INTERCEPTORS, useClass: APIInterceptor, multi: true, }, ]; ``` --- # React Query Generate fully typed [TanStack Query](https://tanstack.com/query) hooks from your OpenAPI specification. ## Configuration Set the `client` option to `react-query`: ```ts import { defineConfig } from 'orval'; export default defineConfig({ petstore: { output: { mode: 'tags-split', target: 'src/api/petstore.ts', schemas: 'src/api/model', client: 'react-query', mock: true, }, input: { target: './petstore.yaml', }, }, }); ``` ## Generated Output Orval generates one custom hook per path in your OpenAPI specification. For example, [this specification](https://github.com/orval-labs/orval/blob/master/samples/react-query/basic/petstore.yaml) generates: ```ts export const showPetById = ( petId: string, options?: AxiosRequestConfig, ): Promise> => { return axios.get(`/pets/${petId}`, options); }; export const getShowPetByIdQueryKey = (petId: string) => [`/pets/${petId}`]; export const useShowPetById = < TData = AsyncReturnType, TError = Error, >( petId: string, options?: { query?: UseQueryOptions, TError, TData>; axios?: AxiosRequestConfig; }, ) => { const { query: queryOptions, axios: axiosOptions } = options ?? {}; const queryKey = queryOptions?.queryKey ?? getShowPetByIdQueryKey(petId); const queryFn = () => showPetById(petId, axiosOptions); const query = useQuery, TError, TData>( queryKey, queryFn, { enabled: petId !== null && petId !== undefined, ...queryOptions, }, ); return { queryKey, ...query, }; }; ``` ## Infinite Queries Generate `useInfiniteQuery` hooks alongside regular queries: ```ts import { defineConfig } from 'orval'; export default defineConfig({ petstore: { output: { client: 'react-query', override: { query: { useQuery: true, useInfinite: true, useInfiniteQueryParam: 'nextId', options: { staleTime: 10000, }, }, }, }, input: { target: './petstore.yaml', }, }, }); ``` ## Per-Operation Overrides Override query options for specific operations: ```ts import { defineConfig } from 'orval'; export default defineConfig({ petstore: { output: { client: 'react-query', override: { operations: { listPets: { query: { useInfinite: true, useInfiniteQueryParam: 'cursor', }, }, }, }, }, input: { target: './petstore.yaml', }, }, }); ``` ## Set Query Data When `useSetQueryData: true` is set, Orval generates type-safe helper functions to update cached query data: ```ts export const useSetListPetsQueryData = () => { const queryClient = useQueryClient(); return ( params: ListPetsParams | undefined, updater: | Awaited> | undefined | (( old: Awaited> | undefined, ) => Awaited> | undefined), $exactMatch: boolean = true, ) => { queryClient.setQueriesData>>( { exact: $exactMatch, queryKey: getListPetsQueryKey(params) }, updater, ); }; }; ``` The helper uses [`setQueriesData`](https://tanstack.com/query/latest/docs/reference/QueryClient#queryclientsetqueriesdata) so query keys can be matched by prefix. Pass `$exactMatch: false` to match and update every cached entry sharing the given path prefix. For endpoints with query params or a body, those args are widened to accept `undefined` — pass `undefined` (together with `$exactMatch: false`) to update every cached entry sharing the same path; the updater is invoked once per matched entry. > **Warning:** > Prior to 8.11.0, the helper called `setQueryData`, which writes (and creates) a single exact-key entry. > Since 8.11.0 the helper calls `setQueriesData`, which only updates entries that already exist (it never creates one), and as of this change it matches the key exactly by default. Pass `$exactMatch: false` to match by prefix instead. > Review existing call sites when upgrading to confirm they still behave as expected. Pass `undefined` for query params or body, and set `$exactMatch` to `false`, to update every cached entry that shares the same path — useful when a mutation should patch a record across all filter/pagination variants: ```ts const updatePetList = useSetListPetsQueryData(); updatePetList( undefined, // invoked once per matched cache entry — return the new shape for that entry (old) => old?.map((pet) => (pet.id === updated.id ? updated : pet)), false, ); ``` ## Get Query Data When `useGetQueryData: true` is set, Orval generates type-safe helper functions to read cached query data: ```ts export const useGetListPetsQueryData = () => { const queryClient = useQueryClient(); return (params: ListPetsParams) => queryClient.getQueryData>>( getListPetsQueryKey(params), ); }; ``` ## Skip Token When `useSkipToken: true` is set, a query whose params are not resolved yet is held with [`skipToken`](https://tanstack.com/query/latest/docs/framework/react/guides/disabling-queries#typesafe-disabling-of-queries-using-skiptoken) rather than the generated `enabled` guard: ```ts export const getShowPetByIdQueryOptions = <...>( petId: string, options?: {...}, ) => { const queryKey = queryOptions?.queryKey ?? getShowPetByIdQueryKey(petId); const queryFn: QueryFunction<...> = ({ signal }) => showPetById(petId, signal); return { queryKey, queryFn: petId === null || petId === undefined ? skipToken : queryFn, ...queryOptions, } as ...; }; ``` This matters in two cases the `enabled` guard does not cover: - `enabled` does not gate `refetch()`, so a retry button sends the request with the unresolved param in the URL. With `skipToken` no request is sent — the query rejects with `Missing queryFn` instead, which is the tradeoff to know about before enabling the option. - `enabled` is a single option and `...queryOptions` is spread last, so `useShowPetById(petId, { query: { enabled: isTabVisible } })` replaces the generated param check. With `skipToken` the two gates are independent. Pair it with [`allParamsOptional`](/docs/reference/configuration/output#allparamsoptional) so callers can pass an unresolved param without a cast. Suspense queries are unaffected: TanStack excludes `SkipToken` from their `queryFn`, which is also why they get no `enabled` guard. ## Full Example See the [complete React Query example](https://github.com/orval-labs/orval/blob/master/samples/react-query/basic) on GitHub. --- # Vue Query Generate fully typed [TanStack Query for Vue](https://tanstack.com/query/latest/docs/vue/overview) composables from your OpenAPI specification. ## Configuration Set the `client` option to `vue-query`: ```ts import { defineConfig } from 'orval'; export default defineConfig({ petstore: { output: { mode: 'tags-split', target: 'src/api/petstore.ts', schemas: 'src/api/model', client: 'vue-query', mock: true, }, input: { target: './petstore.yaml', }, }, }); ``` ## Generated Output Orval generates one composable per path in your OpenAPI specification. For example, [this specification](https://github.com/orval-labs/orval/blob/master/samples/vue-query/petstore.yaml) generates: ```ts export const showPetById = ( petId: string, options?: AxiosRequestConfig, ): Promise> => { return axios.get(`/pets/${petId}`, options); }; export const getShowPetByIdQueryKey = (petId: string) => [`/pets/${petId}`]; export const useShowPetById = < TData = AsyncReturnType, TError = Error, >( petId: string, options?: { query?: UseQueryOptions, TError, TData>; axios?: AxiosRequestConfig; }, ) => { const { query: queryOptions, axios: axiosOptions } = options ?? {}; const queryKey = queryOptions?.queryKey ?? getShowPetByIdQueryKey(petId); const queryFn = () => showPetById(petId, axiosOptions); const query = useQuery, TError, TData>( queryKey, queryFn, { enabled: petId !== null && petId !== undefined, ...queryOptions, }, ); return { queryKey, ...query, }; }; ``` ## Reactive Parameters Generated composables accept reactive parameters so the query re-runs when an input changes. On **Vue Query v5** (which requires Vue 3.3+), every parameter is typed as [`MaybeRefOrGetter`](https://vuejs.org/api/utility-types.html#maybereforgetter) - that is `T | Ref | (() => T)` - and is unwrapped internally with [`toValue()`](https://vuejs.org/api/reactivity-utilities.html#tovalue). This lets you pass the getter directly, with no `computed` wrapper: ```ts // All three forms work: useShowPetById(petId.value); useShowPetById(petId); useShowPetById(() => props.petId); ``` `MaybeRefOrGetter` is a superset of `MaybeRef`, so existing call sites that pass refs or plain values keep working unchanged. Projects generating against Vue Query v4 (or the legacy `vue-query` v3 package) continue to use `MaybeRef`/`unref()`, since those targets may run on Vue versions older than 3.3. ## Infinite Queries Generate `useInfiniteQuery` composables: ```ts import { defineConfig } from 'orval'; export default defineConfig({ petstore: { output: { client: 'vue-query', override: { query: { useQuery: true, useInfinite: true, useInfiniteQueryParam: 'nextId', options: { staleTime: 10000, }, }, }, }, input: { target: './petstore.yaml', }, }, }); ``` ## Per-Operation Overrides Override query options for specific operations: ```ts import { defineConfig } from 'orval'; export default defineConfig({ petstore: { output: { client: 'vue-query', override: { operations: { listPets: { query: { useInfinite: true, }, }, }, }, }, input: { target: './petstore.yaml', }, }, }); ``` ## Set Query Data When `useSetQueryData: true` is set, Orval generates type-safe helper functions to update cached query data: ```ts export const setListPetsQueryData = ( queryClient: QueryClient, params: MaybeRefOrGetter | undefined, updater: | Awaited> | undefined | (( old: Awaited> | undefined, ) => Awaited> | undefined), $exactMatch: boolean = true, ) => { queryClient.setQueriesData>>( { exact: $exactMatch, queryKey: getListPetsQueryKey(params) }, updater, ); }; ``` The helper uses [`setQueriesData`](https://tanstack.com/query/latest/docs/reference/QueryClient#queryclientsetqueriesdata) so query keys can be matched by prefix. Pass `$exactMatch: false` to match and update every cached entry sharing the given path prefix. For endpoints with query params or a body, those args are widened to accept `undefined` — pass `undefined` (together with `$exactMatch: false`) to update every cached entry sharing the same path; the updater is invoked once per matched entry. > **Warning:** > Prior to 8.11.0, the helper called `setQueryData`, which writes (and creates) a single exact-key entry. > Since 8.11.0 the helper calls `setQueriesData`, which only updates entries that already exist (it never creates one), and as of this change it matches the key exactly by default. Pass `$exactMatch: false` to match by prefix instead. > Review existing call sites when upgrading to confirm they still behave as expected. ## Get Query Data When `useGetQueryData: true` is set, Orval generates type-safe helper functions to read cached query data: ```ts export const getListPetsQueryData = ( queryClient: QueryClient, params: ListPetsParams, ) => queryClient.getQueryData>>( getListPetsQueryKey(params), ); ``` ## Full Example See the [complete Vue Query example](https://github.com/orval-labs/orval/tree/master/samples/vue-query) on GitHub. --- # Pinia Colada Set `client: 'pinia-colada'` to generate request functions, query keys, query/mutation options and Vue composables. Install `@pinia/colada` 1.x (1.4.4 or later) and its Vue/Pinia peer dependencies in your application. ## Configuration ```ts import { defineConfig } from 'orval'; export default defineConfig({ pets: { input: './openapi.json', output: { target: './src/api/pets.ts', client: 'pinia-colada', httpClient: 'fetch', // or 'axios' override: { fetch: { includeHttpResponseReturnType: false } }, }, }, }); ``` Register Pinia and the `PiniaColada` plugin in your Vue application. GET and HEAD operations generate queries; other methods generate mutations. ```ts import { createApp } from 'vue'; import { createPinia } from 'pinia'; import { PiniaColada } from '@pinia/colada'; import App from './App.vue'; createApp(App).use(createPinia()).use(PiniaColada).mount('#app'); ``` ## Generated output For an operation named `getPet`, the client generates the request function, `getGetPetQueryKey`, `getGetPetQueryOptions`, and `useGetPet`. For a mutation named `createPet`, it generates `createPet`, `CreatePetMutationVariables`, `getCreatePetMutationOptions`, and `useCreatePet`. Query options factories can be used without creating a composable: ```ts const key = getGetPetQueryKey(1); const options = getGetPetQueryOptions(1, { query: { staleTime: 30_000 }, }); ``` ## Queries Operation inputs accept plain values, refs and getters. Generated query keys include the HTTP method, route and operation arguments. Colada receives matching key/request values when reactive arguments change. ```ts import { ref } from 'vue'; const petId = ref(1); const pet = useGetPet(petId, { query: { staleTime: 30_000 } }); const key = getGetPetQueryKey(1); const options = getGetPetQueryOptions(1); ``` Each reactive input is typed as `MaybeRefOrGetter`, so a path or query input can be a value, a Vue ref, or a getter: ```ts useGetPet(petId.value); useGetPet(petId); useGetPet(() => petId.value); ``` Use `options.query` for Colada options, including a custom key, and `options.request` for Fetch/Axios request options. For reactive query options, pass the composable an options getter, such as `() => ({ query: { enabled: isReady.value } })`. The query's AbortSignal is forwarded through request options when available. The built-in Fetch transport throws for HTTP failures so Colada can enter its error state. Axios preserves its normal response object; Fetch response shape follows `fetch.includeHttpResponseReturnType`. When overriding `query.key`, include every variable that affects the response. For reactive inputs, return the key from the same options getter: ```ts const pet = useGetPet(petId, () => ({ query: { key: ['pets', petId.value] }, })); ``` A constant key shared by different pet IDs would reuse the same cache entry. Invalidate custom keys using the same key factory; `getGetPetQueryKey()` still returns the default generated key. See Colada's [query key guidance](https://pinia-colada.esm.dev/guide/query-keys.html). ## Mutations Mutation variables contain the operation's path, query, header and body arguments, using their generated names and types. Pass Colada lifecycle callbacks through `options.mutation`. ```ts const cache = useQueryCache(); const createPet = useCreatePet({ mutation: { onSuccess: () => cache.invalidateQueries({ key: getListPetsQueryKey().slice(0, 2), }), }, }); await createPet.mutateAsync({ createPetBody: { name: 'Milo' } }); ``` The generator does not guess which queries a mutation invalidates. Choose the affected keys in application code. `getCreatePetMutationOptions()` also exposes reusable options without creating a composable. Mutation variables keep the generated operation argument types. The same types are used by `mutate` and `mutateAsync`, and lifecycle callbacks receive typed data, variables, and context: ```ts const createPet = useCreatePet({ mutation: { onMutate: () => ({ previousName: 'Milo' }), onSuccess(data, variables, context) { console.log(data.id, variables.createPetBody.name, context.previousName); }, }, }); createPet.mutate({ createPetBody: { name: 'Milo' } }); await createPet.mutateAsync({ createPetBody: { name: 'Luna' } }); ``` ## Request and query options Colada options are passed inside `options.query`, while Fetch or Axios request options are passed inside `options.request`: ```ts useGetPet(petId, { query: { enabled: true, staleTime: 30_000 }, request: { headers: { 'x-client': 'web' } }, }); ``` For reactive options, pass a getter so the query key and options are resolved together: ```ts const isReady = ref(true); useGetPet(petId, () => ({ query: { enabled: isReady.value }, request: { headers: { 'x-client': 'web' } }, })); ``` When `requestOptions: false` is configured, the generated operation does not accept request options and the generated Colada wrapper does not expose `options.request`. When request options are enabled together with `optionsParamRequired: true`, the request options argument is required for operations that support it. `useNamedParameters` changes the generated operation argument shape; the Pinia Colada wrapper follows that generated shape. ## Compatibility The client uses Orval's existing Fetch/Axios serializers and supports plain function mutators. Custom mutators must reject failed requests themselves. Hook mutators are not supported. JSON, multipart and URL-encoded bodies follow the request generator's existing configuration. The built-in Fetch transport is configured to reject failed HTTP responses so that Colada receives them through its error state. Axios keeps its normal response shape. A plain Fetch mutator should reject failed responses itself, and a plain Axios mutator should return the value expected by the generated request function. For example, a plain Fetch mutator can preserve the generated request contract while rejecting failed responses: ```ts export async function customFetch( url: string, options?: RequestInit, ): Promise { const response = await fetch(url, options); if (!response.ok) throw new Error(`HTTP ${response.status}`); return response.status === 204 || response.status === 205 ? (undefined as T) : response.json(); } ``` An Axios mutator can return the response data directly when that is the shape expected by the generated operation: ```ts import axios, { type AxiosRequestConfig } from 'axios'; export const customAxios = ( config: AxiosRequestConfig, options?: AxiosRequestConfig, ): Promise => axios({ ...config, ...options }).then((response) => response.data); ``` The generated query function forwards Colada's `AbortSignal` to the request when request options are available. If a reactive query input changes while a request is pending, the previous request can be cancelled by the query library. The client works with the `single`, `split`, `tags`, and `tags-split` output modes. It does not generate dedicated infinite-query helpers or Nuxt integration, and hook mutators are not supported. Use the generated options and keys with Colada's cache APIs. The sample at `samples/pinia-colada` demonstrates queries, CRUD mutations, cache invalidation, and error recovery in English. ## Full Example See the [complete Pinia Colada example](https://github.com/orval-labs/orval/tree/master/samples/pinia-colada) for Fetch and Axios output, custom mutators, payload serialization, type checks, and browser tests. --- # Svelte Query Generate fully typed [TanStack Query for Svelte](https://tanstack.com/query/latest/docs/svelte/overview) stores from your OpenAPI specification. ## Configuration Set the `client` option to `svelte-query`: ```ts import { defineConfig } from 'orval'; export default defineConfig({ petstore: { output: { mode: 'tags-split', target: 'src/api/petstore.ts', schemas: 'src/api/model', client: 'svelte-query', mock: true, }, input: { target: './petstore.yaml', }, }, }); ``` ## Generated Output Orval generates one custom store per path in your OpenAPI specification. For example, [this specification](https://github.com/orval-labs/orval/blob/master/samples/svelte-query/petstore.yaml) generates: ```ts export const showPetById = ( petId: string, options?: AxiosRequestConfig, ): Promise> => { return axios.get(`/pets/${petId}`, options); }; export const getShowPetByIdQueryKey = (petId: string) => [`/pets/${petId}`]; export const useShowPetById = < TData = AsyncReturnType, TError = Error, >( petId: string, options?: { query?: UseQueryOptions, TError, TData>; axios?: AxiosRequestConfig; }, ) => { const { query: queryOptions, axios: axiosOptions } = options ?? {}; const queryKey = queryOptions?.queryKey ?? getShowPetByIdQueryKey(petId); const queryFn = () => showPetById(petId, axiosOptions); const query = useQuery, TError, TData>( queryKey, queryFn, { enabled: petId !== null && petId !== undefined, ...queryOptions, }, ); return { queryKey, ...query, }; }; ``` ## Infinite Queries Generate infinite query stores: ```ts import { defineConfig } from 'orval'; export default defineConfig({ petstore: { output: { client: 'svelte-query', override: { query: { useQuery: true, useInfinite: true, useInfiniteQueryParam: 'nextId', options: { staleTime: 10000, }, }, }, }, input: { target: './petstore.yaml', }, }, }); ``` ## Set Query Data When `useSetQueryData: true` is set, Orval generates type-safe helper functions to update cached query data: ```ts export const setListPetsQueryData = ( queryClient: QueryClient, params: ListPetsParams | undefined, updater: | Awaited> | undefined | (( old: Awaited> | undefined, ) => Awaited> | undefined), $exactMatch: boolean = true, ) => { queryClient.setQueriesData>>( { exact: $exactMatch, queryKey: getListPetsQueryKey(params) }, updater, ); }; ``` The helper uses [`setQueriesData`](https://tanstack.com/query/latest/docs/reference/QueryClient#queryclientsetqueriesdata) so query keys can be matched by prefix. Pass `$exactMatch: false` to match and update every cached entry sharing the given path prefix. For endpoints with query params or a body, those args are widened to accept `undefined` — pass `undefined` (together with `$exactMatch: false`) to update every cached entry sharing the same path; the updater is invoked once per matched entry. > **Warning:** > Prior to 8.11.0, the helper called `setQueryData`, which writes (and creates) a single exact-key entry. > Since 8.11.0 the helper calls `setQueriesData`, which only updates entries that already exist (it never creates one), and as of this change it matches the key exactly by default. Pass `$exactMatch: false` to match by prefix instead. > Review existing call sites when upgrading to confirm they still behave as expected. ## Get Query Data When `useGetQueryData: true` is set, Orval generates type-safe helper functions to read cached query data: ```ts export const getListPetsQueryData = ( queryClient: QueryClient, params: ListPetsParams, ) => queryClient.getQueryData>>( getListPetsQueryKey(params), ); ``` ## Full Example See the [complete Svelte Query example](https://github.com/orval-labs/orval/tree/master/samples/svelte-query) on GitHub. --- # Solid Query Generate fully typed [TanStack Query for Solid](https://tanstack.com/query/latest/docs/solid/overview) primitives from your OpenAPI specification. ## Configuration Set the `client` option to `solid-query`: ```ts import { defineConfig } from 'orval'; export default defineConfig({ petstore: { output: { mode: 'tags-split', target: 'src/api/petstore.ts', schemas: 'src/api/model', client: 'solid-query', mock: true, }, input: { target: './petstore.yaml', }, }, }); ``` ## Generated Output Orval generates one primitive per path using `@tanstack/solid-query`. For example: ```ts export const showPetById = ( petId: string, options?: AxiosRequestConfig, ): Promise> => { return axios.get(`/pets/${petId}`, options); }; export const getShowPetByIdQueryKey = (petId: string) => [`/pets/${petId}`]; export const createShowPetById = < TData = Awaited>, TError = Error, >( petId: string, options?: { query?: CreateQueryOptions< Awaited>, TError, TData >; axios?: AxiosRequestConfig; }, ) => { const { query: queryOptions, axios: axiosOptions } = options ?? {}; const queryKey = queryOptions?.queryKey ?? getShowPetByIdQueryKey(petId); const queryFn = () => showPetById(petId, axiosOptions); const query = createQuery>, TError, TData>( () => ({ queryKey, queryFn, enabled: petId !== null && petId !== undefined, ...queryOptions, }), ); return { queryKey, ...query, }; }; ``` ## Key Differences from React Query Solid Query uses the `create` prefix instead of `use`: - `createQuery` instead of `useQuery` - `createMutation` instead of `useMutation` - `createInfiniteQuery` instead of `useInfiniteQuery` The generated primitives follow SolidJS conventions and work seamlessly with Solid's reactivity system. ## Infinite Queries Generate infinite query primitives: ```ts import { defineConfig } from 'orval'; export default defineConfig({ petstore: { output: { client: 'solid-query', override: { query: { useQuery: true, useInfinite: true, useInfiniteQueryParam: 'nextId', options: { staleTime: 10000, }, }, }, }, input: { target: './petstore.yaml', }, }, }); ``` TanStack Query requires `initialPageParam` and `getNextPageParam` on every infinite query, and orval cannot infer either of them. Solid Query is generated without the `as` cast the other frameworks use, so the generated primitive asks the caller for them instead of asserting over the gap: ```ts const pets = createListPetsInfinite(params, { query: { initialPageParam: undefined, getNextPageParam: (lastPage) => lastPage.nextId, }, }); ``` Supply them once in `override.query.options` instead, and they are baked into the generated primitive — the `options` argument then goes back to being optional: ```ts options: { initialPageParam: undefined, getNextPageParam: (lastPage) => lastPage.nextId, }, ``` `initialData` is not accepted on the `query` object. Pass it through the generated primitive's own overload instead, which is what preserves the distinction between `data` being defined and possibly undefined. ## SolidStart vs Solid Query If you're using SolidStart, consider using the [SolidStart client](/docs/guides/solid-start) instead, which uses native Solid Router primitives (`query()` and `action()`) for better SSR integration. ## Set Query Data When `useSetQueryData: true` is set, Orval generates type-safe helper functions to update cached query data: ```ts export const setListPetsQueryData = ( queryClient: QueryClient, params: ListPetsParams | undefined, updater: | Awaited> | undefined | (( old: Awaited> | undefined, ) => Awaited> | undefined), $exactMatch: boolean = true, ) => { queryClient.setQueriesData>>( { exact: $exactMatch, queryKey: getListPetsQueryKey(params) }, updater, ); }; ``` The helper uses [`setQueriesData`](https://tanstack.com/query/latest/docs/reference/QueryClient#queryclientsetqueriesdata) so query keys can be matched by prefix. Pass `$exactMatch: false` to match and update every cached entry sharing the given path prefix. For endpoints with query params or a body, those args are widened to accept `undefined` — pass `undefined` (together with `$exactMatch: false`) to update every cached entry sharing the same path; the updater is invoked once per matched entry. > **Warning:** > Prior to 8.11.0, the helper called `setQueryData`, which writes (and creates) a single exact-key entry. > Since 8.11.0 the helper calls `setQueriesData`, which only updates entries that already exist (it never creates one), and as of this change it matches the key exactly by default. Pass `$exactMatch: false` to match by prefix instead. > Review existing call sites when upgrading to confirm they still behave as expected. ## Get Query Data When `useGetQueryData: true` is set, Orval generates type-safe helper functions to read cached query data: ```ts export const getListPetsQueryData = ( queryClient: QueryClient, params: ListPetsParams, ) => queryClient.getQueryData>>( getListPetsQueryKey(params), ); ``` ## Full Example See the [complete Solid Query example](https://github.com/orval-labs/orval/tree/master/samples/solid-query) on GitHub. --- # Angular Query Generate [TanStack Query for Angular](https://tanstack.com/query/latest/docs/framework/angular/overview) injectable functions from your OpenAPI specification. ## Configuration ```ts import { defineConfig } from 'orval'; export default defineConfig({ petstore: { output: { mode: 'tags-split', target: 'src/petstore.ts', schemas: 'src/model', client: 'angular-query', httpClient: 'angular', mock: true, }, input: { target: './petstore.yaml', }, }, }); ``` ## Generated Output Orval generates injectable query functions using Angular's native `HttpClient`: ```ts // Base operation function export const showPetById = ( http: HttpClient, petId: string, options?: { signal?: AbortSignal | null }, ): Promise => { const url = `/pets/${petId}`; const request$ = http.get(url); if (options?.signal) { return lastValueFrom( request$.pipe(takeUntil(fromEvent(options.signal, 'abort'))), ); } return lastValueFrom(request$); }; // Query key factory export const getShowPetByIdQueryKey = (petId?: string) => [`/pets/${petId}`] as const; // Injectable query function export function injectShowPetById( petId: string | (() => string), options?: { query?: Partial> }, ): CreateQueryResult { const http = inject(HttpClient); const query = injectQuery(() => { const _petId = typeof petId === 'function' ? petId() : petId; return getShowPetByIdQueryOptions(http, _petId, options); }); return query; } ``` ## Signal Reactivity Parameters support Angular signals via getter functions: ```ts @Component({...}) export class PetDetailComponent { petId = signal('1'); // Query re-executes when petId() changes pet = injectShowPetById(() => this.petId()); // Dynamic enabled/disabled conditionalPet = injectShowPetById( () => this.petId(), () => ({ query: { enabled: this.isEnabled() } }), ); } ``` ## Mutations For POST, PUT, PATCH, DELETE operations, Orval generates mutation options factories and injectable mutation functions: ```ts // Mutation options factory export const getCreatePetsMutationOptions = ( http: HttpClient, queryClient: QueryClient, options?: { mutation?: CreateMutationOptions< Awaited>, TError, { data: CreatePetsBody }, TContext >; fetch?: RequestInit; }, ) => { const mutationKey = ['createPets']; const mutationFn = async (props: { data: CreatePetsBody }) => { return createPets(http, props.data, options?.fetch); }; return { mutationKey, mutationFn, ...options?.mutation }; }; // Injectable mutation function export const injectCreatePets = ( options?: { mutation?: CreateMutationOptions<...>; fetch?: RequestInit; }, ): CreateMutationResult<...> => { const http = inject(HttpClient); const queryClient = inject(QueryClient); const mutationOptions = getCreatePetsMutationOptions(http, queryClient, options); return injectMutation(() => mutationOptions); }; ``` Usage: ```ts @Component({...}) export class CreatePetComponent { createPet = injectCreatePets(); onSubmit(pet: CreatePetsBody) { this.createPet.mutate({ data: pet }); } } ``` ## Query Invalidation When `useInvalidate: true` is set, Orval generates helper functions to invalidate queries: ```ts export const invalidateShowPetById = async ( queryClient: QueryClient, petId: string, options?: InvalidateOptions, ): Promise => { await queryClient.invalidateQueries( { queryKey: getShowPetByIdQueryKey(petId) }, options, ); return queryClient; }; ``` ## Set Query Data When `useSetQueryData: true` is set, Orval generates type-safe helper functions to update cached query data: ```ts export const setShowPetByIdQueryData = ( queryClient: QueryClient, petId: string, updater: | Awaited> | undefined | (( old: Awaited> | undefined, ) => Awaited> | undefined), $exactMatch: boolean = true, ) => { queryClient.setQueriesData>>( { exact: $exactMatch, queryKey: getShowPetByIdQueryKey(petId) }, updater, ); }; ``` The helper uses [`setQueriesData`](https://tanstack.com/query/latest/docs/reference/QueryClient#queryclientsetqueriesdata) so query keys can be matched by prefix. Pass `$exactMatch: false` to match and update every cached entry sharing the given path prefix. For endpoints with query params or a body, those args are widened to accept `undefined` — pass `undefined` (together with `$exactMatch: false`) to update every cached entry sharing the same path; the updater is invoked once per matched entry. > **Warning:** > Prior to 8.11.0, the helper called `setQueryData`, which writes (and creates) a single exact-key entry. > Since 8.11.0 the helper calls `setQueriesData`, which only updates entries that already exist (it never creates one), and as of this change it matches the key exactly by default. Pass `$exactMatch: false` to match by prefix instead. > Review existing call sites when upgrading to confirm they still behave as expected. ## Get Query Data When `useGetQueryData: true` is set, Orval generates type-safe helper functions to read cached query data: ```ts export const getListPetsQueryData = ( queryClient: QueryClient, params: ListPetsParams, ) => queryClient.getQueryData>>( getListPetsQueryKey(params), ); ``` ## Automatic Mutation Invalidation Configure automatic invalidation when mutations succeed: ```ts import { defineConfig } from 'orval'; export default defineConfig({ petstore: { output: { client: 'angular-query', override: { query: { useInvalidate: true, mutationInvalidates: [ { onMutations: ['createPets'], invalidates: ['listPets'], }, { onMutations: ['deletePet', 'updatePet'], invalidates: [ 'listPets', { query: 'showPetById', params: ['petId'] }, ], }, ], }, }, }, }, }); ``` Usage: ```ts @Component({ template: `
    @for (pet of pets.data(); track pet.id) {
  • {{ pet.name }}
  • }
`, }) export class PetListComponent { pets = injectListPets(); deletePet = injectDeletePet(); onDelete(petId: string) { // listPets and showPetById(petId) are automatically invalidated this.deletePet.mutate({ petId }); } } ``` ### Composing with user callbacks When you provide your own `onSuccess`, both the auto-invalidation and your callback run: ```ts deletePet = injectDeletePet({ mutation: { onSuccess: () => this.snackbar.open('Pet deleted!'), }, // invalidation still runs automatically before onSuccess }); ``` ### Skipping auto-invalidation Pass `skipInvalidation: true` to opt out of auto-invalidation at runtime — for example when you need to delay or conditionally run invalidation yourself: ```ts deletePet = injectDeletePet({ mutation: { onSuccess: (_data, variables) => { // Custom delayed invalidation setTimeout(() => { this.queryClient.invalidateQueries({ queryKey: getListPetsQueryKey(), }); }, 700); }, }, skipInvalidation: true, }); ``` ## Query Options Configure infinite queries and default options: ```ts import { defineConfig } from 'orval'; export default defineConfig({ petstore: { output: { override: { query: { useQuery: true, useInfinite: true, useInfiniteQueryParam: 'nextId', options: { staleTime: 10000, }, }, }, }, }, }); ``` ## Per-Operation Configuration You can override the `query` options for a single operation or tag: ```ts import { defineConfig } from 'orval'; export default defineConfig({ petstore: { output: { override: { operations: { listPets: { query: { useInfinite: true, useInfiniteQueryParam: 'cursor', options: { staleTime: 60000, }, }, }, }, }, }, }, }); ``` ## Zod Runtime Validation When using Zod schemas, you can enable automatic runtime validation of HTTP responses. `runtimeValidation` accepts either: - `true` (shorthand for `{ strategy: 'throw' }`) to parse and throw - `{ strategy: 'both' }` to log the raw `ZodError` then throw ```ts import { defineConfig } from 'orval'; export default defineConfig({ petstore: { output: { client: 'angular-query', httpClient: 'angular', schemas: { path: 'src/model', type: 'zod' }, override: { query: { runtimeValidation: { strategy: 'both' }, }, }, }, input: { target: './petstore.yaml', }, }, }); ``` This generates: ```ts const request$ = http.get(url, { params: httpParams }).pipe( map((data) => { const result = Pets.safeParse(data); if (!result.success) { console.error('[orval] listPets response validation failed', result.error); throw result.error; } return result.data; }), ); ``` Use `runtimeValidation: true` to keep the existing `Schema.parse()` behavior. Validation is automatically skipped for primitive response types (`string`, `void`, etc.) and for operations using a custom mutator. A validated request function is typed with the schema's `zod.output` alias (`Promise` above), since that is what the parse returns at runtime — relevant when the schema transforms values through `coerce`, `useDates`, defaults or `.transform()`. Mutator operations keep the schema (input) type, as the generated parse never runs there. ## Full Example See the [Angular Query sample](https://github.com/orval-labs/orval/tree/master/samples/angular-query) on GitHub. --- # SWR Generate fully typed [SWR](https://swr.vercel.app/) hooks from your OpenAPI specification. ## Configuration Set the `client` option to `swr`: ```ts import { defineConfig } from 'orval'; export default defineConfig({ petstore: { output: { mode: 'tags-split', target: 'src/api/petstore.ts', schemas: 'src/api/model', client: 'swr', mock: true, }, input: { target: './petstore.yaml', }, }, }); ``` ## Generated Output Orval generates one custom hook per path in your OpenAPI specification. For example, [this specification](https://github.com/orval-labs/orval/blob/master/samples/react-app-with-swr/petstore.yaml) generates: ```ts export const showPetById = ( petId: string, options?: AxiosRequestConfig, ): Promise> => { return axios.get(`/pets/${petId}`, options); }; export const getShowPetByIdKey = (petId: string) => [`/pets/${petId}`]; export const useShowPetById = ( petId: string, options?: { swr?: SWRConfiguration, TError> & { swrKey?: Key; enabled?: boolean; }; axios?: AxiosRequestConfig; }, ) => { const { swr: swrOptions, axios: axiosOptions } = options ?? {}; const isEnabled = swrOptions?.enabled !== false && petId !== null && petId !== undefined; const swrKey = swrOptions?.swrKey ?? (() => (isEnabled ? getShowPetByIdKey(petId) : null)); const swrFn = () => showPetById(petId, axiosOptions); const query = useSwr, TError>( swrKey, swrFn, swrOptions, ); return { swrKey, ...query, }; }; ``` ## Usage ```tsx import { useShowPetById } from './api/petstore'; function PetDetails({ petId }: { petId: string }) { const { data, error, isLoading } = useShowPetById(petId); if (isLoading) return
Loading...
; if (error) return
Error: {error.message}
; return (

{data?.name}

ID: {data?.id}

); } ``` ## Conditional Fetching Disable the hook conditionally: ```tsx const { data } = useShowPetById(petId, { swr: { enabled: petId !== null && petId !== undefined, // Only fetch when petId is defined }, }); ``` ## Full Example See the [complete SWR example](https://github.com/orval-labs/orval/blob/master/samples/react-app-with-swr) on GitHub. --- # Angular Generate fully typed Angular services using `HttpClient` or signal-first `httpResource` functions from your OpenAPI specification. If you want the short version: - use `httpClient` for classic Angular service-based APIs - use `httpResource` for signal-first read flows - use `both` when you want resource-based reads and service-based writes together - enable `override.angular.runtimeValidation` when you generate Zod schemas and want runtime response validation ## Configuration Set `output.client` to `angular` to enable Orval's Angular generator: ```ts import { defineConfig } from 'orval'; export default defineConfig({ petstore: { output: { mode: 'tags-split', target: 'src/api/petstore.ts', schemas: 'src/api/model', client: 'angular', mock: true, }, input: { target: './petstore.yaml', }, }, }); ``` `output.client = 'angular'` selects the Angular generator. After that, use `override.angular.retrievalClient` to choose how **retrieval-style** Angular operations should be generated. The older `override.angular.client` key remains supported as a backward-compatible alias. ## Generated Output By default, the Angular client generates injectable service classes backed by `HttpClient`. Available Angular retrieval modes: - `httpClient` — keep retrievals as injectable service methods backed by `HttpClient` - `httpResource` — generate signal-first `httpResource` functions for retrieval-style operations - `both` — generate `httpResource` retrievals and keep `HttpClient` methods for imperative request handling Mutation-style operations such as create, update, delete, and most imperative `POST` calls continue to use generated `HttpClient` service methods by default. If you need different behavior for a specific operation, use an operation-level Angular override. Register the generated service via DI (standalone or module-based apps both work): ```ts import { provideHttpClient } from '@angular/common/http'; import { ApplicationConfig } from '@angular/core'; import { PetstoreService } from './api/petstore'; export const appConfig: ApplicationConfig = { providers: [provideHttpClient(), PetstoreService], }; ``` `providedIn` applies to generated service classes only. `httpResource` functions are plain exports, so you import and call them directly where you need them. ## Choosing the Angular output mode Use `override.angular.retrievalClient` to choose the generated Angular retrieval mode: ```ts import { defineConfig } from 'orval'; export default defineConfig({ petstore: { output: { mode: 'tags-split', target: 'src/api/petstore.ts', schemas: 'src/api/model', client: 'angular', override: { angular: { retrievalClient: 'httpClient', }, }, }, input: { target: './petstore.yaml', }, }, }); ``` If you omit `override.angular.retrievalClient` (or the legacy `override.angular.client` alias), Orval uses the default Angular `HttpClient` service-class output. ### Which mode should you choose? | `override.angular.retrievalClient` | What Orval generates | Choose it when | |---|---|---| | `httpClient` | Retrievals stay on injectable service classes backed by `HttpClient` | You want conventional Angular services, imperative request methods, and the broadest compatibility with request options and mutators | | `httpResource` | Retrieval-style operations become signal-first `httpResource` functions; mutations still use `HttpClient` service methods | You want Angular-native signal ergonomics for reads and prefer resource-based data fetching | | `both` | Service classes plus sibling `*.resource.ts` retrieval helpers | You want signal-first reads and imperative writes together | Quick rule of thumb: - choose `httpClient` for classic Angular service-based APIs - choose `httpResource` for signal-first read flows - choose `both` when your app wants both patterns side-by-side - if you do not set `override.angular.retrievalClient`, Orval defaults to `httpClient` - create, update, delete, and other mutation-style operations still use `HttpClient` methods by default ## Setting the Backend URL ### Single API Use an HTTP interceptor to automatically add the API base URL. In modern standalone Angular apps, a functional interceptor keeps the setup compact: ```ts import { HttpInterceptorFn } from '@angular/common/http'; export const apiInterceptor: HttpInterceptorFn = (req, next) => { return next( req.clone({ url: `https://api.example.com${req.url}`, }), ); }; ``` ```ts import { provideHttpClient, withInterceptors } from '@angular/common/http'; import { ApplicationConfig } from '@angular/core'; import { apiInterceptor } from './api.interceptor'; export const appConfig: ApplicationConfig = { providers: [provideHttpClient(withInterceptors([apiInterceptor]))], }; ``` An interceptor is global, though: it can only route by sniffing the outgoing `req.url`. That falls apart once you generate more than one Angular API into the same app and sit them behind a gateway or proxy that assigns each API its own path prefix (or host) — the interceptor has no reliable way to know which generated client a given request came from. ### DI-based base URL composition (multiple APIs / gateway routing) Set `override.angular.baseUrl` to compose the base URL for a specific output through Angular's dependency injection instead of a global interceptor. Unlike the interceptor, the base URL is resolved per generated API, so a gateway that maps different generated clients to different upstream paths can be modeled directly in DI. `apiId` is required and explicit — Orval never derives it from the specification title or file name — so the generated token and helper names stay stable across regenerations: ```ts import { defineConfig } from 'orval'; export default defineConfig({ petstore: { output: { mode: 'tags-split', target: 'src/api/petstore.ts', schemas: 'src/api/model', client: 'angular', override: { angular: { baseUrl: { apiId: 'petstore' }, }, }, }, input: { target: './petstore.yaml', }, }, }); ``` #### Generated artifacts With `override.angular.baseUrl` set, Orval emits a sibling `.base-url.ts` file alongside the generated client: ```ts import { InjectionToken, inject, type Provider } from '@angular/core'; export const PETSTORE_SERVER_URL: string = 'http://petstore.swagger.io/v1'; export function normalizeBaseUrl(baseUrl: string): string { return baseUrl.replace(/\/+$/, ''); } export interface PetstoreBaseUrlResolverContext { readonly apiId: 'petstore'; readonly serverUrl: string; } export type PetstoreBaseUrlResolver = ( context: PetstoreBaseUrlResolverContext, ) => string; export const PETSTORE_BASE_URL_RESOLVER = new InjectionToken('PETSTORE_BASE_URL_RESOLVER', { providedIn: 'root', factory: (): PetstoreBaseUrlResolver => (context) => context.serverUrl, }); export const PETSTORE_BASE_URL = new InjectionToken( 'PETSTORE_BASE_URL', { providedIn: 'root', factory: (): string => { const resolver = inject(PETSTORE_BASE_URL_RESOLVER); return normalizeBaseUrl( resolver({ apiId: 'petstore', serverUrl: PETSTORE_SERVER_URL }), ); }, }, ); export function providePetstoreBaseUrl(baseUrl: string): Provider { return { provide: PETSTORE_BASE_URL, useValue: normalizeBaseUrl(baseUrl) }; } export function providePetstoreBaseUrlResolver( resolver: PetstoreBaseUrlResolver, ): Provider { return { provide: PETSTORE_BASE_URL_RESOLVER, useValue: resolver }; } ``` Identifiers are derived from `apiId` alone (`petstore` → `PETSTORE_*` / `Petstore*` / `providePetstore*`), so they stay collision-free when multiple outputs with different `apiId`s are generated into the same app. Every generated `HttpClient` service method and `httpResource` function in this output injects `PETSTORE_BASE_URL` and prefixes its route with it. #### Precedence `PETSTORE_BASE_URL` resolves in this order: 1. A directly provided value via `providePetstoreBaseUrl(...)` — wins outright, the resolver below is never invoked. 2. A resolver provided via `providePetstoreBaseUrlResolver(...)`. 3. The default resolver factory, which just returns the embedded server URL. 4. The embedded `PETSTORE_SERVER_URL` constant, resolved at generation time from the specification's `servers` field (empty string `''` when the specification has no `servers` entry, which yields relative URLs). Whatever value wins is passed through `normalizeBaseUrl`, which strips trailing slashes (`'/api/x/'` → `'/api/x'`, `'https://h/'` → `'https://h'`, `'/'` → `''`). Generated routes always start with `/`, so plain interpolation (`` `${baseUrl}${route}` ``) can never double up or drop the separator between them. #### Multiple APIs behind one gateway Because `InjectionToken` identity is per output, two generated outputs can't literally share one token instance without a shared runtime package — Orval's Angular output has no runtime dependency, by design. Instead, share one resolver *function* and register it against each output's resolver token. Type it against a small structural interface so it's assignable to every generated `BaseUrlResolver`, regardless of `apiId`: ```ts import { provideHttpClient } from '@angular/common/http'; import { ApplicationConfig } from '@angular/core'; import { providePetstoreBaseUrlResolver } from './api/petstore.base-url'; import { provideInventoryBaseUrlResolver } from './api/inventory.base-url'; interface GatewayContext { apiId: string; serverUrl: string; } // One registry, one resolver function, shared across every generated API. const gatewayRoutes: Record = { petstore: '/gateway/petstore', inventory: '/gateway/inventory', }; const gatewayResolver = (ctx: GatewayContext): string => gatewayRoutes[ctx.apiId] ?? ctx.serverUrl; export const appConfig: ApplicationConfig = { providers: [ provideHttpClient(), providePetstoreBaseUrlResolver(gatewayResolver), provideInventoryBaseUrlResolver(gatewayResolver), ], }; ``` Each output still resolves its *own* token independently — `gatewayResolver` is just dispatched with a different `apiId` depending on which token invoked it — so requests from the petstore client and the inventory client can land on different upstream paths (or hosts) through the same gateway. #### Testing Override the token (or the resolver) in `TestBed` like any other provider: ```ts import { provideHttpClient } from '@angular/common/http'; import { HttpTestingController, provideHttpClientTesting, } from '@angular/common/http/testing'; import { TestBed } from '@angular/core/testing'; import { providePetstoreBaseUrl, providePetstoreBaseUrlResolver, } from './api/petstore.base-url'; import { PetsService } from './api/pets/pets.service'; describe('PetsService', () => { let httpMock: HttpTestingController; beforeEach(() => { TestBed.configureTestingModule({ providers: [ provideHttpClient(), provideHttpClientTesting(), providePetstoreBaseUrl('/gateway/petstore'), // or: providePetstoreBaseUrlResolver((ctx) => `/gateway/${ctx.apiId}`), ], }); httpMock = TestBed.inject(HttpTestingController); }); afterEach(() => httpMock.verify()); it('prefixes requests with the provided base URL', () => { TestBed.inject(PetsService) .createPets({ name: 'Rex', tag: 'dog' }) .subscribe(); httpMock.expectOne('/gateway/petstore/v1/pets').flush(null); }); }); ``` The same token, provided once in `TestBed`, backs both the `HttpClient` service and any `httpResource` functions generated for the same output — so a single provider override is enough to redirect every request in a test. #### `httpResource` and injection context Generated `httpResource` functions read the token with `inject()` when called inside an injection context, and fall back to `options.injector.get(...)` when an explicit `injector` is passed — the same rule that already applies to every other injected dependency in generated `httpResource` functions: ```ts export function showPetByIdResource( petId: Signal, accept?: ShowPetByIdAccept, version?: Signal, options?: OrvalHttpResourceOptions, ): HttpResourceRef { const baseUrl = options?.injector ? options.injector.get(PETSTORE_BASE_URL) : inject(PETSTORE_BASE_URL); // ... } ``` Call these functions during Angular's injection context (a constructor, a field initializer, or `runInInjectionContext`), or pass an explicit `injector` in `options` when you can't. #### Notes - **Zod runtime validation is unaffected.** `override.angular.runtimeValidation` keeps validating responses exactly as before — the base URL token only changes how the request URL is composed, not how the response is parsed. - **Custom mutators receive the composed URL.** The `${baseUrl}` prefix is applied to the route before it's handed to `generateMutatorConfig`, so a configured `mutator` sees the same fully composed URL a plain `HttpClient` call would use. - **MSW mocks stay relative.** Mock route matching is unaffected by `override.angular.baseUrl` — MSW handlers keep matching on the route path, not the composed base URL. - **Mutually exclusive with `output.baseUrl`.** `output.baseUrl` bakes a static prefix into every generated route string for *all* clients; combining it with `override.angular.baseUrl` would double-prefix (or conflict with) every URL, so Orval throws a config-time error if both are set on the same output. Remove `output.baseUrl` and use `providePetstoreBaseUrlResolver` if you need the equivalent of a runtime-configurable prefix. - **`apiId` is always explicit.** It's never derived from the specification's `info.title` or the target file name, so renaming your spec or output file doesn't silently rename the generated DI tokens. - Only the specification's top-level `servers` field is embedded as the fallback URL. If your specification sets `servers` per path/operation, the token still falls back to the spec-level `servers` entry (selected via `index`/`variables`), not a per-path override. ## httpResource Output (Angular v19.2+) Enable the `httpResource` retrieval mode with `override.angular.retrievalClient`. This mode targets Angular's `httpResource` API, which is available in Angular v19.2+: ```ts import { defineConfig } from 'orval'; export default defineConfig({ petstore: { output: { mode: 'tags-split', target: 'src/api/http-resource/petstore.ts', schemas: 'src/api/model', client: 'angular', override: { angular: { retrievalClient: 'httpResource', }, }, }, input: { target: './petstore.yaml', }, }, }); ``` Generated resource functions use signals and return `HttpResourceRef`: ```ts export function showPetByIdResource( petId: Signal, ): HttpResourceRef { return httpResource(() => `/pets/${petId()}`); } ``` Because Orval returns Angular's native `HttpResourceRef`, you automatically get Angular's resource APIs such as `hasValue()`, `status()`, `error()`, `reload()`, and the resource snapshot API for advanced composition. ### Which operations become `httpResource` functions? Orval generates `httpResource` helpers for retrieval-style operations. - `GET` operations are generated as resources - retrieval-style `POST` operations, such as search/list/find/query endpoints, can also be generated as resources - mutation-style operations remain `HttpClient` service methods If you need to override the default classification for a specific operation, use an operation-level Angular override: ```ts import { defineConfig } from 'orval'; export default defineConfig({ petstore: { output: { client: 'angular', target: 'src/api/petstore.ts', override: { angular: { retrievalClient: 'httpResource', }, operations: { searchPets: { angular: { retrievalClient: 'httpResource', }, }, }, }, }, input: { target: './petstore.yaml', }, }, }); ``` This is especially useful when your API uses `POST` for retrieval-style endpoints. ### Consume generated resources safely Guard `value()` reads with `hasValue()`. Angular resources throw if you read `value()` while the resource is in an error state, so `hasValue()` is the safe and recommended gate. ```ts import { computed, signal } from '@angular/core'; import { showPetByIdResource } from './api/http-resource/pets.service'; const petId = signal('1'); const petResource = showPetByIdResource(petId); const petName = computed(() => { if (!petResource.hasValue()) { return undefined; } return petResource.value().name; }); ``` Prefer returning a fallback from a `computed()` when `hasValue()` is false instead of reading `value()` unconditionally. That keeps loading and error states safe in templates and component logic. ### Multiple content types When an operation can return multiple response content types, generated resources may expose an `accept` parameter and return different result types for different `Accept` values. ```ts export function showPetByIdResource( petId: Signal, accept: 'application/json', ): HttpResourceRef; export function showPetByIdResource( petId: Signal, accept: 'text/plain', ): HttpResourceRef; ``` This keeps the generated Angular API aligned with your OpenAPI response matrix, including plain-text, blob, and array-buffer resource variants where needed. Content-negotiated resources are just as reactive as single-content-type ones: every signal input — path params, the `params` query object, `version`, and request-body signals — is read inside the `httpResource` factory, so updating any of them triggers a refetch. The `accept` argument itself is a plain value, not a signal: it only selects which resource variant (`httpResource`, `.text`, or `.arrayBuffer`) gets created, so to switch content types you create a new resource rather than mutating a signal. Because `accept` is read once at creation time, don't wrap it in a signal — call the resource function again with a different `accept` value if you need to switch content types at runtime. ### `both` mode Use `both` when you want signal-first retrievals and imperative `HttpClient` methods in the same generated area: ```ts import { defineConfig } from 'orval'; export default defineConfig({ petstore: { output: { mode: 'tags-split', target: 'src/api/petstore.ts', schemas: 'src/api/model', client: 'angular', override: { angular: { retrievalClient: 'both', }, }, }, input: { target: './petstore.yaml', }, }, }); ``` In `both` mode, Orval keeps the service class in your main generated file and emits the retrieval resources in a sibling `*.resource.ts` file. ```ts import { PetstoreService } from './api/petstore'; import { listPetsResource } from './api/petstore.resource'; ``` This separation works well when your app prefers signal-first reads but still needs service methods for writes or imperative request flows. ### httpResource options You can customize generated `httpResource` calls through `override.angular.httpResource`: ```ts import { defineConfig } from 'orval'; export default defineConfig({ petstore: { output: { mode: 'tags-split', target: 'src/api/http-resource/petstore.ts', schemas: { type: 'zod', path: 'src/api/model-zod', }, client: 'angular', override: { angular: { retrievalClient: 'httpResource', httpResource: { defaultValue: { id: 'fallback' }, debugName: 'getPetByIdResource', }, }, }, }, input: { target: './petstore.yaml', }, }, }); ``` Common options: - `defaultValue` — initial value exposed while the resource is idle/loading - `debugName` — name shown in Angular DevTools Advanced options: - `injector` — raw expression passed to `HttpResourceOptions.injector` - `equal` — raw expression passed to `HttpResourceOptions.equal` For more advanced cases, you can also pass raw expressions for Angular's resource options: ```ts override: { angular: { retrievalClient: 'httpResource', httpResource: { injector: 'inject(Injector)', equal: '(a, b) => a?.id === b?.id', }, }, } ``` Operation-level `httpResource` options override global ones: ```ts override: { angular: { retrievalClient: 'httpResource', httpResource: { debugName: 'globalPetResource', }, }, operations: { showPetById: { angular: { retrievalClient: 'httpResource', httpResource: { debugName: 'showPetByIdResource', }, }, }, }, } ``` Orval also emits a shared helper type for these options. The exact default generic for `TOmitParse` depends on the generated file, but the emitted shape is: ```ts export type OrvalHttpResourceOptions< TValue, TRaw = unknown, TOmitParse extends boolean = false, > = (TOmitParse extends true ? Omit, 'parse'> : HttpResourceOptions) & OrvalHttpResourceRequestExtension; ``` When `defaultValue` is configured, generated resource overloads return `HttpResourceRef` instead of `HttpResourceRef`. ### Request headers and HttpContext Generated `httpResource` helpers can expose the same request-descriptor surface as the generated `HttpClient` service methods: OpenAPI `in: header` parameters, plus a call-site escape hatch for extra headers and Angular's `HttpContext` (for example to flag a request for a global HTTP interceptor). **OpenAPI `in: header` parameters.** When the top-level `output.headers` option is enabled, header parameters declared in your spec are generated as a `headers: Signal` argument and forwarded on every request: ```ts import { defineConfig } from 'orval'; export default defineConfig({ petstore: { output: { target: 'src/api/http-resource/petstore.ts', schemas: 'src/api/model', client: 'angular', headers: true, // generate `in: header` parameters override: { angular: { retrievalClient: 'httpResource', }, }, }, input: { target: './petstore.yaml', }, }, }); ``` ```ts const correlationId = signal('abc-123'); const headers = computed(() => ({ 'X-Correlation-Id': correlationId() })); listPetsResource(params, headers); ``` **Extra headers and `HttpContext` at the call site.** Every generated `httpResource` helper's trailing options bag also accepts `headers`, `context`, and `request` — independent of whether the operation declares any `in: header` parameters. This mirrors the `HttpClientOptions` already available on generated `HttpClient` service methods, so you no longer have to drop down to `HttpClient` just to attach interceptor policy or a per-request header. The extension fields live on the **trailing options parameter** — the same bag that carries `defaultValue`/`debugName`. Pass every other generated argument first: path/query signals, an `in: header` signal when `output.headers` is enabled, and the `accept` argument on multi-content helpers all come before the options bag. ```ts import { HttpContext } from '@angular/common/http'; import { BYPASS_GLOBAL_ERROR_HANDLER } from './http-context-tokens'; const requestId = signal('req-42'); // operation with a single path param and one content type: // the options bag is the second argument const itemEvents = getItemEventsResource(itemId, { headers: () => ({ 'x-request-id': requestId() }), context: new HttpContext().set(BYPASS_GLOBAL_ERROR_HANDLER, true), }); // multi-content operation from the petstore sample // (signature: petId, accept, version?, options?) — fill every generated // parameter before the options bag, using `undefined` for optional ones // you want to skip const pet = showPetByIdResource(petId, 'application/json', undefined, { context: new HttpContext().set(BYPASS_GLOBAL_ERROR_HANDLER, true), }); ``` - `headers` — extra headers merged over any generated headers. Accepts either an `HttpHeaders` instance / plain record, or a function returning one. - `context` — an Angular `HttpContext` forwarded to the underlying request. Accepts either an `HttpContext` instance or a function returning one. - `request` — a last-resort escape hatch that receives the fully-built `HttpResourceRequest` and returns the request Orval actually sends. > **Note:** > Pass the function form (`headers: () => ...`, `context: () => ...`, or > `request: (req) => ...`) whenever the value reads a signal. Orval invokes > these functions **inside** the `httpResource` reactive factory, so the > resource automatically reloads when the signals they read change. A plain > object or `HttpContext` instance is read once per request computation and > does not itself trigger a reload. **Precedence.** Generated OpenAPI headers are applied first, extra `headers` from the options bag are merged on top (overriding any header with the same name), and `request` runs last. For operations with multiple success content types, the generated `Accept` header is always re-applied after your extension so response-branch dispatch (the `accept` argument) stays correct — your `headers`/`context`/`request` can add or override any other header, but never `Accept`. Orval emits the extension as a small, reusable surface alongside `OrvalHttpResourceOptions`: ```ts export interface OrvalHttpResourceRequestExtension { headers?: HttpResourceRequest['headers'] | (() => HttpResourceRequest['headers']); context?: HttpContext | (() => HttpContext); request?: (request: HttpResourceRequest) => HttpResourceRequest; } ``` `applyOrvalRequestExtension` is the emitted helper that applies these fields; you won't normally call it directly, but you may see it referenced from `httpResource` factories in the generated output. Testing works the same way as any other generated `httpResource` call — with `HttpTestingController`, assert on the intercepted request's `headers` and `context`: ```ts const req = httpMock.expectOne('/items/42/events'); expect(req.request.headers.get('x-request-id')).toBe('req-42'); expect(req.request.context.get(BYPASS_GLOBAL_ERROR_HANDLER)).toBe(true); ``` ### Mutator compatibility `httpResource` output supports request-compatible mutators, but it cannot use `HttpClient`-style mutators that require the generated `HttpClient` instance as an extra argument. If your mutator depends on that injected `HttpClient` parameter, prefer `httpClient` or `both` mode for that operation. ## httpResource + Zod schema types When `schemas.type` is set to `zod`, Orval can generate Angular `httpResource` functions alongside the Zod-backed model files. The generated resources still return Angular's native `HttpResourceRef`, so the main best-practice remains the same: guard `value()` reads with `hasValue()` and test loading/error states the same way you would for any other `httpResource`. With `override.angular.runtimeValidation: true`, generated JSON resources also emit `parse: Schema.parse` and expose Zod output types such as `PetOutput` and `PetsOutput`. > **Note:** > Zod is a runtime dependency when you enable `schemas.type: 'zod'`. > Make sure your app installs `zod` (for example, `zod` in `dependencies`). ```ts import { defineConfig } from 'orval'; export default defineConfig({ petstore: { output: { mode: 'tags-split', target: 'src/api/http-resource-zod/petstore.ts', schemas: { type: 'zod', path: 'src/api/model-zod', }, client: 'angular', override: { angular: { retrievalClient: 'httpResource', runtimeValidation: true, // opt-in }, }, }, input: { target: './petstore.yaml', }, }, }); ``` ## Testing generated `httpResource` functions Generated resources use Angular's standard `HttpClient` stack, so test them the same way you test any other `HttpClient`-based code: configure `provideHttpClient()` before `provideHttpClientTesting()`, flush requests with `HttpTestingController`, then wait for Angular to propagate the new signal values. ```ts import { provideHttpClient } from '@angular/common/http'; import { HttpTestingController, provideHttpClientTesting, } from '@angular/common/http/testing'; import { TestBed } from '@angular/core/testing'; import { PetsPage } from './pets.page'; describe('PetsPage', () => { let httpMock: HttpTestingController; beforeEach(async () => { await TestBed.configureTestingModule({ imports: [PetsPage], providers: [provideHttpClient(), provideHttpClientTesting()], }).compileComponents(); httpMock = TestBed.inject(HttpTestingController); }); afterEach(() => { httpMock.verify(); }); it('renders fetched pets', async () => { const fixture = TestBed.createComponent(PetsPage); fixture.detectChanges(); const req = httpMock.expectOne('/v1/pets'); expect(req.request.method).toBe('GET'); req.flush([{ id: 1, name: 'Rex', requiredNullableString: null }]); await fixture.whenStable(); fixture.detectChanges(); expect(fixture.nativeElement.textContent).toContain('Rex'); }); }); ``` This is also the right place to verify loading and backend-error behavior for `schemas.type: 'zod'` outputs, especially when your component uses guarded `hasValue()` checks to avoid unsafe `value()` reads. When `override.angular.runtimeValidation` is enabled, add at least one test that flushes an invalid JSON payload and asserts that the resource exposes a `ZodError`. ## Zod Runtime Validation Angular output supports runtime validation for Zod-backed responses when `schemas.type` is set to `zod` and `override.angular.runtimeValidation` is enabled. For JSON model responses, the default `throw` strategy runs `Schema.parse()` on eligible responses. The `both` strategy uses `safeParse()`, logs the raw `ZodError` with `console.error`, then re-throws. This applies to generated Angular `HttpClient` services too, not just `httpResource`. If you keep the default Angular retrieval mode (`retrievalClient: 'httpClient'`), eligible generated `GET` methods already pipe JSON model responses through Zod. ### Setup 1. Set `schemas.type` to `zod` to generate Zod schemas 2. Set `override.angular.runtimeValidation` to either: - `true` (shorthand for `{ strategy: 'throw' }`) - `{ strategy: 'both' }` for log-then-throw behavior - either object form with `requestBodies: true` to also parse JSON request bodies before they are sent (see [Parsing request bodies before they are sent](#parsing-request-bodies-before-they-are-sent)) ```ts import { defineConfig } from 'orval'; export default defineConfig({ petstore: { output: { mode: 'tags-split', target: 'src/api/petstore.ts', schemas: { type: 'zod', path: 'src/api/schemas', }, client: 'angular', override: { angular: { runtimeValidation: { strategy: 'both' }, }, }, }, input: { target: './petstore.yaml', }, }, }); ``` ### How It Works For generated `HttpClient` services, JSON responses are validated in the RxJS pipeline. Generated `httpResource` JSON retrievals use Angular's `HttpResourceOptions.parse` hook with the same strategy semantics. ```ts // Generated code (simplified) getPet(options?: HttpClientBodyOptions): Observable { return this.http .get(`/pet`, { observe: 'body' }) .pipe(map((data) => Pet.parse(data))); } showPetByIdResource( petId: Signal, ): HttpResourceRef { return httpResource(() => `/pet/${petId()}`, { parse: Pet.parse, }); } ``` **Validation applies to:** - JSON model responses in generated `HttpClient` services - JSON model responses returned by generated `GET` methods - JSON model responses returned by generated mutation methods such as `POST`, `PUT`, `PATCH`, and `DELETE` - JSON `httpResource` retrievals with model response types **Validation is skipped for:** - Primitive types (`string`, `number`, `boolean`, `void`, `unknown`) - Custom mutator paths - Non-JSON content types (text, blob, arrayBuffer) For generated `HttpClient` services, runtime validation also applies when the JSON response body is surfaced through `response` and response-event observe flows. ### Parsing request bodies before they are sent When you generate Zod schemas, Orval also emits request-body schemas such as `CreatePetsBody`, plus matching `z.input<>` / `z.output<>` types. By default, generated `HttpClient` methods only use those types. They send the body as the caller passed it. Set `requestBodies: true` to parse JSON request bodies before transport: ```ts override: { angular: { runtimeValidation: { strategy: 'throw', requestBodies: true, }, }, } ``` The generated method then looks like this: ```ts createPets(createPetsBody: CreatePetsBody, options?: HttpClientObserveOptions) { return defer(() => { const parsedCreatePetsBody = CreatePetsBody.parse(createPetsBody); // The real method branches on `options.observe`; each branch sends the parsed body. return this.http.post(url, parsedCreatePetsBody, options); }); } ``` An invalid body never reaches the network. The observable errors with the `ZodError`, so `catchError` and the `error` callback see it like any other request failure. Parsing happens on subscribe, not when you call the method. The parameter keeps the schema's input type, and the parsed output is what gets sent. Defaults and transforms in the schema therefore apply to the payload. Form data, url-encoded, binary and text bodies are skipped, and so are operations with a custom `mutator`. So is a body with a key that [`namingConvention.properties`](/docs/reference/configuration/output#namingconvention-property-keys) renames. Orval logs a warning for that operation and sends the body exactly as passed, without validation, defaults or transforms. See [`runtimeValidation.requestBodies`](/docs/reference/configuration/output#requestbodies) for the details. Without the option, validate before calling the generated client: ```ts const payload = CreatePetsBody.parse(formValue); return this.petsService.createPets(payload); ``` That is also the way to go when you want field-level feedback in a form: `.safeParse()` gives you the issues without throwing. ## Request-body readonly guidance Angular output also supports `override.preserveReadonlyRequestBodies` to control how OpenAPI `readOnly` fields are handled in generated request body types: - `'strip'` (default) — recommended for most OpenAPI specs - `'preserve'` — use when your request DTOs are intentionally immutable ```ts export default defineConfig({ petstore: { output: { override: { preserveReadonlyRequestBodies: 'strip', }, }, }, }); ``` `'strip'` is the best default because OpenAPI `readOnly` properties are usually response-oriented. The same guidance applies to both Angular `HttpClient` and `httpResource` output: `httpResource` can still issue request payloads, so it should not preserve readonly request fields by default. ### Runtime validation support matrix | Client | Config key | Runtime validation | |--------|------------|--------------------| | `angular` | `override.angular.runtimeValidation` | ✅ Supported | | `angular-query` | `override.query.runtimeValidation` | ✅ Supported | | `fetch` | `override.fetch.runtimeValidation` | ✅ Supported | | custom mutator paths | varies | ⚠️ See [#2858](https://github.com/orval-labs/orval/issues/2858) | ### Error Handling When validation fails, Zod throws a `ZodError`. For `HttpClient` services, handle it in your subscription, effect, or error interceptor: ```ts this.petService.getPet().subscribe({ next: (pet) => console.log('Validated pet:', pet), error: (err) => { if (err.name === 'ZodError') { console.error('Response validation failed:', err.issues); } }, }); ``` For generated `httpResource` helpers, read the error through the resource's `error()` signal and keep `value()` reads guarded with `hasValue()`. ## Query parameter filtering Angular's `HttpParams` only accepts primitives (`string`, `number`, `boolean`, and arrays of those). Anything else — objects, arrays of objects, `null`, `undefined` — is rejected or silently stringified to `"[object Object]"`. To make `HttpParams` safe, the generated Angular client passes every query parameter through an internal `filterParams` helper before handing it to `HttpClient`. ### Default behaviour (schema-aware passthrough) 1. **Primitive params** keep the existing nullish-stripping, with one exception: a param the spec declares both `required` and `nullable`. Silently dropping a required param would send a request that violates the OpenAPI contract, so it is never simply removed. Without a `paramsSerializer`, a `null` value for such a param is sent as an empty string (`?key=`) so the key still reaches the wire. With a `paramsSerializer` configured, the literal `null` is passed through for the serializer to encode instead. Optional nullable params are still dropped when `null` — only required-nullable params get this treatment. See [issue #3712](https://github.com/orval-labs/orval/issues/3712). 2. **Schema-declared object params** are serialized according to the OpenAPI parameter's `style`/`explode` (see [Object query parameters](#object-query-parameters-style--explode) below) **when no `paramsSerializer` is configured**. When a `paramsSerializer` *is* configured, the object is instead passed through untouched — that serializer becomes the consumer responsible for turning the raw object into something `HttpParams` can send. 3. **Arrays of objects** are passed through untouched **only when a `paramsSerializer` is configured**; without one (or a `paramsFilter`, see below) there is no consumer that can handle a raw object, so the param is dropped. OpenAPI defines no style/explode encoding for arrays of objects, so — unlike plain object params — this case is unaffected by the object-serialization behavior above. ```yaml parameters: - name: filters in: query schema: type: object additionalProperties: type: string ``` With the spec above and **no** `paramsSerializer` configured, the generated client now serializes `filters` per the OpenAPI default (`style: form`, `explode: true`) — spreading its properties as top-level query params. With a configured `paramsSerializer` (e.g. `qs.stringify`), the generated client instead preserves `filters` as a raw object and forwards it to the serializer. If you need the raw object even without a serializer — for example a `mutator` that flattens it into bracketed keys — use `override.paramsFilter` (see below). See [issue #3326](https://github.com/orval-labs/orval/issues/3326) and [issue #3705](https://github.com/orval-labs/orval/issues/3705) for the motivating bugs. ### Object query parameters (style & explode) > Added in the fix for > [issue #3705](https://github.com/orval-labs/orval/issues/3705). OpenAPI's default for an `in: query` parameter is `style: form` + `explode: true`. For a `type: object` schema that means the object's properties are spread as separate top-level query params — not nested under the parameter's own name. Orval's generated Angular client now honors this (and the `form` + `explode: false` and `deepObject` variants) at request time, as long as no `paramsSerializer`/`paramsFilter` is configured for the operation. ```yaml paths: /catalog-items: get: operationId: searchCatalog parameters: - name: arg0 in: query schema: type: object properties: itemReferences: type: array items: type: string pageNumber: type: integer ``` ```ts export type SearchCatalogParams = { arg0?: { itemReferences?: string[]; pageNumber?: number; }; }; ``` ```ts searchCatalog({ arg0: { itemReferences: ['a', 'b'], pageNumber: 1 } }); // -> GET /catalog-items?itemReferences=a&itemReferences=b&pageNumber=1 ``` The generated parameter **type** intentionally keeps the shape your spec declares — `arg0` stays nested, mirroring the parameter name reported by your tooling. Orval does not invent a flatter name for it, since that name is usually an artifact of the source toolchain rather than something meaningful in your API. If `arg0` (or similar) is showing up because your Spring/ springdoc-based backend isn't compiled with parameter names preserved, fix it at the source instead: enable the `-parameters` javac flag (or annotate the controller method parameter with `@ParameterObject` from `springdoc-openapi`) so the generated spec reports the real parameter name. | `style` | `explode` | Wire format | Strategy | | --- | --- | --- | --- | | `form` (default) | `true` (default) | `itemReferences=a&itemReferences=b&pageNumber=1` | `flatten` | | `form` | `false` | `arg0=itemReferences,a,b,pageNumber,1` | `comma` | | `deepObject` | (any) | `arg0[itemReferences]=a&arg0[itemReferences]=b&arg0[pageNumber]=1` | `deepObject` | Nested non-primitive values inside an exploded/deepObject-serialized object (an object-in-object, or an array of objects) are dropped using the same primitive-only rules the top-level filter already applies — OpenAPI does not define a style/explode encoding for them either, so they never reach the wire as `"[object Object]"`. To restore the pre-#3705 behavior (object-typed query params silently dropped), set: ```ts export default defineConfig({ petstore: { output: { client: 'angular', target: './src/api/petstore.ts', override: { angular: { queryObjectSerialization: 'legacy', }, }, }, }, }); ``` `queryObjectSerialization` can be set globally, per-tag, or per-operation, just like `retrievalClient`/`runtimeValidation`. See the [configuration reference](/docs/reference/configuration/output#queryobjectserialization). ### Custom filter with `override.paramsFilter` When the schema cannot describe what you need — for example you want to preserve empty strings, encode arrays differently, or rename keys before they hit the wire — supply a `paramsFilter` mutator. It **completely replaces** the built-in `filterParams` for every operation that resolves to it; you own nullish-stripping and any non-primitive handling. Even though `paramsFilter` is Angular-only, it lives at `override.paramsFilter` instead of `override.angular.paramsFilter` so you can keep using the standard global / per-operation / per-tag override pattern that Orval already uses for request shaping. ```ts import { defineConfig } from 'orval'; export default defineConfig({ petstore: { output: { client: 'angular', target: './src/api/petstore.ts', override: { paramsFilter: { path: './src/api/params-filter.ts', name: 'filterPetstoreParams', }, }, }, }, }); ``` ```ts // You are now the filter. orval will not strip null, undefined, or object // values for you — return exactly the shape Angular's HttpParams should see. export const filterPetstoreParams = ( params: Record, ): Record => { const result: Record = {}; for (const [key, value] of Object.entries(params)) { if (value === undefined) continue; result[key] = value; } return result; }; ``` #### What you take over when you opt in The built-in helper is **not** called alongside your filter. In particular, you are responsible for: - stripping `undefined` (Angular's `HttpParams` does not safely represent it for query encoding; handle it explicitly) - handling `null` (Angular treats it as the literal string `"null"`) - filtering arrays (`new HttpParams({ fromObject: { tags: [{}] } })` is invalid) - serializing object-valued params per their `style`/`explode` (or coercing/ flattening them yourself) — or relying on a downstream `paramsSerializer` to do it - the required-nullable empty-string fallback described above — opting into `paramsFilter` also replaces that behavior, so if you prefer the old drop-silently semantics for required-nullable params, `paramsFilter` is the escape hatch to restore it The exact rules the default helper applies — useful as a starting point you can adapt: ```ts // This is the logic orval emits when no `paramsFilter` is configured. // `passthroughKeys` is only non-empty when a `paramsSerializer` is also // configured (it carries the schema-declared object/array-of-object keys). // `objectParamStrategies` carries the per-key style/explode strategy // (issue #3705) and is empty when a `paramsSerializer` is configured or // `queryObjectSerialization: 'legacy'` is set. // `preserveRequiredNullables` is true only when a `paramsSerializer` is // configured — that's what lets the literal `null` survive for the // serializer to encode; without one, a required-nullable `null` becomes an // empty string so the key still reaches the wire (see #3712). // Copy/adapt the parts you want; drop the rest. type Primitive = string | number | boolean; type Value = Primitive | Primitive[] | null; type ObjectParamStrategy = 'flatten' | 'comma' | 'deepObject'; export const defaultFilter = ( params: Record, requiredNullableKeys = new Set(), preserveRequiredNullables = false, passthroughKeys = new Set(), objectParamStrategies: Readonly> = {}, ): Record => { const out: Record = {}; const filterPrimitives = (value: unknown[]) => value.filter( (item): item is Primitive => item != null && (typeof item === 'string' || typeof item === 'number' || typeof item === 'boolean'), ); for (const [key, value] of Object.entries(params)) { if (passthroughKeys.has(key)) { if (value !== undefined) out[key] = value; continue; } const strategy = objectParamStrategies[key]; if (strategy && value != null && typeof value === 'object' && !Array.isArray(value)) { const entries = Object.entries(value as Record); if (strategy === 'comma') { const parts = entries .filter(([, v]) => v != null && typeof v !== 'object') .flatMap(([k, v]) => [k, String(v)]); if (parts.length) out[key] = parts.join(','); } else { for (const [prop, propValue] of entries) { const targetKey = strategy === 'deepObject' ? `${key}[${prop}]` : prop; if (Array.isArray(propValue)) { const filtered = filterPrimitives(propValue); if (filtered.length) out[targetKey] = filtered; } else if (propValue != null && typeof propValue !== 'object') { out[targetKey] = propValue as Value; } } } continue; } if (Array.isArray(value)) { const filtered = filterPrimitives(value); if (filtered.length) out[key] = filtered; } else if (value === null && requiredNullableKeys.has(key)) { out[key] = preserveRequiredNullables ? null : ''; } else if ( value != null && (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') ) { out[key] = value; } } return out; }; ``` #### Composing with `paramsSerializer` If you configure both, orval calls them in this order so the serializer operates on the filtered result: ``` raw params → paramsFilter → paramsSerializer → HttpParams ``` `paramsFilter` controls *which keys/values reach the request*; `paramsSerializer` controls *how the surviving values are encoded into the URL*. Both are optional and independent. Configuring a `paramsSerializer` also disables the built-in object-serialization strategies described in [Object query parameters](#object-query-parameters-style--explode) above — the raw object is passed through to your serializer instead. ## Advanced helpers Generated `httpResource` files also include small convenience helpers such as `ResourceState` and `toResourceState()` for integrating Angular resources into your own state abstractions. ## Full Example See the [complete Angular example](https://github.com/orval-labs/orval/tree/master/samples/angular-app) on GitHub. --- # SolidStart Generate fully typed SolidStart primitives using `query()` and `action()` from `@solidjs/router`. ## Configuration Set the `client` option to `solid-start`: ```ts import { defineConfig } from 'orval'; export default defineConfig({ petstore: { output: { mode: 'tags-split', target: 'src/api/petstore.ts', schemas: 'src/api/model', client: 'solid-start', mock: true, }, input: { target: './petstore.yaml', }, }, }); ``` ## Generated Output Orval generates code using native SolidStart primitives. The exported object is named after the spec title, or after the tag in `tags` and `tags-split` mode. If that name collides with a schema the same file imports, orval suffixes the export with `Api` (`Pets` becomes `PetsApi`) and logs a warning naming both, since TypeScript rejects a file that both imports and exports the same name. Rename the schema if you would rather keep the plain name. ```ts import { query, action, revalidate } from '@solidjs/router'; export const SwaggerPetstore = { listPets: query(async (params: ListPetsParams) => { const queryString = new URLSearchParams(params as any).toString(); const url = queryString ? `/pets?${queryString}` : `/pets`; const response = await fetch(url, { method: 'GET', headers: { 'Content-Type': 'application/json' }, }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } return response.json() as Promise; }, 'listPets'), createPets: action(async (createPetsBody: CreatePetsBody) => { const response = await fetch('/pets', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(createPetsBody), }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } return response.json() as Promise; }, 'createPets'), showPetById: query(async (petId: string) => { const response = await fetch(`/pets/${petId}`, { method: 'GET', headers: { 'Content-Type': 'application/json' }, }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } return response.json() as Promise; }, 'showPetById'), }; ``` ## Basic Usage ```tsx import { createAsync } from '@solidjs/router'; import { Suspense } from 'solid-js'; import { SwaggerPetstore } from './petstore'; function PetDetails(props: { petId: string }) { const pet = createAsync(() => SwaggerPetstore.showPetById(props.petId)); return ( Loading...}>

{pet()?.name}

ID: {pet()?.id}

); } ``` ## Cache Invalidation SolidStart's `query()` function automatically generates cache keys: ```tsx import { revalidate } from '@solidjs/router'; import { SwaggerPetstore } from './petstore'; // Invalidate a specific pet revalidate(SwaggerPetstore.showPetById.keyFor('pet-123')); // Invalidate all pets revalidate(SwaggerPetstore.listPets.key); ``` ### Practical Example: Refresh Button ```tsx import { createAsync, revalidate } from '@solidjs/router'; import { Suspense } from 'solid-js'; import { SwaggerPetstore } from './petstore'; function PetDetails(props: { petId: string }) { const pet = createAsync(() => SwaggerPetstore.showPetById(props.petId)); const handleRefresh = () => { // Invalidate this specific pet's cache to trigger a refetch revalidate(SwaggerPetstore.showPetById.keyFor(props.petId)); }; return ( Loading...}>

{pet()?.name}

ID: {pet()?.id}

); } ``` ## Using Actions Actions are used for mutations (POST, PUT, PATCH, DELETE). You can use them directly with forms or programmatically with `useAction()`: ### With Forms ```tsx import { SwaggerPetstore } from './petstore'; function CreatePetForm() { return (
); } ``` > **Note:** When using actions with forms, you may need to adapt the generated actions to accept `FormData` instead of typed parameters. ### Programmatically with useAction ```tsx import { useAction } from '@solidjs/router'; import { SwaggerPetstore } from './petstore'; function DeletePetButton(props: { petId: string }) { const deletePet = useAction(SwaggerPetstore.deletePetById); const handleDelete = async () => { await deletePet(props.petId); // Actions automatically revalidate all active queries on the page }; return ; } ``` ## Key Features ### Native Fetch API SolidStart client uses the native Fetch API without any HTTP client dependencies: - Automatic query parameter serialization via `URLSearchParams` - JSON request/response handling - Proper error handling with `response.ok` checks - Full TypeScript type safety ### Automatic Cache Management - **Queries** (`query()`) are automatically cached based on the function name and arguments - **Actions** (`action()`) are not cached and always execute fresh - Use `.key` to get the base cache key for all calls to a query - Use `.keyFor(...args)` to get the cache key for a specific set of arguments ### SolidStart Primitives - `query()` — For GET requests, automatically cached and reactive - `action()` — For mutations (POST, PUT, PATCH, DELETE) - `cache()` — Advanced caching (available via import) - `revalidate()` — Manual cache invalidation ## Custom HTTP Client (Mutator) You can use a custom HTTP client with the `mutator` configuration: ```ts import { defineConfig } from 'orval'; export default defineConfig({ petstore: { output: { target: 'src/petstore.ts', client: 'solid-start', override: { mutator: { path: './custom-instance.ts', name: 'customInstance', }, }, }, input: { target: './petstore.yaml', }, }, }); ``` This will generate code that uses your custom instance: ```ts export const SwaggerPetstore = { showPetById: query(async (petId: string) => { const url = `/pets/${petId}`; return customInstance(url, { method: 'GET', headers: { 'Content-Type': 'application/json' }, }); }, 'showPetById'), }; ``` Your custom instance should follow the Fetch API signature: ```ts export const customInstance = async ( url: string, options: RequestInit, ): Promise => { const response = await fetch(url, options); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } return response.json(); }; ``` ## SolidStart vs Solid Query **SolidStart** is a meta-framework for SolidJS with strong opinions about application architecture. It uses **Solid Router** under the hood, which provides built-in data primitives for queries and actions. **If you are using SolidStart, it's highly recommended to use its built-in primitives** (`query()` and `action()` from `@solidjs/router`) instead of Solid Query. These primitives are designed to work seamlessly with SolidStart's server-side rendering, routing, and caching systems. | Feature | Solid Query | SolidStart | | --- | --- | --- | | Package | `@tanstack/solid-query` | `@solidjs/router` (via SolidStart) | | Use Case | Standalone Solid apps | SolidStart applications | | Query Hook | `createQuery` | `query()` | | Mutation Hook | `createMutation` | `action()` | | Cache Keys | Manual query key functions | Automatic via `.key` and `.keyFor()` | | HTTP Client | Configurable (axios, fetch, etc.) | Native fetch API | | Query Options | Full TanStack Query options | Simpler, opinionated options | | SSR Integration | Manual configuration | Built-in and automatic | Choose **SolidStart client** when building a SolidStart application. Choose **Solid Query client** only if you're building a standalone SolidJS app without the SolidStart framework and need advanced TanStack Query features like optimistic updates, background refetching, and complex caching strategies. ## Full Example See the [complete SolidStart example](https://github.com/orval-labs/orval/tree/master/samples/solid-start) on GitHub. --- # Hono Generate [Hono](https://hono.dev/) server templates with full validation from your OpenAPI specification. ## Configuration Set the `client` option to `hono`: ```ts import { defineConfig } from 'orval'; export default defineConfig({ petstore: { input: { target: './petstore.yaml', }, output: { mode: 'split', client: 'hono', target: 'src/petstore.ts', override: { hono: { handlers: 'src/handlers', }, }, }, }, }); ``` ## Generated Files ``` src/ ├── handlers/ │ ├── createPets.ts │ ├── listPets.ts │ ├── showPetById.ts │ └── updatePets.ts ├── petstore.ts # Hono app with routes ├── petstore.context.ts # Type-safe context ├── petstore.schemas.ts # Request/response schemas ├── petstore.validator.ts # Hono validator └── petstore.zod.ts # Zod schemas ``` ## Handler Template Orval generates handler templates with built-in validation: ```ts /** * Generated by orval 🍺 * Do not edit manually. * Swagger Petstore * OpenAPI spec version: 1.0.0 */ import { createFactory } from 'hono/factory'; import { zValidator } from '../petstore.validator'; import { ListPetsContext } from '../petstore.context'; import { ListPetsQueryParams, listPetsResponse } from '../petstore.zod'; const factory = createFactory(); export const listPetsHandlers = factory.createHandlers( zValidator('query', ListPetsQueryParams), zValidator('response', listPetsResponse), async (c: ListPetsContext) => { // Implement your logic here }, ); ``` ## Implementing Handlers Add your business logic to the generated handlers: > Re-running orval reconciles your handler files according to > [`override.hono.handlerGenerationStrategy`](/docs/reference/configuration/output#handlergenerationstrategy) > (default `smart`). With `smart`, orval only updates the parts it owns — its own > imports (names, paths, casing) and the `zValidator(...)` arguments, and appends > handlers for new operations — while preserving your custom imports, middleware, > handler bodies, and top-level helpers. Use `skip` to freeze a file, or `full` > for the legacy behavior that rebuilds the wrapper and keeps only the body. ```ts export const listPetsHandlers = factory.createHandlers( zValidator('query', ListPetsQueryParams), zValidator('response', listPetsResponse), async (c: ListPetsContext) => { return c.json([ { id: 1, name: 'Buddy' }, { id: 2, name: 'Max' }, ]); }, ); ``` ## Running the Server ```bash wrangler dev src/petstore.ts curl http://localhost:8787/pets # => [{"id":1,"name":"Buddy"},{"id":2,"name":"Max"}] ``` ## Composite Routes (tags-split) For larger APIs, use `tags-split` mode with composite routes: ```ts import { defineConfig } from 'orval'; export default defineConfig({ petstore: { input: { target: './petstore.yaml', }, output: { mode: 'tags-split', client: 'hono', target: 'src/endpoints', schemas: 'src/schemas', override: { hono: { compositeRoute: 'src/routes.ts', }, }, }, }, }); ``` This generates a combined routes file: ```ts import { Hono } from 'hono'; import { listPetsHandlers, createPetsHandlers, showPetByIdHandlers, } from './endpoints/pets/pets.handlers'; const app = new Hono(); app.get('/pets', ...listPetsHandlers); app.post('/pets', ...createPetsHandlers); app.get('/pets/:petId', ...showPetByIdHandlers); export default app; ``` ## Full Examples - [Basic Hono with Zod](https://github.com/orval-labs/orval/tree/master/samples/hono/hono-with-zod) - [Hono with Fetch Client](https://github.com/orval-labs/orval/tree/master/samples/hono/hono-with-fetch-client) - [Composite Routes](https://github.com/orval-labs/orval/tree/master/samples/hono/composite-routes-with-tags-split) --- # Zod Generate [Zod](https://zod.dev/) schemas from your OpenAPI specification for runtime validation. ## Configuration Set the `client` option to `zod`: ```ts import { defineConfig } from 'orval'; export default defineConfig({ petstore: { output: { client: 'zod', mode: 'single', target: './src/api/schemas', override: { zod: { // Prefer Mini for more tree-shakeable generated schemas. variant: 'mini', version: 4, }, }, }, input: { target: './petstore.yaml', }, }, }); ``` ## Generated Output Orval generates a Zod schema for each model in your OpenAPI specification: ```ts export const CreatePetsBody = /*#__PURE__*/ zod.object({ id: /*#__PURE__*/ zod.number(), name: /*#__PURE__*/ zod.string(), tag: /*#__PURE__*/ zod.optional(/*#__PURE__*/ zod.string()), }); ``` ## Tree Shaking Prefer `variant: 'mini'` when startup time, memory usage, or bundle size matter. Zod Mini uses top-level helpers, so generated validators can be dropped more reliably when their exports are unused: ```ts // Regular Zod export const User = zod.object({ email: zod.email().min(5).max(255), }); // Zod Mini export const User = /*#__PURE__*/ zod.object({ email: /*#__PURE__*/ zod .email() .check(/*#__PURE__*/ zod.minLength(5)) .check(/*#__PURE__*/ zod.maxLength(255)), }); ``` When `User` is unused, a bundled regular Zod output can still keep the schema initializer as top-level work even if nothing uses it: ```js // possible bundled regular Zod output (no export const User, but the zod call is still there) object({ email: email().min(userEmailMin).max(userEmailMax), }); ``` With Zod Mini + pure annotations, the unused schema can disappear entirely: ```js // possible bundled Zod Mini output // no User schema code emitted at all ``` In large generated clients this can remove unused schema initializers from the final bundle. In real builds this can mean hundreds of kilobytes less generated validation code loaded at startup. ## Array response items Inline array response items can use OpenAPI compositions such as `allOf`, `oneOf`, or `anyOf`. Orval generates a schema for the item type, for example: ```ts export const ListAllOf200Item = zod.object({ id: zod.int().optional(), }).and( zod.object({ extra: zod.string().optional(), }), ); ``` The generated client can then use `ListAllOf200Item[]` for the response. When an array item is a component `$ref`, the component schema writer owns that schema and the operation does not generate a duplicate item schema. ## Zod version Orval uses **Zod 4** as its output baseline (`z.strictObject`, `z.looseObject`, `z.iso.datetime()`, `.meta()`, …), while projects still on Zod 3 are fully supported. The emitted syntax follows whichever `zod` major your project resolves. With the default (`'auto'`), Orval infers the target from the `zod` version resolved in your project's `package.json`. When no `zod` package can be detected — for example in a fresh or partially-installed workspace — it falls back to **Zod 4**. To make generation deterministic, pin the target with `override.zod.version`: ```ts import { defineConfig } from 'orval'; export default defineConfig({ petstore: { output: { client: 'zod', target: './src/api/schemas', override: { zod: { variant: 'mini', version: 4, // 3 | 4 | 'auto' }, }, }, input: { target: './petstore.yaml', }, }, }); ``` | Value | Output | | -------- | -------------------------------------------------------------------------------- | | `4` | Always emit Zod 4 syntax, regardless of the installed `zod` version. | | `3` | Always emit Zod 3-compatible syntax, regardless of the installed `zod` version. | | `'auto'` | (default) Infer from the resolved `zod` version; fall back to Zod 4 when none is detected. | Pinning the version keeps output stable: the same spec produces the same schemas on every machine and in CI, independent of which `zod` version happens to be installed. ## Zod Mini Set `override.zod.variant` to `'mini'` to generate schemas against [Zod Mini](https://zod.dev/packages/mini): ```ts export default defineConfig({ petstore: { output: { client: 'zod', override: { zod: { variant: 'mini', version: 4, }, }, }, }, }); ``` Mini output imports from `zod/mini` and uses Zod Mini's functional/check-based API. It requires Zod 4; `version: 3` or `version: 'auto'` resolving to Zod 3 is rejected. Prefer Zod Mini when generated schemas are bundled into frontend apps, Cloudflare Workers, or other startup-sensitive runtimes where tree-shaking and bundle size matter. Use regular Zod when those constraints are not important; it has the more familiar chainable API and better autocomplete ergonomics. One behavior difference: Zod Mini does not load the English locale by default. Configure it once if you want regular Zod's default English messages: ```ts import * as zod from 'zod/mini'; zod.config(zod.locales.en()); ``` ## Scoping options per operation or tag Most `override.zod` settings can be applied to part of your API instead of globally, via `override.operations[operationId].zod` (a single operation) or `override.tags[tagName].zod` (every operation sharing a tag): ```ts export default defineConfig({ petstore: { output: { client: 'zod', override: { zod: { variant: 'mini', version: 4, strict: { response: true }, // applies everywhere }, operations: { listPets: { zod: { coerce: { query: ['number'] }, // only this operation }, }, }, tags: { admin: { zod: { strict: { body: true }, // every operation tagged "admin" }, }, }, }, }, }, }); ``` Per-operation and per-tag overrides accept the settings that apply to an individual schema: `strict`, `generate`, `coerce`, `preprocess`, `params`, `useBrandedTypes`, and `generateCompanionTypes`. Output-wide settings — `variant`, `version`, `dateTimeOptions`, `timeOptions`, `generateEachHttpStatus`, `generateReusableSchemas`, `generateMeta`, `generateDiscriminatedUnion`, and `exactOptional` — only make sense for the whole output and must stay on `override.zod`. If you place one on an operation or tag it is ignored — the value from `override.zod` still applies — and Orval prints a build warning: ``` ⚠️ override.operations.listPets.zod only supports strict, generate, coerce, preprocess, params, useBrandedTypes, and generateCompanionTypes. Ignoring unsupported field: zod.version. ``` ## Usage ### Parsing Data ```ts import { CreatePetsBody } from './src/api/schemas'; const pet = { id: 1, name: 'Buddy', tag: 'dog' }; const parsedPet = CreatePetsBody.parse(pet); // => { id: 1, name: "Buddy", tag: "dog" } ``` ### Type Inference ```ts import type { z } from 'zod/mini'; import { CreatePetsBody } from './src/api/schemas'; type Pet = z.infer; const pet: Pet = { id: 1, name: 'Buddy', tag: 'dog' }; ``` ### Safe Parsing ```ts const result = CreatePetsBody.safeParse(unknownData); if (result.success) { console.log(result.data); } else { console.error(result.error); } ``` ## Combining with HTTP Clients See the [Client with Zod](/docs/guides/client-with-zod) guide for using Zod schemas with React Query, SWR, or other HTTP clients. --- # Client with Zod Use Zod schemas alongside your SWR or TanStack Query client for runtime validation. ## Configuration Generate both the HTTP client and Zod schemas: ```ts import { defineConfig } from 'orval'; export default defineConfig({ // HTTP client generation petstore: { input: { target: './petstore.yaml', }, output: { mode: 'tags-split', client: 'swr', target: 'src/api/endpoints', schemas: 'src/api/models', mock: true, }, }, // Zod schema generation petstoreZod: { input: { target: './petstore.yaml', }, output: { mode: 'tags-split', client: 'zod', target: 'src/api/endpoints', fileExtension: '.zod.ts', }, }, }); ``` > **Note:** > Use `fileExtension: '.zod.ts'` to avoid filename conflicts with the HTTP client files. ## Generated Files ``` src/api/ ├── endpoints/ │ └── pets/ │ ├── pets.ts # SWR hooks │ ├── pets.msw.ts # MSW mocks │ ├── pets.faker.ts # Faker mocks │ └── pets.zod.ts # Zod schemas └── models/ └── ... ``` ## Usage ```tsx import { useListPets, useCreatePets } from './api/endpoints/pets/pets'; import { CreatePetsBodyItem } from './api/endpoints/pets/pets.zod'; function App() { const { data } = useListPets(); const { trigger } = useCreatePets(); const createPet = async () => { const pet = { name: 'Buddy', tag: 'dog' }; try { // Validate before sending const validatedPet = CreatePetsBodyItem.parse(pet); await trigger([validatedPet]); } catch (error) { if (error instanceof ZodError) { console.error('Validation failed:', error.errors); } } }; return (
{data?.map((pet) => (
{pet.name}
))}
); } ``` ## Full Example See the [complete SWR with Zod example](https://github.com/orval-labs/orval/tree/master/samples/swr-with-zod) on GitHub. --- # Effect Generate [Effect Schema](https://effect.website/docs/schema/introduction/) schemas from your OpenAPI specification for runtime validation. ## Configuration Set the `client` option to `effect`: ```ts import { defineConfig } from 'orval'; export default defineConfig({ petstore: { output: { client: 'effect', mode: 'single', target: './src/api/schemas', }, input: { target: './petstore.yaml', }, }, }); ``` ## Generated Output Orval generates an Effect schema for each model in your OpenAPI specification: ```ts import { Schema as S } from 'effect'; export const CreatePetsBody = S.Struct({ id: S.Number, name: S.String, tag: S.optional(S.String), }); ``` ## Effect Version Orval reads the `effect` version installed in your project and emits Effect 3 or Effect 4 Schema syntax to match. When no `effect` package is found, it emits Effect 4. Set [`override.effect.version`](/docs/reference/configuration/output#overrideeffect) to pin the target. ## Usage ### Decoding Data ```ts import { Schema as S } from 'effect'; import { CreatePetsBody } from './src/api/schemas'; const pet = { id: 1, name: 'Buddy', tag: 'dog' }; const parsedPet = S.decodeUnknownSync(CreatePetsBody)(pet); // => { id: 1, name: "Buddy", tag: "dog" } ``` ### Type Inference ```ts import { Schema as S } from 'effect'; import { CreatePetsBody } from './src/api/schemas'; type Pet = S.Schema.Type; const pet: Pet = { id: 1, name: 'Buddy', tag: 'dog' }; ``` ### Safe Decoding ```ts import { Exit, Schema as S } from 'effect'; const result = S.decodeUnknownExit(CreatePetsBody)(unknownData); if (Exit.isSuccess(result)) { console.log(result.value); } else { console.error(result.cause); } ``` With Effect 3, use `S.decodeUnknownEither` and `Either.isRight` instead. ## Combining with HTTP Clients See the [Client with Effect](/docs/guides/client-with-effect) guide for using Effect schemas with React Query, SWR, or other HTTP clients. --- # Client with Effect Use Effect schemas alongside your SWR or TanStack Query client for runtime validation. ## Configuration Generate both the HTTP client and Effect schemas: ```ts import { defineConfig } from 'orval'; export default defineConfig({ // HTTP client generation petstore: { input: { target: './petstore.yaml', }, output: { mode: 'tags-split', client: 'swr', target: 'src/api/endpoints', schemas: 'src/api/models', mock: true, }, }, // Effect schema generation petstoreEffect: { input: { target: './petstore.yaml', }, output: { mode: 'tags-split', client: 'effect', target: 'src/api/endpoints', fileExtension: '.effect.ts', }, }, }); ``` > **Note:** > Use `fileExtension: '.effect.ts'` to avoid filename conflicts with the HTTP client files. ## Generated Files ``` src/api/ ├── endpoints/ │ └── pets/ │ ├── pets.ts # SWR hooks │ ├── pets.msw.ts # MSW mocks │ ├── pets.faker.ts # Faker mocks │ └── pets.effect.ts # Effect schemas └── models/ └── ... ``` ## Usage ```tsx import { Schema as S } from 'effect'; import { useListPets, useCreatePets } from './api/endpoints/pets/pets'; import { CreatePetsBodyItem } from './api/endpoints/pets/pets.effect'; function App() { const { data } = useListPets(); const { trigger } = useCreatePets(); const createPet = async () => { const pet = { name: 'Buddy', tag: 'dog' }; try { // Validate before sending const validatedPet = S.decodeUnknownSync(CreatePetsBodyItem)(pet); await trigger([validatedPet]); } catch (error) { console.error('Validation failed:', error); } }; return (
{data?.map((pet) => (
{pet.name}
))}
); } ``` ## Full Example See the [complete SWR with Effect example](https://github.com/orval-labs/orval/tree/master/samples/swr-with-effect) on GitHub. --- # MSW Generate [MSW (Mock Service Worker)](https://mswjs.io/) handlers from your OpenAPI specification to mock your API during development and testing. For mock data factories without MSW request handlers, see the [Faker guide](/docs/guides/faker). For typed fixtures per endpoint that MSW or any other library can serve, see the [Fixtures guide](/docs/guides/fixtures). ## Configuration Set the `mock` option to `true` (emits both MSW handlers and Faker factories), or scope it to MSW only via the generator entry: ```ts import { defineConfig } from 'orval'; export default defineConfig({ petstore: { output: { mode: 'single', target: './src/api/petstore.ts', schemas: './src/api/model', mock: { generators: [{ type: 'msw' }], }, }, input: { target: './petstore.yaml', }, }, }); ``` ## Generated Output The MSW generator emits two types of functions per operation, plus an aggregator. Mock *data* (the `getResponseMock` factories) is produced by the Faker generator — see the [Faker guide](/docs/guides/faker) for details on overriding values and formats. ### 1. Request Handlers Functions that bind mock data to [MSW](https://mswjs.io/) `http.*` handlers using the recommended [`HttpResponse`](https://mswjs.io/docs/api/http-response) class: ```ts import { HttpResponse, delay, http } from 'msw'; import type { HttpResponseResolver, RequestHandlerOptions } from 'msw'; export const getShowPetByIdMockHandler = ( overrideResponse?: | Pet | ((info: Parameters>[0]) => Promise | Pet), options?: RequestHandlerOptions, ) => { return http.get<{ petId: string }>('*/pets/:petId', async (info) => { await delay(1000); return HttpResponse.json( overrideResponse !== undefined ? typeof overrideResponse === 'function' ? await overrideResponse(info) : overrideResponse : getShowPetByIdResponseMock(), { status: 200 }, ); }, options); }; ``` The resolver `info` (and the `overrideResponse` callback) is typed with the operation's path params and request body, so `info.params.petId` is a `string` and `await info.request.json()` returns the request body type (for example `CreatePetsBody`). Operations without path params use MSW's `PathParams`. Orval uses the content type from your OpenAPI spec to pick the right `HttpResponse` helper. If multiple content types are defined, you can set a preferred default; otherwise the spec order is used: | Content type | Response helper | |---|---| | `application/json` | `HttpResponse.json()` | | `application/xml`, `*+xml` | [`HttpResponse.xml()`](https://mswjs.io/docs/api/http-response) | | `text/html` | [`HttpResponse.html()`](https://mswjs.io/docs/api/http-response) | | `text/plain`, other `text/*` | `HttpResponse.text()` | | `application/octet-stream`, `image/*`, etc. | `HttpResponse.arrayBuffer()` | | No body (204, etc.) | `new HttpResponse(null, { status })` | To prefer a specific content type when several are defined (for example, choose JSON over XML), set `mock.preferredContentType`: ```ts export default defineConfig({ petstore: { output: { mock: { generators: [ { type: 'msw', preferredContentType: 'application/json', }, ], }, }, }, }); ``` `preferredContentType` accepts common MIME literals and any custom string (via a loose `(string & {})` fallback), so vendor-specific types are supported too. `preferredContentType` only **selects** a declared media type — it does not convert the payload. If the chosen media type is not among the operation's responses, the preference is ignored and spec order is used. When the response schema is a structured object or array, prefer a JSON media type: pairing a non-JSON preference (for example, `text/plain` or `application/xml`) with a structured schema serves a stringified body instead of the structured mock, and `application/octet-stream` falls back to JSON with the selected media type as the `Content-Type`. ### Array Item Factories Set `arrayItems: true` on the MSW generator entry to emit reusable mock factories for object-like array item schemas in operation responses (for example, `getTenantResponseModelDtoMock` for `value: TenantResponseModelDto[]`). The factories are written into the same `.msw.ts` / endpoints mock file as the handlers and response mocks. See the [Faker guide — Array Item Factories](/docs/guides/faker#array-item-factories) for configuration details and supported shapes. ```ts mock: { generators: [ { type: 'msw', arrayItems: true, }, ], } ``` ### 2. Aggregated Handlers Functions that combine all handlers for easy setup: ```ts export const getPetsMock = () => [ getListPetsMockHandler(), getCreatePetsMockHandler(), getShowPetByIdMockHandler(), ]; ``` ## Usage ### Basic Setup Use the aggregated handler functions with MSW's [`setupServer`](https://mswjs.io/docs/api/setup-server) (Node.js) or [`setupWorker`](https://mswjs.io/docs/api/setup-worker) (browser): ```ts import { getPetsMock } from './api/petstore.msw'; import { setupServer } from 'msw/node'; const server = setupServer(...getPetsMock()); beforeAll(() => server.listen()); afterEach(() => server.resetHandlers()); afterAll(() => server.close()); ``` ### Custom Response Pass static data directly: ```ts const showPetByIdMockHandler = getShowPetByIdMockHandler({ id: 1, name: 'Custom Pet', tag: 'custom', }); ``` ### Dynamic Response Pass a function for dynamic responses based on request data: ```ts getShowPetByIdMockHandler(async (info) => { const petId = info.params.petId; return { id: Number(petId), name: `Pet ${petId}`, tag: 'dynamic' }; }); ``` ### Override with `server.use()` (MSW best practice) Following the [MSW best practice for network behavior overrides](https://mswjs.io/docs/best-practices/network-behavior-overrides), use `server.use()` to override specific handlers in individual tests while keeping the default happy-path handlers: ```ts import { http, HttpResponse } from 'msw'; it('handles server error', () => { server.use( getShowPetByIdMockHandler(() => { throw new HttpResponse(null, { status: 500 }); }), ); // Test error UI... }); ``` ### Passing MSW `RequestHandlerOptions` Each generated handler accepts an optional second `options` parameter (e.g. `{ once: true }`): ```ts server.use( getShowPetByIdMockHandler(myOverride, { once: true }), ); ``` ### Testing with Vitest ```ts import { expect, vi } from 'vitest'; import { getShowPetByIdMockHandler, getShowPetByIdResponseMock } from './api/petstore.msw'; const mockFn = vi.fn(); const handlers = [ getShowPetByIdMockHandler(async (info) => { mockFn(info.params.petId); return getShowPetByIdResponseMock(); }), ]; // ... run test expect(mockFn).toHaveBeenCalledWith('123'); ``` ## Base URL By default, handlers use a wildcard `*` prefix (e.g. `*/pets/:petId`) so they match any host. To use a specific base URL, set `baseUrl` on the MSW generator entry: ```ts export default defineConfig({ petstore: { output: { mock: { generators: [ { type: 'msw', baseUrl: 'https://api.example.com', }, ], }, }, }, }); ``` This produces handlers like `http.get('https://api.example.com/pets/:petId', ...)`, which aligns with MSW's recommendation to use [absolute URLs](https://mswjs.io/docs/http/intercepting-requests) for precise matching. ## Dynamic Imports Enable `mock.indexMockFiles` to emit a root-level `index..ts` file for each generator entry in `split` and `tags-split` modes. The MSW entry produces an `index.msw.ts` that can be dynamically imported. This keeps MSW in a dedicated barrel that your production/model barrels never re-export — handy under `jsdom`/`vitest`, where MSW references stream globals at module-evaluation time: ```ts export default defineConfig({ petstore: { output: { mode: 'tags-split', mock: { indexMockFiles: true, generators: [{ type: 'msw' }], }, }, }, }); ``` ```ts // node.ts import * as mocks from './endpoints/index.msw'; import { setupServer } from 'msw/node'; const handlers = Object.entries(mocks).flatMap(([, getMock]) => getMock()); const server = setupServer(...handlers); export { server }; ``` If both `msw` and `faker` generators are configured with `indexMockFiles: true`, you also get an `index.faker.ts` alongside `index.msw.ts`. ## MSW Best Practices The generated code follows [MSW best practices](https://mswjs.io/docs/best-practices): - **`HttpResponse` class** — uses `HttpResponse.json()`, `.xml()`, `.html()`, `.text()`, and `.arrayBuffer()` instead of raw `Response` constructors, as [recommended by MSW](https://mswjs.io/docs/api/http-response). The helper is chosen based on `mock.preferredContentType` when set, otherwise the first matching content type in your OpenAPI spec. - **`delay()` function** — uses the standalone `delay()` import (not the legacy `ctx.delay`), configurable per-operation or globally. - **Handler structure** — each handler is a factory function returning `http.get/post/...()`, which can be spread into `setupServer()` or `setupWorker()`. - **Runtime overrides** — use [MSW's `server.use()`](https://mswjs.io/docs/best-practices/network-behavior-overrides) to override individual handlers in tests. The generated handler factories make this easy. - **No query parameters in predicates** — path predicates never include query strings, per [MSW's guidance](https://mswjs.io/docs/http/intercepting-requests). - **Path parameters** — OpenAPI `{param}` is converted to MSW's `:param` syntax for correct matching. --- # Faker Generate mock data factories powered by [Faker.js](https://fakerjs.dev/) from your OpenAPI specification. Faker output has no `msw` dependency, so it's useful for unit tests, Storybook stories, seed scripts, and any test setup that doesn't go through a network mock. For Mock Service Worker request handlers, see the [MSW guide](/docs/guides/msw). For typed fixtures per endpoint built on these factories, see the [Fixtures guide](/docs/guides/fixtures). ## Configuration Add a `faker` generator entry to `output.mock.generators`: ```ts import { defineConfig } from 'orval'; export default defineConfig({ petstore: { output: { mode: 'single', target: './src/api/petstore.ts', schemas: './src/api/model', mock: { generators: [{ type: 'faker' }], }, }, input: { target: './petstore.yaml', }, }, }); ``` You can also combine `msw` and `faker` to emit both files in the same run: ```ts mock: { generators: [{ type: 'msw' }, { type: 'faker' }], } ``` The Faker output is written to `.faker.ts` and only depends on `@faker-js/faker`. ## Generated Output ### Response Factories By default, Orval emits a `getResponseMock` factory per operation that returns a fully-populated response value. Disable this with `operationResponses: false` (typically when combined with `schemas: true` — see below). ```ts import { faker } from '@faker-js/faker'; export const getShowPetByIdResponseMock = ( overrideResponse: Partial = {}, ): Pet => ({ id: faker.number.int({ min: undefined, max: undefined }), name: faker.string.alpha(20), tag: faker.string.alpha(20), ...overrideResponse, }); ``` Pass overrides for any subset of fields: ```ts const pet = getShowPetByIdResponseMock({ name: 'Buddy' }); // => { id: 7272122785202176, name: "Buddy", tag: "..." } ``` ### Schema Factories Set `schemas: true` to emit a `getMock()` factory per entry under `components/schemas`. Factories are written to a single consolidated file at `/index.faker.ts` so they can be imported uniformly and reference each other directly: ```ts mock: { generators: [ { type: 'faker', schemas: true, // emit components/schemas factories operationResponses: true, // also emit per-operation response factories (default) }, ], } ``` ```ts import { faker } from '@faker-js/faker'; import type { Pet } from '.'; export const getPetMock = (overrideResponse: Partial = {}): Pet => ({ id: faker.number.int(), name: faker.string.alpha(20), ...overrideResponse, }); ``` When both options are enabled, the per-operation `getResponseMock` factories **delegate** to the schema-level factories instead of re-inlining the schema body: ```ts import { getPetMock } from './model/index.faker'; export const getShowPetByIdResponseMock = (): Pet => ({ ...getPetMock() }); ``` If an operation- or tag-level `override.mock` rule targets a property of a referenced schema (by name, regex, or exact `#.path`), that single ref falls back to inlining so the override actually applies. Requires `output.schemas` to be configured (the consolidated file is written into that directory). ### Array Item Factories Set `arrayItems: true` on any mock generator entry to emit reusable mock factories for **object-like array item schemas** found in operation responses. This covers array elements that are inlined in the response body (not just entries under `components/schemas`). Works with both `faker` and `msw` generators: ```ts mock: { generators: [ { type: 'faker', arrayItems: true, }, ], } ``` For a paginated list response like `{ value: TenantResponseModelDto[], count: number }`, Orval emits both the operation factory and a reusable item factory: ```ts export const getTenantResponseModelDtoMock = ( overrideResponse: Partial = {}, ): TenantResponseModelDto => ({ /* ... */, ...overrideResponse }); export const getGetTenantsByRefResponseMock = ( overrideResponse: Partial = {}, ): TenantListResponse => ({ value: Array.from(/* ... */).map(() => ({ ...getTenantResponseModelDtoMock() })), count: faker.number.int(), ...overrideResponse, }); ``` - **`$ref` array items** → `getMock` (shared across operations referencing the same schema) when the referenced schema is object-like. - **Inline object array items** → `getResponseItemMock` typed as `Item` (matching Orval's generated item type aliases). Orval only extracts factories for shapes it can name and mock reliably. The following fall back to inline `.map()` bodies (same as `arrayItems: false`): `$ref` to scalar schemas, `oneOf` / `anyOf` item compositions, nullable object items, and nested arrays whose parent context is not the generated response wrapper (e.g. two `items` properties under `outer` and `inner` in the same operation). Plain object items, `$ref`-to-object items, and inline `allOf` items are supported. Top-level array responses (the array itself, not a wrapper object) reuse the same generated element alias as the schema output — a `$ref`'d array schema `CatalogItems` produces item factories typed `CatalogItemsItem`, and an inline top-level array reuses its generated `Item` alias. Shapes where that alias cannot be derived with certainty (e.g. `$ref` array items composed via a multi-schema `allOf` with no direct properties) are inlined instead. When `schemas: true` is also enabled, `$ref` items delegate to the consolidated schema factory instead (same as today). `arrayItems` is useful when item types only appear inside response wrappers or when you want item factories without emitting every `components/schemas` entry. With both options enabled, `$ref` items are not re-exported from the operation mock file — import `getMock` from `/index.faker.ts` instead. ## Options Shared mock options apply to both `faker` and `msw` generator entries. Faker-only options are listed separately below. ```ts mock: { generators: [ { type: 'faker', useExamples: true, generateEachHttpStatus: true, locale: 'en_GB', preferredContentType: 'application/json', arrayItems: true, }, ], } ``` | Option | Type | Default | Description | |---|---|---|---| | `useExamples` | `boolean` | `false` | Seed mock values from OpenAPI `example`/`examples` fields when present. | | `generateEachHttpStatus` | `boolean` | `false` | Emit a separate factory per HTTP status code defined in the spec (not just the success response). | | `locale` | `keyof typeof allLocales` | — | Faker locale. Switches the import to `@faker-js/faker/locale/` (e.g. `'en_GB'`, `'fr'`, `'ja'`). | | `preferredContentType` | `string` | — | When an operation has multiple response content types, mock the one matching this MIME type. | | `arrayItems` | `boolean` | `false` | Emit reusable mock factories for object-like array item schemas in operation responses. See [Array Item Factories](#array-item-factories). | Faker-only options: | Option | Type | Default | Description | |---|---|---|---| | `schemas` | `boolean` | `false` | Emit a consolidated `getMock` factory per `components/schemas` entry into `/index.faker.ts`. See [Schema Factories](#schema-factories). | | `operationResponses` | `boolean` | `true` | Emit per-operation `getResponseMock` factories. Set to `false` (typically with `schemas: true`) to skip operation-level factories. | ## Customizing Mock Values Use `override.mock` to control how individual schemas, properties, and formats are mocked. These options apply to both `faker` and `msw` generators. ```ts override: { mock: { properties: { // Match by property name (string or regex) email: () => faker.internet.email(), '/.*Id$/': () => faker.string.uuid(), }, format: { // Match by OpenAPI `format` keyword date: () => faker.date.past().toISOString(), 'date-time': () => faker.date.recent().toISOString(), }, required: true, // Always populate optional fields nonNullable: true, // Never randomize nullable fields to null arrayMin: 3, arrayMax: 5, stringMin: 4, stringMax: 20, numberMin: 0, numberMax: 100, fractionDigits: 2, }, } ``` You can also scope overrides per-operation or per-tag via `override.operations` and `override.tags`. ### Per-schema overrides `override.mock.properties` matches by property name, so a `color` override applies to **every** schema that has a `color` property. To give the same property name different mock values depending on the schema it belongs to, scope the override to a schema by name under `override.mock.schemas`: ```ts override: { mock: { schemas: { Apple: { properties: { color: () => faker.helpers.arrayElement(['red', 'green']), }, }, Car: { properties: { color: () => 'midnight black', }, }, }, }, } ``` `getAppleMock()` now mocks `color` as a fruit color and `getCarMock()` as a car color, even though both schemas declare `color: string`. The keys under `properties` use the same matching rules as `override.mock.properties` — bare property name, regex (`/.../`), or exact path (`#.foo.bar`) — but only apply to properties of the named schema (matched against the schema's own `parentName`). When `schemas: true` is enabled, each `getMock` factory bakes its schema-scoped override in, so references that delegate to the factory keep the override. **Precedence** (first match wins): `override.operations` → `override.tags` → `override.mock.schemas` → `override.mock.properties`. ## Usage ### Unit Tests ```ts import { describe, it, expect } from 'vitest'; import { getShowPetByIdResponseMock } from './api/petstore.faker'; describe('PetDetails', () => { it('renders the pet name', () => { const pet = getShowPetByIdResponseMock({ name: 'Buddy' }); render(); expect(screen.getByText('Buddy')).toBeInTheDocument(); }); }); ``` ### Storybook ```ts import type { Meta, StoryObj } from '@storybook/react'; import { getShowPetByIdResponseMock } from '../api/petstore.faker'; import { PetDetails } from './PetDetails'; const meta: Meta = { component: PetDetails, }; export default meta; export const Default: StoryObj = { args: { pet: getShowPetByIdResponseMock() }, }; export const NamedPet: StoryObj = { args: { pet: getShowPetByIdResponseMock({ name: 'Buddy' }) }, }; ``` ### Seed Scripts ```ts import { writeFile } from 'node:fs/promises'; import { getListPetsResponseMock } from './api/petstore.faker'; const seed = Array.from({ length: 50 }, () => getListPetsResponseMock()); await writeFile('seed/pets.json', JSON.stringify(seed, null, 2)); ``` ## Deterministic Output Faker's PRNG is seedable. Set a seed before invoking factories to get reproducible output, which is helpful for snapshot testing: ```ts import { faker } from '@faker-js/faker'; import { getShowPetByIdResponseMock } from './api/petstore.faker'; beforeEach(() => { faker.seed(42); }); it('matches snapshot', () => { expect(getShowPetByIdResponseMock()).toMatchSnapshot(); }); ``` ## Dynamic Imports In `split` and `tags-split` modes, enable `mock.indexMockFiles` to emit an `index.faker.ts` barrel — aggregating the per-tag faker files in `tags-split`, or re-exporting the single faker file in `split`: ```ts export default defineConfig({ petstore: { output: { mode: 'tags-split', mock: { indexMockFiles: true, generators: [{ type: 'faker' }], }, }, }, }); ``` --- # Fixtures A fixture is one endpoint's fake response, coupled to that endpoint. Orval generates `defineFixture` for your API, typed by the spec: you name an operation, write only the fields a test cares about, and the rest comes from the endpoint's [Faker](/docs/guides/faker) mock. The fixture knows its method, path and URL, so nock, [MSW](/docs/guides/msw), Playwright, a fetch stub or any other tool can serve it. It doesn't depend on a mocking library. ## Configuration Fixtures need a faker generator and `indexMockFiles: true`: ```ts import { defineConfig } from 'orval'; export default defineConfig({ petstore: { output: { mode: 'tags-split', target: './src/api/endpoints.ts', schemas: './src/api/model', mock: { indexMockFiles: true, fixtures: true, // -> ./src/api/index.fixtures.ts generators: [{ type: 'faker' }], }, }, input: { target: './petstore.yaml', }, }, }); ``` Take this part of the spec: ```yaml paths: /pets: get: operationId: listPets parameters: - { name: limit, in: query, schema: { type: integer } } responses: '200': { $ref: '#/components/responses/Pets' } /pets/{petId}: parameters: - { name: petId, in: path, required: true, schema: { type: string } } get: operationId: showPetById responses: '200': { $ref: '#/components/responses/Pet' } default: { $ref: '#/components/responses/Error' } delete: operationId: deletePetById responses: '204': { description: Deleted } default: { $ref: '#/components/responses/Error' } ``` `index.fixtures.ts` describes every operation, and exports `defineFixture`, `defineFixtures` and `respond`: ```ts export type FixtureRoutes = { 'GET /pets/:petId': { operation: 'showPetById'; method: 'GET'; path: '/pets/:petId'; params: { petId: string }; // what a fixture function receives, from the URL pathParams: { petId: string }; // the spec's types, for url() query: never; body: never; response: Pet; error: Error; mocked: true; // answers with getShowPetByIdResponseMock() by default }; // ... }; export type FixtureOperations = { listPets: 'GET /pets'; showPetById: 'GET /pets/:petId'; deletePetById: 'DELETE /pets/:petId'; }; ``` ## Defining a fixture Name the operation, or its ` ` route, and give the fields you care about: ```ts import { defineFixture, respond } from '../index.fixtures'; export const rex = defineFixture('showPetById', { name: 'Rex' }); export const sameRex = defineFixture('GET /pets/:petId', { name: 'Rex' }); // Both are Fixture<'GET /pets/:petId'> rex.reply().body; // { id: 7272122785202176, name: 'Rex', tag: '…' } ``` The value is deep-merged onto the endpoint's seeded mock, so `id` and `tag` are fake but the same on every run. A value can be: ```ts import { getShowPetByIdResponseMock } from '../index.faker'; // Nothing: the mock as is, or an empty 204 for deletePetById. const anyPet = defineFixture('showPetById'); const deleted = defineFixture('deletePetById'); // A partial body. Objects merge; arrays, null and primitives replace. const tagged = defineFixture('showPetById', { tag: 'adopted' }); // A body from orval's faker factories. const pets = defineFixture('listPets', [ getShowPetByIdResponseMock({ name: 'Rex' }), getShowPetByIdResponseMock({ name: 'Fido' }), ]); // A status, a body and headers. The body must be the route's response or error type. const missing = defineFixture('showPetById', respond(404, { code: 404, message: 'Not found' })); const conflict = defineFixture('deletePetById', respond(409, { code: 409, message: 'Adopted' })); // A function of the request, returning either. It runs synchronously. const byId = defineFixture('showPetById', ({ params }) => ({ id: Number(params.petId) })); ``` `{ query }` makes a fixture match only requests with those query params, any of the route's. `with(overrides)` returns a new fixture; bodies deep-merge, anything else replaces: ```ts const firstPage = defineFixture('listPets', [], { query: { limit: 10 } }); const adopted = rex.with({ tag: 'adopted' }); ``` Everything is checked against the spec. These fail to compile: ```ts defineFixture('showPet'); // unknown operation defineFixture('GET /pets/:id'); // unknown route defineFixture('showPetById', { name: 1 }); // name is a string defineFixture('showPetById', respond(500, 'down')); // the body must be a Pet or an Error defineFixture('listPets', [], { query: { page: 1 } }); // listPets has no page param ``` ## Using a fixture A fixture has what a mocking library needs to serve it: | Member | Example | | |---|---|---| | `route` | `'GET /pets/:petId'` | The method and path. | | `operation` | `'showPetById'` | | | `method` | `'GET'` | | | `path` | `'/pets/:petId'` | In the path syntax of MSW, Express and the like. | | `query` | `{ limit: '10' }` | The query params a request must have. | | `pattern` | `/^\/pets\/[^/]+(?:\?.*)?$/` | Matches the route's paths, with or without a query string. | | `url(params?, query?)` | `rex.url({ petId: '1' })` → `'/pets/1'` | Typed by the spec's path and query params. | | `matches(request)` | `rex.matches({ method: 'GET', url: '/pets/1' })` → `true` | Its method, path and query. | | `reply(request?)` | `rex.reply({ url: '/pets/1' })` | The response, from a URL or from `params` and `query`. | | `with(overrides)` | `rex.with({ tag: 'adopted' })` | A new fixture. | `reply` returns `{ ok: true, status, body: Pet }` for a 2xx and `{ ok: false, status, body: Error }` otherwise, so checking `ok` narrows `body`. It works without a network too, for a component's props or a query cache. With nock: ```ts nock('https://petstore.test') .intercept(rex.pattern, rex.method) .reply((uri) => { const { status, body } = rex.reply({ url: uri }); return [status, body]; }); ``` With MSW: ```ts server.use( http.get(`*${rex.path}`, ({ request }) => { const { status, body } = rex.reply({ url: request.url }); return HttpResponse.json(body, { status }); }), ); ``` ## Fixture sets `defineFixtures` combines fixtures for many endpoints, and is typed by them: ```ts import { defineFixtures } from '../index.fixtures'; export const petstore = defineFixtures([rex, firstPage, deleted]); // FixtureSet | Fixture<'GET /pets'> | Fixture<'DELETE /pets/:petId'>> petstore.get('showPetById'); // Fixture<'GET /pets/:petId'> petstore.get('createPets'); // fails to compile: not in the set const adoptedStore = petstore.extend([adopted]); // a new set; the same key deep-merges ``` A set's `map` and `forEach` visit each fixture, typed as the set's fixtures, so each one can be registered with a mocking library as its own handler. A set is iterable too. They all go most specific first: literal segments before params, and query constraints before none. Libraries like MSW and nock take the first matching handler, so registering them in that order is enough. Inside the callback, `fixture.route === 'GET /pets/:petId'` narrows the fixture. With MSW, one handler per fixture. `fixture.matches` checks the method and query, and a handler that returns nothing hands the request to the next one: ```ts import { http, HttpResponse, passthrough } from 'msw'; import { setupServer } from 'msw/node'; import { petstore } from '../src/api/fixtures'; // A JSON response, or an empty one for a 204. const toResponse = ({ status, body, headers }: { status: number; body?: unknown; headers?: Record }) => new HttpResponse(body === undefined ? null : JSON.stringify(body), { status, headers: { 'Content-Type': 'application/json', ...headers }, }); export const server = setupServer( ...petstore.map((fixture) => http.all(`*${fixture.path}`, ({ request }) => fixture.matches(request) ? toResponse(fixture.reply({ url: request.url })) : undefined, ), ), ); ``` Or one catch-all handler, with the same `toResponse`: the set's `reply` answers any request from its most specific fixture, and returns `undefined` when none matches. ```ts export const server = setupServer( http.all('*', async ({ request }) => { const answer = petstore.reply({ method: request.method, url: request.url, body: request.body ? await request.clone().json() : undefined, }); return answer ? toResponse(answer) : passthrough(); }), ); ``` With nock, one interceptor per fixture: ```ts petstore.forEach((fixture) => { nock('https://petstore.test') .intercept(fixture.pattern, fixture.method) .reply((uri, body) => { const reply = fixture.reply({ url: uri, body }); return [reply.status, reply.body]; }); }); ``` With Playwright, through the set's `reply`: ```ts await page.route('**/*', (route) => { const answer = petstore.reply({ method: route.request().method(), url: route.request().url() }); return answer ? route.fulfill({ status: answer.status, json: answer.body }) : route.fallback(); }); ``` The set's reply also has the `route`, `key` and `fixture` that answered, so `answer.route === 'GET /pets/:petId'` narrows `answer.body` to `Pet | Error`. ### Map form `defineFixtures` also takes fixtures by operation or route. A `?name=value` suffix on a route only matches requests with that query, and `untyped` holds routes the spec doesn't describe, without type checks: ```ts const petstore = defineFixtures( { showPetById: { name: 'Rex' }, 'GET /pets?limit=10': [], deletePetById: respond(204), }, { untyped: { 'GET /legacy/pets': [] } }, ); ``` `untyped` routes answer through `reply`, but iterating a set yields only typed fixtures. ## Deterministic values Each route's mock is seeded from its route, so it answers with the same values on every run and in every test, wherever the test runs. `defineFixtures(fixtures, { seed })` picks a different, still fixed, set of values: ```ts const other = defineFixtures([rex], { seed: 1 }); ``` Replying from a mocked route reseeds faker's shared instance, so unseeded faker calls after it see a different sequence. ## Sharing fixtures Fixtures don't depend on a test runner or a mocking library, so an API package can export them for its consumers to define their own: ```json { "name": "@acme/petstore-api", "exports": { ".": "./dist/endpoints.js", "./fixtures": "./dist/index.fixtures.js" } } ``` ```ts import { defineFixture, defineFixtures } from '@acme/petstore-api/fixtures'; export const rex = defineFixture('showPetById', { name: 'Rex' }); export const petstore = defineFixtures([rex]); ``` See [`mock.fixtures`](/docs/reference/configuration/output#fixtures) in the configuration reference. --- # Enum Extensions Use OpenAPI extensions and OpenAPI 3.1 enum metadata to generate enums with meaningful names and descriptions. ## OpenAPI Schema Use `x-enumNames` to assign meaningful names to enum members, and `x-enumDescriptions` to add JSDoc comments that appear in IDE tooltips. ```yaml openapi: '3.1.0' info: version: 1.0.0 title: Swagger Petstore components: schemas: MyObject: type: object properties: myEnum: type: number enum: - 1 - 2 - 3 - 4 x-enumNames: - One - Two - Three - Four x-enumDescriptions: - Represents the first value - Represents the second value - Represents the third value - Represents the fourth value ``` ## Generated Output ```ts export const MyEnum = { /** Represents the first value */ One: 1, /** Represents the second value */ Two: 2, /** Represents the third value */ Three: 3, /** Represents the fourth value */ Four: 4, } as const; export type MyEnum = typeof MyEnum[keyof typeof MyEnum]; ``` ## Supported Extensions Orval recognizes the following extensions: **Enum names:** - `x-enumNames` - `x-enumnames` - `x-enum-varnames` **Enum descriptions:** - `x-enumDescriptions` - `x-enumdescriptions` - `x-enum-descriptions` ## OpenAPI 3.1 const enums > **Warning:** > `enumGenerationType: 'enum'` emits a TypeScript `enum` declaration. `enum` > syntax cannot be erased by type stripping, so generated `'enum'` output does not > compile under TypeScript 5.8+ `erasableSyntaxOnly` (e.g. Node's native > TypeScript support). In that environment use `const` (default) or `union` — see > the [`enumGenerationType`](/docs/reference/configuration/output#enumgenerationtype) reference. OpenAPI 3.1 enum-like schemas using `oneOf` with `const` branches can use `title`, `description`, and `deprecated` as member metadata. ```yaml components: schemas: Status: oneOf: - const: PENDING title: Pending description: Awaiting manual review - const: LEGACY title: Legacy description: No longer issued deprecated: true ``` With const enum generation, Orval preserves the branch metadata: ```ts export const Status = { /** Awaiting manual review */ Pending: 'PENDING', /** * No longer issued * @deprecated */ Legacy: 'LEGACY', } as const; export type Status = typeof Status[keyof typeof Status]; ``` When a branch has no `title`, its member name is derived from the `const` value and respects `output.override.namingConvention.enum`. Unannotated `oneOf`/`const` branches retain their existing union output. ## Zod Metadata generates `z.enum({ ... })` for Zod 4 and `z.nativeEnum({ ... } as const)` for Zod 3, while boolean or mixed-type enums remain literal unions and enums without metadata retain their existing output When metadata produces an enum **object** (`z.enum({ Cat: 'cat', ... })`), the generator always emits constant-style `key: value` pairs and deliberately ignores the [`enumGenerationType`](/docs/reference/configuration/output#enumgenerationtype) setting. The object is inserted as an object-literal argument, where `union` (`'cat' | 'dog'`) and `enum` (`Cat = 'cat'`) output are not valid — either would make the generated code fail to parse. If you need union or enum-style output, apply it to enums **without** metadata instead. ### `oneOf`/`const` branches Annotated `oneOf`/`const` branches keep the union-of-literals shape. Each annotated branch carries its `title`, `description` and `deprecated` in [`.meta()`](https://zod.dev/metadata), so the values stay readable at runtime through `zod.globalRegistry`: ```ts export const Status = zod.union([ zod .literal('PENDING') .meta({ title: 'Pending', description: 'Awaiting manual review' }), zod.literal('LEGACY').meta({ title: 'Legacy', description: 'No longer issued', deprecated: true, }), ]); Status.options.map((o) => ({ value: o.value, ...zod.globalRegistry.get(o) })); ``` This does not need `output.override.zod.generateMeta`. Per Zod version: | Zod version | Output | | ----------- | ------------------------------------------------------------------------------------------- | | Zod 4 | `.meta({ title, description, deprecated })` on each annotated branch | | Zod 4 Mini | `.check(zod.meta({ ... }))` on each annotated branch | | Zod 3 | No registry, so branches keep `.describe(description)` and `title`/`deprecated` are dropped | Branches that carry only a `description` keep `.describe()`. Branches with no metadata stay a bare `zod.literal(...)`, so a spec without member metadata produces the same output as before. --- # Stream NDJSON Orval generates type-safe code for [NDJSON](https://en.wikipedia.org/wiki/JSON_streaming#Newline-delimited_JSON) streaming responses, commonly used for large datasets. ## OpenAPI Schema ```yaml openapi: 3.1.0 info: version: 1.0.0 title: Stream paths: /stream: get: operationId: stream description: Stream results responses: '200': content: application/x-ndjson: schema: $ref: '#/components/schemas/StreamEntry' components: schemas: StreamEntry: type: object properties: foo: type: number bar: type: string ``` ## Configuration ```ts import { defineConfig } from 'orval'; export default defineConfig({ petstore: { input: { target: './stream.yaml', }, output: { client: 'fetch', target: 'src/endpoints.ts', schemas: 'src/model', }, }, }); ``` ## Generated Output ```ts interface TypedResponse extends Response { json(): Promise; } export type streamResponse200 = { stream: TypedResponse; status: 200; }; export const stream = async ( options?: RequestInit, ): Promise => { const stream = await fetch(getStreamUrl(), { ...options, method: 'GET', headers: { Accept: 'application/x-ndjson', ...options?.headers }, }); return { status: stream.status, stream, headers: stream.headers, } as streamResponse; }; ``` ## Reading the Stream Orval provides type safety; implement stream parsing yourself: ```ts export const readStream = ( response: Response & { json(): Promise }, processLine: (value: T) => void, ): Promise => { if (!response.body) return Promise.resolve(); const stream = response.body.getReader(); const decoder = new TextDecoder(); let buffer = ''; const loop = (): Promise => stream.read().then(({ done, value }) => { if (done) { if (buffer.length > 0) processLine(JSON.parse(buffer)); return; } buffer += decoder.decode(value, { stream: true }); const parts = buffer.split(/\r?\n/); buffer = parts.pop() ?? ''; for (const part of parts.filter(Boolean)) { processLine(JSON.parse(part) as T); } return loop(); }); return loop(); }; ``` ## Usage Example ```ts export const getResults = async (): Promise => { const results: StreamEntry[] = []; const response = await stream(); if (response.status !== 200) return results; await readStream(response.stream, (obj) => { // obj is typed as StreamEntry results.push(obj); }); return results; }; ``` > **Note:** > NDJSON streaming is only supported with the Fetch client. --- # MCP Server Generate [Model Context Protocol (MCP)](https://modelcontextprotocol.io/introduction) servers from your OpenAPI specification for AI agent integration. ## Overview MCP servers relay API clients to AI agents, eliminating the need to wait for third-party implementations. Create MCP servers for any service with an OpenAPI specification and use them with AI agents like Claude, Cline, and others. ## Configuration ```ts import { defineConfig } from 'orval'; export default defineConfig({ petstore: { input: { target: './petstore.yaml', }, output: { mode: 'single', client: 'mcp', baseUrl: 'https://petstore3.swagger.io/api/v3', target: 'src/handlers.ts', schemas: 'src/http-schemas', }, }, }); ``` > **Note:** > The `mcp` client currently only works in `single` mode. ## Requirements The generated server targets the [MCP TypeScript SDK](https://github.com/modelcontextprotocol/typescript-sdk) v2. Install `@modelcontextprotocol/server` 2.x and `zod` 4.2 or later. The SDK requires Node.js 20 or later. ```bash npm install @modelcontextprotocol/server zod ``` > **Note:** > Upgrading a server generated for SDK v1 (`@modelcontextprotocol/sdk`)? Regenerate with the current orval, then run the official codemod on your hand-written files such as a custom server or custom handler: > ```bash npx @modelcontextprotocol/codemod@latest v1-to-v2 . ``` > > The tool call context is now `ServerContext`: `ctx.signal` became `ctx.mcpReq.signal` and `ctx.requestId` became `ctx.mcpReq.id`. ## Generated Structure ``` src/ ├── http-schemas/ │ ├── createPetsBodyItem.ts │ ├── error.ts │ ├── index.ts │ └── pet.ts ├── handlers.ts # Handler functions returning MCP format ├── http-client.ts # Generated fetch client ├── server.ts # MCP tools and server configuration └── tool-schemas.zod.ts # Zod schemas for tool inputs and outputs, grouped per tool ``` ## Usage ### 1. Build Docker Image ```bash docker build ./ -t mcp-petstore ``` ### 2. Configure AI Agent For Cline: ```json { "mcpServers": { "petstore": { "command": "docker", "args": ["run", "-i", "--rm", "mcp-petstore"], "disabled": false, "alwaysAllow": [] } } } ``` This allows your AI agent to interact with the API through the MCP protocol. ## Request Cancellation When an MCP client cancels a tool call, the generated `server.ts` passes the cancellation signal of that call to the underlying HTTP request. The in-flight `fetch` is aborted with an `AbortError` instead of running to completion. ```ts tools.findPetsByStatus = server.registerTool( 'findPetsByStatus', { // ... }, (args, ctx) => findPetsByStatusHandler(args, { ...options, signal: options?.signal ? AbortSignal.any([options.signal, ctx.mcpReq.signal]) : ctx.mcpReq.signal, }), ); ``` If you pass a `signal` through `createMcpServer(options)`, for example from a custom server to abort every in-flight request on shutdown, it is combined with the per-call signal using `AbortSignal.any`. Either signal aborts the request. ```ts const shutdown = new AbortController(); const { server } = createMcpServer({ signal: shutdown.signal }); process.on('SIGTERM', () => shutdown.abort()); ``` > **Note:** > `AbortSignal.any` requires Node.js 20.3 or later at runtime. ## Custom Handler By default, each generated handler returns the response body as text, marks HTTP status codes of 400 and above as errors, and returns the body as `structuredContent` while the tool declares a matching `outputSchema`. The body is first validated with the operation's response schema, which drops fields that are not in the spec, so `structuredContent` always satisfies the declared schema. When the body does not match the schema, the handler returns the raw body together with the validation error as an `isError` result, so the agent still sees what the API returned. MCP requires both to be objects, so a plain JSON object is passed as is and any other body (array, primitive, union) is wrapped as `{ result: body }`. Empty responses get neither, and so do responses that cannot be expressed as JSON Schema: those containing `allOf` (or `oneOf` with sibling properties) anywhere, whose JSON Schema becomes an `allOf` of closed objects that no value satisfies, and those containing `Date` (with `useDates`) or binary bodies, which have no JSON Schema representation. To take over response shaping and error mapping, or to use the tool call context for logging, authorization, or elicitation, provide a custom handler via `override.mcp.handler`. ```ts import { defineConfig } from 'orval'; export default defineConfig({ petstore: { input: { target: './petstore.yaml', }, output: { mode: 'single', client: 'mcp', baseUrl: 'https://petstore3.swagger.io/api/v3', target: 'src/handlers.ts', schemas: 'src/http-schemas', override: { mcp: { handler: { path: './custom-handler.ts', name: 'customHandler', }, }, }, }, }, }); ``` When set, every generated handler binds the tool arguments to a `fetcher` and delegates to your function together with the tool call context and the `toStructuredContent` function that `server.ts` builds for that operation, next to its `outputSchema`: ```ts import { customHandler } from '../custom-handler'; import type { ServerContext } from '@modelcontextprotocol/server'; export const findPetsByStatusHandler = async ( args: findPetsByStatusArgs, options: RequestInit, ctx: ServerContext, toStructuredContent: ( data: unknown, ) => | { success: true; data: Record | undefined } | { success: false; error: { message: string } }, ) => { const fetcher = (overrides?: RequestInit) => findPetsByStatus(args.queryParams, { ...options, ...overrides, headers: { ...Object.fromEntries(new Headers(options.headers)), ...Object.fromEntries(new Headers(overrides?.headers)), }, }); return customHandler(fetcher, ctx, toStructuredContent); }; ``` ```ts tools.findPetsByStatus = server.registerTool( 'findPetsByStatus', { inputSchema: FindPetsByStatusInput, outputSchema: FindPetsByStatusOutput, annotations: { readOnlyHint: true }, }, (args, ctx) => findPetsByStatusHandler( args, { ...options, signal: options?.signal ? AbortSignal.any([options.signal, ctx.mcpReq.signal]) : ctx.mcpReq.signal, }, ctx, (data: unknown) => FindPetsByStatusOutput.safeParse({ result: data }), ), ); ``` Implement the handler with the following signature. Its return value is used as the tool result as is. Pass the body through `toStructuredContent` so that `structuredContent` matches the `outputSchema` the generated server declares for the tool. ```ts import type { CallToolResult, ServerContext, } from '@modelcontextprotocol/server'; export const customHandler = async ( fetcher: ( overrides?: RequestInit, ) => Promise<{ status: number; data: unknown; headers: Headers }>, ctx: ServerContext, toStructuredContent: ( data: unknown, ) => | { success: true; data: Record | undefined } | { success: false; error: { message: string } }, ): Promise => { const res = await fetcher({ headers: ctx.sessionId ? { 'Mcp-Session-Id': ctx.sessionId } : undefined, }); if (res.status >= 400) { return { content: [ { type: 'text', text: JSON.stringify({ requestId: ctx.mcpReq.id, status: res.status, error: 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, }; }; ``` `fetcher` already carries the tool arguments and the `RequestInit` passed to `createMcpServer`, including the cancellation signal described above. Pass overrides to add headers or to replace the signal. Headers from overrides are merged into the base headers instead of replacing them, so headers such as `Authorization` set through `createMcpServer(options)` stay on the request. `ctx` is the SDK's `ServerContext` for the current tool call. It exposes `sessionId`, `mcpReq` (with `id`, `signal`, `send`, and `notify`), and `http` (with `req` and `authInfo` when served over HTTP). `toStructuredContent` validates the body with the operation's response schema and returns the `safeParse` result. On success, `data` is the body as is when the response is a plain object (with fields that are not in the spec dropped), `{ result: body }` for any other response, and `undefined` when the tool declares no `outputSchema`. On failure, `error.message` describes the mismatch; return it together with the raw body so the agent still sees what the API returned. > **Note:** > Place the custom handler file outside the generated output directory. If `clean: true` is set, orval deletes the output directory on each run and will remove any file inside it. ## Custom Server By default, the generated `server.ts` serves stdio via `serveStdio`. To use a different transport (e.g., Streamable HTTP for container deployments), provide a custom server function via `override.mcp.server`. ```ts import { defineConfig } from 'orval'; export default defineConfig({ petstore: { input: { target: './petstore.yaml', }, output: { mode: 'single', client: 'mcp', baseUrl: 'https://petstore3.swagger.io/api/v3', target: 'src/handlers.ts', schemas: 'src/http-schemas', override: { mcp: { server: { path: './custom-server.ts', name: 'customServer', }, }, }, }, }, }); ``` The generated `server.ts` calls your function with a `createMcpServer` factory: ```ts import { customServer } from '../custom-server'; const createMcpServer = ( options?: RequestInit, ): { server: McpServer; tools: Record } => { // ...tool registrations }; customServer(createMcpServer); ``` Implement the custom server function to set up any transport. The following example uses [Hono](https://hono.dev) with [`@modelcontextprotocol/hono`](https://www.npmjs.com/package/@modelcontextprotocol/hono) for Streamable HTTP: ```ts import { createMcpHonoApp } from '@modelcontextprotocol/hono'; import { type McpServer, type RegisteredTool, WebStandardStreamableHTTPServerTransport, } from '@modelcontextprotocol/server'; // `createMcpHonoApp` parses JSON bodies into this variable. declare module 'hono' { interface ContextVariableMap { parsedBody?: unknown; } } export const customServer = ( createMcpServer: (options?: RequestInit) => { server: McpServer; tools: Record; }, ) => { const app = createMcpHonoApp(); app.all('/mcp', async (c) => { // Stateless: a fresh server and transport per request. const { server } = createMcpServer(); const transport = new WebStandardStreamableHTTPServerTransport({ sessionIdGenerator: undefined, }); await server.connect(transport); return transport.handleRequest(c.req.raw, { parsedBody: c.get('parsedBody'), }); }); Bun.serve({ fetch: app.fetch, port: Number(process.env.PORT ?? 3000) }); }; ``` > **Note:** > Place the custom server file outside the generated output directory. If `clean: true` is set, orval deletes the output directory on each run and will remove any file inside it. ## Full Example See the [MCP Petstore sample](https://github.com/orval-labs/orval/tree/master/samples/mcp/petstore) and [MCP Custom Server sample](https://github.com/orval-labs/orval/tree/master/samples/mcp/custom-server) on GitHub. --- # CLI Generate type-safe clients from OpenAPI specifications. ## Basic Usage ```bash orval ``` By default, Orval searches for an `orval.config.js` file. ## Options ### --input, -i Path or URL to your OpenAPI specification: ```bash orval --input ./petstore.yaml ``` ### --output, -o Output path for generated files: ```bash orval --output ./api/endpoints/petstoreFromFileSpec.ts ``` ### --config, -c Path to your Orval config: ```bash orval --config ./api/orval.config.js ``` ### --project, -p Focus on specific projects in your config: ```bash orval --project petstore orval --project dogstore catstore ``` ### --watch, -w Watch files and regenerate on changes: ```bash orval --watch orval --watch ./src ``` ### --clean Clean the output directories before regenerating: ```bash orval --clean orval --clean ./src ``` > **Warning:** > This cleans all output target and schemas folders. ### --formatter Format generated files with the specified formatter (`prettier`, `biome`, or `oxfmt`): ```bash orval --formatter prettier orval --formatter biome orval --formatter oxfmt ``` ### --tsconfig Specify the path to your tsconfig: ```bash orval --tsconfig ./src/tsconfig.json ``` ### --log-level Set the log level. One of `error`, `warn`, `info`, `verbose`, or `debug`. The default is `info`. Mutually exclusive with `--verbose` and `--quiet`: ```bash orval --log-level warn ``` ### --verbose Shortcut for `--log-level verbose`. Mutually exclusive with `--log-level` and `--quiet`: ```bash orval --verbose ``` ### --quiet Shortcut for `--log-level warn`. Errors and warnings are printed; info, verbose, and debug are suppressed. Mutually exclusive with `--log-level` and `--verbose`: ```bash orval --quiet ``` ### --fail-on-warnings Exit with error code 1 when warnings are emitted during generation. Useful in CI pipelines to enforce warning-free builds: ```bash orval --fail-on-warnings ``` --- # Programmatic API Integrate Orval code generation into your build processes, scripts, or tools. ## Basic Usage ```ts import orval from 'orval'; // Generate using a config file path await orval('./orval.config.js'); // Generate using current directory's orval config await orval(); ``` Or use the named export: ```ts import { generate } from 'orval'; await generate('./orval.config.js'); ``` ## Direct Configuration Pass a configuration object directly: ```ts import { generate, type Options } from 'orval'; const config: Options = { input: { target: './api-spec.yaml', }, output: { target: './src/api.ts', client: 'axios', }, }; await generate(config); ``` ## Global Options Override Override config settings with global options. See the [GlobalOptions interface](https://github.com/orval-labs/orval/blob/master/packages/core/src/types.ts#L718) for all available options. ```ts import { generate, type GlobalOptions } from 'orval'; const globalOptions: GlobalOptions = { // File watching watch: true, // Enable watch mode (boolean) // watch: './specs/**/*.yaml', // Watch specific file pattern (string) // watch: ['./specs/*.yaml'], // Watch multiple patterns (string[]) // Output control clean: true, // Clean all output directories (boolean) // clean: ['./src/api', './types'], // Clean specific directories (string[]) output: './src/generated', // Override output directory // Code formatting (only one formatter can be used at a time) formatter: 'prettier', // 'prettier' | 'biome' | 'oxfmt' // HTTP client configuration client: 'fetch', // Override HTTP client httpClient: 'fetch', // HTTP implementation: 'axios' | 'fetch' // Generation mode mode: 'split', // Output mode: 'single' | 'split' | 'tags' | 'tags-split' // Mock data generation mock: true, // Enable mock data generation // mock: { generators: [{ type: 'msw', delay: 1000 }] }, // Configure mock options // TypeScript configuration tsconfig: './tsconfig.json', // Custom tsconfig path // tsconfig: { compilerOptions: {...} }, // Inline tsconfig // Package configuration packageJson: './package.json', input: './api-spec.yaml', // Logging logLevel: 'error', // Only log errors // Error handling throwOnError: true, // Throw initial generation errors instead of only logging them }; await generate('./orval.config.js', process.cwd(), globalOptions); ``` ## Custom Workspace Specify a custom workspace directory: ```ts import { generate } from 'orval'; const workspace = '/path/to/your/project'; await generate('./orval.config.js', workspace); ``` ## Function Signature ```ts function generate( optionsExport?: string | OptionsExport, workspace?: string, options?: GlobalOptions, ): Promise; ``` **Parameters:** - `optionsExport`: Path to config file or configuration object - `workspace`: Working directory (defaults to `process.cwd()`) - `options`: Global options to override config settings ## Error Handling By default, `generate()` logs initial generation errors and resolves after processing the configured project or projects. Set `throwOnError` when a script needs to catch initial generation failures and decide how to handle them: ```ts import { generate } from 'orval'; try { await generate('./orval.config.js', process.cwd(), { throwOnError: true, }); } catch { process.exit(1); } ``` `generate()` logs the error through the active reporter before rethrowing. `logger.error` is a no-op outside `withReporter`, so do not use it in this catch block. When a config file contains multiple projects, `throwOnError` stops on the first generation error during the initial run instead of continuing with later projects. Watch mode generation errors are still logged after the watcher has started. ## Reporter Reporter selection is a programmatic execution concern, not an `orval.config.ts` option. Built-in packages emit structured events (`message`, `packageName`, and optional `projectName`) to the reporter that is active for the current async call chain. | Entry point | Default | | --- | --- | | Lower-level APIs (`generateSpec`, helpers, formatters) | No-op | | CLI (`orval`) | Console | | Public `generate()` | Console, unless an outer `withReporter` is already active | Use `withReporter` to install a reporter for one call chain. An outer reporter always wins over `generate()`'s console default, so tests can silence output without changing production callers. ```ts import { generate, withReporter, noopReporter, consoleReporter } from 'orval'; // Quiet a programmatic generate() in tests await withReporter(noopReporter, () => generate(config)); // Opt lower-level APIs into the existing terminal output await withReporter(consoleReporter, () => generateSpec(workspace, options)); ``` Custom reporters receive the structured event. When a project is in scope, the logger prefixes `message` with that project name. Messages keep their current wording and ANSI styling; strip colors if you need plain text. ```ts import { generate, withReporter, type OrvalReporter } from 'orval'; const reporter: OrvalReporter = { info: (event) => process.stdout.write(`${event.packageName}: ${event.message}\n`), warn: (event) => process.stderr.write(`${event.packageName}: ${event.message}\n`), error: (event) => process.stderr.write(`${event.packageName}: ${event.message}\n`), verbose: (event) => process.stderr.write(`${event.packageName}: ${event.message}\n`), debug: (event) => { if (process.env.DEBUG) { process.stderr.write(`${event.packageName}: ${event.message}\n`); } }, }; await withReporter(reporter, () => generate(config)); ``` To assert one or more events in a test, override only the levels you care about: ```ts import { generate, noopReporter, withReporter } from 'orval'; import { vi } from 'vitest'; const warn = vi.fn(); await withReporter({ ...noopReporter, warn }, () => generate(config)); expect(warn).toHaveBeenCalledWith( expect.objectContaining({ packageName: 'orval', message: expect.stringContaining('symbolic link'), }), ); ``` ## Watch Mode ```ts import { generate } from 'orval'; // Enable watch mode await generate('./orval.config.js', process.cwd(), { watch: true, }); // Watch specific files/directories await generate('./orval.config.js', process.cwd(), { watch: ['./specs/*.yaml', './src/custom-types.ts'], }); ``` ## Build Scripts Integration ```json { "scripts": { "generate": "node scripts/generate-api.js", "dev": "npm run generate && next dev", "build": "npm run generate && next build" } } ``` ```js const { generate } = require('orval'); async function main() { try { await generate(); console.log('API client generated successfully'); } catch (error) { console.error('Failed to generate API client:', error); process.exit(1); } } main(); ``` --- # Configuration Configure Orval using an `orval.config.(js|mjs|ts)` file at the root of your project. ## Configuration Categories | Category | Description | |----------|-------------| | **Input** | Path to the specification, or a configuration object for transforming the specification before generation | | **Output** | Output path and configuration for what is generated and how | | **Hooks** | Scripts to run on certain events | ## Basic Example ```ts import { defineConfig } from 'orval'; export default defineConfig({ petstore: { input: './petstore.yaml', output: './petstore.ts', hooks: { afterAllFilesWrite: 'prettier --write', }, }, }); ``` ## Multiple Specifications ```ts import { defineConfig } from 'orval'; export default defineConfig({ petstore: { input: './petstore.yaml', output: './src/api/petstore.ts', }, users: { input: './users.yaml', output: './src/api/users.ts', }, }); ``` --- # Input ## target Path or URL to your OpenAPI specification. **Type:** `string | string[] | OpenApiDocument | Record` ```ts import { defineConfig } from 'orval'; export default defineConfig({ petstore: { input: { target: './petstore.yaml', }, }, }); ``` You can also pass an array of targets. Orval will try each target in order and use the first one that resolves successfully (file exists on disk or URL is reachable). This is useful for fallback scenarios, such as preferring a local spec file when available while falling back to a remote URL. ```ts import { defineConfig } from 'orval'; export default defineConfig({ petstore: { input: { target: [ './local-petstore.yaml', 'https://petstore.swagger.io/v2/swagger.json', ], }, }, }); ``` The shorthand `input` property supports arrays as well: ```ts import { defineConfig } from 'orval'; export default defineConfig({ petstore: { input: [ './local-petstore.yaml', 'https://petstore.swagger.io/v2/swagger.json', ], }, }); ``` ## override ### transformer Transform the OpenAPI specification before generation. **Type:** `String | Function` ```ts import { defineConfig } from 'orval'; export default defineConfig({ petstore: { input: { override: { transformer: 'src/api/transformer/add-version.js', }, }, }, }); ``` The transformer runs **before** spec validation (and before the internal OpenAPI 2.0 → 3.1 upgrade), so you can use it to repair malformed specs (e.g. fix non-compliant fields) without having to disable validation entirely. If your spec is OpenAPI 2.0 (Swagger), the transformer receives the 2.0 document — not the upgraded 3.1 form. The transformer function receives an `OpenApiDocument` and must return an `OpenApiDocument` or a `Promise` — async transformers are supported, so you can fetch overrides or run any awaitable repair step inside. You may mutate the input in place or return a new object. The `defineTransformer` helper from `orval` provides type inference for both sync and async returns. ```ts import { defineTransformer } from 'orval'; export default defineTransformer((inputSchema) => ({ ...inputSchema, info: { ...inputSchema.info, title: `${inputSchema.info?.title} - Custom`, }, })); ``` Async example: ```ts import { defineTransformer } from 'orval'; export default defineTransformer(async (inputSchema) => { const overrides = await fetch('https://example.com/overrides.json').then( (r) => r.json(), ); return { ...inputSchema, ...overrides }; }); ``` See [example transformer](https://github.com/orval-labs/orval/blob/master/samples/basic/api/transformer/add-version.js). ## filters Filter which endpoints to generate. **Default:** `{}` ### mode **Type:** `'include' | 'exclude'` **Default:** `'include'` ```ts import { defineConfig } from 'orval'; export default defineConfig({ petstore: { input: { filters: { mode: 'exclude', tags: ['pets'], }, }, }, }); ``` ### tags **Type:** `(String | RegExp)[]` **Default:** `[]` Filter by OpenAPI tags: ```ts import { defineConfig } from 'orval'; export default defineConfig({ petstore: { input: { filters: { tags: ['pets', /health/], }, }, }, }); ``` When `tags` is set and `schemas` is not, orval automatically limits the output to only the schemas referenced (directly or transitively) by the matching operations. This prevents unrelated schemas from appearing in the generated output. If you also specify `schemas`, it takes precedence and the automatic inference is skipped. To filter endpoints by tags while still outputting **all** schemas, set [`includeUnreferencedSchemas: true`](#includeunreferencedschemas) — it works in both `include` and `exclude` mode. ### schemas **Type:** `(String | RegExp)[]` Filter by schema names explicitly: ```ts import { defineConfig } from 'orval'; export default defineConfig({ petstore: { input: { filters: { schemas: ['Error', /Cat/], }, }, }, }); ``` ### includeUnreferencedSchemas **Type:** `Boolean` **Default:** `false` When `tags` is set (and `schemas` is not), orval emits only the schemas referenced by the matching operations. Set `includeUnreferencedSchemas: true` to keep **every** `#/components/schemas` entry — including schemas referenced by no operation — while still filtering endpoints by `tags`. The other component sections (`responses`, `parameters`, `requestBodies`) remain pruned to what the matching operations use. This works in both `include` and `exclude` mode, and is ignored when `schemas` is set. ```ts import { defineConfig } from 'orval'; export default defineConfig({ petstore: { input: { filters: { mode: 'exclude', tags: ['stream'], includeUnreferencedSchemas: true, }, }, }, }); ``` ## parserOptions Optional configuration for the OpenAPI spec parser, particularly useful for fetching specs from protected URLs. ### headers **Type:** `Array<{ domains: string[]; headers: Record }>` Domain-specific headers to send when fetching the OpenAPI specification from remote URLs. Headers are matched based on the domain of the URL being fetched. ```ts import { defineConfig } from 'orval'; export default defineConfig({ petstore: { input: { target: 'https://api.example.com/openapi.json', parserOptions: { headers: [ { domains: ['api.example.com'], headers: { Authorization: 'Bearer YOUR_TOKEN', 'X-API-Key': 'your-api-key', }, }, ], }, }, }, }); ``` Configure different headers for different domains: ```ts import { defineConfig } from 'orval'; export default defineConfig({ petstore: { input: { target: 'https://api.example.com/openapi.json', parserOptions: { headers: [ { domains: ['api.example.com', 'api.prod.example.com'], headers: { Authorization: 'Bearer PROD_TOKEN', }, }, { domains: ['api.dev.example.com'], headers: { Authorization: 'Bearer DEV_TOKEN', }, }, ], }, }, }, }); ``` ### compress **Type:** `(value: string) => Promise | string` Optional callback forwarded to `@scalar/json-magic` when external references are bundled. It may be synchronous or asynchronous. The callback receives Scalar's normalized external document identity/path, not necessarily the literal `$ref`, and returns the key used internally for that document. Returning stable service names makes external schema names easier to read when using `externalRefs.strategy: 'always'`. ```ts import { defineConfig } from 'orval'; const externalIdentity = (value: string) => { if (value.includes('billing')) return 'billing'; if (value.includes('catalog')) return 'catalog'; return value; }; export default defineConfig({ petstore: { input: { target: './openapi.yaml', parserOptions: { compress: externalIdentity, externalRefs: { allow: ['*'], strategy: 'always', }, }, }, }, }); ``` With this configuration, a `User` schema from `billing.yaml` is emitted as `User_billing`. An asynchronous callback uses the same configuration shape: ```ts const externalIdentity = async (value: string) => value.includes('billing') ? 'billing' : value; ``` The callback must produce unique values for distinct external documents. Scalar will retry collisions, but a callback that cannot produce a unique value fails instead of silently merging two documents. ### externalRefs Control how external `$ref` targets (local files or remote URLs) are resolved. By default, orval refuses to resolve any external `$ref` and prints a config snippet you can paste into your `parserOptions`. #### allow **Type:** `string[]` **Default:** `[]` External `$ref` document targets to allow. Each entry should be the document part of the `$ref` (without the `#/...` fragment). File paths are relative to the spec file. ```ts import { defineConfig } from 'orval'; export default defineConfig({ petstore: { input: { target: './openapi.yaml', parserOptions: { externalRefs: { allow: [ './schemas/pet.yaml', './schemas/user.yaml', 'https://example.com/schemas/shared.json', ], }, }, }, }, }); ``` Use `['*']` to allow all external refs (previous behavior). Orval will emit a warning listing external documents referenced by the top-level spec: ```ts parserOptions: { externalRefs: { allow: ['*'], }, } ``` #### strategy **Type:** `'default' | 'always'` **Default:** `'default'` Controls the names assigned to schemas imported from external documents. - `default` preserves the current behavior: an external schema keeps its original name and receives a suffix only when that name collides with an existing schema. - `always` appends the sanitized external document key to every external schema. For example, `User` from the `billing` document becomes `User_billing`. The external document key is produced by `parserOptions.compress` when it is configured. Without `compress`, `always` still works, but Scalar's default generated, hash-like key is used, so names may look like `User_`. Use a stable custom `compress` callback such as one that maps `billing.yaml` to `billing` when human-readable names are needed. Orval does not silently overwrite an existing final component name. A collision with a local or external component, including a collision after suffix sanitization, produces an explicit error. This deliberately prevents an ambiguous suffix from overwriting a schema as could happen in the legacy behavior. > **Warning:** > **Security:** External `$ref` values come from the spec being processed, which may > be untrusted. Allowing all external refs (`['*']`) means orval will read arbitrary > local files and fetch arbitrary URLs referenced by the spec. Prefer listing > specific documents you trust. `externalRefs.allow` still controls which external documents may be loaded; `compress` and `strategy` only control the names of external schemas that have already been allowed. ## unsafeDisableValidation Disable OpenAPI spec validation during code generation. **Type:** `boolean` **Default:** `false` > **Warning:** > **Use at your own risk.** Code generation from an invalid OpenAPI spec is not > guaranteed to work and may break in minor updates. Bug reports with validation > disabled will not be accepted. When `true`, orval skips both spec-level validation (`@scalar/openapi-parser`) and the component-key check, and proceeds with code generation regardless of spec errors. Intended as an escape hatch for specs that use non-standard extensions (e.g. FastAPI's `itemSchema` on `text/event-stream` responses) which a compliant validator would otherwise reject. Prefer [`override.transformer`](#transformer) — which runs before validation — when the spec can be repaired in-place. ```ts import { defineConfig } from 'orval'; export default defineConfig({ petstore: { input: { target: './petstore.yaml', unsafeDisableValidation: true, }, }, }); ``` --- # Output ## target **Type:** `String` Output path for generated files. ```ts import { defineConfig } from 'orval'; export default defineConfig({ petstore: { output: { target: 'src/petstore.ts', }, }, }); ``` ## client **Type:** `String | Function` **Default:** `'axios-functions'` **Options:** `angular`, `angular-query`, `axios`, `axios-functions`, `react-query`, `solid-start`, `solid-query`, `svelte-query`, `vue-query`, `pinia-colada`, `swr`, `zod`, `effect`, `hono`, `fetch`, `mcp` See the [Pinia Colada guide](/docs/guides/pinia-colada) for generated Vue queries, mutations, query keys, and options. ```ts export default defineConfig({ petstore: { output: { client: 'react-query', }, }, }); ``` You can also provide a function to create a custom client generator. ### custom client A custom client is a function that receives the built-in generators (keyed by client name) and returns a `ClientGeneratorsBuilder`. Only `client` is required; `header`, `footer`, `dependencies`, `title`, and `extraFiles` are optional, and you can reuse any built-in generator for them. ```ts import { defineConfig } from 'orval'; export default defineConfig({ petstore: { output: { target: './src/api.ts', client: (clients) => ({ // Reuse the fetch generator for every operation... ...clients.fetch, // ...and append custom code once per generated file. footer: ({ operationNames }) => `\nexport const operationNames = ${JSON.stringify(operationNames)};\n`, }), }, }, }); ``` The `footer` function receives a single object: | Property | Type | Description | | ---------------- | ------------------------------------- | ------------------------------------------------- | | `operationNames` | `string[]` | Names of the operations generated into the file | | `operations` | `GeneratorOperation[] \| undefined` | The generated operations | | `title` | `string \| undefined` | The generated implementation title | | `hasMutator` | `boolean` | Whether a mutator is used in the file | | `hasAwaitedType` | `boolean` | Whether the TypeScript version supports `Awaited` | | `output` | `NormalizedOutputOptions \| undefined` | The normalized output options | It must return the string to append to the file. > **Warning:** > Before v8.42.0, a custom client's `footer` was first called with a bare > `string[]` of operation names, and only received the object above if that > call threw. It now always receives the object. ### axios client The `axios` client generates a factory function with an optional axios instance parameter for dependency injection: ```typescript import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios'; export const getPetsApi = (axiosInstance: AxiosInstance = axios) => ({ listPets: (params?: ListPetsParams) => axiosInstance.get('/pets', { params }), }); ``` You can inject your own axios instance for testing or custom configuration: ```typescript const customAxios = axios.create({ baseURL: 'https://api.example.com' }); const api = getPetsApi(customAxios); ``` The generated factory also exposes a `getUrl` helper for each operation that does not use a custom mutator: ```typescript 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 `getUrl` (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. ```ts export default defineConfig({ petstore: { output: { client: 'swr', httpClient: 'axios', }, }, }); ``` ## schemas **Type:** `String | Object | false` **Default:** Same as `target` Output path for generated model types. Set to `false` to disable separate schema file output. ### String form ```ts export default defineConfig({ petstore: { output: { schemas: './api/model', }, }, }); ``` ### Object form ```ts 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.ts ``` When `splitByTags` is also enabled, tag directories are nested under their route. Schemas used by multiple tags (or by no operation) are placed under `/shared`, for example `schemas/models/pets`, `schemas/models/shared`, and `schemas/types/pets`. ### Single-file schemas `schemas` is written independently of [`mode`](#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: ```ts 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 `/index` with [`schemaFileExtension`](#schemafileextension). No additional barrel is generated, regardless of `indexFiles`. Mock generators import their types from this module. This mode cannot be combined with `splitByTags`, `routes`, `operationSchemas`, or `factoryMethods`. #### Zod Set `schemas.mode: 'single'` to write component and operation schemas into `/index.zod.ts`. The API client stays in its own output file and imports the schemas and their derived TypeScript types from this module. ```ts 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 `/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. Zod single 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: ```ts export default defineConfig({ petstore: { output: { target: './libs/client/angular/src/lib/endpoints', schemas: { path: './libs/client/models/src/lib', type: 'typescript', importPath: '@acme/client/models', }, }, }, }); ``` ```ts // Without importPath — computed relative path: import type { Pet } from '../models/pet'; // With importPath: '@acme/client/models': import type { Pet } from '@acme/client/models'; ``` Schemas are still written to the filesystem `path` — only the generated import statements change. **Requirements when using `importPath`:** - The target package must export the types at the specified import path. - With `indexFiles: true` (recommended), all types are imported from the single `importPath` (e.g., `@acme/models`). - With `indexFiles: false`, each schema is imported individually (e.g., `@acme/models/pet`). The package must support these subpath exports. For Zod schemas (`type: 'zod'`) the per-file suffix is `.zod`, so the package must also expose `./pet.zod` (e.g., `@acme/models/pet.zod`). - If using faker schema factories (`mock: { generators: [{ type: 'faker', schemas: true }] }`), the package must also export `./index.faker`. When the package can't expose a sub-path (e.g. `importPath` resolves to a single barrel file via tsconfig path mappings), set [`schemasImportPath`](#schemasimportpath) on the faker generator to point faker factories at a separate import path. - If using factory methods (`factoryMethods`), each schema is imported individually regardless of `indexFiles`. - When `importPath` is set, the relative-path computation in `factoryMethods.outputDirectory` is bypassed: factories resolve imports against the package specifier rather than the on-disk factory output directory. - Config normalization rejects invalid `importPath` values (empty, whitespace, relative, or absolute paths) before generation runs — see [Validation of `importPath`](#validation-of-importpath). #### Validation of `importPath` During config normalization, orval rejects the following `importPath` values with a clear error message before generation runs: - Empty string. - Strings that are empty after trimming whitespace (e.g. `" "`), or that contain leading/trailing whitespace around a valid-looking specifier. - Relative specifiers starting with `./` or `../` (e.g. `./models`, `../models`). - Absolute paths — POSIX (starting with `/`, e.g. `/abs/models`) or Windows (drive-letter like `C:\models`, or UNC like `\\server\share\models`). ### splitByTags When `splitByTags` is `true`, schemas are organized into per-tag subdirectories instead of a single flat directory. Schemas referenced by only one tag go in that tag's directory; schemas referenced by multiple tags (or not referenced by any operation) go in `shared`. If an operation has multiple tags, Orval uses its first tag, matching the existing Orval routing behavior. Works with any `output.mode` (`single`, `split`, `tags`, `tags-split`). ```ts export default defineConfig({ petstore: { output: { mode: 'tags-split', schemas: { path: './api/model', splitByTags: true, }, }, }, }); ``` Result: ``` api/model/ ├── shared/ ← schemas used by 2+ tags (or unreferenced) │ ├── error.ts │ └── pagination.ts ├── pets/ ← schemas only used by "pets" operations │ ├── pet.ts │ ├── createPetsBody.ts │ ├── listPetsParams.ts │ └── index.ts └── index.ts ← root barrel re-exporting shared/ + tag dirs ``` Cross-tag imports from within a tag subdirectory resolve to the parent: ```ts // 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 `/shared`. - Incompatible with `operationSchemas` — operation-derived types are placed within their tag directories automatically. - Schema-to-tag mapping is transitive: if `Pet` imports `Dog` which imports `Dachshund`, all three land in the same directory. ## operationSchemas **Type:** `String` Separate path for operation-derived types (params, bodies, responses). ```ts export default defineConfig({ petstore: { output: { schemas: './api/model', operationSchemas: './api/model/params', }, }, }); ``` ## fileExtension **Type:** `String` **Default:** `.ts` Customize file extension for generated files: ```ts export default defineConfig({ petstore: { output: { mode: 'split', target: './gen/endpoints', schemas: './gen/model', fileExtension: '.gen.ts', }, }, }); ``` Result: ``` src/gen/ ├── endpoints │ └── swaggerPetstore.gen.ts └── model ├── listPetsParams.ts └── pets.ts ``` ## schemaFileExtension **Type:** `String` **Default:** `'.zod.ts'` when generating Zod schemas (`schemas: { type: 'zod' }` or `client: 'zod'` + [`generateReusableSchemas`](#generatereusableschemas)), otherwise the same as [`fileExtension`](#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`](#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: ```ts export default defineConfig({ petstore: { output: { mode: 'split', client: 'zod', target: './gen/endpoints', schemas: './gen/model', fileExtension: '.ts', // Keep client files at .ts but emit reusable Zod schemas as .zod.ts: schemaFileExtension: '.zod.ts', override: { zod: { generateReusableSchemas: true } }, }, }, }); ``` ## namingConvention **Type:** `'camelCase' | 'PascalCase' | 'snake_case' | 'kebab-case'` **Default:** `'camelCase'` Naming convention for generated **files**: ```ts export default defineConfig({ petstore: { output: { namingConvention: 'PascalCase', mode: 'split', target: './gen/endpoints', }, }, }); ``` ## workspace **Type:** `String` Base folder for all generated files. Creates an `index.ts` with exports: ```ts export default defineConfig({ petstore: { output: { workspace: 'src/', target: './petstore.ts', }, }, }); ``` ## workspaceExports **Type:** `(exports: string[]) => string[]` Controls which modules the [`workspace`](#workspace) `index.ts` re-exports. The function receives the relative specifiers Orval would export (for example `['./petstore', './model']`) and returns the list to write. Use it to keep generated schemas out of the public API, or to re-export your own files: ```ts export default defineConfig({ petstore: { output: { workspace: 'src/', target: './petstore.ts', schemas: './model', workspaceExports: (exports) => [ ...exports.filter((e) => e !== './model'), './custom-schemas', ], }, }, }); ``` Specifiers you filter out are also removed from an existing `index.ts`. Specifiers you add are written as-is, so include the import extension (e.g. `.js`) if your module resolution requires one. Has no effect when [`indexFiles`](#indexfiles) is `false`. ## mode **Type:** `'single' | 'split' | 'tags' | 'tags-split' | 'tags-operations' | 'tags-operations-split'` **Default:** `'single'` ### single Everything in one file. ### split Separate files for implementation, schemas, and mocks: ``` my-app/src/ ├── petstore.schemas.ts ├── petstore.msw.ts └── petstore.ts ``` ### tags One file per OpenAPI tag: ``` my-app/src/ ├── pets.ts └── petstore.schemas.ts ``` ### tags-split Folder per tag with split files: ``` my-app/src/ ├── petstore.schemas.ts └── pets/ ├── petstore.msw.ts └── petstore.ts ``` When [`schemas`](#schemas) is configured, models go to a dedicated directory with per-file output. With [`indexFiles`](#indexfiles) enabled (the default), a barrel `index.ts` re-exports all schemas so service files import from a single path. With `indexFiles: false`, each schema is imported individually (e.g., `../models/pet`). All schemas — domain types, generic wrappers, and bound aliases — live in this single shared directory and are never duplicated per tag: ``` my-app/src/ ├── models/ │ ├── index.ts │ ├── pet.ts │ ├── listResponse.ts │ ├── pagination.ts │ └── userListResponse.ts ├── pets/ │ └── pets.ts ← imports from ../models └── users/ └── users.ts ← imports from ../models ``` To organize schemas into per-tag subdirectories instead of a flat directory, use [`splitByTags`](#splitbytags): ``` my-app/src/ ├── models/ │ ├── pagination.ts ← shared schemas at root │ ├── error.ts │ ├── pets/ │ │ ├── pet.ts │ │ ├── listPetsParams.ts │ │ └── index.ts │ ├── users/ │ │ ├── user.ts │ │ ├── listUsersParams.ts │ │ └── index.ts │ └── index.ts ← root barrel ├── pets/ │ └── pets.ts └── users/ └── users.ts ``` ### tags-operations Generate one implementation file per operation inside a directory for each OpenAPI tag. When `indexFiles` is enabled (the default), a per-tag `index.ts` re-exports the operation files: ``` my-app/src/ └── pets/ ├── get-pet.ts ├── list-pets.ts └── index.ts ``` The operation file contains that operation's types and runtime implementation. With `indexFiles: false`, import the operation files directly. This mode is supported for the `react-query`, `svelte-query`, `vue-query`, `swr`, and `fetch` clients. ### tags-operations-split Generate one implementation file and one `.schemas.ts` file per operation, nested under the operation's tag directory. When `indexFiles` is enabled (the default), a per-tag `index.ts` re-exports both files: ``` my-app/src/ └── pets/ ├── get-pet.ts ├── get-pet.schemas.ts ├── list-pets.ts ├── list-pets.schemas.ts └── index.ts ``` This mode uses the same supported clients as `tags-operations`. With `indexFiles: false`, import the operation and schema files directly. Component schemas referenced by an operation are emitted into that operation's schema file; schemas configured through `output.schemas` continue to use that output location. ## baseUrl **Type:** `String | Object` **Default:** `''` ```ts export default defineConfig({ petstore: { output: { baseUrl: 'https://api.example.com', }, }, }); ``` > **Note:** > For the `angular` client, prefer > [`override.angular.baseUrl`](#baseurl-1) 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. ```ts export default defineConfig({ petstore: { output: { baseUrl: { runtime: 'process.env.API_BASE_URL', }, }, }, }); ``` #### imports **Type:** `GeneratorImport[]` Optional. When `runtime` references a symbol from another module, list the imports Orval should emit into generated clients. Paths are relative to the generated file, same idea as mutator imports. The `runtime` expression must be valid where the generated code runs (after those imports). Use a default import: ```ts export default defineConfig({ petstore: { output: { baseUrl: { runtime: 'apiBase', imports: [{ name: 'apiBase', importPath: '../config/api' }], }, }, }, }); ``` Or a named export used as an object (for example `import { env } from '../../env'` and `env.API_BASE_URL` in application code) — set `runtime` to that property access and import the object under `name`: ```ts export default defineConfig({ petstore: { output: { baseUrl: { runtime: 'env.API_BASE_URL', imports: [{ name: 'env', importPath: '../../env' }], }, }, }, }); ``` Adjust `importPath` so it resolves from the generated client file to your module (the example assumes the client is nested deeper than `env.ts`). ### getBaseUrlFromSpecification **Type:** `Boolean` Read the base URL from the OpenAPI `servers` field instead of a fixed string. When `true`, Orval resolves it from the spec’s `servers` entry (optionally with `variables` and `index` below). ```ts export default defineConfig({ petstore: { output: { baseUrl: { getBaseUrlFromSpecification: true, variables: { environment: 'api.dev', }, }, }, }, }); ``` #### variables **Type:** `Record` 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: ```ts export default defineConfig({ petstore: { output: { baseUrl: { getBaseUrlFromSpecification: true, index: 1, // Use second server URL }, }, }, }); ``` ## mock **Type:** `Boolean | Object | Function` **Default:** `false` Configures one or more mock generators. The shorthand `mock: true` enables both MSW and Faker mock files with default options: ```ts export default defineConfig({ petstore: { output: { mock: true, }, }, }); ``` Each entry in `mock.generators` produces its own file (`.msw.ts`, `.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 ```ts export default defineConfig({ petstore: { output: { mock: { indexMockFiles: true, generators: [ { type: 'msw', delay: 1000, useExamples: false, generateEachHttpStatus: false, baseUrl: '/api', locale: 'en', }, { type: 'faker', useExamples: false, }, ], }, }, }, }); ``` | Option | Type | Default | Description | |--------|------|---------|-------------| | `indexMockFiles` | `Boolean` | `false` | In `split`, `tags-split`, `tags-operations` and `tags-operations-split` modes, emit one root-level `index..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 the `tags-operations` modes it re-exports the per-operation 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. | | `fixtures` | `Boolean` | `false` | Write `index.fixtures.ts` next to `index.faker.ts`: typed fixtures for each endpoint, by route or operation, answered with the faker response mocks. See [Fixtures](#fixtures). Requires a faker generator and `indexMockFiles: true`. | | `generators` | `Array` | `[]` | One entry per output mock file. Each entry can be an object (`MockOptions`) or a custom `ClientMockBuilder` function. | ```ts export default defineConfig({ petstore: { output: { mock: { path: './src/api/mocks', generators: [ { type: 'msw', path: './src/api/mocks/msw' }, { type: 'faker' }, ], }, }, }, }); ``` ### Fixtures `mock.fixtures: true` writes `index.fixtures.ts` next to `index.faker.ts`. A fixture is one endpoint's fake response, coupled to that endpoint. It knows its method, path, URL, params and response types, so nock, MSW, Playwright, a fetch stub or any other tool can serve it. It doesn't depend on a mocking library. Requires a faker generator and `indexMockFiles: true`. The [Fixtures guide](/docs/guides/fixtures) walks through defining and serving them. ```ts export default defineConfig({ petstore: { output: { mode: 'tags-split', target: './src/api/endpoints.ts', mock: { indexMockFiles: true, fixtures: true, // -> ./src/api/index.fixtures.ts generators: [{ type: 'faker' }], }, }, }, }); ``` ```ts import { defineFixture, defineFixtures, respond } from './api/index.fixtures'; // By operation or by route: both are Fixture<'GET /pets/:petId'>. const rex = defineFixture('showPetById', { name: 'Rex' }); // deep-merged onto the seeded mock const same = defineFixture('GET /pets/:petId', { name: 'Rex' }); rex.route; // 'GET /pets/:petId' rex.operation; // 'showPetById' rex.method; // 'GET' rex.path; // '/pets/:petId' rex.url({ petId: '1' }); // '/pets/1', with the spec's param types rex.pattern; // a RegExp for the route's paths rex.matches({ method: 'GET', url: '/pets/1' }); // true rex.reply({ params: { petId: '1' } }); // { ok: true, status: 200, body: Pet } const missing = defineFixture('showPetById', respond(404, { code: 404, message: 'Not found' })); missing.reply(); // { ok: false, status: 404, body: Error } const adopted = rex.with({ tag: 'adopted' }); const sorted = defineFixture('listPets', [], { query: { sort: 'name' } }); const pets = defineFixtures([rex, sorted, defineFixture('deletePetById', respond(204))]); // FixtureSet | Fixture<'GET /pets'> | Fixture<'DELETE /pets/:petId'>> ``` - A fixture is keyed by a ` ` route from the spec, with `:param` placeholders named like the generated function arguments, or by its operation. A static base URL is part of the path (`GET /api/pets` with `baseUrl: '/api'`); the host is ignored. Unknown routes and operations, wrong fields, and wrong params or query params fail to compile. - A value is a body, a `respond(status, body?, headers?)`, or a function of `{ method, url, params, body }` that returns either. Functions run synchronously. `respond`'s body must be the route's response or error type. - On a route with a response mock, a body is deep-merged onto the mock: objects merge, arrays and `null` replace, and no value answers with the mock as is. - `reply` answers a request given by its `url`, or by `params` and `query`. Its `body` is the route's response when `ok` (2xx), and its error otherwise. - `with(overrides)` returns a new fixture. Bodies deep-merge; anything else replaces. - `{ query }` only matches requests with those query params, any of the route's. - Each route's mock is seeded, so it answers with the same values on every run. `defineFixtures(fixtures, { seed })` picks a different, still fixed, set of values. Replying from a mocked route reseeds faker's shared instance, so unseeded faker calls after it see a different sequence. A set holds many fixtures and is typed by them. Its `map` and `forEach` (and iterating it) visit each fixture, typed as the set's fixtures and most specific first (literal segments before params, query constraints before none), to register one handler per fixture with a mocking library. Or its `reply` answers any request from the most specific match, returning the `route`, `key` and `fixture` that answered, or `undefined` when none matches. See [Fixture sets](/docs/guides/fixtures#fixture-sets) for MSW, nock and Playwright. ```ts const handlers = pets.map((fixture) => http.all(`*${fixture.path}`, () => { /* ... */ })); pets.get('showPetById'); // Fixture<'GET /pets/:petId'> pets.extend([adopted]); // a new set; fixtures with the same key deep-merge const answer = pets.reply({ method: 'GET', url: '/pets/1' }); if (answer?.route === 'GET /pets/:petId') answer.body; // Pet | Error ``` `defineFixtures` also takes a map by route or operation. A `?name=value` suffix on a route (`'GET /pets?limit=1'`) only matches requests with that query, and routes outside the spec go in `{ untyped: { 'GET /legacy': … } }`, without type checks. `resolveFixture(set, request)` answers like `set.reply`, with the matched key as `route`. ```ts const listing = defineFixtures({ showPetById: { name: 'Rex' }, 'GET /pets/:petId/owner': ({ params }) => ({ tag: params.petId }), 'DELETE /pets/:petId': respond(204), }); ``` ### 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 `getResponseMock` 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`, `tags-split`, `tags-operations` and `tags-operations-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 `getResponseMock` 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`, `tags-split`, `tags-operations` and `tags-operations-split` modes, configuring Faker alongside MSW moves the `getResponseMock` 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 (`getMock`) 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 `getResponseMock` factories from it instead of through a relative path into the faker directory. Use it when MSW and Faker live in different packages. `split`, `tags-split`, `tags-operations` and `tags-operations-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: ```ts export default defineConfig({ petstore: { output: { target: './libs/client/sdk/generated', schemas: { path: './libs/data-layer/sdk/generated', importPath: '@acme/data-layer/sdk', }, mock: { path: './libs/client/sdk/mocks', generators: [ { type: 'faker', schemas: true, schemasImportPath: '@acme/data-layer/sdk/fakers', }, ], }, }, }, }); ``` ```ts // 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`](#target) | `index.ts` ([`indexFiles`](#indexfiles)) | Your HTTP client, e.g. `@angular/*` | | Schemas | [`schemas.path`](#schemas) | `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`](#mocks-options) is set, and [`mock.inline`](#mocks-options) 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`](#importpath): client, MSW and Faker files import schema types from it. - Faker [`importPath`](#faker-generator-type-faker): MSW files import the response factories from it. - Faker [`schemasPath`](#faker-generator-type-faker): writes the `schemas: true` factories into the Faker group instead of the schemas directory, which keeps the schemas package free of `@faker-js/faker`. - Faker [`schemasImportPath`](#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 `getResponseMock` 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 `getMock` 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. ```ts export default defineConfig({ petstore: { input: { target: './petstore.yaml' }, output: { mode: 'tags-split', client: 'angular', target: './src/generated/angular', // client group indexFiles: true, // -> src/generated/angular/index.ts schemas: { path: './src/generated/schemas', importPath: '@acme/models', }, mock: { indexMockFiles: true, // -> index.msw.ts, index.faker.ts generators: [ { type: 'msw', path: './src/generated/msw' }, { type: 'faker', path: './src/generated/faker', importPath: '@acme/mocks/faker', schemas: true, schemasPath: './src/generated/faker/schemas', }, ], }, }, }, }); ``` ```text 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 getMock factories ``` Orval writes the groups and their barrels. Nx project files, `ng-package.json` and `package.json` `exports` maps are up to your project. `samples/angular-app` generates all four groups from one config (`petstoreArtifactGroups`) and has a Node test that loads the MSW barrel without loading Angular. ## indexFiles **Type:** `Boolean` **Default:** `true` Generate `index.ts` files for schemas. ## operationManifest **Type:** `Boolean | { path?: String; types?: Boolean }` **Default:** `false` Writes one file that describes every generated operation, for test helpers and tooling that need to know an API's operations without parsing the generated code: route matchers, cache seeding, typed mock factories. With `true`, the file is `.operations.ts` next to the target. `path` writes it elsewhere, and `types: false` leaves out the `OperationTypes` type map. ```ts export default defineConfig({ petstore: { output: { target: './src/api/endpoints.ts', schemas: './src/api/model', baseUrl: '/api', operationManifest: true, // writes ./src/api/endpoints.operations.ts }, }, }); ``` ```ts import type { Error, Pet } from './model'; export const operations = { showPetById: { operationId: 'showPetById', operationName: 'showPetById', method: 'GET', path: '/pets/{petId}', url: '/api/pets/{petId}', route: 'GET /api/pets/:petId', tag: 'pets', tags: ['pets'], pathParams: [{ name: 'petId', key: 'petId' }], queryParams: [], serialization: { explode: [], arrayFormat: [], deepObject: [], explodeOnly: true }, body: null, mock: 'getShowPetByIdResponseMock', deprecated: false, }, } as const; export type OperationName = keyof typeof operations; export type OperationTypes = { showPetById: { pathParams: { petId: string }; query: never; body: never; headers: never; response: Pet; error: Error; }; }; ``` - Entries are keyed by `operationName`, the name of the generated function. - `path` is the spec path. `url` adds the static base URL, and is left out when the base URL is only known at runtime. - `route` is the method and URL path with `:param` placeholders named like the generated function arguments, e.g. `GET /api/pets/:petId`, without the base URL's host. It's the same key [fixtures](#fixtures) use. - `pathParams` pairs each placeholder's spec name with the identifier the client takes. - `serialization` lists how query params are written to the URL, as the fetch client's `getUrl` does: `explode` arrays repeat their key, `arrayFormat` arrays follow `override.fetch.arrayFormat` (only for clients whose URLs are built by the fetch client: `fetch`, `mcp`, and the query, SWR and Pinia Colada clients over `httpClient: 'fetch'`), and `deepObject` params use `name[key]`. `explodeOnly` means every query param is in one of those lists. - `mock` is the `getResponseMock` factory a [mock generator](#mock) emits for the operation, or `null`. The manifest only names it and never imports mock code. - `OperationTypes` references the types orval already generates. `response` is the payload type, not the client's return type, which a mutator or `includeHttpResponseReturnType` can wrap. It's left out for the `zod` and `effect` clients and for Zod schemas. The manifest isn't re-exported from the workspace `index.ts`. ## 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`](#indexfiles) control separate behaviors: - Shared types that would otherwise be duplicated across per-tag files are collected and written once to `[commonTypesFileName].ts` - Each per-tag file imports shared types from the common file instead of declaring them inline - When `indexFiles: true`, a barrel `index.ts` is generated with named re-exports for public shared types plus `export *` re-exports for each per-tag implementation file ```ts export default defineConfig({ petstore: { output: { mode: 'tags-split', target: './src/api/endpoints.ts', schemas: './src/api/model', client: 'fetch', indexFiles: true, tagsSplitDeduplication: true, }, }, }); ``` Resulting structure: ``` src/api/ ├── common-types.ts ← shared types extracted once ├── index.ts ← barrel with named + wildcard re-exports ├── pets/ │ └── pets.ts ← import type { ... } from '../common-types' └── health/ └── health.ts ← import type { ... } from '../common-types' ``` When `tagsSplitDeduplication` is disabled (default), shared types are inlined per tag. Shared-type extraction is only available when `tagsSplitDeduplication` is enabled and `workspace` is not set. With that condition satisfied, the shared types file can still be generated when `indexFiles` is `false`, but the root barrel is not generated. Shared-type extraction is suppressed when [`workspace`](#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`](#tagssplitdeduplication) is enabled. ```ts export default defineConfig({ petstore: { output: { mode: 'tags-split', indexFiles: true, tagsSplitDeduplication: true, commonTypesFileName: 'shared', // generates shared.ts }, }, }); ``` ## docs **Type:** `Boolean | Object` **Default:** `false` Generate API docs using [TypeDoc](https://typedoc.org/). TypeDoc is an optional peer dependency, so install it alongside the markdown plugin (and any other TypeDoc plugin you configure) before enabling this option: ```sh npm install typedoc typedoc-plugin-markdown -D ``` ```ts export default defineConfig({ petstore: { output: { docs: true, // or with config docs: { configPath: './typedoc.config.mjs', }, }, }, }); ``` ## clean **Type:** `Boolean | String[]` **Default:** `false` Remove files left over from previous runs before regenerating. What is removed depends on whether Orval owns the directory. **`target` and `schemas` are wiped.** Every file in them is removed (`.d.ts` files are preserved) — not only files produced by Orval, but also any other file that happens to live there. These are the directories Orval asks you to keep hand-written files out of, so it takes them as its own. **Configured mock directories are pruned.** Set [`mock.path`](#mock), 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` and `**/*.faker`. `` is your [`fileExtension`](#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. > **Warning:** > 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. ```ts export default defineConfig({ petstore: { output: { // preserve `important.ts` when wiping `target` / `schemas` clean: ['!**/important.ts'], }, }, }); ``` For example, to keep a committed `swagger.json` next to the generated output: ```ts export default defineConfig({ petstore: { output: { target: './src/generated', clean: ['!**/swagger.json'], }, }, }); ``` > **Warning:** > `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. > **Warning:** > 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. ```ts export default defineConfig({ petstore: { output: { formatter: 'prettier', }, }, }); ``` ## headers **Type:** `Boolean` **Default:** `false` Generate typed parameters for the HTTP request headers an operation declares in the specification. When disabled, header parameters are omitted from the generated function signatures. This is unrelated to [`override.header`](#header), which controls the comment block written at the top of each generated file. ```ts export default defineConfig({ petstore: { output: { headers: true, }, }, }); ``` ## tsconfig **Type:** `String | Object` Custom TypeScript configuration path or inline config. When omitted, Orval looks for the nearest `tsconfig.json` (or `jsconfig.json`). ```ts export default defineConfig({ petstore: { output: { tsconfig: './tsconfig.json', // or inline tsconfig: { compilerOptions: { moduleResolution: 'NodeNext', }, }, }, }, }); ``` Orval reads a small number of `compilerOptions` to shape the emitted imports — `module` and `moduleResolution` (whether generated relative imports carry a `.js` extension), `allowImportingTsExtensions`, `allowSyntheticDefaultImports` and `esModuleInterop` (default vs namespace import form), `baseUrl`, and `exactOptionalPropertyTypes`. `target` is **not** one of them, and it does not need to be set for Orval's sake. In particular, a [mutator](#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: ```ts export default defineConfig({ petstore: { output: { override: { transformer: 'src/yourfunction.js', }, }, }, }); ``` ### mutator **Type:** `String | Object` Custom HTTP client implementation: ```ts export default defineConfig({ petstore: { output: { override: { mutator: { path: './api/mutator/custom-instance.ts', name: 'customInstance', }, }, }, }, }); ``` Example implementation: ```ts import Axios, { AxiosRequestConfig } from 'axios'; export const AXIOS_INSTANCE = Axios.create({ baseURL: '' }); export const customInstance = (config: AxiosRequestConfig): Promise => { return AXIOS_INSTANCE({ ...config }).then(({ data }) => data); }; export type ErrorType = AxiosError; export type BodyType = BodyData; ``` `path` can also be an import specifier: a package (`@acme/http/fetch`) or a tsconfig `paths` alias (`@/lib/custom-instance`, or `api/custom-instance` with `"*": ["./src/*"]`). The generated files import it verbatim instead of through a relative path, and Orval resolves it through the [`tsconfig`](#tsconfig) `paths` to read the mutator. Write it the way you would import it, without a `.ts` extension. A file that exists at that path relative to the workspace takes precedence over an alias. #### `inferred` **Type:** `Boolean` **Default:** `false` When `true`, the generated fetch functions omit the `async` keyword and the `: Promise` 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. ```ts export default defineConfig({ petstore: { output: { client: 'fetch', override: { mutator: { path: './api/mutator/custom-instance.ts', name: 'customInstance', inferred: true, }, }, }, }, }); ``` ### title **Type:** `String | Function` Customize the API service title (only for `axios` and `angular` clients): ```ts export default defineConfig({ petstore: { output: { override: { title: (title) => `${title}Api`, }, }, }, }); ``` ### namingConvention (property keys) **Type:** `Object` Change naming convention for **property keys** (not files): ```ts export default defineConfig({ petstore: { output: { override: { namingConvention: { enum: 'PascalCase', // camelCase, PascalCase, snake_case, kebab-case properties: 'camelCase', // schema property names }, }, }, }, }); ``` `properties` renames the keys of schema objects, e.g. `widget_id` becomes `widgetId` with `camelCase`. Keys that would collide after conversion (such as `first_name` next to `firstName`) keep their original name. - **TypeScript models** use the converted names. `required` is still matched against the original spec key, and form-data serialization writes the original key on the wire. - **Zod schemas** (the `zod` client and `schemas: { type: 'zod' }`) still validate the original spec keys, then a `.transform()` moves each value to its converted name. `zod.input` (the exported `X` type) is the wire shape and `zod.output` (`XOutput`) has the converted names. With `fetch` `runtimeValidation`, responses therefore come back with converted keys while request bodies are sent with the spec's keys. For the same reason, the Angular client's [`runtimeValidation.requestBodies`](#requestbodies) sends a body unparsed when the convention renames one of its keys. > **Note:** > 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`](#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`](https://spec.openapis.org/oas/v3.1.1#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. ```ts export default defineConfig({ petstore: { output: { override: { header: (info) => [ `Generated by Orval`, `Do not edit manually.`, ...(info.title ? [info.title] : []), ], }, }, }, }); ``` --- ## override.query TanStack Query options: ```ts export default defineConfig({ petstore: { output: { override: { query: { useQuery: true, useSuspenseQuery: true, useMutation: true, useInfinite: true, useSuspenseInfiniteQuery: true, useInfiniteQueryParam: 'nextId', usePrefetch: true, useInvalidate: true, useSetQueryData: true, useGetQueryData: true, signal: true, runtimeValidation: true, options: { staleTime: 10000, }, }, }, }, }, }); ``` ### useQuery **Type:** `Boolean` **Default:** `true` for `GET` operations; `false` otherwise. Generate `useQuery` hooks. When set explicitly, applies to **all operations regardless of HTTP verb** — setting `useQuery: true` routes `POST`, `PUT`, `PATCH`, and `DELETE` operations to `useQuery` hooks as well. This is useful for APIs that use `POST` for read-style endpoints (e.g. complex search bodies, GraphQL-style single-endpoint APIs). Cache keys for non-`GET` operations are automatically namespaced by HTTP verb to avoid collisions with `GET` operations on the same path (e.g. `['POST', '/pets', body]`). Set to `false` to suppress `useQuery` hook generation; pair with `useMutation: true` (default for non-`GET`) if you want the request to be wired up as a Mutation instead. ### useSuspenseQuery **Type:** `Boolean` **Default:** unset — opt-in. Generate `useSuspenseQuery` hooks. When set globally, this only applies to `GET` operations; per-operation overrides (`override.operations..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..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..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..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`](#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. ```ts export default defineConfig({ petstore: { output: { override: { query: { useInfinite: true, useInfiniteQueryParam: ['page', 'cursor'], }, operations: { listBets: { useInfinite: true, useInfiniteQueryParam: "cursor.marker", } } }, }, }, }); ``` ### useInfiniteQueryParamLocation **Type:** `'query' | 'body'` **Default:** `'query'` Where the `useInfiniteQueryParam` page param lives. With `'query'` it must be one of the operation's URL query parameters. With `'body'` it is a property of the JSON request body instead, which suits `POST` endpoints that paginate through a filter body. The infinite query then spreads `pageParam` into the body, and `TPageParam` is typed from the body schema (`ElementFilter['offset']`). The candidates are matched against the body schema's properties. An operation without a JSON body, or whose body declares none of the candidates, gets no infinite hook. If the operation opted in explicitly, orval also logs a warning. When the body schema doesn't list its properties directly (an `allOf`, for example), the first candidate is used as configured. ```ts export default defineConfig({ petstore: { output: { override: { operations: { searchPageableElementsByFilter: { query: { useQuery: true, useInfinite: true, useInfiniteQueryParam: 'offset', useInfiniteQueryParamLocation: 'body', }, }, }, }, }, }, }); ``` The generated `queryFn` then passes the page param through the body: ```ts 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`](https://tanstack.com/query/latest/docs/reference/QueryClient#queryclientsetqueriesdata). 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`](https://tanstack.com/query/latest/docs/framework/react/guides/disabling-queries#typesafe-disabling-of-queries-using-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): ```ts export default defineConfig({ petstore: { output: { override: { query: { useInvalidate: true, mutationInvalidates: [ { onMutations: ['createPets'], invalidates: ['listPets'], }, { onMutations: ['deletePet', 'updatePet'], invalidates: [ 'listPets', { query: 'showPetById', params: ['petId'], invalidationMode: 'reset' }, { query: 'adminPets', file: './admin' }, ], }, ], }, }, }, }, }); ``` Each entry in `params` is either a **variable reference** (`string`) or a **literal value** (`{ literal: string }`): | 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: ```ts mutationInvalidates: [ { onMutations: ['updateProfile'], invalidates: [ { query: 'getProfile', params: [{ literal: '@me' }] }, ], }, ], ``` When a user provides their own `onSuccess` callback, both the auto-invalidation and the user callback run — the generated `onSuccess` composes them together. To opt out of auto-invalidation at runtime, pass `skipInvalidation: true`: ```ts // 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`: ```ts mutationInvalidates: [ { onMutations: ['postNotes'], // not 'PostNotes' invalidates: ['getNotes'], }, ], ``` `invalidates` resolves its target through the query key function name, so the document's own casing happens to work there too. `onMutations` is matched exactly, so it does not. orval warns when either field names an operation the output does not generate, since the rule is otherwise ignored without a trace: ``` ⚠️ mutationInvalidates references an unknown operation 'PostNotes' in onMutations. No operation with that name is generated for this output, so the rule is ignored — did you mean 'postNotes'? ``` An operation excluded by an input filter or by [`useDeprecatedOperations: false`](#usedeprecatedoperations) is not generated either, so naming one warns for the same reason. An `invalidates` target with a `file` is imported from another module, such as a query generated by a different output, so it is not checked against this output's operations and never warns. ### signal **Type:** `Boolean` Include abort signal in queries. ### queryKey / queryOptions / mutationOptions **Type:** `String | Object` Custom query/mutation key or options functions. When a `queryOptions` or `mutationOptions` mutator declares a third parameter, orval passes operation identity so the mutator can branch on it (for example, to attach per-operation metadata or invalidate by `operationId`). The exact shape differs between the two: - `queryOptions` mutator — `{ url, operationId, operationName }` - `mutationOptions` mutator — `{ operationId, operationName }` (the `url` is supplied in the second parameter) Each option takes a mutator, so point it at a file and an exported name: ```ts export default defineConfig({ petstore: { output: { client: 'react-query', override: { query: { queryKey: { path: './src/mutators/custom-query-key.ts', name: 'customQueryKey', }, queryOptions: { path: './src/mutators/custom-query-options.ts', name: 'customQueryOptions', }, mutationOptions: { path: './src/mutators/custom-mutation.ts', name: 'useCustomMutation', }, }, }, }, }, }); ``` `queryKey` replaces the generated key factory. It receives the operation's query properties and a context carrying the `url`: ```ts export function customQueryKey( // The properties dictionary varies per operation (params, petId, ...), so // type it broadly if one mutator serves every endpoint. properties: Record, 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: ```ts import type { QueryKey } from '@tanstack/react-query'; export function customQueryOptions( options: T, _queryProperties: Record, 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: ```ts import { type UseMutationOptions, useQueryClient } from '@tanstack/react-query'; export const useCustomMutation = ( options: UseMutationOptions, _: { url: string }, operation: { operationId: string; operationName: string }, ) => { const queryClient = useQueryClient(); if (operation.operationId !== 'deletePetById') return options; return { ...options, onSuccess: (...args: Parameters>) => { 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 `getQueryOptions` or `getMutationOptions` 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`. ```ts export function customQueryOptions( options: T, ): T { return options; } ``` ```ts export default defineConfig({ petstore: { output: { client: 'react-query', override: { query: { queryOptions: { path: './src/mutators/custom-query-options.ts', name: 'customQueryOptions', useHooks: false, }, mutationOptions: { path: './src/mutators/custom-mutation-options.ts', name: 'customMutationOptions', useHooks: false, }, }, }, }, }, }); ``` With this configuration, Orval generates `getPetstoreQueryOptions(...)` and `getPetstoreMutationOptions(...)` instead of their `use`-prefixed variants. The query options factory can then be used safely with `prefetchQuery` / `ensureQueryData`: ```ts export async function loader(queryClient: QueryClient) { return queryClient.ensureQueryData(getPetstoreQueryOptions()); } ``` > **Note:** > The `use`/`get` prefix only affects the *name* of the exported options > factory. The hook itself is still exported as `use` 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: ```ts export const getShowPetByIdQueryKey = (petId: string) => { return ['pets', petId] as const; }; export const getCreatePetsMutationKey = () => ['createPets'] as const; ``` ### shouldFilterQueryKey **Type:** `Boolean` **Default:** `false` Add `.filter(q => q !== undefined)` to the query key. If false, `as const` is added instead. The filter can be adjusted with the `queryKeyFilter` option When shouldFilterQueryKey is true: ```ts export const getShowPetByIdQueryKey = (petId: string) => { return ['pets', petId].filter(q => q !== undefined); }; ``` When shouldFilterQueryKey is false: ```ts export const getShowPetByIdQueryKey = (petId: string) => { return ['pets', petId] as const; }; ``` ### queryKeyFilter **Type:** `String` **Default:** `'q => q !== undefined'` Adjusts how the queryKey is filtered, when `shouldFilterQueryKey` is `true`. Default is `'q => q !== undefined'`, which will result in it ending up beeing ```ts .filter(q => q !== undefined) ``` One option could be to only make it filter out all falsy keys: ```ts shouldFilterQueryKey: true, queryKeyFilter: 'Boolean' ``` which would result in the generated code being ```ts .filter(Boolean) ``` ### shouldSplitQueryKey **Type:** `Boolean` **Default:** `false` Generate query keys as arrays instead of strings. ### useOperationIdAsQueryKey **Type:** `Boolean` **Default:** `false` Use operation ID instead of route path for query keys. ### version **Type:** `Number` **Default:** Detected from package.json Force a specific version for generated hooks. ### runtimeValidation **Type:** `boolean | { strategy: 'throw' | 'both' }` **Default:** `false` Enable Zod runtime validation for `angular-query` responses. Requires `schemas: { type: 'zod' }`. When enabled, responses are validated in the RxJS pipeline according to the configured strategy (`Schema.parse()` for the default `throw`). Skipped for primitive types and custom mutators. This option only drives the Angular `HttpClient` pipeline that `angular-query` generates. `react-query`, `vue-query`, `svelte-query` and `solid-query` issue their requests through the fetch client, so they enable validation through `override.fetch.runtimeValidation` with `httpClient: 'fetch'` instead — as does `swr`. A validated response is typed with the schema's `zod.output` alias (e.g. `PetsOutput`) instead of the input-typed schema name, since that is what the parse returns at runtime — relevant when the schema transforms values through `coerce`, `useDates`, defaults or `.transform()`. Operations using a custom mutator keep the schema (input) type: the mutator issues the request itself, so the generated parse never runs there. The boolean form is preserved for backward compatibility: `true` ≡ `{ strategy: 'throw' }`. With `{ strategy: 'both' }` an invalid response is first logged through `console.error('[orval] 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: ```ts export default defineConfig({ petstore: { output: { override: { swr: { useInfinite: true, useSuspense: true, generateErrorTypes: false, swrOptions: { dedupingInterval: 10000, }, swrMutationOptions: { revalidate: true, }, swrInfiniteOptions: { initialSize: 10, }, }, }, }, }, }); ``` ### useInfinite **Type:** `Boolean` Generate `useSWRInfinite` hooks. ### useSWRMutationForGet **Type:** `Boolean` Generate `useSWRMutation` for GET requests. ### useSuspense **Type:** `Boolean` **Default:** `false` Generate Suspense-compatible hooks. ### generateErrorTypes **Type:** `Boolean` **Default:** `false` Generate custom error type aliases. ### swrOptions / swrMutationOptions / swrInfiniteOptions **Type:** `Object` Override SWR hook options. --- ## override.zod Zod schema generation options: For inline array responses, item schemas using `allOf`, `oneOf`, or `anyOf` are also emitted as operation item schemas. Array items that are component `$ref`s continue to use the component schema writer. See the [Zod guide](/docs/guides/zod#array-response-items) for an example. ```ts export default defineConfig({ petstore: { output: { override: { zod: { variant: 'mini', version: 4, strict: { response: true, query: true, param: true, header: true, body: true, }, coerce: { query: ['string', 'number', 'boolean'], }, generate: { param: true, body: true, response: true, query: true, header: true, }, generateEachHttpStatus: true, useBrandedTypes: true, generateReusableSchemas: true, generateDiscriminatedUnion: true, }, }, }, }, }); ``` ### variant **Type:** `'classic' | 'mini'` — defaults to `'classic'` Select the generated Zod API style. | 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](/docs/guides/zod#zod-version) for details. ### strict **Type:** `Object` Enable strict mode for schemas. ### coerce **Type:** `Object` Enable [coercion](https://zod.dev/api?id=coercion) for specified types. ### generate **Type:** `Object` Control which schemas are generated. ### preprocess **Type:** `Object` Add preprocess functions to schemas. ### params **Type:** [`Mutator`](#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). ```ts export default defineConfig({ petstore: { output: { override: { zod: { params: { path: './zod-params.ts', name: 'zodParams' }, }, }, }, }, }); ``` ```ts import type { ZodParamsContext } from 'orval'; import { i18n } from './i18n'; export const zodParams = (ctx: ZodParamsContext) => ({ error: (issue: { input: unknown; path: PropertyKey[] }) => i18n.t( `errors.${ctx.schemaName}.${ctx.fieldPath.join('.')}.${ctx.validator}`, { value: issue.input }, ), }); ``` The `'schema'` location is used for shared component schemas emitted under [`generateReusableSchemas`](#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): ```ts import { zodParams } from './zod-params'; export const CreateUserBody = zod.object({ email: zod .string(zodParams({ operationId: 'createUser', location: 'body', schemaName: 'CreateUserBody', fieldPath: ['email'], validator: 'string' })) .email(zodParams({ operationId: 'createUser', location: 'body', schemaName: 'CreateUserBody', fieldPath: ['email'], validator: 'email' })), }); ``` Injection scope: - Applied to base types (`string`, `number`, `boolean`, `bigint`, `date`, `integer`), constraints (`min`, `max`, `gt`, `lt`, `multipleOf`, `regex`, `length`), formats (`email`, `url`, `uuid`, `hostname`, `datetime`, `time`), and `literal`, `enum`, `instanceof`, `stringFormat`. - Skipped on modifiers (`optional`, `nullable`, `nullish`, `default`, `describe`) and structural calls (`object`, `array`, `tuple`, `union`, `rest`, `passthrough`, `strict`). - `fieldPath` only includes object property names, mirroring Zod's own `issue.path`. Array indices and tuple positions are not appended — the inner element of `{ tags: array }` and a top-level `tags: string` both see `fieldPath: ['tags']`. Use the `validator` field to distinguish a container (`'array'`, `'tuple'`) from its element (`'string'`, `'number'`). For static messages, return an object with a string `error`: `return { error: 'My message' }`. The function may return `undefined` to fall back to Zod defaults for a specific call. > The `{ error }` shape is Zod v4-only — on v3 it is silently ignored and the default message is used. If your project supports both Zod v3 and v4, return `{ message: 'My message' }` instead, which works on both. ### dateTimeOptions / timeOptions **Type:** `Object` **Default (dateTimeOptions):** `{ offset: true }` Configure Zod datetime/time validation options. `dateTimeOptions` defaults to `{ offset: true }` so generated schemas accept RFC3339 timestamps with timezone offsets (e.g. `2026-03-27T12:00:00+01:00`) — matching the OpenAPI `format: date-time` contract. Pass an explicit object to override (e.g. `{ offset: false }` or `{ offset: true, precision: 3 }`). ### useBrandedTypes **Type:** `boolean` **Default:** `false` Append [`.brand()`](https://zod.dev/api?id=branded-types) to generated Zod schemas using the schema name as the brand identifier. For array request/response bodies, only the top-level array wrapper schema is branded — the exported `*Item` helper schema is not branded. ### generateReusableSchemas **Type:** `boolean` **Default:** `false` Emit one reusable Zod schema per OpenAPI `#/components/schemas/*` `$ref` instead of inlining. The exported name is the last `$ref` segment with `namingConvention` applied. Other schemas and operation files reference the export by name (cycles are wrapped in `zod.lazy(() => Name)` only on the edges that close them). Behavior: - When `schemas:` is configured (string or `{ type: 'zod' }`) and `client: 'zod'` is set, the schemas directory holds reusable Zod schemas instead of TypeScript types. The schema files default to a `.zod.ts` extension; use [`schemaFileExtension`](#schemafileextension) to override it independently from the global [`fileExtension`](#fileextension) if you keep both TS types and reusable Zod schemas in the same directory. - Operation files import the named exports — pure-`$ref` body/response wrappers (e.g. `PetCreateBody`) are skipped so consumers import the component schema directly. - `$ref` siblings: `nullable`, `default`, `description` chain onto the named ref (e.g. `Pet.nullable().describe(...)`). `properties`, `example`, and other non-chainable siblings fall back to inlining at that one site. - The `namingConvention` must produce valid JavaScript identifiers (`camelCase`, `PascalCase`, or `snake_case`). `kebab-case` is rejected with a clear error because it would emit dashed exports. - **Trade-off — `readOnly` on shared component schemas:** without this flag, request bodies generated from `$ref` schemas strip `readOnly: true` properties so they don't appear in input validators. With this flag on, request and response endpoints share the same exported schema, so `readOnly` properties remain in body validators too. Either avoid `readOnly` on component schemas you share between requests and responses, or split into separate request/response schemas in the OpenAPI source. ### generateMeta **Type:** `boolean` **Default:** `false` Attach registry metadata to generated **component** schemas via [`.meta()`](https://zod.dev/metadata) (zod v4 only): `id` is the schema name, plus `description` and `deprecated` when the OpenAPI schema provides them. ```ts // override: { zod: { generateMeta: true } } export const Pet = zod .object({ name: zod.string() }) .meta({ id: 'Pet', description: 'A pet in the store', deprecated: true }); ``` Behavior: - Applies only to **component** schemas emitted as named exports (`schemas: { type: 'zod' }`, or `client: 'zod'` + [`generateReusableSchemas`](#generatereusableschemas)). Operation wrapper schemas are left untouched, so registry `id`s stay unique. - `id` is always emitted; `description`/`deprecated` only when present. Property-level descriptions still use `.describe()`. - **zod v3** has no `.meta()` — the option is a no-op there, and descriptions continue to emit via `.describe()`. - The registry `id` makes [`z.toJSONSchema()`](https://zod.dev/json-schema) reference the schema as `#/$defs/`, round-tripping the component structure. ### generateDiscriminatedUnion **Type:** `boolean` **Default:** `false` Emit a `oneOf`/`anyOf` that carries an OpenAPI [`discriminator`](https://spec.openapis.org/oas/v3.1.0#discriminator-object) as [`zod.discriminatedUnion(key, [...])`](https://zod.dev/api?id=discriminated-unions) 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. ```ts // override: { zod: { generateDiscriminatedUnion: true } } export const Pet = zod.discriminatedUnion('petType', [ zod.object({ petType: zod.literal('cat'), meows: zod.boolean() }), zod.object({ petType: zod.literal('dog'), barks: zod.boolean() }), ]); ``` Behavior: - **Opt-in.** Left `false`, unions are emitted exactly as before, so existing output is unchanged. - **Safe fallback.** A discriminated union is emitted only when every branch can be represented as an object carrying a literal (`const`/`enum`) discriminator. If any branch is a non-object, a nested union, or lacks a literal discriminator, generation falls back to a plain `zod.union([...])` rather than emitting code that throws at construction. - **Inheritance (`allOf`).** Branches composed with `allOf` are flattened into a single object so they remain valid discriminated-union options — this is the case that previously forced the feature to be reverted ([#2085](https://github.com/orval-labs/orval/issues/2085)). With [`generateReusableSchemas`](#generatereusableschemas), a branch that references an `allOf` schema stays a plain union (the referenced schema can't be guaranteed to be an object from the reference alone). - Works with both Zod v3 (>= 3.20) and v4, and with the [`mini`](#variant) variant. ### exactOptional **Type:** `boolean` **Default:** `false` Emit optional object properties with `.exactOptional()` (classic) / `zod.exactOptional()` (mini) instead of `.optional()`, so consumers compiling with [`exactOptionalPropertyTypes`](https://www.typescriptlang.org/tsconfig/#exactOptionalPropertyTypes) infer `{ x?: T }` rather than `{ x?: T | undefined }`. ```ts // override: { zod: { exactOptional: true } } export const Pet = zod.object({ name: zod.string().exactOptional() }); ``` Behavior: - **Opt-in.** Left `false`, optional properties emit `.optional()` as before, so existing output is unchanged. - **zod v4 only.** zod v3 has no `.exactOptional()`, so the option is a no-op there and `.optional()` is emitted. - Applies to optional properties in both the classic and [`mini`](#variant) variants. ### generateCompanionTypes **Type:** `boolean` **Default:** `false` Follow every generated per-operation `export const` with a `zod.input`/[`zod.output`](https://zod.dev/basics?id=inferring-types) type alias pair, so consumers can import a TypeScript type instead of writing one by hand. ```ts // override: { zod: { generateCompanionTypes: true } } export const CreatePetsBody = zod.object({ name: zod.string() }); export type CreatePetsBody = zod.input; export type CreatePetsBodyOutput = zod.output; ``` Behavior: - **Opt-in.** Left `false`, output is unchanged. - Applies to per-operation schemas — `Params`, `QueryParams`, `Header`, `Body`, and `Response` — including the Hono `*.zod.ts` output, which goes through the same generator. Component schemas emitted under [`generateReusableSchemas`](#generatereusableschemas) already carry this pair unconditionally and are unaffected by this flag. - For an array request/response body, both the `*Item` schema and the array wrapper get their own pair. - Uses `zod.input`, not `z.infer` (an alias of `zod.output`): request-side schemas commonly carry `.default()`, `coerce`, or `.transform()`, where the pre-parse (`input`) and post-parse (`output`) shapes differ. - With [`useBrandedTypes`](#usebrandedtypes), the clean `zod.input` alias stays unbranded — `.brand()` only affects the output type — so the brand shows up on the `...Output` alias. - Helper value consts (`...Max`, `...Min`, `...RegExp`, `...Default`) are plain values, not schemas, and never get a companion pair. - Supported as a per-operation or per-tag override alongside `strict`, `generate`, `coerce`, `preprocess`, `params`, and `useBrandedTypes` (see [Scoping options per operation or tag](/docs/guides/zod#scoping-options-per-operation-or-tag)). --- ## override.effect Effect schema generation options: ```ts export default defineConfig({ petstore: { output: { override: { effect: { strict: { response: true, query: true, param: true, header: true, body: true, }, generate: { param: true, body: true, response: true, query: true, header: true, }, generateEachHttpStatus: true, useBrandedTypes: true, }, }, }, }, }); ``` ### strict **Type:** `Object` Enable strict mode for schemas. ### generate **Type:** `Object` Control which schemas are generated. ### useBrandedTypes **Type:** `boolean` **Default:** `false` Append `S.brand()` to generated Effect schemas using the schema name as the brand identifier. For array request/response bodies, only the top-level array wrapper schema is branded. ### exactOptional **Type:** `boolean` **Default:** `false` Emit optional Struct properties with `S.optionalWith(schema, { exact: true })` (Effect 3) or `S.optionalKey(schema)` (Effect 4) instead of `S.optional(schema)`, so consumers compiling with [`exactOptionalPropertyTypes`](https://www.typescriptlang.org/tsconfig/#exactOptionalPropertyTypes) infer `{ x?: T }` rather than `{ x?: T | undefined }`. ### version **Type:** `3 | 4 | 'auto'` — defaults to `'auto'` Pin the Effect major version that generated output targets, instead of inferring it from the installed `effect` package. | Value | Output | | -------- | --------------------------------------------------------------------------------- | | `4` | Always emit Effect 4 syntax (`S.Union([...])`, `.check(...)`, `S.optionalKey`, …). | | `3` | Always emit Effect 3 syntax (`S.Union(...)`, `.pipe(...)`, `S.optionalWith`, …). | | `'auto'` | Infer from the resolved `effect` version; fall back to Effect 4 when none is found. | `version` is output-wide: an operation or tag override cannot change it. --- ## override.angular Angular client options: ```ts export default defineConfig({ petstore: { output: { override: { angular: { provideIn: 'root', // 'root' | 'any' | '' | false retrievalClient: 'httpClient', runtimeValidation: true, httpResource: { debugName: 'getPetByIdResource', }, }, }, }, }, }); ``` > **Note:** > `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`](#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. > **Note:** > `provideIn` affects generated service classes only. `httpResource` > functions are plain exports, not injectables. ### retrievalClient **Type:** `'httpClient' | 'httpResource' | 'both'` **Default:** `'httpClient'` Controls how **retrieval-style** Angular operations are generated. - `httpClient`: keep retrievals as injectable services backed by Angular `HttpClient` - `httpResource`: generate signal-first retrieval functions using Angular `httpResource` - `both`: keep `HttpClient` service methods and emit retrieval resources in a sibling `*.resource.ts` file Mutation-style operations still use generated `HttpClient` service methods by default unless a per-operation override changes the classification. ### client **Type:** `'httpClient' | 'httpResource' | 'both'` Backward-compatible alias for `retrievalClient`. Prefer `override.angular.retrievalClient` in new configs to make the retrieval-only scope clearer. ### runtimeValidation **Type:** `boolean | { strategy?: 'throw' | 'both'; requestBodies?: boolean }` **Default:** `false` Enable Zod runtime validation for Angular output. Requires `schemas: { type: 'zod' }`. This option is opt-in for backward compatibility. - For generated `HttpClient` services, eligible JSON body responses are validated against the schema in the RxJS pipeline. - For generated `httpResource` functions, eligible JSON resources are validated against the schema via the resource's `parse` option. The exact mechanism depends on the strategy: `throw` parses directly (e.g. `Schema.parse()`), while `both` validates through a safe-parse wrapper so it can log before re-throwing. The boolean form is preserved for backward compatibility: `true` ≡ `{ strategy: 'throw' }`. With `{ strategy: 'both' }` an invalid response is first logged through `console.error('[orval] 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`. ```ts 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] 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. A body is also sent unparsed when [`namingConvention.properties`](#namingconvention-property-keys) renames one of its keys, at any depth, and Orval logs a warning for that operation. The body's Zod schema outputs the converted keys, and the server expects the spec's keys. Orval sends such a body exactly as the caller passed it, without validation, defaults or transforms. The parameter type already uses the spec's keys. Bodies whose keys already match the convention are parsed as usual. ### 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](/docs/guides/angular#object-query-parameters-style--explode) for the full explanation and a worked example. - `spec` (default): honor the OpenAPI parameter's `style`/`explode` — `form` + `explode: true` (the OpenAPI default) spreads the object's properties as top-level query params, `form` + `explode: false` joins them into a single comma-separated value, and `deepObject` emits bracketed `name[prop]` keys. - `legacy`: restore the pre-#3705 behavior of silently dropping object-typed query params from the generated request. ```ts export default defineConfig({ petstore: { output: { override: { angular: { queryObjectSerialization: 'legacy', }, }, }, }, }); ``` Like `retrievalClient`/`runtimeValidation`, this can be set globally, per-tag (`override.tags[...].angular`), or per-operation (`override.operations[...].angular`). It has no effect when a `paramsSerializer` or `paramsFilter` is configured — those remain in full control of the raw value. See [issue #3705](https://github.com/orval-labs/orval/issues/3705). ### observeOverloads **Type:** `Boolean` **Default:** `true` By default, generated `HttpClient` service methods expose one overload per Angular `observe` mode (`'body'`, `'events'`, `'response'`). Set this to `false` to emit only the `observe: 'body'` signature. That makes generated services easier to mock with Vitest, Jasmine, Spectator or ng-mocks, because TypeScript only uses the last overload when inferring mock types. ```ts export default defineConfig({ petstore: { output: { client: 'angular', override: { angular: { observeOverloads: false, }, }, }, }, }); ``` generates: ```ts showPetById( petId: string, options?: HttpClientBodyOptions, ): Observable; ``` Like `queryObjectSerialization`, this can be set globally, per-tag, or per-operation. See [issue #4289](https://github.com/orval-labs/orval/issues/4289). ### httpResource **Type:** `Object` Options forwarded into generated `httpResource` calls. ```ts export default defineConfig({ petstore: { output: { override: { angular: { retrievalClient: 'httpResource', httpResource: { defaultValue: { id: 'fallback' }, debugName: 'getPetByIdResource', injector: 'inject(Injector)', equal: '(a, b) => a?.id === b?.id', }, }, }, }, }, }); ``` #### defaultValue **Type:** `unknown` Initial value exposed while the resource is idle/loading. When configured, generated overloads return `HttpResourceRef` instead of `HttpResourceRef`. #### 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` ```ts export default defineConfig({ petstore: { output: { override: { angular: { baseUrl: { apiId: 'petstore', }, }, }, }, }, }); ``` Opt-in: compose this output's runtime base URL through Angular dependency injection (an `InjectionToken`) instead of baking a static prefix into every generated route string. See the [Angular guide](/docs/guides/angular#di-based-base-url-composition-multiple-apis--gateway-routing) for the full precedence chain, the generated artifacts, and a multi-API gateway-routing example. > **Note:** > `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 | |---|---| | `_SERVER_URL` | Embedded fallback URL constant | | `_BASE_URL_RESOLVER` | `InjectionToken` for the runtime resolver hook | | `_BASE_URL` | `InjectionToken` for the composed, normalized base URL | | `BaseUrlResolverContext` | Resolver context type (`{ apiId, serverUrl }`) | | `BaseUrlResolver` | Resolver function type | | `provideBaseUrl(baseUrl)` | Directly provides the base URL, bypassing the resolver | | `provideBaseUrlResolver(resolver)` | Provides a custom resolver | `` is `apiId` upper-snake-cased (e.g. `petstore` → `PETSTORE`); `` 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`](#index) on the top-level `baseUrl` option. #### variables **Type:** `Record` Values for any `{variable}` placeholders in the selected server URL. #### Error and warning behavior - **Missing/invalid `apiId`** — throws `` `override.angular.baseUrl.apiId` must be a non-empty string matching /^[A-Za-z][A-Za-z0-9_-]*$/ `` at config-normalization time. - **Combined with `output.baseUrl`** — throws: `` `override.angular.baseUrl` cannot be combined with the top-level `output.baseUrl` ``. Remove `output.baseUrl` from the output; the token's fallback already reads the specification's `servers` field, and a runtime override belongs in a provided resolver. - **Set on a non-`angular` client** — logs a warning and is otherwise ignored. - **Set under `override.operations[...].angular` or `override.tags[...].angular`** — logs a warning and is ignored. `baseUrl` is an output-level concern configured once via `override.angular.baseUrl`, not per operation or tag. --- ## override.hono Hono server options: ```ts export default defineConfig({ petstore: { output: { override: { hono: { handlers: 'src/handlers', handlerGenerationStrategy: 'smart', validatorOutputPath: 'src/validator.ts', compositeRoute: 'src/routes.ts', }, }, }, }, }); ``` ### handlers **Type:** `String` Changes output path for Hono handlers. ### handlerGenerationStrategy **Type:** `'smart' | 'skip' | 'full'` **Default:** `'smart'` Controls how an **existing** handler file is treated when you re-run orval. A file that does not exist yet is always generated fresh. - `smart` (default) — non-destructively reconcile only the parts orval owns: its own imports (names, module paths, casing) and the `zValidator(...)` arguments, and append handlers for new operations. Your custom imports, middleware, handler bodies, and top-level helpers are preserved. Requires the optional `typescript` peer dependency (see note below); if it is absent, smart falls back to `skip` with a warning. - `skip` — leave an existing handler file byte-for-byte unchanged. New operations still get fresh files (in `split` mode). - `full` — rebuild the file header, imports, and validator chain from the spec, splicing back only each handler body. **Destructive:** custom imports, middleware, and top-level helpers are dropped. Use only if you keep handlers minimal and want maximal sync with the spec. > **Note:** > `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. > **Warning:** > 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: ```ts export default defineConfig({ petstore: { output: { override: { mcp: { server: { path: './custom-server.ts', name: 'customServer', }, handler: { path: './custom-handler.ts', name: 'customHandler', }, cacheHints: { 'tools/list': { ttlMs: 3_600_000, cacheScope: 'public' }, }, }, }, }, }, }); ``` ### server **Type:** `String | Object` Custom server function to use instead of the default stdio bootstrap (`serveStdio`). When set, the generated `server.ts` calls the function with `createMcpServer`. Example implementation using `@modelcontextprotocol/hono`: ```ts import { createMcpHonoApp } from '@modelcontextprotocol/hono'; import { type McpServer, type RegisteredTool, WebStandardStreamableHTTPServerTransport, } from '@modelcontextprotocol/server'; // `createMcpHonoApp` parses JSON bodies into this variable. declare module 'hono' { interface ContextVariableMap { parsedBody?: unknown; } } export const customServer = ( createMcpServer: (options?: RequestInit) => { server: McpServer; tools: Record; }, ) => { const app = createMcpHonoApp(); app.all('/mcp', async (c) => { // Stateless: a fresh server and transport per request. const { server } = createMcpServer(); const transport = new WebStandardStreamableHTTPServerTransport({ sessionIdGenerator: undefined, }); await server.connect(transport); return transport.handleRequest(c.req.raw, { parsedBody: c.get('parsedBody'), }); }); 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. ```ts import type { CallToolResult, ServerContext, } from '@modelcontextprotocol/server'; export const customHandler = async ( fetcher: ( overrides?: RequestInit, ) => Promise<{ status: number; data: unknown; headers: Headers }>, ctx: ServerContext, toStructuredContent: ( data: unknown, ) => | { success: true; data: Record | undefined } | { success: false; error: { message: string } }, ): Promise => { 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](/docs/guides/mcp#custom-handler) for details on `fetcher` and `ctx`. ### cacheHints **Type:** `Object` **Default:** none (the SDK default of `ttlMs: 0` and `cacheScope: 'private'` applies) Cache hints passed to `McpServer` as is, keyed by method such as `tools/list`. They only take effect on MCP 2026-07-28 connections. The generated tool list is fixed at build time, so `tools/list` can usually be `public` with a TTL of a few minutes to an hour. ```ts export default defineConfig({ petstore: { output: { override: { mcp: { cacheHints: { 'tools/list': { ttlMs: 3_600_000, cacheScope: 'public' }, }, }, }, }, }, }); ``` --- ## override.axios Axios client options: ```ts export default defineConfig({ petstore: { output: { override: { axios: { includeHttpResponseReturnType: true, }, }, }, }, }); ``` ### includeHttpResponseReturnType **Type:** `Boolean` **Default:** `false` Include the HTTP status in the return type and correlate it with the response data type. For example, an operation with a JSON `200` response and an empty `204` response returns a union that narrows `data` to the JSON body or `void` based on `status`. The built-in Axios client normalizes spec-declared empty responses to `undefined`. With a custom `mutator`, the option changes the generated type but not the runtime value because the mutator issues the request itself. The mutator must return a complete Axios response with the same status-correlated shape. --- ## override.fetch Fetch client options: ```ts export default defineConfig({ petstore: { output: { override: { fetch: { includeHttpResponseReturnType: false, forceSuccessResponse: true, }, }, }, }, }); ``` ### includeHttpResponseReturnType **Type:** `Boolean` **Default:** `true` Include HTTP status in return type. Set to `false` to return data directly. When an exact response status and a matching wildcard are both declared, the exact status takes precedence in the generated type. For example, `200` and `2XX` produce `status: 200` and `status: Exclude`. ### 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. ```ts export default defineConfig({ api: { input: './openapi.yaml', output: { target: './api.ts', client: 'react-query', httpClient: 'fetch', override: { mutator: { path: './mutator.ts', name: 'customFetch' }, fetch: { includeHttpResponseReturnType: true, forceSuccessResponse: true, includeHttpErrorResponse: true, }, }, }, }, }); ``` For Query clients, a mutator exporting `ErrorType` receives the generated error response union as `T`, preserving the relationship between `status` and `data` for both queries and mutations: ```ts // With this option enabled: type GetItemQueryError = ErrorType; // 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: ```ts 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` 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. ```ts export default defineConfig({ petstore: { output: { client: 'react-query', httpClient: 'fetch', override: { fetch: { serializeResponseHeaders: true, }, }, }, }, }); ``` ### jsonReviver **Type:** `String | Object` Custom JSON reviver function (useful for date parsing). `useDatesTransform` does not replace or disable this reviver. With both enabled the two run in sequence: the fetch client still passes the reviver to `JSON.parse`, and the generated `deserializeResponse` 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] 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` | ```ts export default defineConfig({ petstore: { output: { client: 'fetch', override: { fetch: { arrayFormat: 'repeat', }, }, }, }, }); ``` For full control over serialization (including custom encoding, nested objects, etc.) use [`override.paramsSerializer`](#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. ```ts export default defineConfig({ petstore: { output: { client: 'react-query', // also works with 'fetch', 'vue-query', 'svelte-query' httpClient: 'fetch', override: { fetch: { useRuntimeFetcher: true, }, }, }, }, }); ``` **Generated output example** ```ts // Request function — gains a fetchFn parameter export const listPets = async ( params: ListPetsParams, options?: RequestInit, fetchFn?: typeof globalThis.fetch, ): Promise => { 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** ```ts // SvelteKit — +page.ts export const load = async ({ fetch }) => { const queryClient = new QueryClient(); await prefetchListPetsQuery(queryClient, params, { fetcher: fetch }); return { queryClient }; }; ``` ### httpClientInjection **Type:** `'factory'` **Default:** `undefined` Only for `client: 'fetch'`. Wraps the generated operations and URL helpers in a factory that receives a `fetch` implementation once, instead of passing it on every call. The factory's name is the OpenAPI title (or the tag in split modes) prefixed with `get`; [`override.title`](#title) renames it. Response types stay exported at the top level. Set it on `output.override.fetch`; tag and operation overrides are ignored. ```ts export default defineConfig({ petstore: { output: { client: 'fetch', override: { fetch: { httpClientInjection: 'factory', }, }, }, }, }); ``` **Generated output example** ```ts export const getSwaggerPetstore = (fetchFn: typeof globalThis.fetch = fetch) => { const getListPetsUrl = (params: ListPetsParams) => { // ... }; const listPets = async ( params: ListPetsParams, options?: RequestInit, ): Promise => { const res = await fetchFn(getListPetsUrl(params), { ...options, method: 'GET', }); // ... }; return { listPets, getListPetsUrl }; }; export type listPetsResponse = listPetsResponseSuccess | listPetsResponseError; ``` **Usage** ```ts const api = getSwaggerPetstore(myFetch); const pets = await api.listPets({ limit: '10' }); ``` ### runtimeImplementationStyle **Type:** `'inline' | 'shared'` **Default:** `'inline'` `'shared'` generates the request execution logic (fetch call, body encoding, response decoding, query serialization and the status-code types) once into a shared runtime module, and turns every operation into a thin wrapper around it. Call signatures and the `{ data, status, headers }` response shape stay the same as with `'inline'`. ```ts export default defineConfig({ petstore: { output: { client: 'fetch', override: { fetch: { runtimeImplementationStyle: 'shared', }, }, }, }, }); ``` > **Note:** > The shared runtime is an output-level setting. `runtimeImplementationStyle`, > `runtimeOutputPath` and `clientConfigOutputPath` are read from > `output.override.fetch` only; setting them under `override.operations` or > `override.tags` has no effect. ### runtimeOutputPath **Type:** `String` **Default:** `runtime.ts` next to `output.target` Where the shared runtime is generated. Point several outputs at the same file to share one runtime between them. ### clientConfigOutputPath **Type:** `String` **Default:** `client-config.ts` next to `output.target` Where the per-output client config is generated. It wires the global `override.mutator` to the shared runtime and is only emitted when a mutator is configured. --- ## 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`](#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](https://github.com/orval-labs/orval/issues/2858)). For the `fetch` client, [`override.includeZodSchemaInArguments`](#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.error`s the raw `ZodError` before re-throwing, for production observability without giving up fail-fast behaviour. --- ## override.mock Mock generation overrides: ```ts export default defineConfig({ petstore: { output: { override: { mock: { properties: { '/tag|name/': 'jon', email: () => faker.internet.email(), }, schemas: { Apple: { properties: { color: () => faker.helpers.arrayElement(['red', 'green']), }, }, }, format: { email: () => faker.internet.email(), iban: () => faker.finance.iban(), }, required: true, nonNullable: true, delay: 500, arrayMin: 1, arrayMax: 10, stringMin: 10, stringMax: 20, numberMin: 0, numberMax: 100, }, }, }, }, }); ``` ### properties Override mock values per property path or regex. Applies to every schema that has a matching property. ### schemas Scope property overrides to a named schema, so the same property name can mock differently per schema (e.g. `color` on `Apple` vs. `Car`). Keyed by schema name; each entry holds a `properties` map using the same matching rules as `properties` (bare name, `/regex/`, exact `#.path`). Takes precedence over the global `properties` overrides. See the [Faker guide](/docs/guides/faker#per-schema-overrides) 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`. ```ts // 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: ```ts export default defineConfig({ petstore: { output: { override: { operations: { listPets: { mutator: 'src/response-type.js', query: { useQuery: true, useInfinite: false, }, mock: { data: () => ({ id: 1, name: 'Buddy' }), }, }, }, }, }, }, }); ``` --- ## override.tags Override by OpenAPI tag (same options as `operations`). --- ## override.operationName **Type:** `Function` Custom function to override generated operation names. The callback receives `(operation, route, verb)`, where `operation` is the OpenAPI Operation Object, `route` is the operation's route string, and `verb` is Orval's HTTP verb value. Return a string to set both the method name and the type-name base, or return `[methodName, typeNameBase]` to control them independently. **Return `string`** to control both the method name and the type-name base together: ```ts export default defineConfig({ petstore: { output: { override: { operationName: (operation, route, verb) => { return `custom_${operation.operationId}`; }, }, }, }, }); ``` **Return `[methodName, typeNameBase]`** to decouple method names from type-identifier names. This is useful for gateway-aggregated specs where multiple services share the same REST patterns (`GET /products`, `GET /orders`) — bare method names are safe per-tag (each service class scopes them), but type names (`*Params`, `*Body`, `*Error`, `*Result`) need to be globally unique to avoid barrel-level collisions with `tags-split` + `splitByTags` + `indexFiles`: ```ts import { pascal } from '@orval/core'; export default defineConfig({ api: { output: { mode: 'tags-split', schemas: { path: './model', splitByTags: true }, override: { operationName: (_operation, route, verb) => { const segments = route.split('/').filter(Boolean); return [ `${verb}${pascal(segments.slice(2).join('-'))}`, // getProducts `${verb}${pascal(segments.slice(1).join('-'))}`, // getCatalogProducts ]; }, }, }, }, }); ``` Result: ```ts // 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: ```ts export default defineConfig({ petstore: { output: { override: { components: { schemas: { suffix: 'DTO' }, responses: { suffix: 'Response' }, parameters: { suffix: 'Params' }, requestBodies: { suffix: 'Bodies' }, }, }, }, }, }); ``` Add prefixes to generated component names (available for `schemas`, `responses`, `parameters`, and `requestBodies`). `prefix` is applied to the component itself; `itemPrefix` is applied to the element type when the component is an array (valid for `components.schemas` only). Prefixes are also applied to `$ref` references, so a `Pet` schema with `prefix: 'I'` is referenced as `IPet` everywhere it appears: ```ts export default defineConfig({ petstore: { output: { override: { components: { schemas: { prefix: 'I', itemPrefix: 'I' }, responses: { prefix: 'I' }, parameters: { prefix: 'I' }, requestBodies: { prefix: 'I' }, }, }, }, }, }); ``` --- The default suffix is `''` for schemas, `'Response'` for responses, `'Parameter'` for parameters, and `'Body'` for request bodies. Set a suffix to `''` explicitly to disable it. For array schemas, `schemas.itemSuffix` controls the suffix of the generated item type. It defaults to `'Item'` and applies only to `components.schemas`: ```ts export default defineConfig({ petstore: { output: { override: { components: { schemas: { itemSuffix: 'Element' }, }, }, }, }, }); ``` An inline array response can consequently produce an item type such as `ListPets200Element`. The item suffix names the generated element type; it does not change the component schema's structure. ## Type Generation Options ### useDates **Type:** `Boolean` **Default:** `false` Convert `date`/`datetime` to JavaScript `Date` objects. ### useBigInt **Type:** `Boolean` **Default:** `false` Convert `int64`/`uint64` to `BigInt`. ### useBrandedTypes **Type:** `Boolean` **Default:** `false` Emit named primitive schemas as branded types. ### 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: ```ts // '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](/docs/guides/enums#zod). > **Warning:** > `'enum'` emits a TypeScript `enum` declaration, which type stripping cannot > remove. If you compile with TypeScript 5.8+ [`erasableSyntaxOnly`](https://www.typescriptlang.org/tsconfig/#erasableSyntaxOnly) — for example under Node's native TypeScript support or any type-stripping build step — generated `'enum'` output fails to compile. Use `'const'` (default) or `'union'` instead in that environment. ### aliasCombinedTypes **Type:** `Boolean` **Default:** `false` Create intermediate type aliases for `anyOf`/`oneOf`/`allOf`. ### suppressReadonlyModifier **Type:** `Boolean` **Default:** `false` Suppress `readonly` modifier on properties. ### preserveReadonlyRequestBodies **Type:** `'strip' | 'preserve'` **Default:** `'strip'` Controls how Orval treats `readonly` properties when a schema is reused as a request body. - `strip` (recommended): removes readonly modifiers from generated request-body types via `NonReadonly`. This is the safest default for most OpenAPI specifications because `readOnly` properties are response-oriented. - `preserve`: keeps readonly modifiers on generated request-body types. Use this only when your request DTOs are intentionally immutable and you want that immutability reflected in the generated TypeScript types. ```ts export default defineConfig({ petstore: { output: { override: { preserveReadonlyRequestBodies: 'strip', }, }, }, }); ``` 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. > **Note:** > 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". ```ts // 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](https://github.com/orval-labs/orval/issues/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. ```ts export default defineConfig({ petstore: { output: { client: 'fetch', schemas: { path: 'src/gen/model', type: 'zod' }, override: { includeZodSchemaInArguments: true, mutator: { path: './src/custom-fetch.ts', name: 'customFetch' }, fetch: { runtimeValidation: true }, }, }, }, }); ``` ```ts import type { ZodType } from 'zod'; export const customFetch = async ( url: string, options: RequestInit & { schema?: ZodType }, ): Promise => { const { schema, ...init } = options; const response = await fetch(url, init); const data = await response.json(); return schema ? schema.parse(data) : (data as T); }; ``` > **Note:** > `schema.parse()` throws a `ZodError` on a mismatch. Use `safeParse()` in the > mutator if you would rather handle validation failures yourself. ## factoryMethods **Type:** `Object` **Default:** `{ generate: false }` Generate factory methods for DTOs (Data Transfer Objects) initialized with safe default values. Useful for testing and initializing empty state. Functionality handles OpenAPI `readOnly` and `writeOnly` flags to generate appropriate payload structures: - **Required properties:** Always included in the factory output, regardless of their visibility flags. - **Optional `readOnly` properties:** Always omitted from the factory output, as they would be dropped by the server. - **Optional `writeOnly` properties:** Always included in the factory output (even if `includeOptionalProperty` is set to `false`). ```ts export default defineConfig({ petstore: { output: { factoryMethods: { functionNamePrefix: 'create', mode: 'split', includeOptionalProperty: true, outputDirectory: `#output.workspace.schemas`, }, }, }, }); ``` ### functionNamePrefix **Type:** `String` **Default:** `'create'` Prefix for the generated factory function names. ### mode **Type:** `'single' | 'split' | 'single-split'` **Default:** `'split'` Where to generate the factory methods: - `single`: Appends the factory function to the schema file. - `split`: Creates a `{schema}.factory.ts` with factory method. By default it is placed next to schema file. - `single-split`: Aggregates all factory methods into a single `factoryMethods.ts` file. ### includeOptionalProperty **Type:** `boolean` **Default:** `true` Determines whether optional schema properties are included in the default factory output. ### outputDirectory **Type:** `String` **Default:** `#output.workspace.schemas` Defaults to the value configured in `#output.workspace.schemas`. Determines where factory methods will be generated (can be used to generated methods away from schema directory). Takes effect only when used `mode` is `split` or `single-split`. --- ## Other Options ### allParamsOptional **Type:** `Boolean` **Default:** `false` Let callers leave parameters unresolved. Path parameters keep their position in the signature but widen to accept a missing value, and the query/header parameters object becomes optional: ```ts // 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`](#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`). | | 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`). | | 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 & { extra }`). | #### Generic template example Define a reusable generic schema with an unbound `$dynamicAnchor` in `$defs`: ```yaml components: schemas: PaginatedResponse: $defs: itemType: $dynamicAnchor: itemType not: {} type: object properties: items: type: array items: $dynamicRef: '#itemType' total: type: integer ``` Then bind it to concrete types: ```yaml 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: ```ts export interface PaginatedResponse { items: itemType[]; total: number; } export type UserListResponse = PaginatedResponse; export type OrderListResponse = PaginatedResponse; ``` 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>`). #### Self-referential `$dynamicAnchor` example When a schema declares `$dynamicAnchor` and uses `$dynamicRef` with the same anchor, the type resolves to itself: ```yaml components: schemas: BaseCategory: $dynamicAnchor: category type: object properties: id: type: string children: type: array items: $dynamicRef: '#category' LocalizedCategory: $dynamicAnchor: category allOf: - $ref: '#/components/schemas/BaseCategory' - type: object properties: displayName: type: string ``` Generated TypeScript: ```ts 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`](#schemas) configured and `tags-split` mode ([`indexFiles`](#indexfiles) shown at its default, `true`): ``` models/ ├── index.ts ← barrel re-exports everything (indexFiles: true) ├── apiEnvelopeTemplate.ts ← generic template (ApiEnvelopeTemplate) ├── paginatedTemplate.ts ← generic template (PaginatedTemplate) ├── pet.ts ← domain type ├── owner.ts ← domain type ├── paginatedPetItems.ts ← bound alias (PaginatedTemplate) └── paginatedOwnerItems.ts ← bound alias (PaginatedTemplate) pets/ └── pets.ts ← import { ... } from '../models' owners/ └── owners.ts ← import { ... } from '../models' ``` With [`indexFiles: false`](#indexfiles), 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`](/docs/reference/configuration/input#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`](/docs/reference/configuration/input#schemas) configuration is needed. #### Limitations - Each schema in `components.schemas` is generated once with a single dynamic scope. If the same named component is referenced by multiple endpoints that each provide different `$defs` bindings, only one binding applies. The common pattern — putting `$defs` bindings on inline response schemas — works correctly. - `$dynamicRef` values targeting external documents (e.g., `other.json#anchor`) fall back to `unknown`. - Inline `$defs` entries without `$ref` that have `$dynamicAnchor` are treated as generic type parameters, not concrete bindings. ### contentType Filter content types: ```ts export default defineConfig({ petstore: { output: { override: { contentType: { include: ['application/json'], exclude: ['application/xml'], }, }, }, }, }); ``` ### splitByContentType **Type:** `Boolean` **Default:** `false` When an endpoint's `requestBody` supports multiple content types (e.g. `application/json` and `multipart/form-data`), generate a separate function for each content type instead of combining them into a single function with a union type parameter. Each generated function is suffixed with the content type name (e.g. `WithJson`, `WithFormData`). ```ts // 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) => { ... } ``` ```ts export default defineConfig({ petstore: { output: { override: { splitByContentType: true, }, }, }, }); ``` > If the endpoint only has a single content type, no suffix is added and the behavior is the same as the default. ### formData **Type:** `Boolean | String | Object` Customize form data generation. If an object is provided, specify `path`, `name`, and optionally `default: true` for default export. ```ts export default defineConfig({ petstore: { output: { override: { formData: { path: './api/mutator/custom-form-data-fn.ts', name: 'customFormDataFn', // default: true }, }, }, }, }); ``` ```ts export const customFormDataFn = (body: Body): FormData => { const formData = new FormData(); // Custom implementation Object.entries(body as Record).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: ```ts export default defineConfig({ petstore: { output: { override: { formData: { arrayHandling: 'serialize-with-brackets', }, }, }, }, }); ``` - `serialize`: `formData.append('items', JSON.stringify(value))` - `serialize-with-brackets`: `formData.append('items[]', JSON.stringify(value))` - `explode`: Expands nested objects with indexed keys ### formUrlEncoded **Type:** `Boolean | String | Object` Customize form URL encoded data generation: ```ts export default defineConfig({ petstore: { output: { override: { formUrlEncoded: { path: './api/mutator/custom-form-url-encoded-fn.ts', name: 'customFormUrlEncodedFn', }, }, }, }, }); ``` ```ts export const customFormUrlEncodedFn = (body: Body): URLSearchParams => { const params = new URLSearchParams(); Object.entries(body as Record).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. ```ts export default defineConfig({ petstore: { output: { override: { paramsSerializer: { path: './api/mutator/custom-params-serializer-fn.ts', name: 'customParamsSerializerFn', }, }, }, }, }); ``` For Axios and Angular the function receives the params object and can return any value Axios/Angular accepts. For the fetch client it must return a `string` (the raw query string without the leading `?`): ```ts // Axios / Angular export const customParamsSerializerFn = ( params: Record, ): 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 | undefined, ): string => new URLSearchParams( Object.entries(params ?? {}) .filter(([_, v]) => v !== undefined) .flatMap(([k, v]) => Array.isArray(v) ? v.map((item) => [k, String(item)]) : [[k, String(v)]], ), ).toString(); ``` ### paramsSerializerOptions **Type:** `Object` > **Note:** Only valid when using Axios or Angular. Only used when `paramsSerializer` is not defined. Use `qs` library for parameter serialization: ```ts export default defineConfig({ petstore: { output: { override: { paramsSerializerOptions: { qs: { arrayFormat: 'repeat', }, }, }, }, }, }); ``` ### paramsFilter **Type:** `String | Object` > **Note:** Only valid for the `angular` client, or `angular-query` when `httpClient: 'angular'`. Replaces the built-in query-parameter filter that the Angular client applies before handing params to `HttpParams`. When set, orval does **not** strip `null`/`undefined` or non-primitive values for you — your function returns exactly the object `HttpParams` (or a configured `paramsSerializer`) receives. This option intentionally lives at `override.paramsFilter` rather than `override.angular.paramsFilter` so the same request-shaping override can be used globally or narrowed per operation/tag, just like `mutator` and `paramsSerializer`. When a `paramsSerializer` is configured, orval already preserves schema-declared object and array-of-object params so the serializer can handle them; without a serializer those params are dropped. See the [Angular guide](/docs/guides/angular#query-parameter-filtering). Use `paramsFilter` when you need the raw object without a serializer, or for any control that the schema cannot express. ```ts export default defineConfig({ petstore: { output: { override: { paramsFilter: { path: './api/mutator/custom-params-filter-fn.ts', name: 'customParamsFilterFn', }, }, }, }, }); ``` ```ts export const customParamsFilterFn = ( params: Record, ): Record => { const result: Record = {}; 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`. ```ts export default defineConfig({ petstore: { output: { override: { useDates: true, }, }, }, }); ``` > **Important:** You must provide an Axios converter to convert serialized date strings to `Date` objects. This option only affects the TypeScript definition. If you also want runtime conversion, prefer `useDatesTransform` below; the interceptor approach traverses every response and can convert date-looking strings that are not schema dates. ```ts import axios from 'axios'; const client = axios.create({ baseURL: '' }); client.interceptors.response.use((originalResponse) => { handleDates(originalResponse.data); return originalResponse; }); export default client; const isoDateFormat = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d*)?(?:[-+]\d{2}:?\d{2}|Z)?$/; function isIsoDateString(value: any): boolean { return value && typeof value === 'string' && isoDateFormat.test(value); } export function handleDates(body: any) { if (body === null || body === undefined || typeof body !== 'object') return body; for (const key of Object.keys(body)) { const value = body[key]; if (isIsoDateString(value)) { body[key] = new Date(value); // default JS conversion // body[key] = parseISO(value); // date-fns conversion // body[key] = luxon.DateTime.fromISO(value); // Luxon conversion } else if (typeof value === 'object') { handleDates(value); } } } ``` > If using `fetch` client with `useDates: true`, query parameters of type Date are stringified using `toISOString()`. In `multipart/form-data` and `application/x-www-form-urlencoded` request bodies, `Date` values are sent as ISO 8601 strings: `toISOString()` for `format: date-time`, and `YYYY-MM-DD` for `format: date`. Both use the UTC calendar day, like the [`useDatesTransform`](#usedatestransform) request serializer, so a `format: date` value built from local midnight (`new Date(2024, 0, 15)`) in a timezone ahead of UTC is sent as the previous day. Build those values with `new Date(Date.UTC(2024, 0, 15))`. An invalid `Date` is sent as `Invalid Date`. Nested objects that a form field sends as JSON use `Date.toJSON()`, so a `format: date` field inside them is sent as the full instant. ### 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 `deserializeResponse` function for every operation whose response schema declares `format: date` or `format: date-time` fields, and chains it onto the generated call: ```ts export const getOrderDetails = (orderId: string) => { return customInstance({ 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: ```ts export const getOrderDetails = async ( orderId: string, options?: RequestInit, ): Promise => { 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 `serializeRequest` function is generated for every operation whose body has exactly one JSON media type declaring one, and wraps the body at the call site: ```ts export const updateAppointment = (appointment: Appointment) => { return customInstance({ 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`](https://spec.openapis.org/oas/v3.1.0#discriminator-object) `mapping` is converted by emitting a `switch` on the discriminator property with one `case` per mapping key. Any other union whose variants are all object-shaped — each one declaring its own `properties` — is converted structurally instead: each property a variant declares is guarded by its presence on the value (`'key' in value`), which is also what lets TypeScript narrow the union. A variant that declares no `properties` of its own — one composed purely with `allOf`, one that is itself a `oneOf`/`anyOf`, or one declaring an empty `properties: {}` — fails that test and disqualifies the whole union, not just itself. A variant that declares its own `properties` *and* composes with `allOf` does qualify, but only its own properties are converted: dates it inherits through the `allOf` are left as strings even though the generated type calls them `Date`. A property the variants treat differently is skipped, while the rest of the union still converts — because the response direction converts every date format identically, a `date`-versus-`date-time` disagreement between variants only affects the request direction. A property one variant marks `readOnly` while another does not counts as such a disagreement and is skipped too, even though both spell the same `Date`. A date property named after an `Object.prototype` member (`constructor`, `toString`, `valueOf`, and so on) is skipped inside a structurally-walked union as well, since `'key' in value` is true for every object and would convert the field on payloads of a variant that never declared it. A union that mixes object variants with array or scalar ones is skipped entirely. `additionalProperties` maps are converted with a loop over `Object.keys`, unless the schema also declares `properties` — directly, via `allOf`, or a sibling `oneOf`/`anyOf` — or declares a `propertyNames` that narrows the keys to an enumerable set (`enum` or `const`, inline or via `$ref`), in which case those take precedence and it is never walked as a map. Current limitations: responses with multiple success shapes are skipped, and recursive schemas are left untouched entirely — converting only the levels above the cycle would leave deeper dates as strings while the generated types claim `Date`. A request body with several media types, several JSON media types, or a blob body generates no serializer, since there is no single JSON schema to walk; a schema that is both array- and object-shaped (OAS 3.1 `type: ['array', 'object']`, or a stray sibling `properties` on a `type: array` schema) also generates no serializer, since the two shapes can't be walked together on the request side — the same conflict with a map-valued `additionalProperties` in place of `properties` drops both the serializer and the deserializer. `Date` values in query and path parameters are still serialized as full ISO datetimes, so date-only parameters are not covered, and the `mcp` client is not transformed. Wired clients: - the axios-based query clients — `react-query`, `vue-query`, `svelte-query` and `solid-query` with `httpClient: 'axios'`; - `client: 'fetch'`, plus `react-query`, `vue-query`, `svelte-query`, `solid-query`, `swr` and `pinia-colada` under `httpClient: 'fetch'`. Under `fetch`, only the declared success status is converted — error bodies are returned untouched. The one exception is a custom mutator combined with `includeHttpResponseReturnType: false`: the mutator resolves with the body alone, so the status is not visible to the generated code and whatever the mutator resolves with is converted. An inferred custom mutator gets no response conversion at all — its return type is unknown to orval and may not even be a promise, so there's nothing to chain the conversion onto; its request body is still serialized. ndjson and blob responses are not converted either: a stream is handed to the caller unparsed, and a blob has no parsed fields to walk. ### useBigInt **Type:** `Boolean` **Default:** `false` Convert OpenAPI `int64` and `uint64` format to JavaScript `BigInt` objects instead of `number`. ```ts export default defineConfig({ petstore: { output: { override: { useBigInt: true, }, }, }, }); ``` ### useBrandedTypes **Type:** `Boolean` **Default:** `false` Emit `components/schemas` entries that resolve to a primitive as branded types, so an alias such as `UserId` is no longer a plain `string`. ```ts export default defineConfig({ petstore: { output: { override: { useBrandedTypes: true, }, }, }, }); ``` ```ts getUser('abc'); // Error: string is not UserId getUser(user.id); // OK ``` - Only schemas that resolve to `string`, `number`, `boolean`, `bigint` or `Date` are branded. Object, enum and composed (`allOf` / `oneOf` / `anyOf`) schemas, and schemas with a `default`, are unchanged. - Independent of the Zod [`useBrandedTypes`](#usebrandedtypes) option, which uses `.brand()` and produces a different type. ### 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: ```ts export default defineConfig({ petstore: { output: { override: { jsDoc: { filter: (schema) => { const allowlist = [ 'type', 'format', 'maxLength', 'minLength', 'description', 'minimum', 'maximum', 'pattern', 'nullable', 'enum', ]; return Object.entries(schema || {}) .filter(([key]) => allowlist.includes(key)) .map(([key, value]) => ({ key, value })) .sort((a, b) => a.key.length - b.key.length); }, }, }, }, }, }); ``` Result: ```ts export interface Pet { /** * @type integer * @format int64 */ id: number; /** * @type string * @description Name of pet */ name: string; } ``` --- # Hooks ## afterAllFilesWrite **Type:** `String | String[] | Function | Object` Runs after Orval writes generated files. File paths are passed as arguments. ```ts import { defineConfig } from 'orval'; export default defineConfig({ petstore: { hooks: { afterAllFilesWrite: 'prettier --write', }, }, }); ``` ### Disable File Injection To prevent Orval from injecting generated files into the command: ```ts import { defineConfig } from 'orval'; export default defineConfig({ petstore: { hooks: { afterAllFilesWrite: { command: 'prettier --write .', injectGeneratedDirsAndFiles: false, }, }, }, }); ``` --- # Full Example ```ts import { faker } from '@faker-js/faker'; import { defineConfig } from 'orval'; export default defineConfig({ petstore: { output: { mode: 'split', target: 'src/petstore.ts', schemas: 'src/model', client: 'react-query', mock: true, override: { operations: { listPets: { mutator: 'src/response-type.js', mock: { properties: () => ({ id: () => faker.number.int({ min: 1, max: 99999 }), }), }, }, showPetById: { mock: { data: () => ({ id: faker.number.int({ min: 1, max: 99 }), name: faker.person.firstName(), tag: faker.helpers.arrayElement([ faker.word.sample(), undefined, ]), }), }, }, }, mock: { properties: { '/tag|name/': () => faker.person.lastName(), }, delay: 500, }, }, allParamsOptional: true, urlEncodeParameters: true, }, input: { target: './petstore.yaml', override: { transformer: 'src/add-version.js', }, }, hooks: { afterAllFilesWrite: 'prettier --write', }, }, }); ``` --- # Upgrading to v8 v8 brings significant improvements focused on modern JavaScript ecosystem support and enhanced Zod schema integration. ## New Features ### ESM (ECMAScript Modules) The entire project has migrated from CommonJS to ESM. **Key Changes:** - All packages use `"type": "module"` in package.json - Import/export syntax instead of `require()` - **Node.js 22.18 or higher required** ```bash # Rename config to .mjs mv orval.config.js orval.config.mjs # Or add to package.json # "type": "module" ``` ### Default HTTP Client: Fetch The default HTTP client changed from `axios` to `fetch` for smaller bundle sizes. To continue using axios: ```ts export default defineConfig({ petstore: { output: { httpClient: 'axios', target: './src/api', }, }, }); ``` ### New Framework Support **Angular Query:** ```ts export default defineConfig({ petstore: { output: { client: 'angular-query', target: './src/api', }, }, }); ``` **Solid Query / SolidStart:** ```ts export default defineConfig({ petstore: { output: { client: 'solid-query', target: './src/api', }, }, }); ``` ### Enhanced Zod Integration **Schema Type Selection:** ```ts export default defineConfig({ petstore: { output: { schemas: { path: './model', type: 'zod', // 'typescript' | 'zod' }, }, }, }); ``` **Runtime Validation:** ```ts export default defineConfig({ petstore: { output: { schemas: { type: 'zod' }, client: 'fetch', override: { fetch: { runtimeValidation: { strategy: 'both' }, }, }, }, }, }); ``` `runtimeValidation: true` remains supported as shorthand for `{ strategy: 'throw' }`. ### React Compiler Support TanStack Query (React Query) updated to work correctly with React Compiler. ### Combined Types Inlining `anyOf`, `oneOf`, and `allOf` are now inlined by default: ```ts // Before export type Response401OneOf = { defined: true; ... }; export type Response401OneOfTwo = { defined: false; ... }; export type Response401 = Response401OneOf | Response401OneOfTwo; // After export type Response401 = { defined: true; ... } | { defined: false; ... }; ``` To maintain previous behavior: ```ts export default defineConfig({ petstore: { output: { override: { aliasCombinedTypes: true, }, }, }, }); ``` ## Breaking Changes ### 1. ESM Migration The project has been fully converted from CommonJS to ESM. **Key Changes:** - Node.js 22.18+ required - All packages now use `"type": "module"` in package.json - Import/export syntax instead of `require()` **How to migrate:** ```bash # Check your Node.js version node --version # Should be >= 22.18.0 ``` Update configuration files (either rename or add type): ```bash mv orval.config.js orval.config.mjs ``` Or add to package.json: ```json { "type": "module" } ``` Update imports if using Orval programmatically: ```diff - const { generate } = require('orval'); + import { generate } from 'orval'; ``` Update dynamic imports: ```diff - const config = require('./orval.config.js'); + const config = await import('./orval.config.js'); ``` > `.ts` files require no changes if written in ESM style. **Benefits:** - Better tree-shaking (unused code elimination) - Native browser support - Async module loading - Stricter module boundaries ### 2. Default httpClient Changed to `fetch` Explicitly set `httpClient: 'axios'` if needed. ### 3. Removed `override.fetch.explode` Specify `explode` directly in your OpenAPI specification. ### 4. Mock delay default changed to `false` Set `delay: 1000` explicitly if needed. ### 5. Removed `override.coerceTypes` Use `override.zod.coerce` instead: ```ts export default defineConfig({ petstore: { output: { override: { zod: { coerce: { param: true, query: true, header: true, body: true, response: true, }, }, }, }, }, }); ``` ### 6. Removed `override.useNativeEnums` Use `enumGenerationType: 'enum'` instead. ### 7. Zod Schema Naming Changed to PascalCase ```diff - export const createPetsBody = zod.object({ ... }) + export const CreatePetsBody = zod.object({ ... }) ``` ### 8. `input.validation` Removed The `input.validation` option no longer exists. Specs are always validated and the validator (`@scalar/openapi-parser`) is now stricter than the v7 IBM validator — what previously surfaced as warnings is now a hard error. If your spec is malformed and you can repair it, use `input.override.transformer` (it now runs **before** validation, so you can fix issues in-place). As a last resort, opt out of validation entirely with `input.unsafeDisableValidation`: ```ts export default defineConfig({ petstore: { input: { target: './petstore.yaml', unsafeDisableValidation: true, }, }, }); ``` See [`input.unsafeDisableValidation`](/docs/reference/configuration/input#unsafedisablevalidation) and [`input.override.transformer`](/docs/reference/configuration/input#transformer) for details. > **Note:** > If your spec is OpenAPI 2.0 (Swagger), `override.transformer` now receives > the 2.0 document — previously it ran on the upgraded 3.1 form. Adjust > transformers that assumed 3.x shape accordingly. ### 9. SWR Error Types Not Generated by Default Custom error type aliases (e.g., `ListPetsQueryError`, `CreatePetsMutationError`) are no longer generated by default. ```ts export default defineConfig({ petstore: { output: { override: { swr: { generateErrorTypes: true, }, }, }, }, }); ``` ### 10. `output.mock` now uses a `generators` array Previously, `output.mock` accepted either `true` or a flat options object (`{ type: 'msw', delay: 1000, locale: 'fr', ... }`). All options lived on a single object and `type: 'msw'` was the only supported type. Now, `output.mock` wraps generator entries in a `generators` array. Each entry produces its own output file (e.g. `.msw.ts`, `.faker.ts`). This enables multiple independent mock generators from a single config. #### `mock: true` behavior change `mock: true` previously emitted only MSW handlers. It now emits **both** MSW handlers (`.msw.ts`) and Faker data factories (`.faker.ts`). To keep the old behavior (MSW only), use the explicit form: ```ts output: { mock: { generators: [{ type: 'msw' }] }, } ``` #### Migrating a flat options object All per-generator options (`delay`, `useExamples`, `locale`, `baseUrl`, etc.) move inside the generator entry: ```diff output: { - mock: { type: 'msw', delay: 1000, useExamples: true, locale: 'fr' }, + mock: { + generators: [{ type: 'msw', delay: 1000, useExamples: true, locale: 'fr' }], + }, } ``` #### `indexMockFiles` moves to the wrapper `indexMockFiles` is now a collection-level option rather than a per-generator option: ```diff output: { - mock: { type: 'msw', indexMockFiles: true }, + mock: { indexMockFiles: true, generators: [{ type: 'msw' }] }, } ``` #### New: Faker-only output To generate only Faker data factories without MSW handlers: ```ts output: { mock: { generators: [{ type: 'faker' }] }, } ``` ## Other Improvements ### Variable Expansion in Fetch Client Headers Variable expansion is now supported in `override.requestOptions.headers`. ### Bug Fixes - Zod `nullable` + `$ref` support (OpenAPI 3.1 compliance) - `oneOf`/`anyOf` common properties support - Date parameter fixes - Svelte 5 reactivity support --- ## Release Timeline | Version | Release Date | Main Changes | | --- | --- | --- | | v8.0.0-rc.0 | 2025-10-27 | ESM migration, basic Breaking Changes | | v8.0.0-rc.1 | 2025-10-30 | ESM module import support (jiti) | | v8.0.0-rc.2 | 2025-11-14 | Bug fixes | | v8.0.0-rc.3 | 2025-12-06 | Bug fixes | | v8.0.0-rc.4 | 2025-12-14 | Zod schema features | | v8.0.0-rc.5 | 2025-12-23 | Angular Query, Zod fixes | --- ## Acknowledgments We sincerely thank all contributors, followers, and users for your support. Your encouragement drives us to keep improving. 🙏 If you would like to support Orval's ongoing development, please consider becoming a sponsor on [Open Collective](https://opencollective.com/orval).