Zod refine vs superRefine
Use Zod refine and superRefine for custom validation, async checks, password confirmation, field-level error paths, and rules that do not belong in JSON Schema.
Quick answer: when should you use zod refine?
Use `refine` when the base schema already describes the shape, but one business rule still needs custom logic. Examples include a password rule, a value that must be in a local allowlist, or an object that needs one cross-field check.
Basic refine example
Start with built-in Zod checks, then add `refine` only for the rule that cannot be expressed by `.min()`, `.email()`, `.regex()`, or an enum.
import { z } from "zod";
const UsernameSchema = z
.string()
.min(3)
.max(24)
.refine((value) => !value.includes("admin"), {
message: "Username cannot contain reserved words",
});
const result = UsernameSchema.safeParse(input);Password confirmation with an error path
For object-level checks, put the error on the field the user can fix. A password confirmation rule should usually report against `confirmPassword`, not the whole object.
const SignupSchema = z
.object({
email: z.string().email(),
password: z.string().min(12),
confirmPassword: z.string(),
})
.refine((data) => data.password === data.confirmPassword, {
path: ["confirmPassword"],
message: "Passwords do not match",
});superRefine for multiple issues
`superRefine` is the better fit when one validation pass can add more than one issue, or when the issue path and code should be controlled explicitly.
const CartSchema = z
.object({
quantity: z.number().int(),
coupon: z.string().optional(),
})
.superRefine((data, ctx) => {
if (data.quantity <= 0) {
ctx.addIssue({
code: "custom",
path: ["quantity"],
message: "Quantity must be greater than zero",
});
}
if (data.coupon === "EXPIRED") {
ctx.addIssue({
code: "custom",
path: ["coupon"],
message: "Coupon has expired",
});
}
});Async refine and safeParseAsync
Use async refinements for uniqueness, permissions, or external checks. Once a schema contains an async refinement, use `parseAsync` or `safeParseAsync` at the call site.
const InviteSchema = z
.object({
email: z.string().email(),
})
.refine(async ({ email }) => {
return await canInviteEmail(email);
}, {
path: ["email"],
message: "Email cannot receive invites",
});
const result = await InviteSchema.safeParseAsync(body);When not to use refine
Do not use refine for rules Zod already supports. Built-in checks are easier to read, easier to convert to JSON Schema, and easier for other developers to maintain.
- Use `.email()`, `.url()`, `.uuid()`, `.min()`, `.max()`, `.regex()`, and `z.enum()` before custom logic.
- Use `.transform()` when the value should change, not just pass or fail.
- Use `superRefine` when one object check can produce multiple field errors.
Refinements and JSON Schema output
Custom TypeScript callbacks do not have a faithful JSON Schema equivalent. If a refined Zod schema also needs JSON Schema or OpenAPI output, convert the structural part and keep the refinement as a separate runtime validation step.
Production checklist
Refinement bugs usually appear as confusing field messages or sync/async mismatches. Test both the happy path and every custom issue path.
- Add tests for valid data, invalid field values, invalid cross-field combinations, and async failure paths.
- Keep issue messages user-safe and avoid exposing database or permission details.
- Use `safeParse` or `safeParseAsync` when validation failure should become a controlled response.
Zod refine and superRefine comparison
| Method | Best use | Review before shipping |
|---|---|---|
| refine | One custom boolean check on a field or object. | Use a clear message and path when the failure should attach to a specific field. |
| superRefine | Multiple issues, custom issue codes, or cross-field validation. | Keep each issue path stable so forms and API clients can display errors predictably. |
| async refine | Checks that need a database, service, or permission lookup. | Call parseAsync or safeParseAsync; synchronous parse will not await the promise. |
| JSON Schema conversion | Portable structural constraints only. | Custom TypeScript refinement logic must stay in Zod or be modeled manually. |
Use FrameworkKit to generate the starter code, then review the output before shipping it in production.
Generate with the JSON to Zod schema generatorZod validation resources
JSON to Zod converter
Convert sample payloads into copy-ready Zod schemas and inferred TypeScript types in the browser.
JSON to Zod schema examples
Open realistic API and product payload examples before adapting the JSON to Zod schema output.
TypeScript to Zod Converter
Convert TypeScript interfaces and type aliases into Zod schemas when the source shape already lives in code.
Zod to JSON Schema Converter
Use the canonical online converter when an existing Zod schema needs JSON Schema, AJV, or OpenAPI output.
Validate API responses with Zod
Validate API responses, request bodies, and fetch boundaries with Zod schemas in TypeScript.
Zod parse vs safeParse
Choose between parse, safeParse, parseAsync, and safeParseAsync for TypeScript validation flows.
Zod safeParse
Validate unknown data without throwing, narrow the result type, and format safeParse errors.
Zod safeParse error messages
Format safeParse failures, field errors, and API validation responses in TypeScript.
Zod nativeEnum in Zod 4
Migrate z.nativeEnum patterns to z.enum, validate TypeScript enums, and avoid enum value mistakes.
Use JSON to Zod for form validation
Start from a submitted form payload, then add business rules such as email, length, and enum checks.
Zod to JSON Schema for OpenAPI
Publish Zod API schemas as OpenAPI-compatible contracts when teams need portable documentation.
Zod vs JSON Schema
Choose between TypeScript-first runtime validation and portable schema contracts.
Zod vs Yup vs Valibot
Compare TypeScript validation libraries for API boundaries, forms, server actions, and bundle tradeoffs.
FAQ
What is zod refine?
Zod refine adds a custom validation predicate to an existing schema. It is useful when built-in string, number, enum, or object checks are not enough.
What is the difference between refine and superRefine?
refine is best for one custom pass or fail rule. superRefine gives access to a context object so the schema can add multiple issues with explicit paths.
Can zod refine be async?
Yes. If a schema uses an async refinement, call parseAsync or safeParseAsync so the validation promise is awaited.
Can zod refine convert to JSON Schema?
Custom refinement callbacks cannot be represented as portable JSON Schema. Convert the structural schema and keep the custom Zod validation step in application code.
Related tools
TypeScript to Zod Converter
Convert TypeScript interfaces and type aliases into Zod schemas with inferred types in your browser.
Zod to JSON Schema Converter
Use a free browser-only online converter to turn Zod 4 schemas into JSON Schema for Draft 2020-12, Draft 7, AJV, or OpenAPI-compatible output.
OpenAPI to Zod
Turn OpenAPI schemas into Zod validators and lightweight typed fetch clients.