Valibot
Valibot is a modular, tree-shakeable schema validation library. Valibot v1.x implements the Standard Schema ~standard.validate interface but does not include ~standard.jsonSchema natively. You need the official @valibot/to-json-schema package to bridge the gap.
Installation
npm install valibot @valibot/to-json-schemaWhy the Wrapper?
to-openapi reads JSON Schema from schema["~standard"].jsonSchema. Zod and ArkType provide this natively, but Valibot v1.x only ships ~standard.validate for runtime validation.
The @valibot/to-json-schema package provides a toStandardJsonSchema() function that wraps a Valibot schema and adds the ~standard.jsonSchema interface. Every Valibot schema must be wrapped before passing it to openapi().
import * as v from "valibot";
import { toStandardJsonSchema } from "@valibot/to-json-schema";
// Raw Valibot schema -- missing ~standard.jsonSchema
const raw = v.object({ name: v.string() });
// Wrapped -- now compatible with to-openapi
const schema = toStandardJsonSchema(raw);Defining Schemas
import * as v from "valibot";
import { toStandardJsonSchema } from "@valibot/to-json-schema";
const UserSchema = toStandardJsonSchema(
v.object({
id: v.pipe(v.string(), v.uuid()),
name: v.pipe(v.string(), v.minLength(1)),
email: v.pipe(v.string(), v.email()),
role: v.picklist(["admin", "user", "guest"]),
bio: v.optional(v.string()),
createdAt: v.pipe(v.string(), v.isoTimestamp()),
}),
);
const CreateUserSchema = toStandardJsonSchema(
v.object({
name: v.pipe(v.string(), v.minLength(1)),
email: v.pipe(v.string(), v.email()),
role: v.picklist(["admin", "user", "guest"]),
}),
);
const ErrorSchema = toStandardJsonSchema(
v.object({
message: v.string(),
code: v.string(),
}),
);Using with to-openapi
Pass wrapped Valibot schemas to openapi() -- the call structure is identical to Zod or ArkType:
import * as v from "valibot";
import { toStandardJsonSchema } from "@valibot/to-json-schema";
import { openapi } from "to-openapi";
const UserSchema = toStandardJsonSchema(
v.object({
id: v.pipe(v.string(), v.uuid()),
name: v.pipe(v.string(), v.minLength(1)),
email: v.pipe(v.string(), v.email()),
createdAt: v.pipe(v.string(), v.isoTimestamp()),
}),
);
const CreateUserSchema = toStandardJsonSchema(
v.object({
name: v.pipe(v.string(), v.minLength(1)),
email: v.pipe(v.string(), v.email()),
}),
);
const UpdateUserSchema = toStandardJsonSchema(
v.object({
name: v.optional(v.pipe(v.string(), v.minLength(1))),
email: v.optional(v.pipe(v.string(), v.email())),
}),
);
const ErrorSchema = toStandardJsonSchema(
v.object({
message: v.string(),
code: v.string(),
}),
);
const spec = openapi({
info: {
title: "User Service",
version: "1.0.0",
},
schemas: {
User: UserSchema,
CreateUser: CreateUserSchema,
UpdateUser: UpdateUserSchema,
Error: ErrorSchema,
},
paths: {
"GET /users": {
summary: "List users",
tags: ["Users"],
200: UserSchema,
},
"POST /users": {
summary: "Create user",
tags: ["Users"],
body: CreateUserSchema,
201: UserSchema,
400: ErrorSchema,
},
"GET /users/{id}": {
summary: "Get user by ID",
tags: ["Users"],
200: UserSchema,
404: ErrorSchema,
},
"PUT /users/{id}": {
summary: "Update user",
tags: ["Users"],
body: UpdateUserSchema,
200: UserSchema,
404: ErrorSchema,
},
"DELETE /users/{id}": {
summary: "Delete user",
tags: ["Users"],
204: null,
404: ErrorSchema,
},
},
});Common Patterns
Optional Fields
Wrap the field schema in v.optional():
const UpdateUserSchema = toStandardJsonSchema(
v.object({
name: v.optional(v.pipe(v.string(), v.minLength(1))),
email: v.optional(v.pipe(v.string(), v.email())),
}),
);Validations with v.pipe()
Valibot uses v.pipe() to chain validations. These are translated into JSON Schema constraints:
const schema = toStandardJsonSchema(
v.object({
email: v.pipe(v.string(), v.email()),
age: v.pipe(v.number(), v.minValue(0), v.maxValue(150)),
username: v.pipe(v.string(), v.minLength(3), v.maxLength(32)),
id: v.pipe(v.string(), v.uuid()),
}),
);Enums
Use v.picklist() for string enums:
const StatusSchema = toStandardJsonSchema(
v.picklist(["active", "inactive", "suspended"]),
);Arrays
Use v.array() and wrap the result:
const TagListSchema = toStandardJsonSchema(v.array(v.string()));Wrapping Pattern
Remember: every schema passed to openapi() must go through toStandardJsonSchema(). A common pattern is to define a helper:
import * as v from "valibot";
import { toStandardJsonSchema } from "@valibot/to-json-schema";
const s = <T extends v.GenericSchema>(schema: T) =>
toStandardJsonSchema(schema);
const UserSchema = s(
v.object({
id: v.pipe(v.string(), v.uuid()),
name: v.string(),
}),
);Related
- Schemas & $ref -- how schemas become
$refreferences - Schema Extensions -- adding OpenAPI-specific fields to schemas
- Multi-Library Example -- the same API defined with Zod, ArkType, and Valibot