Convert Zod to Yup

Migrate Zod schemas back to Yup for legacy codebase integration, team familiarity with Yup’s API, or ecosystem requirements where Yup is the established standard. SchemaShift handles the AST-based transformation automatically.

Quick Start

Install SchemaShift globally and run the migration with a single command:

npm install -g schemashift-cli
schemashift migrate ./src -f zod -t yup
Pro+ Required

Backward migrations (Zod → Yup) require a Pro or Team license. View pricing.

Before & After

Before (Zod)
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']),
  status: z.literal('active'),
  bio: z.string().nullable(),
});

type User = z.infer<typeof UserSchema>;
After (Yup)
import * as yup from 'yup';

const UserSchema = yup.object({
  name: yup.string().required().min(2).max(100),
  email: yup.string().required().email(),
  age: yup.number().integer().min(0).notRequired(),
  role: yup.mixed().oneOf(['admin', 'user', 'guest']).required(),
  status: yup.mixed().oneOf(['active']).required(),
  bio: yup.string().nullable().required(),
});

type User = yup.InferType<typeof UserSchema>;

Conversion Reference

Zod Yup Notes
z.object({...}) yup.object({...}) Direct mapping
z.string() yup.string().required() Zod is required by default; Yup needs explicit .required()
z.number() yup.number().required() Same required semantics difference
z.boolean() yup.boolean().required() Same required semantics difference
.optional() .notRequired() Yup uses .notRequired() instead of .optional()
.nullable() .nullable() Direct mapping
.refine(fn, msg) .test(name, msg, fn) Different argument order; Yup requires a test name
z.enum(['a', 'b']) yup.mixed().oneOf(['a', 'b']) Yup has no native enum; uses .oneOf()
z.literal('val') yup.mixed().oneOf(['val']) Expressed as single-value .oneOf()
z.union([a, b]) yup.mixed().oneOf([...]) Limited; works for literal unions only
z.array(z.string()) yup.array().of(yup.string()) Direct mapping with .of()
z.infer<typeof S> yup.InferType<typeof S> Type helper rewritten automatically

Edge Cases & Gotchas

Automated Migration

Run the full migration with dry-run first to preview changes:

# Preview changes without modifying files
schemashift migrate ./src -f zod -t yup --dry-run

# Run the actual migration
schemashift migrate ./src -f zod -t yup

# Export a diff for code review
schemashift migrate ./src -f zod -t yup --dry-run --output-diff changes.patch

# Verbose output with HTML report
schemashift migrate ./src -f zod -t yup -v --report html

Post-Migration Checklist

Frequently Asked Questions

Can I convert Zod schemas back to Yup automatically?

Yes. SchemaShift supports backward migration from Zod to Yup using AST-based transformations. Run schemashift migrate ./src -f zod -t yup to convert z.object, z.string, z.enum, .refine, and other Zod patterns to their Yup equivalents. This requires a Pro or Team license.

What Zod features cannot be automatically converted to Yup?

z.discriminatedUnion requires manual refactoring since Yup has no direct equivalent. z.record maps to yup.object with limited key typing. z.tuple converts to yup.array but loses per-element type safety. z.branded types have no Yup equivalent. These patterns receive TODO comments with guidance.

Does Yup auto-coerce values differently than Zod?

Yes. Yup auto-coerces by default (e.g., yup.number() will coerce "42" to 42), while Zod requires explicit z.coerce.number(). After migrating from Zod to Yup, your schemas may accept inputs that were previously rejected. SchemaShift warns about this behavioral difference during migration.

Related Guides

Ready to migrate?

Convert your Zod schemas to Yup automatically with SchemaShift.

Get SchemaShift