
Security News
pnpm 11.5 Adds Support for Recognizing npm Staged Publishes
pnpm 11.5 now recognizes npm staged publish approvals in release metadata, preventing those releases from being mistaken for lower-trust package publishes.
valibot-serialize
Advanced tools
Serialize / deserialize Valibot schemas, including "to code" for dep-free tree-shaking.
Copyright (c) 2025 by Gadi Cohen. MIT licensed.
// On JSR, it's @valibot/valibot and @gadicc/valibot-serialize
import * as v from "valibot";
import * as vs from "valibot-serialize";
const LoginSchema = v.object({
email: v.string(),
password: v.string(),
});
const serialized = vs.fromValibot(LoginSchema);
const NewLoginSchema = vs.toValibot(serialized);
// { email: 'jane@example.com', password: '12345678' }
const parsed = v.parse(NewLoginSchema, {
email: "hello@example.com",
password: "password",
});
// Write this to a file to benefit from dep-free tree-shaking
const code = vs.toCode(serialized);
// "v.object({email:v.string(),password:v.string()});"
See CLI Usage section for quick no-code usage.
Obviously transforms and callbacks can't be serialized. You should store these elsewhere and apply after deserialization.
To avoid requiring valibot-serialize as a (non-dev) dependency and pulling
in the entire valibot library, use toCode instead of toValibot and write
the results to a file. This way you'll still benefit tree-shaking. See
CLI below for a code-free way to scan a directory and auto-write
modules for each detected schema.
This is a new project (mid September, 2025) - please let us know if anything doesn't work as expected.
I use drizzle-valibot to create
validation schemas (valibot) from my database schemas (drizzle-orm /
postgresql), which works great, but, sometimes I want them on the client side
too without bundling drizzle and pg. This way, we can use a small script to
create dep-free versions of those schemas.
Scan files for supported exports (valibot schema or drizzle tables) and generate static code to recreate them that works with tree-shaking.
Suggested usage: put it in your package.json:
{
"scripts": {
"schema:gen": "vs_tocode --include 'src/db/schema/*.ts' --outDir src/db/valibot/generated"
}
}
vs_tocode --help for all options. You can also import
"valibot-serialize/vs_tocode" and run it programatically. Formatters like
prettier, biome or deno will be used if found.
Alternative ways to run:
$ npx -p valibot-serialize vs_tocode
$ deno run --allow-read --allow-write jsr:@gadicc/valibot-serialize/vs_tocode
Sample input:
import { integer, pgTable, text } from "drizzle-orm/pg-core";
export const users = pgTable("users", { id: integer().primaryKey() /* ... */ });
Sample output:
export const usersSelect = v.object({ name: v.string() /*...*/ });
export const usersInsert = v.object({ id: v.pipe(v.number() /*...*/) });
export const usersUpdate = v.object({ name: v.optiona(v.string()) /*...*/ });
export type UsersSelect = v.InferOutput<typeof usersSelect>;
export type UsersInsert = v.InferInput<typeof usersInsert>;
export type UsersUpdate = v.InferInput<typeof usersUpdate>;
If you don't like this opinionated output structure for drizzle tables, simply
use drizzle-valibot yourself and export the structure you like. Or use the
programatic API (by simply importing "valibot-serialize/vs_tocode").
We also suggest to auto-load in watch-mode with e.g. .vscode/tasks.json:
{
"version": "2.0.0",
"tasks": [
{
"label": "schema watch",
"command": "npm",
"args": ["run", "schema:gen", "--watch"],
"isBackground": true,
"type": "shell",
"runOptions": { "runOn": "folderOpen" },
"presentation": {
"echo": true,
"reveal": "always",
"focus": false,
"panel": "shared",
"showReuseMessage": true,
"clear": false
}
}
]
}
FAQ:
Should I commit generated files to git/vcs?
Totally up to you. No problem to generate at build time, but also nice to have them for clear, easy viewing in your repo.
Is anything here useful? Anything else you'd like?
Convert a serialized AST (read from stdin) to JSON Schema:
echo
'{"kind":"schema","vendor":"valibot","version":1,"format":1,"node":{"type":"object","entries":{"a":{"type":"string"}},"policy":"strict"}}'\
| deno task tojson
Outputs a JSON Schema for the data shape.
Generate Valibot builder code from a serialized AST (read from stdin):
// Removed for now. If you'd find this useful, please open an issue. echo
'{"kind":"schema","vendor":"valibot","version":1,"format":1,"node":{"type":"object","entries":{"email":{"type":"string"},"password":{"type":"string"}}}}'\
| deno task tocode
Outputs:
v.object({email:v.string(),password:v.string()});
fromValibot(schema: v.BaseSchema): SerializedSchema
{ kind, vendor, version, format, node }.toValibot(data: SerializedSchema): v.BaseSchema
isSerializedSchema(x: unknown): x is SerializedSchema
serializedSchemaJson
toJsonSchema(serialized: SerializedSchema): JsonSchema
fromJsonSchema(json: JsonSchemaLike): SerializedSchema
toCode(serialized: SerializedSchema): string
src/types/.
For example: string.ts, number.ts, object.ts, enum.ts, and
picklist.ts. This keeps detection/encode/decode/codegen/JSON‑Schema logic
focused and easy to maintain. When adding support for a new schema, prefer
creating src/types/<kind>.ts and export it via src/types/index.ts.string with:
minLength, maxLength, exact lengthpattern (+ patternFlags), startsWith, endsWithemail, rfcEmail, url, uuid, ip, ipv4,
ipv6, hexColor, slug, digits, emoji, hexadecimal, creditCard,
imei, mac, mac48, mac64, base64, ids ulid, nanoid, cuid2,
ISO time/date variants isoDate, isoDateTime, isoTime, isoTimeSecond,
isoTimestamp, isoWeekminGraphemes, maxGraphemes, minWords, maxWordstrim, trimStart, trimEnd, toUpperCase, toLowerCase,
normalizenumber with min, max, gt, lt, integer, safeInteger,
multipleOf, finiteboolean, literalarray with item + minLength, maxLength, lengthobject with entries, optionalKeys hint, policy (loose/strict),
rest, minEntries, maxEntriesoptional, nullable, nullishunion, tuple (+ rest), recordenum with valuespicklist with values (string options)set with value, minSize, maxSizemap with key, value, minSize, maxSizedate, file (minSize, maxSize, mimeTypes), blob (minSize,
maxSize, mimeTypes)format: 1).fromValibot.fromJsonSchema is intentionally minimal and lossy; prefer authoring schemas
in Valibot and using fromValibot as the source of truth.This was never a main goal for the project especially since other, mature tools
exist for this purpose (i.e.
@valibot/to-json-schema
and
json-schema-to-valibot,
however, the AI offered to implement it and I said why not :) Let us know if you
find it useful.
toJsonSchema converts:
ulid, nanoid, cuid2 via patterns.creditCard, imei, mac, mac48, mac64,
base64 via patterns.enum.fromJsonSchema converts back a subset:
type string/number/integer/boolean, const (literal), enum,
array/object, tuple (prefixItems), union (anyOf), and anyOf of
constants → picklist (all strings) or enum (mixed types).toJsonSchema for startsWith/endsWith, hexColor, slug,
digits, hexadecimal, ids (ulid, nanoid, cuid2) and sets flags
accordingly.| Valibot/AST | toJsonSchema | fromJsonSchema back |
|---|---|---|
| string.email | type: string, format: email | email: true |
| string.url | type: string, format: uri | url: true |
| string.uuid | type: string, format: uuid | uuid: true |
| string.ipv4/ipv6 | format: ipv4/ipv6 | ipv4/ipv6: true |
| string.ip | anyOf [ipv4, ipv6] | ip: true |
| string.startsWith/endsWith | pattern/allOf anchored | starts/ends: true |
| string.hexColor | regex | hexColor: true |
| string.slug | regex | slug: true |
| string.digits/hexadecimal | regex | digits/hexadecimal |
| ulid/nanoid/cuid2 | regex | flags: true |
| creditCard/imei/mac/... | regex | flags: true |
| number min/max/gt/lt | min/max/exclusiveMin/Max | fields restored |
| array min/max/len | minItems/maxItems | fields restored |
| object min/max entries | minProperties/maxProperties | fields restored |
| union of literals | enum | enum node |
| enum values | enum | enum node |
| set/map | array uniqueItems / object additional | approximated |
| tuple/rest | prefixItems (+ items/rest) | fields restored |
| date | string (format: date-time) | approximated |
| file/blob | string binary (+ mediaType) | approximated |
This was "vibe-coded" (with AI) over a weekend. I set up minimalist structure with a test case for how I wanted the code to work, and some empty functions with signatures. I then asked OpenAI Codex to complete the code.
Codex did so, and consistently gave some great suggestions on what to do next, and I kept saying yes to see where it would go. Eventually then I moved on to prompts for cleanup, refactoring, project structure, etc, but definitely more is needed.
Please do bring any weird issues to our attention, and feel free to request clearer docs, examples, etc. Working on that next.
See CONTRIBUTING.md for project layout, test naming, and workflow conventions.
MIT
FAQs
Convert Valibot schemas to/from a stable serialized AST
The npm package valibot-serialize receives a total of 4 weekly downloads. As such, valibot-serialize popularity was classified as not popular.
We found that valibot-serialize demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
pnpm 11.5 now recognizes npm staged publish approvals in release metadata, preventing those releases from being mistaken for lower-trust package publishes.

Security News
Federal audit finds NIST lacked a plan to clear the NVD backlog, wasted funds on duplicate work, and delayed use of CISA data.

Research
/Security News
A mini Shai-Hulud campaign compromised Red Hat Cloud Services npm packages to steal developer and CI/CD secrets during installation.