Safe Action Client

next-safe-action/skills/skills/safe-action-client

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

Use when creating or configuring a next-safe-action client, defining actions with input/output validation, handling server errors, or setting up createSafeActionClient with Standard Schema (Zod, Yup, Valibot)

AI-generated overview

Guides creating and configuring next-safe-action clients and actions with validation and error handling.

What it does
This skill provides reference instructions for building next-safe-action clients and server actions in a Next.js project. It covers the chainable client API, entry points for server and client hooks, input and output validation with Standard Schema libraries such as Zod, Yup and Valibot, and server error handling. It also lists anti-patterns and the parameters passed to server code functions, and points to three supporting documents on client setup, validation, and error handling.
When to use it
Use it when creating or configuring a next-safe-action client, defining actions with input or output validation, handling server errors, or setting up createSafeActionClient with Standard Schema. It is aimed at developers working on Next.js server actions.
Requirements
Requires a Next.js project using the next-safe-action package and a Standard Schema validation library such as Zod, Yup or Valibot. It ships no scripts; it is instructions only.

next-safe-action Client & Action Definition

Quick Start

ts
// src/lib/safe-action.tsimport { createSafeActionClient } from "next-safe-action";
export const actionClient = createSafeActionClient();
ts
// src/app/actions.ts"use server";
import { z } from "zod";import { actionClient } from "@/lib/safe-action";
export const greetUser = actionClient  .inputSchema(z.object({ name: z.string().min(1) }))  .action(async ({ parsedInput: { name } }) => {    return { greeting: `Hello, ${name}!` };  });

Chainable API Order

createSafeActionClient(opts?)  .use(middleware)              // repeatable, adds pre-validation middleware  .metadata(data)              // required if defineMetadataSchema is set  .inputSchema(schema, utils?) // Standard Schema or async factory function  .bindArgsSchemas([...])      // schemas for .bind() arguments (order with inputSchema is flexible)  .useValidated(middleware)    // repeatable, adds post-validation middleware (requires inputSchema or bindArgsSchemas before it)  .outputSchema(schema)        // validates action return value  .action(serverCodeFn, utils?)      // creates SafeActionFn  .stateAction(serverCodeFn, utils?) // creates SafeStateActionFn (for useStateAction, useOptimisticStateAction, or React's useActionState)

Each method returns a new client instance — the chain is immutable.

Entry Points

Entry pointEnvironmentExports
next-safe-actionServercreateSafeActionClient, createMiddleware, createValidatedMiddleware, returnValidationErrors, returnServerError, flattenValidationErrors, formatValidationErrors, DEFAULT_SERVER_ERROR_MESSAGE, error classes, all core types
next-safe-action/hooksClientuseAction, useOptimisticAction, useStateAction, useOptimisticStateAction, hook types
next-safe-action/stateful-hooksClientuseStateAction (re-export from hooks for backward compatibility)

Supporting Docs

  • Client setup & configuration
  • Input & output validation with Standard Schema
  • Server error handling

Anti-Patterns

ts
// BAD: Missing "use server" directive — action won't workimport { actionClient } from "@/lib/safe-action";export const myAction = actionClient.action(async () => {});
// GOOD: Always include "use server" in action files"use server";import { actionClient } from "@/lib/safe-action";export const myAction = actionClient.action(async () => {});
ts
// BAD: Calling .action() without .metadata() when metadataSchema is definedconst client = createSafeActionClient({  defineMetadataSchema: () => z.object({ actionName: z.string() }),});client.action(async () => {}); // TypeScript error!
// GOOD: Always provide metadata before .action() when schema is definedclient  .metadata({ actionName: "myAction" })  .action(async () => {});
ts
// BAD: Returning an error instead of throwingexport const myAction = actionClient  .inputSchema(z.object({ email: z.string().email() }))  .action(async ({ parsedInput }) => {    const exists = await db.user.findByEmail(parsedInput.email);    if (exists) {      return { error: "Email taken" }; // Not type-safe, not standardized    }  });
// GOOD: Use returnValidationErrors for field-level errorsimport { returnValidationErrors } from "next-safe-action";
export const myAction = actionClient  .inputSchema(z.object({ email: z.string().email() }))  .action(async ({ parsedInput }) => {    const exists = await db.user.findByEmail(parsedInput.email);    if (exists) {      returnValidationErrors(z.object({ email: z.string().email() }), {        email: { _errors: ["Email is already in use"] },      });    }    return { success: true };  });

For expected non-validation business errors ("out of stock", "not found"), use returnServerError(payload) instead — it sets result.serverError to the typed payload, bypassing handleServerError. See Server error handling.

Server Code Function Parameters

The function passed to .action() receives a single object:

ts
.action(async ({  parsedInput,           // validated input (typed from inputSchema)  clientInput,           // raw client input (unknown)  bindArgsParsedInputs,  // validated bind args tuple  bindArgsClientInputs,  // raw bind args  ctx,                   // context from middleware chain  metadata,              // metadata set via .metadata()}) => {  // return data});

For .stateAction(), a second argument is added:

ts
.stateAction(async ({ parsedInput, ctx }, { prevResult }) => {  // prevResult is the previous SafeActionResult (structuredClone'd)  return { count: (prevResult.data?.count ?? 0) + 1 };});

Source and attribution

Source:next-safe-action/skillsinskills/safe-action-clientat commita2605bd

License: No license

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

Report or request removal