safe-action-validation-errors

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

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "safe-action-validation-errors" with this command: npx skills add next-safe-action/skills/next-safe-action-skills-safe-action-validation-errors

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:

// 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.

"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:

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

Supporting Docs

Displaying Validation Errors

// 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>
))}
// 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 Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

safe-action-testing

No summary provided by upstream source.

Repository SourceNeeds Review
General

safe-action-hooks

No summary provided by upstream source.

Repository SourceNeeds Review
General

safe-action-advanced

No summary provided by upstream source.

Repository SourceNeeds Review