Migration guide
This is a migration guide to walk you through the process of upgrading to Zod 3.
- If you're upgrading directly from Zod 1, you should read through the list of features + changes in both Zod 2 and Zod 3.
- If you're upgrading from Zod 2 -> 3, you can skip to the
Upgrading from Zod 1 → Zod 2
Zod 2 is being retired and will not leave beta. This is due to some unintuitive ramifications of the transformers API: details here.
New features
- Transformers! These let you provide default values, do casting/coercion, and a lot more. Read more here: Transformers
- Asynchronous refinements and new .parseAsync and .safeParseAsync methods. Read more here: Refinements
- Modify unknown key behavior for object schemas:
.strip()
(the default),.passthrough()
, and.strict()
- New .catchall() method for object schemas: catchall
Breaking changes
- Object schemas now strip unknown keys by default.
- Schema parsing now returns a deep clone of the data you pass in (instead of the exact value you pass in)
- Relatedly, Zod no longer supports cyclical data. Recursive schemas are still supported, but Zod can't properly parse nested objects that contain cycles.
- Optional and nullable schemas are now represented with the dedicated ZodOptional and ZodNullable classes, instead of using ZodUnion.
Upgrading from Zod 2 → Zod 3
New features
- You can now import Zod like
import { z } from 'zod';
instead of usingimport * as
syntax. - Structured error messages. Use the
.format()
method to ZodError to convert the error into a strongly-typed, nested object: format method - Easier unions. Use the
or
method to ZodType (the base class for all Zod schemas) to easily create union types likez.string().or(z.number())
- Easier intersections. Use the
and
method to ZodType (the base class for all Zod schemas) to easily create intersection types - Global error customization. Use
z.setErrorMap(myErrorMap)
to globally customize the error messages produced by Zod: setErrorMap - Maps and sets. Zod now supports
Map
andSet
schemas. - Optional and nullable unwrapping. ZodOptional and ZodNullable now have a
.unwrap()
method for retrieving the schema they wrap. - A new implementation of transformers. Details below.
Breaking changes
The minimum TypeScript version is now 4.1 (up from 3.7 for Zod 2). Several features have been rewritten to use recursive conditional types, an incredibly powerful new feature introduced in TS4.1.
Transformers syntax. Previously, creating a transformer required an input schema, an output schema, and a function to transform between them. You created transformers like
z.transform(A, B, func)
, whereA
andB
are Zod schemas. This is no longer the case. Accordingly:The old syntax is no longer available:
ts# not available z.transformer(A, B, func); A.transform(B, func)
# not available z.transformer(A, B, func); A.transform(B, func)
Instead, apply transformations by simply using the
.transform()
method that exists on all Zod schemas.tsz.string().transform((val) => val.length);
z.string().transform((val) => val.length);
Under the hood, all refinements and transformations are executed inside a dedicated "ZodEffects" class. Post-parsing, ZodEffects passes the data through a chain of refinements and transformations, then returns the final value. As such, you can now interleave transformations and refinements. For instance:
tsconst test = z .string() .transform((val) => val.length) .refine((val) => val > 5, { message: "Input is too short" }) .transform((val) => val * 2); test.parse("12characters"); // => 24
const test = z .string() .transform((val) => val.length) .refine((val) => val > 5, { message: "Input is too short" }) .transform((val) => val * 2); test.parse("12characters"); // => 24
Type guards (the
.check()
method) have been removed. Type guards interact with transformers in unintuitive ways so they were removed. Use.safeParse
instead.Object merging now behaves differently. If you merge two object schema (
A.merge(B)
), the fields of B will overwrite the fields of A if there are shared keys. This is how the.extend
method already works. If you're looking to create an intersection of the two types, usez.intersection(A, B)
or use the new.and
method (A.and(B)
).Default values: default value logic is now implemented inside a
ZodDefault
class, instead of using transformers. (In a previous alpha version of Zod 3, default values were implemented inside the ZodOptional class.)There have been small internal changes to the ZodIssue subtypes. See the new subtypes in the Error Handling guide. This may impact user who have written a custom error maps. Most users will not be affected.