Skip to main content

Orval vs openapi-typescript vs Kubb 2026

·PkgPulse Team
0

Orval vs openapi-typescript vs Kubb: OpenAPI TypeScript Client Generators 2026

TL;DR

openapi-typescript is the gold standard for type generation — it converts your OpenAPI spec to TypeScript types with zero runtime dependencies, giving you complete control over the HTTP client. Orval is the batteries-included option — it generates React Query/SWR/Axios code with full CRUD operations, mock data, and Zod validation in one config file. Kubb is the new modular powerhouse — it generates multiple output types (TypeScript types, Zod schemas, React Query hooks, MSW mocks) from the same spec via composable plugins. Use openapi-typescript when you want types only; Orval when you want everything generated for you; Kubb when you need maximum flexibility and control over outputs.

Key Takeaways

  • openapi-typescript GitHub stars: ~11k — the most popular types-only generator
  • Orval GitHub stars: ~7k — the dominant full-stack code generator with React Query integration
  • Kubb GitHub stars: ~3.5k — youngest but fastest-growing, especially in enterprise TypeScript teams
  • All three consume OpenAPI 3.x specs — works with Swagger 2.0 via upgrade tools
  • Orval generates ready-to-use React Query hooks — including mutations, infinite queries, and key factories
  • openapi-typescript generates zero-runtime types — just .d.ts files you use with fetch or any HTTP client
  • Kubb's plugin architecture lets you pick exactly what to generate — types only, or types + schemas + hooks + mocks

The OpenAPI Code Generation Problem

Writing TypeScript types for every API endpoint by hand is tedious and error-prone. OpenAPI specs (formerly Swagger) contain all the information needed to auto-generate:

  • TypeScript interfaces for request/response shapes
  • Type-safe HTTP client functions
  • React Query / SWR hooks with correct type signatures
  • Zod schemas for runtime validation
  • MSW mock handlers for testing

The three tools here approach this from different angles: types-first (openapi-typescript), full-code-gen (Orval), and modular (Kubb).


openapi-typescript: Types Without the Runtime

openapi-typescript converts an OpenAPI 3.x spec into TypeScript types. It generates zero runtime code — just type definitions. You use these types with openapi-fetch (the companion fetch wrapper) or any HTTP client.

Installation

npm install -D openapi-typescript
npm install openapi-fetch       # Optional: typed fetch client

Generate Types

# From a URL
npx openapi-typescript https://api.example.com/openapi.json -o src/api/schema.d.ts

# From a local file
npx openapi-typescript openapi.yaml -o src/api/schema.d.ts

# With npm script
{
  "scripts": {
    "generate:types": "openapi-typescript openapi.yaml -o src/api/schema.d.ts"
  }
}

Using Generated Types with openapi-fetch

// Generated schema.d.ts (excerpt)
interface paths {
  "/users": {
    get: {
      parameters: {
        query?: {
          page?: number;
          limit?: number;
          search?: string;
        };
      };
      responses: {
        200: {
          content: {
            "application/json": {
              users: components["schemas"]["User"][];
              total: number;
              page: number;
            };
          };
        };
        401: { content: { "application/json": components["schemas"]["Error"] } };
      };
    };
    post: {
      requestBody: {
        content: {
          "application/json": components["schemas"]["CreateUserInput"];
        };
      };
      responses: {
        201: { content: { "application/json": components["schemas"]["User"] } };
        422: { content: { "application/json": components["schemas"]["ValidationError"] } };
      };
    };
  };
  "/users/{id}": {
    get: {
      parameters: { path: { id: string } };
      responses: {
        200: { content: { "application/json": components["schemas"]["User"] } };
        404: { content: { "application/json": components["schemas"]["Error"] } };
      };
    };
  };
}

// Use with openapi-fetch
import createClient from "openapi-fetch";
import type { paths } from "./api/schema.d.ts";

const client = createClient<paths>({
  baseUrl: "https://api.example.com",
  headers: {
    Authorization: `Bearer ${getToken()}`,
  },
});

// Fully typed GET
const { data, error } = await client.GET("/users", {
  params: {
    query: { page: 1, limit: 20, search: "alice" },
  },
});

if (data) {
  console.log(data.users[0].email); // TypeScript knows the shape
  console.log(data.total);
}

// Typed POST
const { data: newUser, error: createError } = await client.POST("/users", {
  body: {
    name: "Bob Smith",
    email: "bob@example.com",
    role: "admin",
  },
});

