Documentation Index
Fetch the complete documentation index at: https://mintlify.com/colinhacks/zod/llms.txt
Use this file to discover all available pages before exploring further.
Async Parsing
Zod supports asynchronous validation for schemas with async refinements or transforms.
Basic Async Parsing
Use .parseAsync() or .safeParseAsync() for async validation:
import * as z from 'zod';
const stringSchema = z.string();
const goodData = "XXX";
const goodResult = await stringSchema.safeParseAsync(goodData);
if (goodResult.success) {
console.log(goodResult.data); // "XXX"
}
const badData = 12;
const badResult = await stringSchema.safeParseAsync(badData);
if (!badResult.success) {
console.log(badResult.error); // ZodError
}
Sync vs Async Parse
const schema = z.string();
// Synchronous - throws immediately
try {
schema.parse(123);
} catch (error) {
console.log(error); // ZodError
}
// Asynchronous - returns Promise
const result = await schema.parseAsync(123).catch((error) => {
console.log(error); // ZodError
});
If a schema contains async refinements, calling .parse() or .safeParse() will throw an error. You must use .parseAsync() or .safeParseAsync().
Async Refinements
Basic Async Refinement
Refinements can be async functions:
const schema = z.string().refine(async (_val) => {
// Async validation logic
return true;
});
const result = await schema.parseAsync("asdf");
console.log(result); // "asdf"
Async Refinement with Promises
const schema1 = z.string().refine((_val) => Promise.resolve(true));
const v1 = await schema1.parseAsync("asdf");
// Success: "asdf"
const schema2 = z.string().refine((_val) => Promise.resolve(false));
await schema2.parseAsync("asdf"); // Throws ZodError
Value-Based Async Validation
const schema = z.string().refine(async (val) => {
// Access the value being validated
return val.length > 5;
});
const r1 = await schema.safeParseAsync("asdf");
console.log(r1.success); // false
const r2 = await schema.safeParseAsync("asdf123");
console.log(r2.success); // true
console.log(r2.data); // "asdf123"
Real-World Example: Database Validation
const userSchema = z.object({
email: z.string().email().refine(
async (email) => {
// Check if email already exists in database
const existing = await db.users.findOne({ email });
return !existing;
},
{ message: "Email already registered" }
),
username: z.string().min(3).refine(
async (username) => {
// Validate username availability
const taken = await db.users.findOne({ username });
return !taken;
},
{ message: "Username already taken" }
)
});
const result = await userSchema.safeParseAsync({
email: "test@example.com",
username: "john"
});
Async Error Behavior
Sync Parse Throws on Async Refinements
const s1 = z.string().refine(async (_val) => true);
// This throws an error!
try {
s1.safeParse("asdf");
} catch (error) {
// Error: Cannot use sync parse on schema with async refinements
}
Multiple Async Errors
Async validation collects all errors, just like synchronous validation:
const base = z.object({
hello: z.string(),
foo: z.number()
});
const testval = { hello: 3, foo: "hello" };
// Synchronous
const result1 = base.safeParse(testval);
console.log(result1.error!.issues.length); // 2
// Asynchronous - same number of errors
const result2 = await base.safeParseAsync(testval);
console.log(result2.error!.issues.length); // 2
Non-Empty String Validation
const base = z.object({
hello: z.string().refine((x) => x && x.length > 0),
foo: z.string().refine((x) => x && x.length > 0)
});
const testval = { hello: "", foo: "" };
const r1 = base.safeParse(testval);
const r2 = await base.safeParseAsync(testval);
// Both have the same number of issues
console.log(r1.error!.issues.length === r2.error!.issues.length); // true
Async Refinement Execution
Early Termination
Async refinements stop executing after the first failure:
let count = 0;
const schema = z.object({
hello: z.string(),
foo: z.number()
.refine(async () => {
count++;
return true;
})
.refine(async () => {
count++;
return true;
}, "Good")
});
const testval = { hello: "bye", foo: 3 };
const result = await schema.safeParseAsync(testval);
if (!result.success) {
console.log(result.error.issues.length); // 1
console.log(count); // 1 (second refinement didn't run)
}
Mixed Sync and Async Validation
const schema = z.object({
hello: z.string(),
foo: z.object({
bar: z.number().refine(
async () => new Promise((resolve) => {
setTimeout(() => resolve(false), 500);
})
)
})
});
const testval = { hello: 3, foo: { bar: 4 } };
// Sync validation
const result1 = schema.safeParse(testval); // Throws
// Async validation - collects all errors
const result2 = await schema.safeParseAsync(testval);
console.log(result2.error!.issues.length); // Multiple issues
Promise Schemas
Validate Promise values:
const promiseSchema = z.promise(z.number());
// Valid: Promise resolves to number
const goodData = Promise.resolve(123);
const goodResult = await promiseSchema.safeParseAsync(goodData);
if (goodResult.success) {
console.log(goodResult.data); // 123 (unwrapped)
console.log(typeof goodResult.data); // "number"
}
// Invalid: Promise resolves to wrong type
const badData = Promise.resolve("XXX");
const badResult = await promiseSchema.safeParseAsync(badData);
if (!badResult.success) {
console.log(badResult.error); // ZodError
}
While not shown in the test files, async transforms work similarly to async refinements:
const schema = z.string().transform(async (val) => {
// Async transformation
const result = await fetchData(val);
return result;
});
const data = await schema.parseAsync("input");
All Schema Types Support Async
Async parsing works with all Zod schema types:
// String
await z.string().safeParseAsync("XXX");
// Number
await z.number().safeParseAsync(1234.2353);
// BigInt
await z.bigint().safeParseAsync(BigInt(145));
// Boolean
await z.boolean().safeParseAsync(true);
// Date
await z.date().safeParseAsync(new Date());
// Array
await z.array(z.string()).safeParseAsync(["XXX"]);
// Object
await z.object({ string: z.string() }).safeParseAsync({ string: "XXX" });
// Union
await z.union([z.string(), z.undefined()]).safeParseAsync(undefined);
// Record
await z.record(z.string(), z.object({})).safeParseAsync({ a: {}, b: {} });
// Literal
await z.literal("asdf").safeParseAsync("asdf");
// Enum
await z.enum(["fish", "whale"]).safeParseAsync("whale");
// Promise
await z.promise(z.number()).safeParseAsync(Promise.resolve(123));
Best Practices
- Always use async methods - Use
.parseAsync() or .safeParseAsync() with async refinements
- Optimize async operations - Async refinements run sequentially; minimize API calls
- Handle errors gracefully - Use
.safeParseAsync() to avoid unhandled promise rejections
- Avoid mixing parse types - Don’t call
.parse() on schemas with async refinements
- Consider performance - Async validation is slower; use sync validation when possible
- Test both paths - Verify both success and failure cases in async validation
Async refinements are perfect for validating against external data sources like databases, APIs, or file systems.