Fix: Orval Not Working — Code Generation Failing, Types Not Matching API, or Hooks Not Updating
Quick Answer
How to fix Orval API client generation issues — OpenAPI spec configuration, custom output targets, React Query and Axios integration, mock generation, type overrides, and CI/CD automation.
The Problem
Orval fails to generate code from your OpenAPI spec:
npx orval
# Error: Unable to resolve $ref: '#/components/schemas/User'Or generated types don’t match the actual API responses:
// Generated type says `id: number` but API returns `id: string`Or React Query hooks have incorrect parameter types:
const { data } = useGetUsers({ limit: 10 });
// Type error: 'limit' does not exist in type 'GetUsersParams'Or generated code is outdated after an API change:
New endpoint added to the API but no hook was generated for itWhy This Happens
Orval generates type-safe API clients from OpenAPI (Swagger) specifications. The generation quality depends entirely on the spec:
- The OpenAPI spec must be valid and complete — Orval reads the spec file (JSON or YAML) and generates TypeScript types and HTTP client code. Missing
$reftargets, invalid schema definitions, or incompletepathscause generation errors or incorrect types. - Orval generates once, then you must re-run — unlike runtime tools, Orval generates static code. API changes aren’t reflected until you re-run
npx orval. Stale generated code is the most common “bug.” - Output format depends on configuration — Orval can generate Axios clients, React Query hooks, SWR hooks, or plain fetch functions. The
orval.config.tsfile controls what gets generated. Misconfigured output targets produce unexpected code. - Response types follow the spec, not the runtime — if the OpenAPI spec says
id: integerbut the API actually returns a string, Orval generatesid: number. The fix is in the spec, not in Orval.
Fix 1: Configure Orval
npm install -D orval// orval.config.ts
import { defineConfig } from 'orval';
export default defineConfig({
petstore: {
input: {
// OpenAPI spec source — URL or local file
target: './openapi.json',
// Or from a URL:
// target: 'https://api.myapp.com/openapi.json',
// Or from Swagger:
// target: 'https://api.myapp.com/swagger/v1/swagger.json',
// Validation
validation: true,
// Filter — only generate for specific paths
filters: {
tags: ['users', 'posts'], // Only endpoints tagged with these
},
},
output: {
// Output directory
target: './src/api/generated.ts',
// Or split into multiple files:
// target: './src/api/',
// mode: 'tags-split', // One file per tag
// Client type
client: 'react-query', // 'axios' | 'react-query' | 'swr' | 'fetch' | 'angular'
// HTTP client
httpClient: 'fetch', // 'axios' | 'fetch'
// Override base URL
baseUrl: '/api',
// Generate mock data (MSW)
mock: true,
// Prettier formatting
prettier: true,
// Override specific types
override: {
mutator: {
path: './src/api/custom-fetch.ts',
name: 'customFetch',
},
query: {
useQuery: true,
useInfinite: true,
useSuspenseQuery: true,
},
},
},
},
});# Generate
npx orval
# Watch mode — regenerate on spec changes
npx orval --watch
# Generate for a specific config
npx orval --config ./orval.config.tsFix 2: React Query Output
// orval.config.ts — React Query v5 output
export default defineConfig({
api: {
input: { target: './openapi.json' },
output: {
target: './src/api/index.ts',
client: 'react-query',
httpClient: 'fetch',
baseUrl: 'https://api.myapp.com',
override: {
query: {
useQuery: true,
useSuspenseQuery: true,
useMutation: true,
},
},
},
},
});// Generated output — src/api/index.ts (example)
// Types
export interface User {
id: string;
name: string;
email: string;
}
export interface GetUsersParams {
page?: number;
limit?: number;
search?: string;
}
// React Query hooks
export const useGetUsers = (params?: GetUsersParams, options?) => {
return useQuery({
queryKey: getGetUsersQueryKey(params),
queryFn: () => getUsers(params),
...options,
});
};
export const useCreateUser = (options?) => {
return useMutation({
mutationFn: (data: CreateUserBody) => createUser(data),
...options,
});
};
// Usage in components
'use client';
import { useGetUsers, useCreateUser } from '@/api';
function UserList() {
const { data, isLoading } = useGetUsers({ page: 1, limit: 20 });
const createUser = useCreateUser();
if (isLoading) return <div>Loading...</div>;
return (
<div>
{data?.map(user => (
<div key={user.id}>{user.name}</div>
))}
<button onClick={() => createUser.mutate({
name: 'Alice',
email: '[email protected]',
})}>
Add User
</button>
</div>
);
}Fix 3: Custom Fetch Client (Auth, Error Handling)
// src/api/custom-fetch.ts — custom HTTP client
export const customFetch = async <T>(
url: string,
options: RequestInit,
): Promise<T> => {
const token = getAuthToken();
const response = await fetch(url, {
...options,
headers: {
...options.headers,
'Content-Type': 'application/json',
...(token ? { Authorization: `Bearer ${token}` } : {}),
},
});
if (response.status === 401) {
// Handle token refresh
await refreshToken();
// Retry the request
return customFetch(url, options);
}
if (!response.ok) {
const error = await response.json().catch(() => ({ message: 'Request failed' }));
throw new ApiError(response.status, error.message);
}
if (response.status === 204) return undefined as T;
return response.json();
};
class ApiError extends Error {
constructor(public status: number, message: string) {
super(message);
}
}// orval.config.ts — use custom fetch
export default defineConfig({
api: {
input: { target: './openapi.json' },
output: {
target: './src/api/index.ts',
client: 'react-query',
httpClient: 'fetch',
override: {
mutator: {
path: './src/api/custom-fetch.ts',
name: 'customFetch',
},
},
},
},
});Fix 4: Mock Generation (MSW)
// orval.config.ts — generate MSW mocks
export default defineConfig({
api: {
input: { target: './openapi.json' },
output: {
target: './src/api/index.ts',
client: 'react-query',
mock: true, // Generate MSW handlers
},
},
});// Generated: src/api/index.msw.ts
import { http, HttpResponse } from 'msw';
export const getGetUsersMock = () =>
http.get('/api/users', () => {
return HttpResponse.json([
{ id: '1', name: 'Alice', email: '[email protected]' },
{ id: '2', name: 'Bob', email: '[email protected]' },
]);
});
export const handlers = [
getGetUsersMock(),
getGetUserByIdMock(),
getCreateUserMock(),
// ... all endpoints
];
// Use in tests
import { setupServer } from 'msw/node';
import { handlers } from '@/api/index.msw';
const server = setupServer(...handlers);
beforeAll(() => server.listen());
afterAll(() => server.close());Fix 5: Type Overrides and Transformations
// orval.config.ts — fine-tune generated types
export default defineConfig({
api: {
input: { target: './openapi.json' },
output: {
target: './src/api/index.ts',
client: 'react-query',
override: {
// Override specific operation names
operations: {
listUsers: {
query: {
useQuery: true,
useInfinite: true, // Generate infinite query hook
useInfiniteQueryParam: 'cursor', // Pagination param
},
},
},
// Transform response types
components: {
schemas: {
// Override a specific schema
User: {
// Add custom properties not in the spec
properties: {
fullName: { type: 'string' },
},
},
},
},
},
},
},
});Fix 6: CI/CD — Auto-Regenerate on API Changes
// package.json
{
"scripts": {
"api:generate": "orval",
"api:check": "orval --check",
"prebuild": "npm run api:generate"
}
}# .github/workflows/api-check.yml
name: API Client Check
on:
pull_request:
paths: ['openapi.json', 'orval.config.ts']
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 20 }
- run: npm ci
- run: npx orval
- name: Check for uncommitted changes
run: |
git diff --exit-code src/api/ || \
(echo "Generated API client is out of date. Run 'npx orval' and commit." && exit 1)Still Not Working?
“Unable to resolve $ref” — the OpenAPI spec has a broken $ref link. Check that all $ref targets exist in components.schemas. If the spec comes from a URL, download it locally and validate with npx @apidevtools/swagger-cli validate openapi.json.
Generated types have unknown everywhere — the OpenAPI spec is missing response schemas. Endpoints without responses.200.content.application/json.schema produce unknown types. Fix the spec or add response schemas.
Hooks re-run npx orval but code doesn’t change — Orval generates deterministic output. If the spec hasn’t changed, the output is identical. Check that the spec file was actually updated. For URL-based specs, Orval fetches on each run but may get a cached response.
“Cannot find module” after generation — the generated file might import from packages you haven’t installed. If client: 'react-query', you need @tanstack/react-query installed. If httpClient: 'axios', you need axios. Check the generated imports and install missing packages.
For related API client issues, see Fix: tRPC Not Working and Fix: TanStack Query Not Working.
Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.
Was this article helpful?
Related Articles
Fix: TanStack Start Not Working — Server Functions Failing, Routes Not Loading, or SSR Errors
How to fix TanStack Start issues — project setup, file-based routing, server functions with createServerFn, data loading, middleware, SSR hydration, and deployment configuration.
Fix: Waku Not Working — Server Components Not Rendering, Client Components Not Interactive, or Build Errors
How to fix Waku React framework issues — RSC setup, server and client component patterns, data fetching, routing, layout system, and deployment configuration.
Fix: Wasp Not Working — Build Failing, Auth Not Working, or Operations Returning Empty
How to fix Wasp full-stack framework issues — main.wasp configuration, queries and actions, authentication setup, Prisma integration, job scheduling, and deployment.
Fix: Better Auth Not Working — Login Failing, Session Null, or OAuth Callback Error
How to fix Better Auth issues — server and client setup, email/password and OAuth providers, session management, middleware protection, database adapters, and plugin configuration.