Convert Valibot’s functional pipe-based API to Zod’s fluent method chains. Whether you need broader ecosystem support, team familiarity with Zod, or first-class tRPC/Drizzle integration, SchemaShift automates the transformation.
Install SchemaShift globally and run the migration:
npm install -g schemashift-cli
schemashift migrate ./src -f valibot -t zod
Valibot → Zod migration requires a Pro or Team license. View pricing.
import * as v from 'valibot';
const UserSchema = v.object({
name: v.pipe(v.string(), v.minLength(2), v.maxLength(100)),
email: v.pipe(v.string(), v.email()),
age: v.optional(v.pipe(v.number(), v.integer(), v.minValue(0))),
role: v.picklist(['admin', 'user', 'guest']),
tags: v.array(v.string()),
bio: v.nullable(v.string()),
score: v.pipe(
v.number(),
v.transform((n) => Math.round(n))
),
});
type User = v.InferOutput<typeof UserSchema>;
import { z } from 'zod';
const UserSchema = z.object({
name: z.string().min(2).max(100),
email: z.string().email(),
age: z.number().int().min(0).optional(),
role: z.enum(['admin', 'user', 'guest']),
tags: z.array(z.string()),
bio: z.string().nullable(),
score: z.number().transform((n) => Math.round(n)),
});
type User = z.infer<typeof UserSchema>;
| Valibot | Zod | Notes |
|---|---|---|
v.object({...}) |
z.object({...}) |
Direct mapping |
v.string() |
z.string() |
Direct mapping |
v.number() |
z.number() |
Direct mapping |
v.boolean() |
z.boolean() |
Direct mapping |
v.pipe(v.string(), v.minLength(3)) |
z.string().min(3) |
Pipe unwrapped; actions become chained methods |
v.pipe(v.string(), v.email()) |
z.string().email() |
Pipe unwrapped; built-in validators chained |
v.optional(schema) |
schema.optional() |
Wrapper → chained method |
v.nullable(schema) |
schema.nullable() |
Wrapper → chained method |
v.picklist(['a', 'b']) |
z.enum(['a', 'b']) |
Direct mapping |
v.union([a, b]) |
z.union([a, b]) |
Direct mapping |
v.check(fn, msg) |
.refine(fn, msg) |
Custom validation; argument order matches |
v.transform(fn) |
.transform(fn) |
Inside pipe → chained method |
v.array(v.string()) |
z.array(z.string()) |
Direct mapping |
v.InferOutput<typeof S> |
z.infer<typeof S> |
Type helper rewritten automatically |
v.InferInput<typeof S> |
z.input<typeof S> |
Input type helper rewritten |
v.pipe() can contain multiple validation actions, transforms,
and custom checks in a single call. SchemaShift extracts the base type from the first argument
and chains remaining actions. Deeply nested pipes with conditional logic may need manual review.
v.variant('type', [...]) maps to z.discriminatedUnion('type', [...]),
but the inner schema structure differs. Each variant branch must be a full z.object()
containing the discriminator field. SchemaShift handles simple cases; complex variants get TODO comments.
v.optional(v.string())) while Zod chains methods
(z.string().optional()). This inverts the nesting direction. SchemaShift handles
this automatically, but deeply nested wrappers like
v.optional(v.nullable(v.pipe(...))) may produce verbose chains.
schemashift analyze --bundle valibot-to-zod to estimate the impact.
v.fallback(schema, defaultValue) provides a fallback on parse failure,
similar to Zod’s .catch(defaultValue). The wrapper-to-chain conversion applies here
as well. SchemaShift handles the mapping automatically.
Run the full migration with dry-run first to preview changes:
# Preview changes without modifying files
schemashift migrate ./src -f valibot -t zod --dry-run
# Run the actual migration
schemashift migrate ./src -f valibot -t zod
# Verbose with report
schemashift migrate ./src -f valibot -t zod -v --report html
# Canary migration: convert 20% of files first
schemashift migrate ./src -f valibot -t zod --canary 20
npm install zod and npm uninstall valibot (after verifying)TODO(schemashift) comments and resolve manuallyvalibotResolver → zodResolverv.pipe() unwrapping produced correct method chainsv.variant() conversions to z.discriminatedUnion()npx tsc --noEmit to verify TypeScript compilation
Valibot’s v.pipe(v.string(), v.minLength(3), v.email()) becomes
z.string().min(3).email() in Zod. SchemaShift automatically unwraps
pipe() calls, extracts the base type as the first argument, and chains
the remaining validation actions as Zod method calls.
Common reasons include ecosystem compatibility (tRPC, Drizzle, and many libraries have
first-class Zod support), team familiarity with Zod’s fluent API, needing
discriminatedUnion or branded types, or consolidating on a single schema
library across a large codebase.
Yes. Valibot uses wrapper functions like v.optional(v.string()) and
v.nullable(v.string()), while Zod uses chained methods like
z.string().optional() and z.string().nullable(). SchemaShift
automatically converts the wrapper-style syntax to Zod’s chain-style syntax.
Convert your Valibot schemas to Zod automatically with SchemaShift.
Get SchemaShift