Safe Action Validation Errors

next-safe-action/skills/skills/safe-action-validation-errors

by next-safe-actiona2605bd2e84321245cba8f5718c144a6e4a5fa47No licenseListed Oct 9, 2026Updated Oct 9, 2026

Use when working with validation errors -- returnValidationErrors, formatted vs flattened shapes, custom validation error shapes, throwValidationErrors, or displaying field-level and form-level errors

Instructions onlySoftware Development
AI-generated overview

Explains next-safe-action validation error shapes and helpers for returning and displaying field-level and form-level errors.

What it does
Documents how next-safe-action produces validation errors from schema validation and manual checks, covering the default formatted shape with nested _errors arrays and the flattened shape with fieldErrors and formErrors. It shows how returnValidationErrors throws an ActionServerValidationError that surfaces as result.validationErrors, including root-level form errors, and gives React examples for rendering field and form errors. Two supporting documents cover custom error shapes and formatted versus flattened output.
When to use it
Use when working with next-safe-action validation errors, including returnValidationErrors, formatted or flattened error shapes, custom validation error shapes, or rendering field-level and form-level errors in a form.
Requirements
No scripts; instructions only. Assumes a next-safe-action project with a schema library such as Zod and a React front end for the display examples.

next-safe-action Validation Errors

Two Sources of Validation Errors

  1. Schema validation — automatic when input doesn't match .inputSchema()
  2. Manual validation — via returnValidationErrors() in server code (e.g., "email already taken")

Both produce the same error structure on the client.

Default Error Shape (Formatted)

Mirrors the schema structure with _errors arrays at each level:

ts
// For schema: z.object({ email: z.string().email(), address: z.object({ city: z.string() }) }){  _errors: ["Form-level error"],                    // root errors  email: { _errors: ["Invalid email address"] },    // field errors  address: {    _errors: ["Address section error"],    city: { _errors: ["City is required"] },        // nested field errors  },}

returnValidationErrors

Throws a ActionServerValidationError that the framework catches and returns as result.validationErrors. It never returns — it always throws.

For expected non-validation errors ("out of stock", "not found"), the sibling helper returnServerError(payload) works the same way (throws internally, never returns) but sets result.serverError to the typed payload, bypassing handleServerError. See the safe-action-client skill.

ts
"use server";
import { z } from "zod";import { returnValidationErrors } from "next-safe-action";import { actionClient } from "@/lib/safe-action";
const registerSchema = z.object({  email: z.string().email(),  username: z.string().min(3),});
export const register = actionClient  .inputSchema(registerSchema)  .action(async ({ parsedInput }) => {    // Check business rules after schema validation passes    const existingUser = await db.user.findByEmail(parsedInput.email);    if (existingUser) {      returnValidationErrors(registerSchema, {        email: { _errors: ["This email is already registered"] },      });    }
    const existingUsername = await db.user.findByUsername(parsedInput.username);    if (existingUsername) {      returnValidationErrors(registerSchema, {        username: { _errors: ["This username is taken"] },      });    }
    // Both checks passed — create the user    const user = await db.user.create(parsedInput);    return { id: user.id };  });

Root-Level Errors

Use _errors at the top level for form-wide errors:

ts
returnValidationErrors(schema, {  _errors: ["You can only create 5 posts per day"],});

Supporting Docs

  • Custom validation errors and returnValidationErrors patterns
  • Formatted vs flattened shapes, per-action override

Displaying Validation Errors

tsx
// Formatted shape (default){result.validationErrors?.email?._errors?.map((error) => (  <p key={error} className="text-red-500">{error}</p>))}
// Root-level errors{result.validationErrors?._errors?.map((error) => (  <p key={error} className="text-red-500">{error}</p>))}
tsx
// Flattened shape{result.validationErrors?.fieldErrors?.email?.map((error) => (  <p key={error} className="text-red-500">{error}</p>))}
// Form-level errors (flattened){result.validationErrors?.formErrors?.map((error) => (  <p key={error} className="text-red-500">{error}</p>))}

Source and attribution

Source:next-safe-action/skillsinskills/safe-action-validation-errorsat commita2605bd

License: No license

Content belongs to its original authors. SourceWeft indexes it from a public repository.

Report or request removal