Generate an OpenAPI 3.1 specification from your Valibot schemas and lightweight endpoint metadata — no runtime magic, no validation side-effects, just clean documentation.
+server routescreateOpenApiSpec)parameterssecuritySchemes / securityThis library does not enforce validation or authentication at runtime. It only documents what your API looks like.
pnpm add @uraniadev/sveltekit-valibot-openapi valibot @valibot/to-json-schema
Inside your route modules, export an _openapi object.
Each key is an HTTP method, and each value comes from defineEndpoint.
The example below uses SvelteKit, but the _openapi pattern works in any
framework as long as you can pass the modules into the spec generator.
// src/routes/api/todos/+server.ts
import type { RequestHandler } from "./$types";
import { object, string, array } from "valibot";
import { defineEndpoint } from "@uraniadev/sveltekit-valibot-openapi";
const TodoSchema = object({
id: string(),
title: string(),
});
const TodoListSchema = array(TodoSchema);
const TodoCreateSchema = object({
title: string(),
});
export const _openapi = {
GET: defineEndpoint({
method: "GET",
path: "/api/todos",
summary: "List todos",
query: object({
search: string().optional(),
}),
responses: {
200: {
description: "List of todos",
schema: TodoListSchema,
},
},
}),
POST: defineEndpoint({
method: "POST",
path: "/api/todos",
summary: "Create a todo",
body: TodoCreateSchema,
responses: {
201: {
description: "Created todo",
schema: TodoSchema,
},
},
}),
} as const;
export const GET: RequestHandler = async () => new Response("...");
export const POST: RequestHandler = async () => new Response("...");
createOpenApiSpec now returns the OpenAPI spec object, not a
framework handler. You can use it wherever you like.
// src/routes/openapi/+server.ts
import type { RequestHandler } from "./$types";
import { json } from "@sveltejs/kit";
import { createOpenApiSpec } from "@uraniadev/sveltekit-valibot-openapi";
const modules = import.meta.glob("../api/**/+server.{ts,js}");
export const GET: RequestHandler = async () => {
const spec = await createOpenApiSpec(modules, {
basePath: "/api",
info: {
title: "My API",
version: "1.0.0",
description: "Example SvelteKit API",
},
servers: [
{ url: "https://api.example.com", description: "Production" },
{ url: "http://localhost:5173", description: "Development" },
],
securitySchemes: {
bearerAuth: {
type: "http",
scheme: "bearer",
bearerFormat: "JWT",
},
},
security: [{ bearerAuth: [] }],
});
return json(spec);
};
Now visit:
/openapi
…and you get your live OpenAPI 3.1 JSON. Point Scalar, Swagger UI, Redoc, or Postman at it.
As long as you can build a GlobModules map (or something compatible),
you can generate the spec anywhere:
import { createOpenApiSpec } from "@uraniadev/sveltekit-valibot-openapi";
const modules = import.meta.glob("./routes/**/route.{ts,js}");
async function buildSpec() {
const spec = await createOpenApiSpec(modules, {
info: {
title: "My Service",
version: "1.0.0",
},
});
// Write to file, feed into a UI, etc.
console.log(JSON.stringify(spec, null, 2));
}
application/json)body: MyJsonSchema;
body: {
required: false,
content: {
"application/json": PartialUserSchema
}
}
const JsonSchema = object({ name: string() });
const MultipartSchema = object({
name: string(),
avatar: string(),
});
defineEndpoint({
method: "POST",
path: "/api/profile",
body: {
content: {
"application/json": JsonSchema,
"multipart/form-data": MultipartSchema,
},
},
responses: {
204: { description: "Profile updated" },
},
});
Supports:
schema (simple JSON response)content (multi-media)defineEndpoint({
method: "GET",
path: "/api/example",
responses: {
200: {
description: "Multiple formats",
content: {
"application/json": object({ ok: string() }),
"text/plain": string(),
"image/png": string(),
},
},
404: {
description: "Not found",
schema: object({ ok: string() }),
},
},
});
Object schemas become OpenAPI parameters.
const QuerySchema = object({
search: string().optional(),
limit: number(),
verbose: boolean().optional(),
sort: union([literal("asc"), literal("desc")]),
});
defineEndpoint({
method: "GET",
path: "/api/items",
query: QuerySchema,
responses: {
200: { description: "OK" },
},
});
You configure security the same way – directly on the spec generator:
const spec = await createOpenApiSpec(glob, {
securitySchemes: {
bearerAuth: {
type: "http",
scheme: "bearer",
bearerFormat: "JWT",
},
},
security: [{ bearerAuth: [] }],
});
defineEndpoint({
method: "GET",
path: "/api/public",
responses: { 200: { description: "OK" } },
security: [], // no auth for this operation
});
It is pure documentation generation, not a framework.
Import from the published package:
import { EndpointDef, OpenApiSpec } from "@uraniadev/sveltekit-valibot-openapi";
This library was not “vibe-coded” or generated blindly. It was built through an iterative workflow where AI was used as a technical assistant, not as an author.
All architectural decisions, schema handling logic, and API design were intentionally crafted by the maintainer, with AI serving as a tool to accelerate refactoring, validate edge cases, and improve TypeScript ergonomics.
Every line of code was reviewed, tested, and integrated with a clear understanding of SvelteKit, Valibot, and OpenAPI constraints.
So any mistake or naivety is purely mine, amplified by AI abuse 😉