Zod has become the de facto standard for TypeScript schema validation thanks to its TypeScript-first design, superior type inference, and rapidly growing ecosystem. If your project uses Yup and you want better type safety, first-class TypeScript support, and compatibility with tools like tRPC, React Hook Form, and Next.js Server Actions, migrating to Zod is the natural next step.
Install SchemaShift and run the migration in two commands:
npm install -g schemashift-cli
# Migrate all Yup schemas to Zod
schemashift migrate ./src -f yup -t zod
Here is a typical Yup schema and its Zod equivalent after migration:
import * as yup from 'yup';
const userSchema = yup.object({
name: yup.string().required().min(2),
email: yup.string().email().required(),
age: yup.number().positive().integer(),
role: yup.mixed().oneOf(['admin', 'user']),
});
type User = yup.InferType<typeof userSchema>;
import { z } from 'zod';
const userSchema = z.object({
name: z.string().min(2),
email: z.string().email(),
age: z.number().positive().int(),
role: z.enum(['admin', 'user']),
});
type User = z.infer<typeof userSchema>;
Notice that Zod fields are required by default, so .required() is removed.
.integer() becomes .int(), and yup.mixed().oneOf()
becomes the more type-safe z.enum().
| Yup | Zod | Notes |
|---|---|---|
yup.string() |
z.string() |
Direct equivalent |
yup.number() |
z.number() |
No auto-coercion in Zod |
yup.boolean() |
z.boolean() |
Direct equivalent |
yup.date() |
z.date() |
Direct equivalent |
yup.mixed() |
z.unknown() |
Zod has no untyped mixed |
.required() |
(removed) | Zod fields are required by default |
.notRequired() |
.optional() |
Explicit opt-in for optional fields |
.nullable() |
.nullable() |
Direct equivalent |
.email() |
.email() |
Direct equivalent |
.url() |
.url() |
Direct equivalent |
.matches(regex) |
.regex(regex) |
Method name change |
.integer() |
.int() |
Shortened method name |
.positive() |
.positive() |
Direct equivalent |
.oneOf([...]) |
z.enum([...]) |
Type-safe enum in Zod |
.test(...) |
.refine(...) |
Arguments reordered (see gotchas) |
InferType<...> |
z.infer<...> |
Type helper migration |
yup.boolean().isTrue() |
z.literal(true) |
Boolean literal type |
yup.boolean().isFalse() |
z.literal(false) |
Boolean literal type |
"42" passes yup.number()). Zod does not. If your application
relies on this behavior, use z.coerce.number() instead of
z.number(). SchemaShift detects implicit coercion patterns and warns you.
.when() has no direct Zod
equivalent. SchemaShift analyzes the condition pattern and generates the appropriate Zod
replacement: z.discriminatedUnion() for simple discriminator patterns,
z.union() for either/or cases, or .superRefine() scaffolding
for complex conditions.
.optional() where Yup fields
lacked .required().
.test('name', 'message', fn) becomes
.refine(fn, 'message') in Zod. The arguments are reordered — the
validation function comes first, then the message. SchemaShift rewrites these
automatically.
.test() becomes
.refine(async ...) in Zod. Remember to switch from .parse()
to .parseAsync() at all call sites that use async validators, or validation
will throw.
SchemaShift provides several flags to control the migration process. Start with a dry run to preview changes before modifying any files:
# Dry run first
schemashift migrate ./src -f yup -t zod --dry-run
# Run migration with backup
schemashift migrate ./src -f yup -t zod
# With git branch isolation
schemashift migrate ./src -f yup -t zod --git-branch migrate/yup-to-zod
# Generate diff output
schemashift migrate ./src -f yup -t zod --dry-run --output-diff changes.diff
After running the automated migration, review these items to ensure everything works correctly:
TODO(schemashift) comments in migrated files.when() conditionals to .superRefine() or z.discriminatedUnion().test() validators and switch to .parseAsync()yupResolver → zodResolver)yup from package.json dependencies
Install SchemaShift CLI with npm install -g schemashift-cli, then run
schemashift migrate ./src -f yup -t zod. The tool uses AST-based
transformations to automatically convert Yup schemas to Zod equivalents, rewrite type
helpers like InferType to z.infer, and update form library
resolver imports. Always run with --dry-run first to preview changes.
Every migration creates a timestamped backup that you can restore with
schemashift rollback.
SchemaShift automatically converts all common Yup patterns: string, number, boolean,
date, array, and object schemas; validation methods like .email(),
.url(), .min(), .max(), .matches(),
.positive(), .integer(); .oneOf() to
z.enum(); .test() to .refine();
.nullable() and .optional();
InferType to z.infer; and boolean literals
.isTrue()/.isFalse() to z.literal(). It also
handles the required/optional field semantics differences between the two libraries.
Several patterns require manual review: Yup .when() conditionals have no
direct Zod equivalent and need conversion to z.discriminatedUnion() or
.superRefine(); Yup auto-coerces strings to numbers while Zod does not
(use z.coerce.number() if needed); async .test() validators
require switching call sites to .parseAsync(); and custom error message
formats differ between libraries. SchemaShift adds
TODO(schemashift) comments for all patterns requiring manual attention.
SchemaShift handles the tedious parts of migration so you can focus on reviewing edge cases. The Yup to Zod migration is available on the free tier.
npm install -g schemashift-cli
Need git integration or advanced analysis? View pricing.