// Path parameters
const { data: user } = await client.GET("/users/{id}", {
  params: { path: { id: "user_123" } },
});

React Query with openapi-fetch

import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import createClient from "openapi-fetch";
import type { paths } from "./api/schema.d.ts";

const client = createClient<paths>({ baseUrl: "/api" });

// Custom hooks wrapping typed client + React Query
export function useUsers(params?: { page?: number; limit?: number }) {
  return useQuery({
    queryKey: ["users", params],
    queryFn: async () => {
      const { data, error } = await client.GET("/users", {
        params: { query: params },
      });
      if (error) throw new Error(error.message);
      return data;
    },
  });
}

export function useCreateUser() {
  const queryClient = useQueryClient();
  return useMutation({
    mutationFn: async (input: paths["/users"]["post"]["requestBody"]["content"]["application/json"]) => {
      const { data, error } = await client.POST("/users", { body: input });
      if (error) throw error;
      return data!;
    },
    onSuccess: () => queryClient.invalidateQueries({ queryKey: ["users"] }),
  });
}

Orval: Full-Stack Code Generation

Orval generates everything from your OpenAPI spec: TypeScript types, Axios/fetch client functions, React Query hooks (including mutations, infinite queries, and suspense queries), Zod schemas, and MSW mock handlers. One config file generates your entire API layer.

Installation

npm install -D orval
npm install axios @tanstack/react-query zod   # Runtime deps

Configuration

// orval.config.ts
import { defineConfig } from "orval";

export default defineConfig({
  // First output: React Query hooks
  userApiHooks: {
    input: "./openapi.yaml",
    output: {
      mode: "tags-split",           // Split by OpenAPI tags
      target: "src/api/hooks",
      schemas: "src/api/model",     // Types here
      client: "react-query",
      override: {
        mutator: {
          path: "./src/lib/axios-instance.ts",
          name: "customInstance",   // Use custom Axios instance
        },
        query: {
          useQuery: true,
          useInfinite: true,        // Generate infinite scroll hooks
          useSuspenseQuery: true,   // Generate suspense variants
        },
      },
    },
  },

  // Second output: Zod validation schemas
  userApiZod: {
    input: "./openapi.yaml",
    output: {
      target: "src/api/zod-schemas",
      client: "zod",
    },
  },

  // Third output: MSW mock handlers for tests
  userApiMocks: {
    input: "./openapi.yaml",
    output: {
      target: "src/mocks/handlers",
      client: "msw",
    },
  },
});

Generated React Query Hooks

// Generated by Orval (src/api/hooks/users.ts)
import { useMutation, useQuery, useInfiniteQuery } from "@tanstack/react-query";
import type { MutationFunction, QueryFunction } from "@tanstack/react-query";
import { customInstance } from "../../lib/axios-instance";
import type {
  User,
  CreateUserInput,
  UsersListParams,
  UsersListResponse,
} from "../model";

// GET /users — list with filtering and pagination
export const getUsersList = (params?: UsersListParams) =>
  customInstance<UsersListResponse>({ url: `/users`, method: "GET", params });

export const getGetUsersListQueryKey = (params?: UsersListParams) =>
  [`/users`, ...(params ? [params] : [])] as const;

export const useGetUsersList = <TError = unknown>(
  params?: UsersListParams,
  options?: { query?: UseQueryOptions<UsersListResponse, TError> }
) => {
  return useQuery({
    queryKey: getGetUsersListQueryKey(params),
    queryFn: () => getUsersList(params),
    ...options?.query,
  });
};

// Infinite query for pagination
export const useGetUsersListInfinite = <TError = unknown>(
  params?: Omit<UsersListParams, "page">,
  options?: { query?: UseInfiniteQueryOptions<UsersListResponse, TError> }
) => {
  return useInfiniteQuery({
    queryKey: getGetUsersListQueryKey(params),
    queryFn: ({ pageParam = 1 }) => getUsersList({ ...params, page: pageParam }),
    getNextPageParam: (lastPage) =>
      lastPage.page < Math.ceil(lastPage.total / 20) ? lastPage.page + 1 : undefined,
    ...options?.query,
  });
};

// POST /users — create
export const createUser = (createUserInput: CreateUserInput) =>
  customInstance<User>({ url: `/users`, method: "POST", data: createUserInput });

export const useCreateUser = (options?: {
  mutation?: UseMutationOptions<User, unknown, CreateUserInput>;
}) => {
  return useMutation({
    mutationFn: createUser,
    ...options?.mutation,
  });
};

