Skip to Content

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"); // => 5

The .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>; // number

Common 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 object

Computing 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); // => 42

Transforms vs Codecs

Transforms

  • Unidirectional (input → output only)
  • Cannot be reversed
  • Simpler API
const transform = z.string().transform(val => val.length); // Can only go string → number

Codecs

  • 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 ⇄ Date

Best Practices

  1. Never throw inside transform functions
  2. Use ctx.issues.push() for validation errors
  3. Return z.NEVER to exit early on errors
  4. Use .parseAsync() with async transforms
  5. Consider codecs if you need bidirectional transforms
  6. Chain transforms with .pipe() for complex transformations
  7. Use z.preprocess() for input normalization before validation