zod refine

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.

Open the JSON to Zod converter

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

MethodBest useReview before shipping
refineOne custom boolean check on a field or object.Use a clear message and path when the failure should attach to a specific field.
superRefineMultiple issues, custom issue codes, or cross-field validation.Keep each issue path stable so forms and API clients can display errors predictably.
async refineChecks that need a database, service, or permission lookup.Call parseAsync or safeParseAsync; synchronous parse will not await the promise.
JSON Schema conversionPortable 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 generator

Zod 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