Using Generated Hooks

// Using Orval-generated hooks in a component
import { useGetUsersList, useCreateUser } from "../api/hooks/users";
import { getGetUsersListQueryKey } from "../api/hooks/users";
import { useQueryClient } from "@tanstack/react-query";

function UserList() {
  const queryClient = useQueryClient();

  const { data, isLoading, error } = useGetUsersList({
    page: 1,
    limit: 20,
    search: "",
  });

  const { mutate: createUser, isPending } = useCreateUser({
    mutation: {
      onSuccess: () => {
        // Invalidate using generated query key
        queryClient.invalidateQueries({
          queryKey: getGetUsersListQueryKey(),
        });
      },
    },
  });

  if (isLoading) return <Skeleton />;
  if (error) return <ErrorBoundary error={error} />;

  return (
    <div>
      {data?.users.map((user) => <UserCard key={user.id} user={user} />)}
      <button onClick={() => createUser({ name: "New User", email: "new@example.com" })}>
        {isPending ? "Creating..." : "Add User"}
      </button>
    </div>
  );
}

Custom Axios Instance

// src/lib/axios-instance.ts — referenced in orval.config.ts
import axios, { AxiosRequestConfig } from "axios";
import { getAuthToken, refreshToken } from "./auth";

const axiosInstance = axios.create({
  baseURL: process.env.NEXT_PUBLIC_API_URL,
  timeout: 10000,
});

axiosInstance.interceptors.request.use((config) => {
  const token = getAuthToken();
  if (token) config.headers.Authorization = `Bearer ${token}`;
  return config;
});

axiosInstance.interceptors.response.use(
  (response) => response,
  async (error) => {
    if (error.response?.status === 401) {
      const newToken = await refreshToken();
      if (newToken) {
        error.config.headers.Authorization = `Bearer ${newToken}`;
        return axiosInstance(error.config);
      }
    }
    return Promise.reject(error);
  }
);

// Orval calls this function with each request config
export const customInstance = <T>(config: AxiosRequestConfig): Promise<T> =>
  axiosInstance(config).then((response) => response.data);

Kubb: Modular Plugin-Based Generation

Kubb is the most flexible of the three — it uses a plugin architecture where each output format is a separate plugin. You compose exactly the outputs you need: TypeScript types, Zod schemas, React Query hooks, SWR hooks, MSW mocks, or all of the above.

Installation

npm install -D @kubb/core @kubb/cli
npm install -D @kubb/plugin-oas          # OpenAPI spec parsing
npm install -D @kubb/plugin-ts           # TypeScript types
npm install -D @kubb/plugin-zod          # Zod schemas
npm install -D @kubb/plugin-react-query  # React Query hooks
npm install -D @kubb/plugin-msw          # MSW mock handlers

Configuration

// kubb.config.ts
import { defineConfig } from "@kubb/core";
import { pluginOas } from "@kubb/plugin-oas";
import { pluginTs } from "@kubb/plugin-ts";
import { pluginZod } from "@kubb/plugin-zod";
import { pluginReactQuery } from "@kubb/plugin-react-query";
import { pluginMsw } from "@kubb/plugin-msw";

export default defineConfig({
  root: ".",
  input: { path: "openapi.yaml" },
  output: { path: "src/gen", clean: true },
  plugins: [
    // 1. Parse the OpenAPI spec
    pluginOas({ validate: true }),

    // 2. Generate TypeScript types
    pluginTs({
      output: { path: "types" },
      enumType: "asConst",
      unknownType: "unknown",
    }),

    // 3. Generate Zod schemas for runtime validation
    pluginZod({
      output: { path: "zod" },
      typed: true,
      dateType: "date",
    }),

    // 4. Generate React Query hooks
    pluginReactQuery({
      output: { path: "hooks" },
      client: {
        importPath: "../../lib/api-client",
      },
      mutation: {
        methods: ["post", "put", "patch", "delete"],
      },
      query: {
        methods: ["get"],
        infinite: {
          queryParam: "page",
          initialPageParam: 1,
          cursorParam: undefined,
        },
      },
    }),

    // 5. Generate MSW handlers for testing
    pluginMsw({
      output: { path: "mocks" },
      handlers: true,
    }),
  ],
});

Generated Output Structure

src/gen/
  types/
    users.ts          ← TypeScript interfaces
    models/
      User.ts
      CreateUserInput.ts
  zod/
    users.ts          ← Zod schemas matching the types
  hooks/
    users/
      useGetUsersList.ts
      useGetUser.ts
      useCreateUser.ts
      useUpdateUser.ts
      useDeleteUser.ts
  mocks/
    users/
      getUsersListHandler.ts
      getUserHandler.ts

