Smithery Logo
MCPsSkillsDocsPricing
Login
Smithery Logo

Accelerating the Agent Economy

Resources

DocumentationPrivacy PolicySystem Status

Company

PricingAboutBlog

Connect

© 2026 Smithery. All rights reserved.

    djankies

    migrating-v3-to-v4

    djankies/migrating-v3-to-v4
    Coding

    About

    SKILL.md

    Install

    Install via Skills CLI

    or add to your agent
    • Claude Code
      Claude Code
    • Codex
      Codex
    • OpenClaw
      OpenClaw
    • Cursor
      Cursor
    • Amp
      Amp
    • GitHub Copilot
      GitHub Copilot
    • Gemini CLI
      Gemini CLI
    • Kilo Code
      Kilo Code
    • Junie
      Junie
    • Replit
      Replit
    • Windsurf
      Windsurf
    • Cline
      Cline
    • Continue
      Continue
    • OpenCode
      OpenCode
    • OpenHands
      OpenHands
    • Roo Code
      Roo Code
    • Augment
      Augment
    • Goose
      Goose
    • Trae
      Trae
    • Zencoder
      Zencoder
    • Antigravity
      Antigravity
    ├─
    ├─
    └─

    About

    Complete migration guide from Zod v3 to v4 covering all breaking changes and upgrade patterns

    SKILL.md

    Migrating to Zod v4

    Purpose

    Comprehensive guide for migrating existing Zod v3 codebases to v4, covering all breaking changes, migration patterns, and testing strategies.

    Migration Overview

    Zod v4 introduced major performance improvements and API refinements:

    • 100x reduction in TypeScript instantiations
    • 14x faster string parsing
    • 57% smaller bundle size
    • Simplified API surface with consistent patterns

    Breaking changes are intentional improvements that require code updates.

    Breaking Changes

    1. String Format Methods → Top-Level Functions

    Impact: Affects ~90% of Zod users using email/uuid/url validation

    Before (v3):

    const emailSchema = z.string().email();
    const uuidSchema = z.string().uuid();
    const datetimeSchema = z.string().datetime();
    const urlSchema = z.string().url();
    const ipSchema = z.string().ipv4();
    const jwtSchema = z.string().jwt();
    

    After (v4):

    const emailSchema = z.email();
    const uuidSchema = z.uuid();
    const datetimeSchema = z.iso.datetime();
    const urlSchema = z.url();
    const ipSchema = z.ipv4();
    const jwtSchema = z.jwt();
    

    Migration script:

    find ./src -name "*.ts" -o -name "*.tsx" | xargs sed -i '' \
      -e 's/z\.string()\.email()/z.email()/g' \
      -e 's/z\.string()\.uuid()/z.uuid()/g' \
      -e 's/z\.string()\.datetime()/z.iso.datetime()/g' \
      -e 's/z\.string()\.url()/z.url()/g' \
      -e 's/z\.string()\.ipv4()/z.ipv4()/g' \
      -e 's/z\.string()\.ipv6()/z.ipv6()/g' \
      -e 's/z\.string()\.jwt()/z.jwt()/g' \
      -e 's/z\.string()\.base64()/z.base64()/g'
    

    2. Error Customization → Unified error Parameter

    Impact: Affects error handling and user-facing validation messages

    Before (v3):

    z.string({ message: "Required field" });
    z.string({ invalid_type_error: "Must be a string" });
    z.string({ required_error: "This field is required" });
    z.object({}, { errorMap: customErrorMap });
    

    After (v4):

    z.string({ error: "Required field" });
    z.string({ error: "Must be a string" });
    z.string({ error: "This field is required" });
    z.object({}, { error: customErrorMap });
    

    Migration pattern: All error-related parameters now use single error field that accepts strings or error map functions.

    3. Schema Merge → Extend

    Impact: Affects schema composition patterns

    Before (v3):

    const baseSchema = z.object({ id: z.string() });
    const extendedSchema = baseSchema.merge(
      z.object({ name: z.string() })
    );
    

    After (v4):

    const baseSchema = z.object({ id: z.string() });
    const extendedSchema = baseSchema.extend({
      name: z.string()
    });
    

    Migration script:

    find ./src -name "*.ts" | xargs sed -i '' \
      -e 's/\.merge(z\.object(\([^)]*\)))/\.extend(\1)/g'
    

    4. Refinements → New Architecture

    Impact: Custom validation logic and error messages

    Before (v3):

    z.string().refine((val) => val.length > 5, {
      message: "Too short"
    });
    

    After (v4):

    z.string().refine((val) => val.length > 5, {
      error: "Too short"
    });
    

    Error customization in refinements also uses unified error parameter.

    5. String Transformations (New in v4)

    Not a breaking change, but highly recommended:

    Before (v3 pattern):

    const schema = z.string();
    const result = schema.parse(input.trim().toLowerCase());
    

    After (v4 recommended):

    const schema = z.string().trim().toLowerCase();
    const result = schema.parse(input);
    

    Benefits:

    • Declarative transformation pipeline
    • Type-safe and composable
    • Better error messages
    • Automatic type inference

    Migration Process

    Step 1: Upgrade Package

    npm install zod@^4.0.0
    

    Or with specific version:

    npm install zod@4.0.0
    

    Step 2: Run Compatibility Check

    Use validation skill to identify deprecated patterns:

    /review zod-compatibility
    

    Or manually scan:

    grep -r "z\.string()\.email(" ./src
    grep -r "z\.string()\.uuid(" ./src
    grep -r "\.merge(" ./src
    grep -r "message:" ./src | grep -v "error:"
    

    Step 3: Apply Automated Migrations

    Run migration scripts:

    ./migrate-string-formats.sh
    ./migrate-error-params.sh
    ./migrate-merge-to-extend.sh
    

    Step 4: Handle Manual Migrations

    Some patterns require manual review:

    Complex error maps:

    const customErrorMap: ZodErrorMap = (issue, ctx) => {
      if (issue.code === z.ZodIssueCode.invalid_type) {
        return { message: "Invalid type!" };
      }
      return { message: ctx.defaultError };
    };
    
    z.string({ errorMap: customErrorMap });
    

    Migration:

    const customErrorMap: ZodErrorMap = (issue, ctx) => {
      if (issue.code === z.ZodIssueCode.invalid_type) {
        return { message: "Invalid type!" };
      }
      return { message: ctx.defaultError };
    };
    
    z.string({ error: customErrorMap });
    

    Nested schema merges:

    const a = z.object({ x: z.string() });
    const b = z.object({ y: z.number() });
    const c = a.merge(b);
    

    Migration:

    const a = z.object({ x: z.string() });
    const b = z.object({ y: z.number() });
    const c = a.extend({ y: z.number() });
    

    Step 5: Add String Transformations

    Identify manual string operations and migrate to built-in methods:

    Before:

    const emailSchema = z.email();
    const processEmail = (input: string) => {
      const trimmed = input.trim().toLowerCase();
      return emailSchema.parse(trimmed);
    };
    

    After:

    const emailSchema = z.email().trim().toLowerCase();
    const processEmail = (input: string) => {
      return emailSchema.parse(input);
    };
    

    Step 6: Run Tests

    Comprehensive test suite after migration:

    npm test
    

    Check for:

    • Schema validation logic still works
    • Error messages display correctly
    • Type inference remains correct
    • No runtime errors from API changes

    Step 7: Update Documentation

    Update code comments and docs referencing Zod APIs:

    • Remove references to deprecated methods
    • Update examples to v4 patterns
    • Document new string transformation methods

    Common Migration Issues

    Issue 1: Type Errors After String Format Migration

    Problem:

    const emailSchema = z.string().email();
    type Email = z.infer<typeof emailSchema>;
    

    After migration:

    const emailSchema = z.email();
    type Email = z.infer<typeof emailSchema>;
    

    Solution: Type inference still works, but type is now more specific to email strings.

    Issue 2: Custom Error Maps Not Working

    Problem: Error map using old parameter names

    Solution: Update error map to use unified error parameter and ensure function signature matches ZodErrorMap type.

    Issue 3: Merge Breaking Complex Compositions

    Problem: Nested merges don't translate directly to extend

    Solution: Use multiple extend calls or restructure schema:

    const result = base.extend(ext1.shape).extend(ext2.shape);
    

    Issue 4: Tests Fail with Different Error Messages

    Problem: v4 error messages may differ from v3

    Solution: Update test assertions to match new error format or use error codes instead of messages:

    expect(result.error.issues[0].code).toBe(z.ZodIssueCode.invalid_type);
    

    Testing Strategy

    Unit Tests

    Test schema validation logic:

    import { z } from 'zod';
    
    describe('User schema validation', () => {
      const userSchema = z.object({
        email: z.email().trim().toLowerCase(),
        username: z.string().trim().min(3)
      });
    
      it('validates correct user data', () => {
        const result = userSchema.safeParse({
          email: '  USER@EXAMPLE.COM  ',
          username: '  john  '
        });
    
        expect(result.success).toBe(true);
        if (result.success) {
          expect(result.data.email).toBe('user@example.com');
          expect(result.data.username).toBe('john');
        }
      });
    
      it('rejects invalid email', () => {
        const result = userSchema.safeParse({
          email: 'not-an-email',
          username: 'john'
        });
    
        expect(result.success).toBe(false);
      });
    });
    

    Integration Tests

    Test form validation with transformed data:

    const formSchema = z.object({
      email: z.email().trim().toLowerCase(),
      password: z.string().min(8)
    });
    
    const handleSubmit = async (formData: FormData) => {
      const result = formSchema.safeParse({
        email: formData.get('email'),
        password: formData.get('password')
      });
    
      if (!result.success) {
        return { errors: result.error.flatten() };
      }
    
      await createUser(result.data);
    };
    

    Type Tests

    Verify type inference works correctly:

    const schema = z.email().trim();
    type Email = z.infer<typeof schema>;
    
    const email: Email = 'test@example.com';
    

    Migration Checklist

    • Upgrade Zod package to v4
    • Run compatibility validation
    • Migrate string format methods to top-level functions
    • Update error customization to use error parameter
    • Replace .merge() with .extend()
    • Add string transformations where applicable
    • Update error maps and refinements
    • Run full test suite
    • Update documentation and examples
    • Review type inference correctness
    • Test error handling in production-like scenarios
    • Update CI/CD pipelines if needed

    Performance Gains

    After migration, expect:

    • Faster TypeScript compilation - 100x reduction in type instantiations
    • Faster runtime parsing - 14x improvement for string validation
    • Smaller bundle size - 57% reduction
    • Better error messages - Clearer validation feedback

    Monitor performance improvements:

    npm run build -- --stats
    

    Compare bundle size before/after migration.

    References

    • Validation skill: Use the validating-schema-basics skill from the zod-4 plugin
    • v4 Features: Use the validating-string-formats skill from the zod-4 plugin
    • Error handling: Use the customizing-errors skill from the zod-4 plugin

    Success Criteria

    • ✅ All v3 deprecated APIs replaced with v4 equivalents
    • ✅ Tests pass with 100% success rate
    • ✅ No TypeScript compilation errors
    • ✅ Error messages display correctly in UI
    • ✅ Type inference works as expected
    • ✅ Performance improvements measurable
    • ✅ Documentation updated to reflect v4 patterns
    Repository
    djankies/claude-configs
    Files