@codewithagents/openapi-gen
Types, fetch client, and Zod schemas from your OpenAPI spec. Learn more
Real output from the Petstore 3.0 spec, generated by the toolchain and committed to the repo. This is what CI compiles on every PR.
// client.ts — auto-generated by @codewithagents/openapi-genimport type { Pet, Order } from './models.js'import { getConfig, type ClientConfig } from './client-config.js'import { z } from 'zod'import { PetSchema, OrderSchema } from './schemas.js'
export class ApiError extends Error { constructor( public readonly status: number, public readonly body: unknown ) { super(`API error ${status}`) this.name = 'ApiError' }}
// Validates both request and response bodies at runtime against Zod schemas.export async function addPet(body: Pet, config?: Partial<ClientConfig>): Promise<Pet> { PetSchema.parse(body) const res = await _request('POST', '/pet', { body }, config) return PetSchema.parse(await res.json())}
export async function findPetsByStatus( params: { status: 'available' | 'pending' | 'sold' }, config?: Partial<ClientConfig>): Promise<Pet[]> { const searchParams = new URLSearchParams() if (params?.status != null) searchParams.set('status', String(params.status)) const res = await _request('GET', '/pet/findByStatus', { searchParams }, config) return z.array(PetSchema).parse(await res.json())}// models.ts — auto-generated by @codewithagents/openapi-genimport type { z } from 'zod'import type { OrderSchema, CategorySchema, UserSchema, TagSchema, PetSchema, ApiResponseSchema,} from './schemas.js'
export type Order = z.infer<typeof OrderSchema>export type Category = z.infer<typeof CategorySchema>export type User = z.infer<typeof UserSchema>export type Tag = z.infer<typeof TagSchema>export type Pet = z.infer<typeof PetSchema>export type ApiResponse = z.infer<typeof ApiResponseSchema>// schemas.ts — bootstrapped by @codewithagents/openapi-gen, then yours to extend.// Re-running the generator will NOT overwrite this file.import { z } from 'zod'
export const TagSchema = z .object({ id: z.number().optional(), name: z.string().optional() }) .passthrough()
export const PetSchema = z .object({ id: z.number().optional(), name: z.string(), category: z.object({ id: z.number().optional(), name: z.string().optional() }).optional(), photoUrls: z.array(z.string()), tags: z.array(TagSchema).optional(), status: z.enum(['available', 'pending', 'sold']).optional(), }) .passthrough()
export const OrderSchema = z .object({ id: z.number().optional(), petId: z.number().optional(), quantity: z.number().optional(), status: z.enum(['placed', 'approved', 'delivered']).optional(), complete: z.boolean().optional(), }) .passthrough()// hooks.ts — auto-generated by @codewithagents/openapi-react-queryimport { useQuery, type UseQueryOptions, useMutation } from '@tanstack/react-query'import { addPet, findPetsByStatus, getPetById, type ApiError } from './client.js'
export const petKeys = { findPetsByStatus: (params: Parameters<typeof findPetsByStatus>[0]) => ['pet', 'findPetsByStatus', params] as const, detail: (petId: string) => ['pet', petId] as const,}
export function useFindPetsByStatus( params: Parameters<typeof findPetsByStatus>[0], options?: Omit< UseQueryOptions<Awaited<ReturnType<typeof findPetsByStatus>>, ApiError>, 'queryKey' | 'queryFn' >) { return useQuery<Awaited<ReturnType<typeof findPetsByStatus>>, ApiError>({ queryKey: petKeys.findPetsByStatus(params), queryFn: () => findPetsByStatus(params), staleTime: 0, gcTime: 300000, ...options, })}
// Path-param hooks set enabled: false when id is nullish — no boilerplate at call sites.export function useGetPetById( petId: string | undefined | null, options?: Omit< UseQueryOptions<Awaited<ReturnType<typeof getPetById>>, ApiError>, 'queryKey' | 'queryFn' >) { return useQuery<Awaited<ReturnType<typeof getPetById>>, ApiError>({ queryKey: petKeys.detail(petId!), queryFn: () => getPetById(petId!), enabled: petId != null && (options?.enabled ?? true), ...options, })}// service.ts — auto-generated by @codewithagents/openapi-serverimport type { Order, Pet, User } from './models.js'
export interface SwaggerPetstoreOpenAPI30Service { /** POST /pet */ addPet(body: Pet): Promise<Pet> /** PUT /pet */ updatePet(body: Pet): Promise<Pet> /** GET /pet/findByStatus */ findPetsByStatus(params: { status: string }): Promise<Pet[]> /** GET /pet/{petId} */ getPetById(petId: string): Promise<Pet> /** DELETE /pet/{petId} */ deletePet(petId: string): Promise<void> /** POST /store/order */ placeOrder(body: Order): Promise<Order> /** GET /user/{username} */ getUserByName(username: string): Promise<User>}Generates clean, type-safe code from real-world specs like:
Each package is independent, but they compose. A single OpenAPI spec drives everything from server contract to form field error UX.
One source of truth. When the spec changes, re-run the generators and the compiler tells you exactly what broke.
@codewithagents/openapi-gen
Types, fetch client, and Zod schemas from your OpenAPI spec. Learn more
@codewithagents/openapi-react-query
React Query v5 hooks, generated. Learn more
@codewithagents/openapi-server
Typed service interface and optional router for Hono, Express, or Fastify. Learn more
@codewithagents/api-errors
Map API errors to form field errors. Learn more
Validated, not just typed
Generate Zod schemas from your spec, then enable runtime validation with one config field
(input_schema). The fetch client checks both request and response bodies against your schemas,
so bugs surface at the boundary, not deep in your UI.
Proven on 128 real specs
Stripe, GitHub, Spotify, OpenAI, Adyen, AWS, and more. Every spec is generated and compiled in CI on every commit. Not benchmarked once and forgotten.
Code you own
Output is plain TypeScript files in your repo. No runtime black box, no proprietary client to call into. Adjust, extend, delete. The compiler is your upgrade guide.
OpenAPI 3.1 first-class
Full support for $ref, allOf, anyOf, oneOf, nullable, and 3.1.1 keyword changes. 3.0.x
is supported in practice: 8 of our 13 showcase specs are 3.0.x and all compile. A few 3.0-only
constructs like nullable are not yet normalized to 3.1 semantics. No Swagger 2.0.
Native fetch. Zero added bundle weight.
The generated client uses only native fetch. No axios, no wrapper libraries. Nothing from the
codegen packages ends up in your bundle.
ESM, strict mode, Prettier-ready
Every generated file compiles with strict: true and "type": "module". Output passes
prettier --check with your own config.
Install the generator.
npm install -D @codewithagents/openapi-genFor React Query hooks, also add:
npm install -D @codewithagents/openapi-react-query && npm install @tanstack/react-queryCreate a config file in your project root.
{ "input_openapi": "./openapi.json", "output": "./src/api"}Point input_openapi at your spec (JSON or YAML, local path or URL). The output directory is created automatically.
Run the generator and import the output.
npx openapi-genThen use the generated barrel directly:
import { configureClient, getPetById, useFindPetsByStatus } from './src/api'
configureClient({ baseUrl: 'https://petstore3.swagger.io/api/v3' })
const pet = await getPetById('1')const { data, isLoading } = useFindPetsByStatus({ status: 'available' })A reserved slot for projects and teams using these packages in production.
Using this in production and enjoying it? We would love to feature your project here.
Open an issue and become one of the first listedQuickstart
Go from a spec file to a working typed client in five minutes. Read the quickstart
GitHub
Source code, issues, and the compatibility matrix across 128 specs. View on GitHub