Usage

// Generated types
import type { User, CreateUserInput } from "../gen/types/users";

// Generated Zod schema for runtime validation
import { userSchema, createUserInputSchema } from "../gen/zod/users";

// Parse and validate API response at runtime
const result = userSchema.safeParse(apiResponse);
if (!result.success) {
  console.error("Invalid API response:", result.error.format());
}

// Generated React Query hooks
import {
  useGetUsersList,
  useGetUser,
  useCreateUser,
} from "../gen/hooks/users/useGetUsersList";

function UserManager() {
  const { data, isLoading } = useGetUsersList({ query: { page: 1 } });
  const { mutate: create } = useCreateUser();

  return (
    <div>
      {data?.data.map((user) => <UserRow key={user.id} user={user} />)}
      <button onClick={() => create({ requestBody: { name: "New", email: "new@example.com" } })}>
        Add User
      </button>
    </div>
  );
}

// Generated MSW handlers for tests
import { http, HttpResponse } from "msw";
import { getUsersListHandler } from "../gen/mocks/users/getUsersListHandler";

const server = setupServer(...getUsersListHandler);

test("shows users", async () => {
  render(<UserManager />);
  expect(await screen.findByText("Alice")).toBeInTheDocument();
});

Feature Comparison

Featureopenapi-typescriptOrvalKubb
TypeScript types
React Query hooksManual (via openapi-fetch)✅ (plugin)
SWR hooksManual✅ (plugin)
Zod schemas✅ (plugin)
MSW mock handlers✅ (plugin)
Axios integrationManual✅ (custom instance)
fetch integration✅ (openapi-fetch)
Custom HTTP client
Infinite query supportManual
Suspense queriesManual
OpenAPI 3.x support
Swagger 2.0
Plugin architecture
Watch mode
Runtime dependencies0 (types only)axios + react-queryDepends on plugins
GitHub stars11k7k3.5k

When to Use Each

Choose openapi-typescript if:

  • You want maximum control over the HTTP client layer — you choose fetch, ky, axios, or anything else
  • You want zero runtime overhead from the generator — only types
  • You need a lightweight setup for a project that doesn't use React Query
  • Your team prefers writing the hooks manually with strong TypeScript safety

Choose Orval if:

  • You want everything generated with minimal configuration — one config file, complete API client
  • React Query is your data fetching library and you want hooks auto-generated
  • You need MSW mock handlers and Zod validation alongside your hooks
  • You're building a standard CRUD-heavy React application

Choose Kubb if:

  • You need maximum flexibility — pick exactly which outputs to generate via plugins
  • Your project uses an unusual combination (e.g., SWR + Zod, but no React Query)
  • You're building a large enterprise codebase and need consistent, predictable output format
  • You want to extend generation with custom plugins for your specific patterns

Ecosystem & Community

openapi-typescript (11k GitHub stars, ~500k weekly downloads) is maintained by Drizzle team contributor Drew Powers and has become the de facto standard for type-only OpenAPI generation. The companion openapi-fetch package provides the runtime layer, and the combination is recommended in the TanStack Query documentation as one of the best patterns for type-safe API integration. The project is actively maintained with regular updates tracking OpenAPI 3.1 features.

Orval (7k GitHub stars, ~350k weekly downloads) was created by Alan Morel and has become the dominant choice for teams that want complete React Query code generation. Its CI integration story is excellent — run orval in your CI pipeline when the OpenAPI spec changes, and your entire client layer updates automatically. Orval's community has produced extensive documentation on patterns for custom mutators, handling auth, and managing multiple API specs in monorepos.

Kubb (3.5k GitHub stars, ~150k weekly downloads) is the newest and fastest-growing. Created by Thomas Verhelst, it targets enterprise TypeScript teams who've found Orval's configuration opinionated and openapi-typescript's approach too minimal. The plugin system allows teams to write custom generators for their specific patterns — for example, a plugin that generates TanStack Table column definitions from the OpenAPI schema, or a plugin that generates form schemas from request bodies. This extensibility makes Kubb particularly powerful for teams with large, consistent codebases.


Real-World Adoption

openapi-typescript is used by Stripe in their public TypeScript SDK documentation examples, and by numerous API-first companies as the foundation for their client libraries. Its zero-runtime approach makes it suitable for publishing — you can ship types alongside your API without bundling a specific HTTP client. Projects that need to support multiple frameworks (React, Vue, Angular, Svelte) prefer openapi-typescript because it doesn't presuppose a data fetching library.

