Zod safeParse vs parse: Which to Use and When
Table of Contents
Zod gives you two methods to validate data: parse() throws a ZodError if validation fails, while safeParse() returns a result object with { success: true, data } or { success: false, error }. Use safeParse() in most application code — it avoids try/catch blocks and gives you structured error handling. Use parse() when you want the function to throw and let the caller handle exceptions.
This page covers both methods with code examples and the exact situations where each fits.
parse(): Throws on Failure
parse() returns the validated data if successful, or throws a ZodError if not:
import { z } from 'zod';
const UserSchema = z.object({
id: z.number(),
name: z.string(),
email: z.string().email()
});
// Success case
const user = UserSchema.parse({ id: 1, name: 'Alice', email: '[email protected]' });
// user is typed as { id: number; name: string; email: string }
// Failure case — throws ZodError
try {
const user = UserSchema.parse({ id: 'not-a-number', name: 'Alice' });
} catch (err) {
if (err instanceof ZodError) {
console.log(err.issues); // array of validation issues
}
}
The ZodError contains an issues array with one entry per failed field, each with a path, message, and code.
safeParse(): Returns a Result Object
safeParse() never throws — it wraps the result in a discriminated union:
const result = UserSchema.safeParse(data);
if (result.success) {
// result.data is typed as the schema type
console.log(result.data.name);
} else {
// result.error is a ZodError
result.error.issues.forEach(issue => {
console.log(issue.path.join('.'), issue.message);
});
}
The result.success check is a TypeScript discriminant — after the if branch, TypeScript knows exactly what result contains. No type narrowing needed.
When to Use Each
| Situation | Use |
|---|---|
| API route handler — return 400 on invalid input | safeParse() |
| Service function — caller handles errors | parse() |
| Express/Fastify middleware | safeParse() |
| tRPC procedure input | tRPC calls parse() internally |
| React Hook Form (via zodResolver) | zodResolver calls safeParse() internally |
| Validating config at app startup | parse() — crash fast if config is wrong |
| Validating user-submitted data | safeParse() — return errors to user |
| Unit tests asserting valid data | parse() — test failure shows the ZodError |
Working with safeParse Error Output
Three ways to use the error from a failed safeParse():
1. issues array — flat list of all errors:
result.error.issues
// [ { path: ['email'], message: 'Invalid email', code: 'invalid_string' } ]
2. flatten().fieldErrors — object keyed by field name (best for forms):
result.error.flatten().fieldErrors
// { email: ['Invalid email'], id: ['Expected number, received string'] }
3. flatten().formErrors — top-level errors not tied to a specific field:
result.error.flatten().formErrors
// ['Passwords do not match'] (from .refine() on the whole object)
For API responses, return flatten().fieldErrors. For logging, use issues directly. To generate a starting schema for what you're validating, use the JSON to Zod converter.
Try It Free — No Signup Required
Runs 100% in your browser. No data is collected, stored, or sent anywhere.
Open Free JSON to Zod ConverterFrequently Asked Questions
What is the difference between Zod parse and safeParse?
parse() throws a ZodError if validation fails. safeParse() returns { success: true, data } or { success: false, error } without throwing. Use safeParse() in most application code to avoid try/catch blocks.
When should I use parse() instead of safeParse()?
Use parse() when you want the error to propagate as an exception — for example, validating app config at startup (crash fast), in service functions where the caller handles errors, or in unit tests where you want the ZodError to surface as a test failure.
How do I get field-level error messages from safeParse?
Call result.error.flatten().fieldErrors on the error from a failed safeParse. It returns an object keyed by field name, with an array of error messages for each field. Ideal for returning validation errors to client-side forms.
Does safeParse return undefined if validation fails?
No. safeParse always returns an object. On failure: { success: false, error: ZodError }. The data property does not exist on the failure case. On success: { success: true, data: T }. The error property does not exist on the success case.

