@valibot/to-json-schema review
@valibot/to-json-schema turns Valibot schemas into draft-07, draft-2020-12, or OpenAPI 3.0 schema objects. That is useful when Valibot validates application data but another system needs a portable description for API docs, forms, code generation, or structured output. Conversion has limits: JavaScript transforms and several Valibot checks have no exact JSON Schema form. Version 1.7.1 fixes references for definition names containing `/` or `~` by encoding those characters as JSON Pointer tokens.
@valibot/to-json-schema 1.7.1 installed in 2.3 seconds, occupied 2 MB, and produced a 3.2 KB gzipped browser bundle in our sandbox, with 0 audit findings. Install it for a Valibot stack that needs draft-07, draft-2020-12, or OpenAPI 3.0 output, but keep conversion errors on and test every generated contract that crosses a system boundary.
We installed it
| Install | ✓ · 2.3s | 2 packages on disk · 2 MB |
| Import | ✓ | ESM import works · require() works · ESM package with exports map |
| Browser | 3.2 KB | gzipped (11.7 KB minified), bundled with esbuild |
| Types | ✓ | TypeScript types bundled |
| Known vulns | 0 | 0 critical · 0 high · 0 moderate · 0 low (npm audit) |
Answers from our run
Does @valibot/to-json-schema install cleanly?
Yes. In a fresh container with an empty cache, npm install @valibot/to-json-schema finished in 2 seconds, leaving 2 packages and 2 MB on disk. npm audit reported no known vulnerabilities.
How much does @valibot/to-json-schema add to a browser bundle?
3.2 KB gzipped (11.7 KB minified) when the whole package is bundled for the browser with esbuild. Importing only part of it is usually smaller.
Does @valibot/to-json-schema work with both ESM and CommonJS?
Yes. Both import '@valibot/to-json-schema' and require('@valibot/to-json-schema') worked in Node 22 in our run. The package is published as ESM with an exports map.
Does @valibot/to-json-schema include TypeScript types?
Yes, type declarations ship inside the package, so no @types install is needed.
@valibot/to-json-schema or zod-to-json-schema: which should you use?
zod-to-json-schema: Use it for an existing Zod 3 schema set that cannot move to Valibot. @valibot/to-json-schema 1.7.1 installed in 2.3 seconds, occupied 2 MB, and produced a 3.2 KB gzipped browser bundle in our sandbox, with 0 audit findings.
When should you not use @valibot/to-json-schema?
Your rules depend on transformations or JavaScript-only checks: the converter cannot express those operations as JSON Schema
Use it if
- Your application already validates with Valibot and must publish JSON Schema or OpenAPI 3.0 descriptions
- You need separate schema descriptions for the input or output side of a transforming Valibot pipeline
- You need reusable definitions or recursive lazy schemas represented with references
- You can test the generated result in the same validator, form tool, or API consumer used in production
- Your rules depend on transformations or JavaScript-only checks: the converter cannot express those operations as JSON Schema
- You require identical results from Valibot and every downstream validator: the README documents differences in formats and Unicode length counting
- Your target is OpenAPI 3.1: the package has an OpenAPI 3.0 target, while its other targets are JSON Schema draft-07 and draft-2020-12
- Your project cannot satisfy the Valibot ^1.4.0 peer range declared by version 1.7.1
- You plan to ignore conversion errors: unsupported schemas can become `{}` and unsupported actions can vanish, leaving a looser contract than the source schema
Setup reality
Our install of 1.7.1 finished in 2.3 seconds and left 2 packages using 2 MB on disk. npm audit found 0 known vulnerabilities. The converter itself has 0 direct dependencies and 1 peer dependency, Valibot ^1.4.0. Its 172 KB package includes TypeScript declarations, an exports map, and working require() and ESM import entry points.
toJsonSchema is synchronous and needs no credentials or config file. It emits draft-07 unless target is set to draft-2020-12 or openapi-3.0. Our browser build measured 11.7 KB minified and 3.2 KB gzipped. OpenAPI 3.0 has narrower vocabulary, so records cannot emit propertyNames, and greater-than or less-than checks have no direct output there.
Conversion errors throw by default. Keeping that default exposes unsupported file schemas, transforms, regex flags, and other rules during a build. warn continues with a console warning, while ignore can replace an unsupported schema with {} or omit an action. For pipelines containing transformations, choose typeMode: 'input' or 'output'; output mode needs a concrete schema after the last transform.
Named definitions produce $defs references, and lazy schemas may receive generated numeric IDs. Version 1.7.1 correctly escapes / and ~ in those reference tokens. Custom reference paths are needed when definitions live under OpenAPI components. The declaration files mark global definitions and several override hooks as beta. Global definitions also live in module-level state, so per-call definitions are easier to isolate in servers and tests.
Patterns
Convert a schema with the default draft convert-basic-schema
import {toJsonSchema} from '@valibot/to-json-schema';
import * as v from 'valibot';
const schema = toJsonSchema(v.string());
console.log(schema.$schema, schema.type);draft-07 is the default target. Set `target` when the receiving tool expects another dialect.
Preserve required and optional object fields convert-object-fields
const UserSchema = v.object({
id: v.pipe(v.string(), v.uuid()),
name: v.pipe(v.string(), v.minLength(1)),
age: v.optional(v.pipe(v.number(), v.minValue(0))),
});
const jsonSchema = toJsonSchema(UserSchema);The generated `required` array contains `id` and `name`; `age` remains optional.
Emit draft 2020-12 target-draft-2020-12
const jsonSchema = toJsonSchema(UserSchema, {
target: 'draft-2020-12',
});This changes the `$schema` dialect to draft 2020-12. The validator consuming it must support that draft too.
Emit an OpenAPI 3.0 schema object target-openapi-3
const openApiSchema = toJsonSchema(
v.nullable(v.string()),
{target: 'openapi-3.0'},
);
// {type: 'string', nullable: true}OpenAPI 3.0 uses `nullable: true` and omits `$schema`; features such as record `propertyNames` are unavailable in this target.
Include documentation metadata attach-schema-metadata
const EmailSchema = v.pipe(
v.string(),
v.email(),
v.metadata({
title: 'Email',
description: 'Primary contact address',
examples: ['jane@example.com'],
}),
);
const jsonSchema = toJsonSchema(EmailSchema);Valid `title`, `description`, and `examples` metadata is copied into the generated schema.
Reference a definition whose name contains pointer characters escape-definition-references
const ProfileSchema = v.object({name: v.string()});
const jsonSchema = toJsonSchema(ProfileSchema, {
definitions: {'user/profile~v1': ProfileSchema},
});
// $ref: '#/$defs/user~1profile~0v1'Version 1.7.1 encodes `/` as `~1` and `~` as `~0` inside generated JSON Pointer references.
Convert a recursive lazy schema convert-recursive-schema
const CategorySchema = v.object({
name: v.string(),
children: v.array(v.lazy(() => CategorySchema)),
});
const jsonSchema = toJsonSchema(CategorySchema);The converter invokes the lazy getter with `undefined` and may create a generated numeric definition ID.
Describe the input side of a transform describe-transform-input
const AmountSchema = v.pipe(
v.string(),
v.decimal(),
v.transform(Number),
v.number(),
v.maxValue(100),
);
const inputSchema = toJsonSchema(AmountSchema, {typeMode: 'input'});Input mode stops before the first possible type transformation or the second schema in the pipeline.
Describe the output side of a transform describe-transform-output
const outputSchema = toJsonSchema(AmountSchema, {
typeMode: 'output',
});Output mode starts at the last schema. A transform without a following schema leaves no dependable output shape to convert.
Represent a file upload in OpenAPI override-file-schema
const UploadSchema = v.object({file: v.file()});
const schema = toJsonSchema(UploadSchema, {
target: 'openapi-3.0',
overrideSchema({valibotSchema}) {
if (valibotSchema.type === 'file') {
return {type: 'string', format: 'binary'};
}
},
});`file` lacks a direct conversion, so the override supplies the OpenAPI binary-string representation.
Build an OpenAPI component map build-openapi-components
import {toJsonSchemaDefs} from '@valibot/to-json-schema';
const schemas = toJsonSchemaDefs(
{Email: EmailSchema, User: UserSchema},
{
target: 'openapi-3.0',
overrideRef: ({referenceId}) =>
`#/components/schemas/${referenceId}`,
},
);`toJsonSchemaDefs` returns the definitions map only; assign it to `components.schemas` in the OpenAPI document.
Stop publication when conversion is incomplete fail-on-unsupported-rules
try {
const schema = toJsonSchema(v.file());
publish(schema);
} catch (error) {
console.error('Schema conversion failed', error);
process.exitCode = 1;
}Throwing is the default. `errorMode: 'ignore'` can turn an unsupported schema into `{}`, which accepts every JSON value.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| zod-to-json-schema | npm | Use it for an existing Zod 3 schema set that cannot move to Valibot. |
| @alcyone-labs/zod-to-json-schema | npm | Use this maintained fork when your schemas are on Zod 4. |
| @sinclair/typebox | npm | Choose it when JSON Schema should be the authored source and TypeScript types should follow from it. |
| arktype | npm | Consider it when ArkType already owns runtime validation and its schema export fits the downstream consumer. |
More utils guides
lru-cache · ajv · type-fest · p-limit · find-up · js-yaml · the whole shelf →
How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.

