| name | safe-action-client |
| description | 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) |
next-safe-action Client & Action Definition
Quick Start
import { createSafeActionClient } from "next-safe-action";
export const actionClient = createSafeActionClient();
"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 middleware to chain
.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)
.outputSchema(schema) // validates action return value
.action(serverCodeFn, utils?) // creates SafeActionFn
.stateAction(serverCodeFn, utils?) // creates SafeStateActionFn (for useActionState)
Each method returns a new client instance — the chain is immutable.
Entry Points
| Entry point | Environment | Exports |
|---|
next-safe-action | Server | createSafeActionClient, createMiddleware, returnValidationErrors, flattenValidationErrors, formatValidationErrors, DEFAULT_SERVER_ERROR_MESSAGE, error classes, all core types |
next-safe-action/hooks | Client | useAction, useOptimisticAction, hook types |
next-safe-action/stateful-hooks | Client | useStateAction (deprecated — use React's useActionState directly) |
Supporting Docs
Anti-Patterns
import { actionClient } from "@/lib/safe-action";
export const myAction = actionClient.action(async () => {});
("use server");
import { actionClient } from "@/lib/safe-action";
export const myAction = actionClient.action(async () => {});
const client = createSafeActionClient({
defineMetadataSchema: () => z.object({ actionName: z.string() }),
});
client.action(async () => {});
client.metadata({ actionName: "myAction" }).action(async () => {});
export 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" };
}
});
import { 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 };
});
Server Code Function Parameters
The function passed to .action() receives a single object:
.action(async ({
parsedInput,
clientInput,
bindArgsParsedInputs,
bindArgsClientInputs,
ctx,
metadata,
}) => {
});
For .stateAction(), a second argument is added:
.stateAction(async ({ parsedInput, ctx }, { prevResult }) => {
return { count: (prevResult.data?.count ?? 0) + 1 };
});