This guide expands on the quick start in the root README and
covers the day-to-day setup flow for next-openapi-gen across the currently
supported frameworks.
- Node.js
>=24 - One of the supported frameworks:
- Next.js with App Router or Pages Router
- TanStack Router
- React Router
- Route handlers documented with JSDoc tags when you want explicit metadata
Choose your package manager:
pnpm add -D next-openapi-gennpm install --save-dev next-openapi-genyarn add --dev next-openapi-geninit can scaffold framework-aware defaults for all supported stacks.
| Framework | init flag |
Default apiDir |
Default docs page output | Default includeOpenApiRoutes |
|---|---|---|---|---|
| Next.js | --framework next |
./src/app/api |
src/app/<docsUrl>/page.tsx when src/ exists |
false |
| TanStack Router | --framework tanstack |
./src/routes/api |
src/routes/<docsUrl>.tsx |
true |
| React Router | --framework react-router |
./src/routes/api |
src/routes/<docsUrl>.tsx or src/routes/_index.tsx |
true |
Next.js is the default, so pnpm exec openapi-gen init and
pnpm exec openapi-gen init --framework next are equivalent.
Initialize the project:
# Next.js (default)
pnpm exec openapi-gen init
# TanStack Router
pnpm exec openapi-gen init --framework tanstack
# React Router
pnpm exec openapi-gen init --framework react-routerThis creates:
- a config file in the project root
- an API docs page at
/api-docsby default unless you pass--ui none - a UI integration, with
Scalaras the default
Then generate the spec:
pnpm exec openapi-gen generateBy default this writes public/openapi.json.
Use init once, then rerun generate whenever your routes or schemas change.
A typical setup is:
- Add or update route JSDoc metadata.
- Update your Zod schemas, TypeScript types, or reusable OpenAPI fragments.
- Run
pnpm exec openapi-gen generate. - Review the generated spec or open
/api-docs.
For local development, --watch is usually the best default:
pnpm exec openapi-gen generate --watchIf you want reusable scripts:
{
"scripts": {
"openapi:generate": "openapi-gen generate",
"openapi:watch": "openapi-gen generate --watch"
}
}init still writes next.openapi.json by default for backward compatibility.
During the CLI rename transition, config discovery also accepts:
openapi-gen.config.tsopenapi-gen.config.jsopenapi-gen.config.json- legacy
next-openapi.config.* - legacy
next.openapi.json
The modern openapi-gen.config.* names are the forward-looking option. Legacy
names still load, but they emit deprecation warnings.
If you prefer typed configuration, use defineConfig:
import { defineConfig } from "next-openapi-gen";
export default defineConfig({
openapi: "3.0.0",
apiDir: "./src/app/api",
schemaDir: "./src",
schemaType: "typescript",
});- Use
3.0.0when you want the most conservative output for downstream tools. - Use
3.1.0when you want JSON Schema 2020-12-aligned features such asjsonSchemaDialect. - Use
3.2.0when you want richer route metadata such asquerystring, enhanced tags, sequential media, and richer example objects.
The checked-in examples follow that split on purpose: most apps stay on 3.0,
../apps/next-app-next-config demonstrates a
typed 3.1 config, and ../apps/next-app-zod
showcases 3.2 route and document features.
init creates a config file like this:
{
"openapi": "3.0.0",
"info": {
"title": "Next.js API",
"version": "1.0.0",
"description": "API generated by next-openapi-gen"
},
"apiDir": "./src/app/api",
"routerType": "app",
"schemaDir": "./src",
"schemaType": "zod",
"schemaFiles": [],
"outputFile": "openapi.json",
"outputDir": "./public",
"docsUrl": "api-docs",
"includeOpenApiRoutes": false,
"ignoreRoutes": [],
"debug": false
}Framework-specific defaults mainly change apiDir, framework.kind, and
includeOpenApiRoutes.
| Option | Purpose |
|---|---|
openapi |
Target OpenAPI version: "3.0.0", "3.1.0", or "3.2.0" |
apiDir |
Route directory to scan |
routerType |
"app" for App Router or "pages" for Pages Router |
schemaDir |
One directory or an array of directories to search for types and schemas |
schemaType |
"zod", "typescript", or both |
schemaFiles |
YAML or JSON OpenAPI fragments merged into the final document |
outputDir / outputFile |
Where the generated spec is written |
docsUrl |
Route path for the generated docs UI |
includeOpenApiRoutes |
When true, only handlers tagged with @openapi are included |
ignoreRoutes |
Wildcard patterns for routes you never want in the output |
defaultResponseSet / responseSets |
Shared error-response bundles |
errorConfig |
Templates for consistent generated error schemas |
debug |
Extra generation logs for diagnosing route or schema discovery |
Use this when your API contracts already live in exported Zod schemas:
{
"schemaType": "zod",
"schemaDir": "./src/schemas"
}Use this when your project primarily documents route contracts with exported TypeScript types:
{
"schemaType": "typescript",
"schemaDir": "./src/types"
}Use this when you are gradually moving from TypeScript to Zod or combining code schemas with external fragments:
{
"schemaType": ["zod", "typescript"],
"schemaDir": "./src/schemas",
"schemaFiles": ["./schemas/external-api.yaml"]
}Resolution priority is:
schemaFileszodtypescript
See workflows and integrations for more mixed-schema examples.
All frameworks can use the CLI directly. Add a framework-specific integration when you want generation tied to the dev server or build pipeline.
| Integration | Use when | Notes |
|---|---|---|
| CLI only | You want explicit generate or generate --watch scripts |
Works across all frameworks and keeps behavior easy to reason about |
next-openapi-gen/next |
You want a Next-specific adapter surface | createNextOpenApiAdapter() generates on build completion |
next-openapi-gen/vite |
You are using TanStack Router on Vite, or want the shared Vite path | Generates on build start and watches in dev unless watch: false |
next-openapi-gen/react-router |
You want a React Router-specific plugin entrypoint | Mirrors the React Router framework adapters explicitly |
Use the CLI directly for the simplest setup, or wire in the adapter entrypoint when you want generation attached to the Next build lifecycle.
createNextOpenApiAdapter() is the behavior-bearing integration:
import { createNextOpenApiAdapter } from "next-openapi-gen/next";
export default createNextOpenApiAdapter();withNextOpenApi() wires the Next build to an internal next-openapi-gen
adapter automatically. Use it when you want to stay in next.config.* without
checking in a separate adapter module.
The public Vite entrypoint is the normal integration path:
import { tanstackStart } from "@tanstack/react-start/plugin/vite";
import react from "@vitejs/plugin-react";
import { createViteOpenApiPlugin } from "next-openapi-gen/vite";
import { defineConfig } from "vite";
export default defineConfig({
plugins: [tanstackStart(), createViteOpenApiPlugin(), react()],
});React Router projects can use the React Router-specific subpath export or the shared Vite integration when the app already runs on Vite.
next-openapi-gen supports the legacy Pages Router.
To use it:
- Set
routerTypeto"pages". - Point
apiDirat your Pages Router API directory, usuallypages/api. - Add
@methodto handlers so the generator can map exported functions to HTTP methods.
See ../apps/next-pages-router for a runnable example.
If your API repeats the same error responses on most routes, define response sets once in your config:
{
"defaultResponseSet": "common",
"responseSets": {
"common": ["400", "401", "500"],
"public": ["400", "500"],
"auth": ["400", "401", "403", "500"]
}
}You can then override the default set per route with @responseSet or add
individual extra responses with @add.
There are two common ways to keep internal or incomplete endpoints out of the spec:
- add
@ignoreto a specific handler - add wildcard patterns to
ignoreRoutes
Example:
{
"ignoreRoutes": ["/internal/*", "/debug", "/admin/test/*"]
}- By default the spec is written to
public/openapi.json, which makes it easy to serve and inspect. If you do not want that file publicly accessible, pointoutputDirsomewhere else and wire the UI or deployment flow accordingly. - The generated docs page is a normal application route. Treat it like any other public route if your API docs should be private.
- If you only want the OpenAPI file and not the UI page, run
initwith--ui none.