Orval has strong adoption in consulting agencies and product teams building React applications with a well-defined REST API. The ability to generate the entire API layer from a single config file is compelling for teams where the backend and frontend are developed by different groups — the OpenAPI spec becomes the contract, and Orval automates the frontend's implementation of that contract. Teams report reducing their "glue code" between API and UI by 60-80% after adopting Orval.

Kubb adoption is concentrated in larger engineering organizations with multiple teams sharing an API spec. The plugin architecture allows platform teams to write standardized generators that all product teams use, ensuring consistent patterns for things like error handling, loading states, and cache key naming across a large codebase.


Developer Experience Deep Dive

The openapi-typescript workflow requires more manual code than Orval or Kubb, but it gives you complete control. The generated types are essentially a structured description of your API that TypeScript understands — you write your own hooks, but they're fully typed. Teams that prefer this approach often cite the predictability: generated types change only when the API spec changes, while Orval and Kubb can produce different generated code across versions due to template changes.

Orval's code generation quality is consistently high and well-maintained. The generated hooks follow React Query best practices and the query key factories (getGetUsersListQueryKey()) enable cache invalidation patterns that work correctly with complex query parameter combinations. The watch mode (orval --watch) makes development smooth — change the spec, and the client layer regenerates immediately.

Kubb's plugin composition model is the most powerful but also the most complex to configure. A kubb.config.ts for a large project with all plugins enabled can be 100+ lines. The payoff is complete control over file structure, naming conventions, and output format. Kubb's built-in code formatting (via Prettier integration) ensures generated code matches your project's style conventions.

All three tools support watch mode for development, and all three integrate well with CI/CD pipelines. The standard pattern — commit the generated code to the repository, regenerate in CI when the spec changes, open a PR for review — works identically with all three.


Performance & Build Time

For a medium-sized API with 50 endpoints, generation times are:

  • openapi-typescript: ~0.5s (types only, minimal processing)
  • Orval: ~3-5s (full code generation with hooks, schemas, mocks)
  • Kubb: ~2-4s (depends on which plugins are enabled)

These times are fast enough to be part of any CI pipeline. For very large APIs (500+ endpoints), Orval and Kubb both support incremental generation and can parallelize work across plugins.

The runtime impact of generated code is minimal. openapi-typescript has zero runtime impact by definition. Orval-generated hooks are thin wrappers around React Query — no more overhead than hand-written hooks. Kubb-generated code follows the same pattern.


Migration Guide

Migrating from hand-written types to openapi-typescript is straightforward — replace your manually maintained interfaces with the generated paths and components types, then update your fetch calls to use openapi-fetch. The main investment is updating error handling to use the typed error responses that openapi-typescript makes available.

Migrating from openapi-typescript to Orval when you decide you want generated hooks is a larger investment. You'll add Orval configuration, verify the generated hooks match your existing patterns, replace your hand-written hooks with generated ones, and remove the openapi-typescript setup. This migration typically takes 1-3 days for a medium-sized project.

Migrating from Orval to Kubb is primarily a configuration migration — Kubb can generate similar output to Orval but with more flexibility. Teams typically migrate when they need to add new output types (like Zod schemas) that Orval doesn't support as cleanly, or when they need to extend generation with custom logic.


Final Verdict 2026

openapi-typescript is the right choice for teams who value control and want to avoid generated runtime code. The types-only approach is the most maintainable long-term and is appropriate for any project where the team is comfortable writing React Query hooks manually.

Orval is the right choice for teams who want maximum productivity with a React + React Query stack. The batteries-included approach — one config generates hooks, types, mocks, and schemas — eliminates significant boilerplate and is the fastest path from an OpenAPI spec to a working client.

Kubb is the right choice for large teams and enterprise codebases where consistency and extensibility matter more than quick setup. The plugin architecture enables platform-level standardization that's impossible with Orval's fixed output format.

For most new projects in 2026, the recommendation is clear: start with openapi-typescript and openapi-fetch for simplicity, upgrade to Orval when React Query integration becomes important, and consider Kubb if you need custom generation patterns, SWR hooks, or non-React Query data fetching integration.

Keeping Generated Code Maintainable

One of the most common pitfalls with OpenAPI code generation is treating generated code as application code. Generated files should be in .gitignore or clearly marked as auto-generated and should never be manually edited. Any divergence from the "always regenerate from spec" principle leads to drift between the spec and the client, which defeats the primary purpose of the tool.

