Zod Transform: Modifying and Reshaping Validated Data


Zod Transform allows you to modify or reshape validated data into a new form using the .transform() method after successful parsing.

Zod’s transform solves real-world problems where you need to clean, format, or reshape user input immediately after validation — for example:

  1. Data normalization – Convert "123" (string) to 123 (number) or "TRUE" to true.
  2. Date parsing – Transform a date string like "2025-10-10" into a Date object.
  3. API response shaping – Reformat validated data to match your backend or frontend structure.
  4. Sensitive data masking – Hide or modify fields (like masking emails) before saving or returning.
  5. Type conversions – Automatically convert form or query string values to the correct types.

In short, it helps bridge the gap between raw input and usable data, making schemas both validators and transformers.

Try It Yourself: Code Example

const userSchema = z.object({
  name: z.string().transform((v) => v.trim()),
  email: z.string().transform((v) => v.toLowerCase()),
});

Edge Cases in Zod Transform

String Transformations:

In many cases, you might want to clean up user input by trimming extra spaces. Zod makes this easy with z.string().transform(val => val.trim()). For example, " hello " becomes "hello", ensuring your strings are always clean before further processing.

You can convert strings to numbers using z.string().transform(val => { const n = Number(val); if (isNaN(n)) throw new Error("Not a number"); return n; }). This converts "42" into 42 and "3.14" into 3.14. Any invalid number string, like "abc", will throw an error, helping you catch incorrect input early.

Sometimes a string represents a boolean value. Using z.string().transform(val => val.toLowerCase() === "true"), "true" becomes true, "false" becomes false, and any other value also evaluates to false. This ensures consistent boolean representation.

If you need to parse a date from a string, Zod allows z.string().transform(val => { const d = new Date(val); if (isNaN(d.getTime())) throw new Error("Invalid date"); return d; }). For instance, "2025-10-11" converts into a Date object, while invalid strings like "invalid" will throw an error.

To split a comma-separated string into an array, use z.string().transform(val => val.split(",").map(v => v.trim())). "a,b,c" becomes ["a","b","c"]. Even empty strings result in [""], allowing predictable array handling.

If a string should match a specific set of values, you can use enums. For example: z.string().transform(val => { if (!Colors.options.includes(val)) throw new Error("Invalid color"); return val; }). "red" passes successfully, while "yellow" throws an error, enforcing strict value control.

If you specifically need an integer, use
z.string().transform(val => { const n = parseInt(val); if (isNaN(n)) throw new Error("Not an integer"); return n; })
"42" becomes 42, "3.14" becomes 3, and "abc" throws an error.

Finally, you can handle empty strings gracefully:

  • To convert empty strings to null: z.string().transform(val => val === "" ? null : val)
  • To convert empty strings to undefined: z.string().transform(val => val === "" ? undefined : val)
    This is useful for optional fields where empty input shouldn’t break your schema.

Number Transformations:

Sometimes you need to convert a number into a string, for example when storing numeric IDs or formatting output. Zod makes this easy with z.number().transform(val => String(val)).
For instance, 42 becomes "42" and 3.14 becomes "3.14". This ensures that numeric values are safely converted to strings, and you can use them wherever a string is required, like in text fields or API payloads.

Array Transformations:

There are cases where you want to serialize an array into a string, for instance to store it in a database or send in an API request. Zod supports this with z.array(z.any()).transform(arr => arr.join(",")).
For example, ["apple","banana","cherry"] becomes "apple,banana,cherry". You can also customize the separator if needed, making it flexible for different formats.

Sometimes you have an array of key-value pairs or structured data that you want to convert into an object. With Zod, you can use z.array(z.tuple([z.string(), z.any()])).transform(arr => Object.fromEntries(arr)).
For example, [["name","Alice"], ["age",25]] becomes { name: "Alice", age: 25 }. This is especially useful when working with APIs or forms that return data as arrays but your application expects an object structure.

Object Transformations:

Sometimes you need to convert an object into a string, for example for logging or sending it over a network. Zod can handle this with z.object({name: z.string(), age: z.number()}).transform(obj => JSON.stringify(obj)).
For instance, { name: "Alice", age: 25 } becomes '{"name":"Alice","age":25}'. This ensures objects are safely serialized for storage or transmission.

You may want to modify or standardize object keys. Using z.object({firstName: z.string(), lastName: z.string()}).transform(obj => Object.fromEntries(Object.entries(obj).map(([k,v]) => [k.toLowerCase(), v]))), the keys firstName and lastName are converted to firstname and lastname. This is helpful when normalizing data from different sources.

If you need to store or transmit validated objects, you can convert them to JSON using z.object({a: z.number(), b: z.string()}).transform(obj => JSON.stringify(obj)). For instance, { a: 1, b: "test" } becomes '{"a":1,"b":"test"}'. This is commonly used when sending data to APIs or local storage.

Date Transformations:

To convert a JavaScript Date object into a string for display, storage, or API requests. Zod can handle this with
z.date().transform(date => date.toISOString())
For example, new Date("2025-10-11") becomes "2025-10-11T00:00:00.000Z". This ensures that date values are serialized in a consistent string format, making them easy to store or transmit.

Miscellaneous Transformations:

Sometimes you want to normalize text to uppercase, for instance to standardize user input. Zod can do this with
z.string().transform(val => val.toUpperCase())
For example, "hello world" becomes "HELLO WORLD". This ensures consistent casing across your application.

If you need to convert keys or strings from camelCase to snake_case, Zod can handle it with
z.string().transform(val => val.replace(/[A-Z]/g, letter => '_' + letter.toLowerCase()))
For instance, "firstName" becomes "first_name". This is useful for API requests or database fields that require snake_case formatting.

Error Handling in Zod Transform

const castToNumber = z.any().transform((val) => {
  if (isNaN(Number(val))) throw new Error('Not a number');
  return Number(val);
});
  • Number(val) converts input to a number.
  • isNaN(Number(val)) checks if the conversion failed.
  • throw new Error('Not a number') stops parsing and signals an invalid input.
  • If valid, return Number(val) gives the transformed number.

Also See

If you’re working with Zod transforms, you might also find the Zod Coerce guide helpful. Zod Coerce lets you automatically convert values to the desired type before validation, such as turning strings into numbers or booleans.

Summary

Zod transforms allow developers to modify or reshape validated data immediately after parsing, making schemas both validators and transformers. With .transform(), you can normalize strings, convert types, parse dates, reshape arrays or objects, and handle edge cases like empty input or invalid formats. Error handling ensures that invalid data triggers informative messages before reaching your application logic.

By leveraging Zod transforms, you can clean, format, and standardize data, bridge the gap between raw input and usable structures, and maintain consistent, safe data across forms, APIs, and databases. This makes Zod an essential tool for type-safe, predictable, and maintainable data handling in modern JavaScript and TypeScript projects.

Sharukhan Avatar

Sharukhan Patan

Sharukhan Patan is the founder of Tecktol. He has worked as a software engineer specializing in full-stack web development.

Popular Posts