Skip to main content

Command Palette

Search for a command to run...

Lessons Learned Migrating a Large Production Codebase from Zod v3 to v4

Updated
4 min read
Lessons Learned Migrating a Large Production Codebase from Zod v3 to v4

The Problem: Inconsistent Email Validation Across the Codebase

A frontend spike revealed that email validation logic between the backend and frontend was not consistent. The backend depended on a large Mailman regex with additional length checks, while the frontend validation was less restrictive.

Why We Chose to Upgrade to Zod v4

As part of improving consistency, maintainability, and centralizing validation logic across the repo, Zod v4 presented a strong opportunity to modernize how validation is handled across the repository.

The goal was to align frontend validation rules with backend requirements while also reducing technical debt. Migrating to standardized Zod-based validation allowed us to:

  • Prevent email addresses accepted on the frontend but rejected by the backend

  • Update the email validation regex on the frontend to match backend validation

  • Maintain consistent validation behavior across the app

  • Reduce manual regex maintenance and duplication

  • Centralize email address validation into a reusable shared schema

While the migration was initially motivated by email validation, it quickly became clear that upgrading Zod would provide long-term improvements in developer experience, type safety, and validation reliability.

Unseen Prerequisites That Blocked the Migration

Like many large-scale dependency upgrades, the Zod migration uncovered several prerequisite upgrades that needed to happen first.

When I initially took on on the the project, I discovered our TypeScript version was still on v4.9 while Zod v4 required TypeScript v5.5 or higher. This meant the migration could not happen without first upgrading TypeScript across the repository.

After upgrading TypeScript, additional compatibility issues surfaced with React Hook Form (RHF) and @hookform/resolvers, both required upgrades to support the newer ecosystem.

Adding a bit more complication, our Design System team maintained shared form components that depended on RHF. Their components were running on an older version than what Zod v4 needed to work with, which created a dependency mismatch that prevented a straightforward upgrade.

To resolve this, I collaborated with the Design System team by outlining:

  • Why upgrading RHF was necessary for Zod v4 compatibility

  • How mismatch impacted validation and form integrations

  • The importance of modernizing our ecosystem

Because these shared components were consumed across multiple applications, the upgrade required careful coordination and testing, which extended the migration timeline.

Once the Design System updates were completed and deployed, I was able to successfully install and integrate Zod v4 without additional compatibility issues.

Migration Challenges Inside the Codebase

Once Zod v4 was installed, I quickly realized how loosely validated Zod v3 was compared to v4. Running TypeScript after the upgrade surfaced 80+ type errors across the codebase. While this initially felt overwhelming, a few of the errors highlighted areas where validation logic had been loosely defined or relied on deprecated APIs that included simple updates such as:

  • Replacing message with error

  • Replacing invalid_type_error and required_error with error

  • Replacing import { schema } from “zod” from import { z } from “zod”

Zod v4 Runs a Stricter Program

Beyond syntax updates, Zod v4 introduced noticeably stricter type enforcement. In several files, schemas that compiled successfully now failed due to mismatches between expected and actual data shapes. The stricter checks surfaced validation issues such as:

  • Optional values being treated as required

  • Inconsistent handling of nullable vs optional fields

  • Incorrect union definitions

  • Even found a case where a value was spelled incorrectly but still passed in v3!

Zod v4 also refined how schemas are composed and extended. Several chained validation patterns that worked in v3 required restructuring to align with the new composition behavior.

In particular, complex validation scenarios involving:

  • .refine()

  • .superRefine()

  • Schema merging and extension

  • transform and coerce changed to type unknown

Resolver and Form Integration Updates

Because Zod was tightly integrated with React Hook Form, upgrading Zod required verifying compatibility with the latest resolver versions. Schema changes occasionally affected how form errors were mapped and surfaced in UI components.

In some cases, validation behavior subtly changed due to stricter typing or updated resolver expectations, requiring regression testing across form-heavy workflows.

Key Lesson

Large dependency migrations rarely fail because of the library upgrade itself. They expose inconsistencies, technical debt, and architectural drift that accumulate over time.

Zod v4 did exactly that for our codebase. While the migration required significant effort, it resulted in stronger type guarantees, more consistent validation behavior, and a cleaner foundation for future development.