Guides and concepts
Type inference
You can extract the TypeScript type of any schema with z.infer<typeof mySchema>
.
const A = z.string();
type A = z.infer<typeof A>; // string
const u: A = 12; // TypeError
const u: A = "asdf"; // compiles
What about transforms?
In reality each Zod schema internally tracks two types: an input and an output. For most schemas (e.g. z.string()
) these two are the same. But once you add transforms into the mix, these two values can diverge. For instance z.string().transform(val => val.length)
has an input of string
and an output of number
.
You can separately extract the input and output types like so:
const stringToNumber = z.string().transform(val => val.length);
// ⚠️ Important: z.infer returns the OUTPUT type!
type input = z.input<typeof stringToNumber>; // string
type output = z.output<typeof stringToNumber>; // number
// equivalent to z.output!
type inferred = z.infer<typeof stringToNumber>; // number
Writing generic functions
With TypeScript generics, you can write reusable functions that accept Zod schemas as parameters. This enables you to create custom validation logic, schema transformations, and more, while maintaining type safety and inference.
When attempting to write a function that accepts a Zod schema as an input, it's tempting to try something like this:
function inferSchema<T>(schema: z.ZodType<T>) {
return schema;
}
This approach is incorrect, and limits TypeScript's ability to properly infer the argument. No matter what you pass in, the type of schema
will be an instance of ZodType
.
inferSchema(z.string());
// => ZodType<string>
This approach loses type information, namely which subclass the input actually is (in this case, ZodString
). That means you can't call any string-specific methods like .min()
on the result of inferSchema
.
A better approach is to infer the schema as a whole instead of merely its inferred type. You can do this with a utility type called z.ZodTypeAny
.
function inferSchema<T extends z.ZodTypeAny>(schema: T) {
return schema;
}
inferSchema(z.string());
// => ZodString
ZodTypeAny
is just a shorthand forZodType<any, any, any>
, a type that is broad enough to match any Zod schema.
The Result is now fully and properly typed, and the type system can infer the specific subclass of the schema.
Inferring the inferred type
If you follow the best practice of using z.ZodTypeAny
as the generic parameter for your schema, you may encounter issues with the parsed data being typed as any
instead of the inferred type of the schema.
function parseData<T extends z.ZodTypeAny>(data: unknown, schema: T) {
return schema.parse(data);
}
parseData("sup", z.string());
// => any
Due to how TypeScript inference works, it is treating schema
like a ZodTypeAny
instead of the inferred type. You can fix this with a type cast using z.infer
.
function parseData<T extends z.ZodTypeAny>(data: unknown, schema: T) {
return schema.parse(data) as z.infer<T>;
// ^^^^^^^^^^^^^^ <- add this
}
parseData("sup", z.string());
// => string
Constraining allowable inputs
The ZodType
class has three generic parameters.
class ZodType<
Output = any,
Def extends ZodTypeDef = ZodTypeDef,
Input = Output
> { ... }
By constraining these in your generic input, you can limit what schemas are allowable as inputs to your function:
function makeSchemaOptional<T extends z.ZodType<string>>(schema: T) {
return schema.optional();
}
makeSchemaOptional(z.string());
// works fine
makeSchemaOptional(z.number());
// Error: 'ZodNumber' is not assignable to parameter of type 'ZodType<string, ZodTypeDef, string>'
Error handling
Zod provides a subclass of Error called ZodError
. ZodErrors contain an issues
array containing detailed information about the validation problems.
const result = z
.object({
name: z.string()
})
.safeParse({ name: 12 });
if (!result.success) {
result.error.issues;
/* [
{
"code": "invalid_type",
"expected": "string",
"received": "number",
"path": [ "name" ],
"message": "Expected string, received number"
}
] */
}
For detailed information about the possible error codes and how to customize error messages, check out the dedicated error handling guide: ERROR_HANDLING
Zod's error reporting emphasizes completeness and correctness. If you are looking to present a useful error message to the end user, you should either override Zod's error messages using an error map (described in detail in the Error Handling guide) or use a third-party library like zod-validation-error
Error formatting
You can use the .format()
method to convert this error into a nested object.
const result = z
.object({
name: z.string()
})
.safeParse({ name: 12 });
if (!result.success) {
const formatted = result.error.format();
/* {
name: { _errors: [ 'Expected string, received number' ] }
} */
formatted.name?._errors;
// => ["Expected string, received number"]
}