Last Updated: 3/12/2026
Transforms
Note — For bi-directional transforms, use codecs .
Transforms are a special kind of schema that perform a unidirectional transformation. Instead of validating input, they accept anything and perform some transformation on the data.
Basic Transforms
To define a transform:
import * as z from "zod";
const castToString = z.transform((val) => String(val));
castToString.parse("asdf"); // => "asdf"
castToString.parse(123); // => "123"
castToString.parse(true); // => "true"Important: Transform functions should never throw. Thrown errors are not caught by Zod.
Validation Inside Transforms
To perform validation logic inside a transform, use ctx. To report a validation issue, push a new issue onto ctx.issues:
const coercedInt = z.transform((val, ctx) => {
try {
const parsed = Number.parseInt(String(val));
return parsed;
} catch (e) {
ctx.issues.push({
code: "custom",
message: "Not a number",
input: val,
});
// this is a special constant with type `never`
// returning it lets you exit the transform without impacting the inferred return type
return z.NEVER;
}
});Using with Pipes
Most commonly, transforms are used in conjunction with Pipes . This combination is useful for performing some initial validation, then transforming the parsed data into another form.
const stringToLength = z.string().pipe(z.transform(val => val.length));
stringToLength.parse("hello"); // => 5The .transform() Method
Piping some schema into a transform is a common pattern, so Zod provides a convenience .transform() method:
const stringToLength = z.string().transform(val => val.length);
stringToLength.parse("hello"); // => 5
stringToLength.parse(123); // ❌ throws (not a string)Async Transforms
Transforms can also be async:
const idToUser = z
.string()
.transform(async (id) => {
// fetch user from database
return db.getUserById(id);
});
const user = await idToUser.parseAsync("abc123");Important: If you use async transforms, you must use .parseAsync() or .safeParseAsync() when parsing data! Otherwise Zod will throw an error.
Type Inference
Transforms change the output type of a schema:
const schema = z.string().transform(val => val.length);
type Input = z.input<typeof schema>; // string
type Output = z.output<typeof schema>; // numberCommon Use Cases
String Trimming
const trimmedString = z.string().transform(val => val.trim());Case Normalization
const lowercase = z.string().transform(val => val.toLowerCase());
const uppercase = z.string().transform(val => val.toUpperCase());Parsing JSON
const jsonString = z.string().transform((str, ctx) => {
try {
return JSON.parse(str);
} catch (e) {
ctx.issues.push({
code: "custom",
message: "Invalid JSON",
input: str,
});
return z.NEVER;
}
});Date String to Date Object
const stringToDate = z.string()
.datetime()
.transform(val => new Date(val));
stringToDate.parse("2020-01-01T00:00:00Z"); // => Date objectComputing Derived Values
const userWithAge = z.object({
birthdate: z.date(),
name: z.string()
}).transform(data => ({
...data,
age: new Date().getFullYear() - data.birthdate.getFullYear()
}));Preprocessing
Piping a transform into another schema is another common pattern, so Zod provides a convenience z.preprocess() function:
const coercedInt = z.preprocess((val) => {
if (typeof val === "string") {
return Number.parseInt(val);
}
return val;
}, z.int());
coercedInt.parse("42"); // => 42
coercedInt.parse(42); // => 42Transforms vs Codecs
Transforms
- Unidirectional (input → output only)
- Cannot be reversed
- Simpler API
const transform = z.string().transform(val => val.length);
// Can only go string → numberCodecs
- Bidirectional (can encode and decode)
- Reversible
- More powerful but more complex
const codec = z.codec(
z.iso.datetime(),
z.date(),
{
decode: (str) => new Date(str),
encode: (date) => date.toISOString()
}
);
// Can go both ways: string ⇄ DateBest Practices
- Never throw inside transform functions
- Use
ctx.issues.push()for validation errors - Return
z.NEVERto exit early on errors - Use
.parseAsync()with async transforms - Consider codecs if you need bidirectional transforms
- Chain transforms with
.pipe()for complex transformations - Use
z.preprocess()for input normalization before validation