next-safe-action
Concepts

Error Handling

next-safe-action has three categories of errors, each handled differently:

Error taxonomy: Action Errors splits into Validation Errors, Server Errors, and Framework Errors

Validation errors

Validation errors occur when the client sends data that doesn't match the input schema. They're returned in the result object (not thrown) and are always safe to show to the user:

const result = await createUser({ name: "", email: "bad" });

result.validationErrors;
// → { name: { _errors: ["Too short"] }, email: { _errors: ["Invalid email"] } }

Validation errors are produced in two ways:

  1. Automatically — when Standard Schema validation fails on input or bind args
  2. Manually — when you call returnValidationErrors() in your server code (e.g., "email already taken")
import { returnValidationErrors } from "next-safe-action";

export const signUp = actionClient
	.inputSchema(schema)
	.action(async ({ parsedInput }) => {
		const exists = await db.user.findByEmail(parsedInput.email);
		if (exists) {
			return returnValidationErrors(schema, {
				email: { _errors: ["Already registered"] },
			});
		}
		// ...
	});

returnValidationErrors actually throws internally (it never returns), which ensures the remaining server code doesn't execute.

See Input Validation for error shapes and Custom Validation Errors for advanced formatting.

Server errors

Server errors are unexpected failures, such as database timeouts, API failures, or bugs in your server code. By default, next-safe-action:

  1. Catches the thrown error
  2. Passes it to handleServerError (which you define in client options)
  3. Returns the handler's return value as result.serverError
const actionClient = createSafeActionClient({
	handleServerError(e) {
		// This runs when any action throws an unexpected error
		console.error("Action error:", e.message);

		// What you return here becomes result.serverError on the client
		// Default: "Something went wrong"
		return e.message;
	},
});

Security: The default handleServerError returns a generic "Something went wrong" message. If you return e.message, make sure your errors don't contain sensitive information (stack traces, database queries, etc.).

Throwing server errors explicitly

Use throwServerError in action utils to re-throw errors instead of catching them:

export const myAction = actionClient
	.action(async ({ parsedInput }, { throwServerError }) => {
		// If this throws, the error is re-thrown (not caught by handleServerError)
		throwServerError("Custom error message");
	});

Or enable it globally in client options:

const actionClient = createSafeActionClient({
	throwValidationErrors: true, // Re-throw validation errors instead of returning them
});

Framework errors

Framework errors are navigation events triggered by Next.js functions like redirect(), notFound(), forbidden(), and unauthorized(). These are special because they're not really "errors", but control flow mechanisms that Next.js uses to trigger navigation.

next-safe-action detects and handles these automatically:

import { redirect } from "next/navigation";

export const loginAction = actionClient
	.inputSchema(loginSchema)
	.action(async ({ parsedInput }) => {
		const user = await authenticate(parsedInput);
		// This triggers a redirect — not a normal return
		redirect("/dashboard");
	});

What happens with framework errors

  1. On the server: The error is caught, identified as a framework error, and re-thrown so Next.js can handle the navigation
  2. On the client: If using useAction, the hook detects the navigation and sets status to "hasNavigated" (instead of "hasSucceeded" or "hasErrored")
  3. Callbacks: The onNavigation callback fires (instead of onSuccess or onError)

The navigationKind property tells you what type of navigation occurred:

FunctionnavigationKind
redirect()"redirect"
notFound()"notFound"
forbidden()"forbidden"
unauthorized()"unauthorized"
const { execute } = useAction(myAction, {
	onNavigation: ({ navigationKind }) => {
		if (navigationKind === "redirect") {
			// Action triggered a redirect
		}
	},
});

See Framework Errors for advanced configuration.

Error handling summary

Error typeHow it's producedWhere it appearsSafe for users?
ValidationSchema parse failure or returnValidationErrors()result.validationErrorsYes
ServerThrown error in server code/middlewareresult.serverErrorDepends on handleServerError
Frameworkredirect(), notFound(), etc.Navigation occursN/A (handled by Next.js)

What's next?

On this page