# Valibot Serialize

> Use valibot-serialize to generate deterministic Valibot modules from Valibot schemas or Drizzle tables, serialize and reconstruct schemas through its versioned AST, migrate stored payloads, convert to or from JSON Schema, or extend generation with explicit source plugins. Use for tasks involving the `valibot-serialize` package, the `vs_tocode` CLI or API, generated-schema check/watch workflows, and serialized Valibot schema compatibility.

- Skill: `gadicc/valibot-serialize` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add gadicc/valibot-serialize`
- Raw SKILL.md: https://api.skillmd.com/api/skills/gadicc/valibot-serialize/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Integrations & APIs
- Author: gadicc (https://skillmd.com/u/gadicc)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/gadicc/valibot-serialize

---


# Valibot Serialize

Use the package's narrowest API for the requested job, preserve the host
project's package-manager conventions, and verify generated or reconstructed
schemas with the host project's tests.

## Choose the surface

| Goal                                                        | Surface                                       |
| ----------------------------------------------------------- | --------------------------------------------- |
| Generate committed Valibot modules                          | `vs_tocode` CLI                               |
| Embed generation, inspect output, or manage watch lifecycle | `generate` from `valibot-serialize/vs_tocode` |
| Store or transmit a Valibot schema                          | `fromValibot` from `valibot-serialize`        |
| Read current or legacy serialized data                      | `migrateSerializedSchema`, then `toValibot`   |
| Emit Valibot builder source                                 | `toCode`                                      |
| Interoperate with JSON Schema                               | `toJsonSchema` or `fromJsonSchema`            |
| Recognize a custom exported source value                    | `defineSourcePlugin` with explicit `handlers` |

Before editing, inspect the installed package version and the project's existing
scripts, generated-file policy, formatter, module system, and package manager.
Do not introduce Node tooling into a Deno-only project.

## Install the package

For Node.js 22 or later, add the library and Valibot. Add `tsx` as a development
dependency only when using the packaged `vs_tocode` binary:

```bash
npm add valibot valibot-serialize
npm add --save-dev tsx
```

Translate those commands to the project's existing npm-compatible package
manager. For Deno, prefer the JSR package:

```bash
deno add jsr:@gadicc/valibot-serialize npm:valibot
```

## Generate static modules

Add separate write and verification scripts. Use an explicit formatter in
reproducible workflows; `auto` depends on the local environment.

```json
{
  "scripts": {
    "schema:gen": "vs_tocode --include 'src/schemas/*.ts' --out-dir src/generated --formatter=none",
    "schema:check": "vs_tocode --include 'src/schemas/*.ts' --out-dir src/generated --formatter=none --check"
  }
}
```

Follow these rules:

- Use positional files for a fixed list, or `--include` plus optional
  `--exclude` for globs. Do not combine positional files with include/exclude.
- Use `--check` in CI when generated files are committed. It compares selected
  outputs without writing and exits nonzero for missing or changed files.
- Do not promise orphan-output detection; check mode only evaluates currently
  selected inputs.
- Use `--watch` only for development. A per-file failure leaves the previous
  owned output in place and can recover after another edit.
- Treat selected source modules as trusted code because generation imports and
  evaluates them.
- Inspect the generated diff and run both the schema check and relevant project
  tests.

For Deno CLI use, grant only the permissions needed to import sources and write
outputs, for example:

```bash
deno run --allow-read --allow-write jsr:@gadicc/valibot-serialize/vs_tocode --help
```

## Use the generator API

Import the non-exiting API from its subpath. Prefer `explicitFiles` for known
inputs and set `projectRoot` when the caller's working directory is ambiguous.

```ts
import { generate } from "valibot-serialize/vs_tocode";

const result = await generate({
  explicitFiles: ["src/schemas/user.ts"],
  outDir: "src/generated",
  formatter: "none",
  check: true,
});

if (!result.check?.upToDate) {
  throw new Error(JSON.stringify(result.check?.mismatches));
}
```

Use `dryRun: true` to inspect `result.files[].contents` without writing. Never
combine `check` with `watch` or `dryRun`. When enabling watch, retain the
handle, close it during shutdown, and await completion when appropriate:

```ts
const result = await generate({
  explicitFiles: ["src/schemas/user.ts"],
  outDir: "src/generated",
  formatter: "none",
  watch: true,
});

await result.watch?.close();
await result.watch?.done;
```

The API rejects errors and does not terminate the host process or set its exit
status.

## Serialize and reconstruct schemas

Serialize only supported Valibot constructs, cross the wire as JSON, and treat
received data as `unknown`. Migrate at the boundary to validate legacy or
current payloads and obtain the canonical current format.

```ts
import * as v from "valibot";
import {
  fromValibot,
  migrateSerializedSchema,
  toValibot,
} from "valibot-serialize";

const source = v.object({ id: v.number(), name: v.string() });
const wireText = JSON.stringify(fromValibot(source));

const received: unknown = JSON.parse(wireText);
const canonical = migrateSerializedSchema(received);
const reconstructed = toValibot(canonical);
const value = v.parse(reconstructed, { id: 1, name: "Ada" });
```

Use `isSerializedSchema` only as a type guard for the current canonical format;
it deliberately rejects supported legacy payloads. Use `migrateSerializedSchema`
when historical stored data is possible. Do not manually edit `kind`, `vendor`,
`version`, `format`, node shapes, or reference paths.

Account for these boundaries:

- Arbitrary transforms, callbacks, accessors, and unsupported pipe actions are
  not serializable; keep custom runtime behavior outside the serialized schema.
- Defaults must be exact JSON values. Callback defaults, non-finite numbers,
  negative zero, sparse arrays, class instances, and richer runtime values fail.
- Recursive lazy schemas can round-trip and produce Valibot code, but recursive
  data JSON Schema conversion is unsupported.
- Readers are not forward-compatible with unknown envelope or format versions.

## Convert code and JSON Schema

Use `toCode(serialized)` when the consumer needs a Valibot builder expression;
it returns source without imports, so add imports and formatting at the caller.

Treat `toJsonSchema` as a best-effort Draft 2020-12 data-schema projection and
`fromJsonSchema` as a deliberately limited, lossy import. Prefer Valibot plus
`fromValibot` as the source of truth. Add semantic tests whenever conversion
crosses the JSON Schema boundary, especially for sets, maps, dates, files,
blobs, tuples, formats, and custom constraints.

## Extend source generation

Define custom source plugins with `defineSourcePlugin`. Keep `available`,
`test`, and `transform` deterministic and safe under concurrent processing.
Supplying `handlers` replaces all built-ins, so compose them explicitly when
they must remain enabled:

```ts
import {
  builtInHandlers,
  defineSourcePlugin,
  generate,
} from "valibot-serialize/vs_tocode";

const taggedString = defineSourcePlugin({
  name: "tagged-string",
  available: () => true,
  test: (value) =>
    typeof value === "object" && value !== null &&
    (value as { kind?: unknown }).kind === "tagged-string",
  transform: (symbol) => ({ exports: { [symbol]: "v.string()" } }),
});

await generate({
  explicitFiles: ["src/schemas.ts"],
  outDir: "src/generated",
  formatter: "none",
  handlers: [taggedString, ...builtInHandlers],
});
```

Do not imply registration or discovery: `defineSourcePlugin` is an identity
helper, plugins are passed per `generate` call, and their output remains
Valibot-oriented.

