
Safe Action Validation Errors
Return field-level, form-level, and nested validation errors from Next.js server actions using next-safe-action without throwing.
Overview
safe-action-validation-errors is an agent skill for the Build phase that teaches how to return typed field, form, and nested validation errors from Next.js server actions with next-safe-action’s returnValidationErrors.
Install
npx skills add https://github.com/next-safe-action/skills --skill safe-action-validation-errorsWhat is this skill?
- Uses returnValidationErrors(schema, errors) with schema-first typing from next-safe-action
- Field-level _errors per input key (e.g. email, password) after business-rule checks
- Multiple _errors strings per field and combined form-level _errors plus field errors
- Nested object error shapes aligned with Zod schema structure
- Reminder that real action modules need a "use server" directive
Adoption & trust: 844 installs on skills.sh; 3/3 security scanners passed (skills.sh audits).
What problem does it solve?
Your server action knows the request is invalid after a DB or auth check, but throwing or opaque errors do not map cleanly onto form fields in the UI.
Who is it for?
Next.js solo builders already using next-safe-action who need business-rule failures surfaced as structured validation errors.
Skip if: Projects not on Next.js server actions, teams handling all validation only on the client, or greenfield apps that have not adopted next-safe-action yet.
When should I use this skill?
Implementing or debugging next-safe-action handlers that must return custom validation errors after server-side checks.
What do I get? / Deliverables
After applying the patterns, actions return schema-shaped validation payloads your client can bind to inputs, including multiple messages per field and optional form-level errors.
- Server action code using returnValidationErrors for field, multi-field, and form-level cases
- Nested-object error mapping consistent with your schema
Recommended Skills
Journey fit
Server actions and typed validation errors are implemented while wiring backend form handling in a Next.js app. Canonical shelf is backend because the skill documents action handlers, Zod schemas, and returnValidationErrors in server-side code paths.
How it compares
Use for server-returned validation maps instead of ad-hoc throw new Error or untyped JSON error blobs in actions.
Common Questions / FAQ
Who is safe-action-validation-errors for?
It is for indie developers shipping Next.js apps with next-safe-action who want login, signup, and similar actions to return field-specific error messages the form can display.
When should I use safe-action-validation-errors?
Use it during Build when implementing server actions after Zod inputSchema is in place and you need returnValidationErrors for failed lookups, password checks, or registration blocks—not only for schema parse failures.
Is safe-action-validation-errors safe to install?
It is documentation-style procedural knowledge with no bundled executables; review the Security Audits panel on this Prism page before trusting any third-party skill package in your agent.
SKILL.md
READMESKILL.md - Safe Action Validation Errors
# Custom Validation Errors > **Note:** Action files require a `"use server"` directive — omitted from examples below for brevity. ## returnValidationErrors ```ts import { returnValidationErrors } from "next-safe-action"; ``` The first argument must be the schema (for type inference). The second argument is the validation errors object matching the schema shape. ### Field-Level Errors ```ts const schema = z.object({ email: z.string().email(), password: z.string().min(8), }); export const login = actionClient .inputSchema(schema) .action(async ({ parsedInput }) => { const user = await db.user.findByEmail(parsedInput.email); if (!user) { returnValidationErrors(schema, { email: { _errors: ["No account found with this email"] }, }); } const validPassword = await verifyPassword(parsedInput.password, user.passwordHash); if (!validPassword) { returnValidationErrors(schema, { password: { _errors: ["Incorrect password"] }, }); } return { userId: user.id }; }); ``` ### Multiple Errors Per Field ```ts returnValidationErrors(schema, { password: { _errors: [ "Must contain at least one uppercase letter", "Must contain at least one number", ], }, }); ``` ### Form-Level + Field-Level Errors ```ts returnValidationErrors(schema, { _errors: ["Unable to complete registration at this time"], email: { _errors: ["This email domain is not allowed"] }, }); ``` ### Nested Object Errors ```ts const schema = z.object({ address: z.object({ street: z.string(), city: z.string(), zip: z.string(), }), }); returnValidationErrors(schema, { address: { zip: { _errors: ["Invalid ZIP code for the selected state"] }, }, }); ``` ## Error Classes > **Note:** `ActionServerValidationError` is used internally by `returnValidationErrors()` — it is **not** exported from the package and should not be imported directly. ### ActionValidationError Thrown when `throwValidationErrors` is enabled and input validation fails. Catch this in try/catch: ```ts import { ActionValidationError } from "next-safe-action"; try { const result = await myAction({ invalidInput: true }); } catch (e) { if (e instanceof ActionValidationError) { console.log(e.validationErrors); // The validation errors object console.log(e.message); // Error message (default or overridden) } } ``` ### ActionBindArgsValidationError Thrown when bind args fail validation: ```ts import { ActionBindArgsValidationError } from "next-safe-action"; ``` ### ActionMetadataValidationError Thrown when metadata doesn't match `defineMetadataSchema`: ```ts import { ActionMetadataValidationError } from "next-safe-action"; ``` ### ActionOutputDataValidationError Thrown when the action's return value doesn't match `outputSchema`. Has a `.validationErrors` property with the validation failure details. ```ts import { ActionOutputDataValidationError } from "next-safe-action"; ``` # Validation Error Shapes > **Note:** Action files require a `"use server"` directive — omitted from examples below for brevity. ## Formatted (Default) Nested structure mirroring the schema, with `_errors` arrays at each level: ```ts // Schema: z.object({ email: z.string().email(), name: z.string().min(2) }) // Formatted errors: { _errors: [], // root-level errors email: { _errors: ["Invalid email"] }, name: { _errors: ["Too short"] }, } ``` Access: `result.validationErrors?.email?._errors?.[0]` ## Flattened Flat structure with `formErrors` (root) and `fieldErrors` (one level deep): ```ts // Same schema, flattened errors: { formErrors: [], // root-level errors fieldErrors: { email: ["Invalid email"], name: ["Too short"], }, } ``` Access: `result.validationErrors?.fieldErrors?.email?.[0]` **Note:** Flattened mode only processes one level deep. Nested object field errors are not included. ## Setting the Default