Auto-generate OpenAPI 3.1 documentation from your SvelteKit API routes. No decorators, no wrappers, no new APIs to learn — just point it at your existing code.
npx @sveltekit-openapi/core generate
SvelteKit deliberately chose not to enforce typed API responses. That's fine — but it means there's no built-in way to generate API docs.
Existing community tools require JSDoc annotations or are deprecated. This tool takes a different approach: it reads your existing code with TypeScript AST analysis and produces an OpenAPI 3.1 spec. No annotations, no comments, no runtime dependencies.
object.# npm
npm install -D @sveltekit-openapi/core
# Deno
deno add jsr:@sveltekit-openapi/core
# Generate from default location (src/routes)
npx sveltekit-openapi generate
# With options
npx sveltekit-openapi generate \
--routes-dir src/routes \
--schema-files "src/lib/schemas/*.ts" \
--output openapi.json \
--title "My API" \
--api-version "1.0.0"
# Preview discovered routes without generating
npx sveltekit-openapi preview
# Serve with interactive API docs viewer
npx sveltekit-openapi serve --theme swagger --open
// vite.config.ts
import { sveltekit } from '@sveltejs/kit/vite';
import { sveltekitOpenApi } from '@sveltekit-openapi/core/vite';
import { defineConfig } from 'vite';
export default defineConfig({
plugins: [
sveltekit(),
sveltekitOpenApi({
output: 'static/openapi.json',
schemaFiles: ['src/lib/schemas/*.ts'],
info: {
title: 'My API',
version: '1.0.0',
},
}),
],
});
The Vite plugin:
+server.ts and schema files for changes, regenerates with 300ms debounce/_openapi during development/_openapi/spec.json// Node / npm
import { generate } from '@sveltekit-openapi/core';
// Deno / JSR
import { generate } from 'jsr:@sveltekit-openapi/core';
const result = await generate({
routesDir: 'src/routes',
schemaFiles: ['src/lib/schemas/*.ts'],
output: 'openapi.json',
});
console.log(`Generated ${result.endpointCount} endpoints from ${result.routeCount} routes`);
// result.document contains the full OpenAPI 3.1 object
The tool uses a tiered inference model — it documents what it can infer and clearly marks what it can't.
Everything below is detected automatically from your +server.ts files via ts-morph AST analysis:
| What | How it's detected |
|---|---|
| Route paths | Filesystem conventions (src/routes/api/v1/users/+server.ts → /api/v1/users) |
| HTTP methods | Named exports (export const GET, export const POST, etc.) |
| Path params | Directory names ([id] → {id}, [[optional]], [...rest]) |
| Query params | event.url.searchParams.get('name') calls |
| Request body fields | const { a, b } = await event.request.json() destructuring |
| Status codes | json(data, { status: 201 }) calls |
| Auth requirements | requireAuth(event) / requireRole(event, 'admin') patterns |
| Tags | Auto-generated from route path segments |
| Operation IDs | Auto-generated from method + route (e.g. getCoursesModules) |
| Route groups | (groupName) directories stripped from output paths |
When you have Zod schemas, the tool extracts full type information by statically walking the Zod method chain (no runtime execution):
// src/lib/schemas/auth.ts
export const registerSchema = z.object({
email: z.string().email().openapi({ example: '[email protected]' }),
password: z.string().min(8).max(128),
firstName: z.string().min(1).max(255),
}).openapi('RegisterRequest');
This produces a fully documented JSON Schema component:
{
"RegisterRequest": {
"type": "object",
"properties": {
"email": { "type": "string", "format": "email", "example": "[email protected]" },
"password": { "type": "string", "minLength": 8, "maxLength": 128 },
"firstName": { "type": "string", "minLength": 1, "maxLength": 255 }
},
"required": ["email", "password", "firstName"]
}
}
Supported Zod features:
| Category | Methods |
|---|---|
| Base types | string, number, boolean, object, array, enum, literal, date |
| String formats | .email(), .url(), .uuid(), .datetime(), .ip() |
| Number modifiers | .int(), .min(), .max() |
| String constraints | .min(), .max(), .length() |
| Optionality | .optional(), .nullable(), .default() |
| Metadata | .describe(), .openapi({ example, description }), .openapi('Name') |
| Composition | Nested z.object(), z.array() |
| Validation | .parse() and .safeParse() detection in route handlers |
Schemas are registered as named components via .openapi('Name') and referenced with $ref in the output.
Endpoints without type information are still documented — request/response bodies are typed as object. You get the route, method, params, and status codes regardless.
The serve command starts a local server with an interactive API documentation viewer. Three themes available, all loaded from CDN with zero extra dependencies:
npx sveltekit-openapi serve --theme swagger # Classic Swagger UI (default)
npx sveltekit-openapi serve --theme scalar # Modern Scalar API Reference
npx sveltekit-openapi serve --theme redoc # Clean Redoc documentation
Options:
npx sveltekit-openapi serve \
--theme swagger \ # swagger | scalar | redoc (default: swagger)
--port 4242 \ # port to serve on (default: 4242)
--open # auto-open browser
The viewer is also available in the Vite plugin at /_openapi during development, configured via the viewer option in your config.
The viewer renders the generated spec — it does not run your SvelteKit app. To test endpoints with "Try it", run your app separately.
Create sveltekit-openapi.config.ts (or .js, .mjs) in your project root:
import type { SvelteKitOpenAPIConfig } from '@sveltekit-openapi/core';
export default {
routesDir: 'src/routes',
output: 'openapi.json',
format: 'json', // or 'yaml'
info: {
title: 'My API',
version: '1.0.0',
description: 'My SvelteKit API',
},
servers: [
{ url: 'https://api.example.com', description: 'Production' },
{ url: 'http://localhost:5173', description: 'Development' },
],
// Glob patterns for Zod schema files
schemaFiles: ['src/lib/schemas/**/*.ts'],
// Auth detection
auth: {
// Function names that indicate authentication
patterns: ['requireAuth', 'requireRole'],
// Custom security scheme (default: Bearer JWT)
securityScheme: {
type: 'http',
scheme: 'bearer',
bearerFormat: 'JWT',
},
},
// Tag generation
tags: {
segmentIndex: 0, // Which path segment after /api/vN/ to use for tags
},
// Viewer theme for serve command and Vite plugin
viewer: 'swagger', // 'swagger' | 'scalar' | 'redoc'
// Glob patterns to exclude
exclude: ['**/internal/**'],
} satisfies SvelteKitOpenAPIConfig;
CLI flags override config file values.
Running against a real SvelteKit project (23 routes, 29 endpoints, 20 Zod schemas):
{
"openapi": "3.1.0",
"info": { "title": "My API", "version": "1.0.0" },
"paths": {
"/api/v1/auth/login": {
"post": {
"operationId": "postAuthLogin",
"tags": ["auth"],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"email": { "type": "string" },
"password": { "type": "string" }
}
}
}
}
},
"responses": {
"200": { "description": "Success" },
"400": {
"description": "Bad Request",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": { "message": { "type": "string" } },
"required": ["message"]
}
}
}
},
"401": { "description": "Unauthorized" },
"403": { "description": "Forbidden" },
"500": { "description": "Internal Server Error" }
}
}
},
"/api/v1/admin/users": {
"get": {
"operationId": "getAdminUsers",
"tags": ["admin"],
"parameters": [
{ "name": "search", "in": "query", "required": false, "schema": { "type": "string" } },
{ "name": "role", "in": "query", "required": false, "schema": { "type": "string" } }
],
"responses": { "200": { "description": "Success" } },
"security": [{ "bearerAuth": [] }],
"summary": "Requires role: admin"
}
}
},
"components": {
"schemas": {
"RegisterRequest": {
"type": "object",
"properties": {
"email": { "type": "string", "format": "email", "example": "[email protected]" },
"password": { "type": "string", "minLength": 8, "maxLength": 128 },
"firstName": { "type": "string", "minLength": 1, "maxLength": 255 }
},
"required": ["email", "password", "firstName"]
}
},
"securitySchemes": {
"bearerAuth": { "type": "http", "scheme": "bearer", "bearerFormat": "JWT" }
}
},
"tags": [
{ "name": "admin" },
{ "name": "auth" },
{ "name": "courses" },
{ "name": "enrollments" }
]
}
src/
├── index.ts # Library entry: generate(config?)
├── cli.ts # CLI: generate, preview, serve commands
├── vite.ts # Vite plugin: sveltekitOpenApi()
├── viewer.ts # HTML viewer: Swagger UI / Scalar / Redoc
├── types.ts # Config + internal types (uses openapi3-ts)
├── core/
│ ├── scanner.ts # Discovers +server.ts files, converts paths
│ ├── parser.ts # Orchestrates per-file AST analysis
│ ├── schema-extractor.ts # Zod chain → JSON Schema conversion
│ └── generator.ts # Assembles final OpenAPI 3.1 document
└── analyzers/
├── auth.ts # requireAuth / requireRole detection
├── route-params.ts # event.params destructuring
├── query-params.ts # searchParams.get() calls
├── request-body.ts # request.json() + schema detection
└── response.ts # json() calls, status codes
Data flow:
Scanner (filesystem) → Parser (ts-morph AST) → Generator (OpenAPI 3.1)
↑ uses 5 analyzers ↑ uses schema registry
↑ ↑
Schema Extractor (Zod files) ─┘
Usage: sveltekit-openapi [command] [options]
Commands:
generate [options] Generate OpenAPI documentation (default)
preview [options] Preview discovered routes without generating
serve [options] Generate and serve API docs with interactive viewer
Generate options:
-c, --config <path> Path to config file
-o, --output <path> Output file path (default: "openapi.json")
-f, --format <format> Output format: json or yaml (default: "json")
-r, --routes-dir <path> Routes directory (default: "src/routes")
-s, --schema-files <globs> Glob patterns for schema files
--title <title> API title
--api-version <version> API version
Serve options:
-c, --config <path> Path to config file
-p, --port <port> Port to serve on (default: 4242)
-t, --theme <theme> Viewer: swagger, scalar, or redoc (default: "swagger")
--open Open browser automatically
(also accepts all generate options)
The examples/ directory contains four SvelteKit API projects you can run the tool against:
| Example | What it tests |
|---|---|
| bare-minimum | Tier 1/3: query params, destructured body, no Zod, no auth |
| zod-heavy | Tier 2: nested objects, arrays, enums, nullable, .openapi() metadata |
| auth-patterns | Public vs requireAuth vs requireRole (single and multi-role) |
| complex-routing | Route groups, 3-level nested params, rest params, optional params |
# Clone and try
git clone https://github.com/moo3/sveltekit-openapi.git
cd sveltekit-openapi
npm install && npm run build
node dist/cli.js serve -c examples/zod-heavy/sveltekit-openapi.config.ts --theme swagger --open
See examples/README.md for details on what each project demonstrates.
| Package | Purpose |
|---|---|
| ts-morph | TypeScript AST analysis |
| openapi3-ts | OpenAPI 3.1 types and builder |
| fast-glob | File discovery |
| commander | CLI framework |
| yaml | YAML output |
All viewer themes (Swagger UI, Scalar, Redoc) are loaded from CDN at runtime — no additional install.
MIT