Skip to content

Fix: Orval Not Working — Code Generation Failing, Types Not Matching API, or Hooks Not Updating

FixDevs ·

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 it

Why 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 $ref targets, invalid schema definitions, or incomplete paths cause 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.ts file controls what gets generated. Misconfigured output targets produce unexpected code.
  • Response types follow the spec, not the runtime — if the OpenAPI spec says id: integer but the API actually returns a string, Orval generates id: 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.ts

Fix 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.

F

FixDevs

Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.

Was this article helpful?

Related Articles