The OpenAPI spec itself becomes the source of truth in your codebase. This changes the development workflow: when you add a new API endpoint, you update the OpenAPI spec first, regenerate the client, and then use the new generated types and hooks. This spec-first workflow requires discipline but produces a codebase where the API contract is always explicit and type-safe.

Schema validation is a related concern. openapi-typescript generates types but doesn't validate runtime responses against those types — if the API returns unexpected data, TypeScript's type safety doesn't protect you at runtime. Orval with Zod generation mode produces runtime validators alongside types, catching response shape mismatches at runtime. For applications where API response reliability is uncertain — third-party APIs, legacy backends — Orval's Zod mode provides runtime protection that pure types cannot.

The versioning strategy for generated clients also matters as your API evolves. When a new major API version introduces breaking changes, you'll have both v1 and v2 clients in your codebase during the transition period. All three tools support generating clients from multiple spec versions simultaneously. The clean separation between specs and generated output means version migration can happen incrementally — add the v2 client, migrate endpoints one at a time, and remove the v1 client when all migrations are complete. This incremental migration is significantly harder to manage without type-safe generated clients, since manual HTTP clients don't have the same clear versioning boundary.

Continuous Integration with OpenAPI Generation

Integrating OpenAPI code generation into CI prevents the generated client from drifting out of sync with the API specification. The approach varies by tool but the principle is the same: run the generator in CI and fail the build if the generated output differs from what's in the repository.

For openapi-typescript, the CI step is straightforward: run the generator and check if the output differs from the committed file using git diff --exit-code. If the spec changed but the types weren't regenerated, the build fails. This catches the common mistake of updating an OpenAPI spec without regenerating the dependent clients.

For Orval and Kubb, the same pattern applies but with more generated files to check. Teams sometimes choose not to commit generated files at all — generating them during the build step instead. This keeps the repository clean but adds a generation step to every CI run. For large Kubb outputs with hundreds of generated files, this tradeoff is worth it; for smaller Orval outputs, committing generated files is simpler and more debuggable.

The spec-first workflow also implies that API changes should be reviewed at the spec level rather than the implementation level. When a PR changes the OpenAPI spec, reviewers see the API contract change explicitly, separate from implementation changes. This makes API design reviews more intentional and catches breaking changes earlier. For teams using TanStack Query v5 alongside Orval's generated hooks, the integration is seamless — Orval's React Query mode generates hooks that use TanStack Query v5's latest API syntax. See TanStack Query v5 vs SWR v3 vs RTK Query 2026 for the full server state management comparison.

Working with Multiple API Backends

Many frontend applications consume multiple API backends — a primary REST API, a third-party payment provider, an analytics service, and sometimes a GraphQL endpoint. Managing OpenAPI clients for each backend requires a clear strategy.

openapi-typescript handles multiple specs elegantly: generate separate type files from each spec and use TypeScript module augmentation or namespace prefixes to avoid naming conflicts. Orval's configuration supports multiple endpoints in a single config file, generating separate output directories for each. Kubb's plugin architecture is designed for this scenario — each API backend can have its own plugin configuration with cleanly separated outputs.

Authentication handling across multiple backends is a common integration challenge. Generated clients from these tools are typically authentication-agnostic — they generate the HTTP calls but don't include auth token headers automatically. Wrapping the generated fetch client or axios instance with an authentication interceptor is the standard pattern. The best Next.js auth solutions 2026 covers how to structure authentication alongside API clients in Next.js applications, where Clerk or better-auth integrates with the type-safe API client layer to provide seamless authenticated requests with full type safety from auth token to API response.


Methodology

Data sourced from GitHub repositories (star counts as of February 2026), npm weekly download statistics (January 2026: openapi-typescript ~500k/week, Orval ~350k/week, Kubb ~150k/week), and official documentation. Generated code examples verified against current versions: openapi-typescript 7.x, Orval 7.x, Kubb 2.x.


Related: Best JavaScript Testing Frameworks 2026 for testing the API clients generated by these tools, Best Next.js Auth Solutions 2026 for authentication with type-safe API clients, and Hono vs Elysia 2026 for the backend frameworks that generate OpenAPI specs these tools consume.

The 2026 JavaScript Stack Cheatsheet

One PDF: the best package for every category (ORMs, bundlers, auth, testing, state management). Used by 500+ devs. Free, updated monthly.