SafeActionClient
The SafeActionClient class is the core of next-safe-action. It provides a chainable, immutable API for building type-safe server actions. Each method returns a new client instance, the original is never modified.
import { createSafeActionClient } from "next-safe-action";
const actionClient = createSafeActionClient();.use()
Add a middleware function to the action execution chain.
client.use(middlewareFn)Parameters:
Prop
Type
The middleware function receives:
Prop
Type
Returns: A new SafeActionClient with NextCtx merged into the context type.
const authClient = actionClient.use(async ({ next }) => {
const session = await getSession();
if (!session) throw new Error("Unauthorized");
return next({ ctx: { user: session.user } });
});
// authClient now has ctx: { user: User }.metadata()
Set metadata for the action. Only available when a metadata schema has been defined via defineMetadataSchema in createSafeActionClient.
client.metadata(data)Parameters:
Prop
Type
Returns: A new SafeActionClient with metadata provided.
const myAction = actionClient
.metadata({ actionName: "createUser" })
.action(async ({ parsedInput, metadata }) => {
console.log(metadata.actionName); // "createUser"
});When a metadata schema is defined, you must call .metadata() before .action() or .stateAction(). TypeScript will error if you forget.
.inputSchema()
Define the input validation schema. Accepts a Standard Schema validator (Zod, Valibot, ArkType, etc.) or an async factory function that returns one.
client.inputSchema(schema, utils?)Parameters:
Prop
Type
Returns: A new SafeActionClient with typed parsedInput.
// Direct schema
const action = actionClient
.inputSchema(z.object({ name: z.string() }))
.action(async ({ parsedInput }) => {
// parsedInput: { name: string }
});
// Async factory (for i18n or extending previous schemas)
const action = actionClient
.inputSchema(async () => {
const t = await getTranslations();
return z.object({ name: z.string().min(2, t("name.tooShort")) });
})
.action(async ({ parsedInput }) => { /* ... */ });.schema() is a deprecated alias for .inputSchema(). Use .inputSchema() instead.
.outputSchema()
Define the output data validation schema. The action's return value is validated against this schema.
client.outputSchema(schema)Parameters:
Prop
Type
Returns: A new SafeActionClient with typed and validated output data.
const action = actionClient
.outputSchema(z.object({ id: z.string(), created: z.boolean() }))
.action(async () => {
return { id: "123", created: true };
// TypeScript + runtime validation ensures this shape
});.bindArgsSchemas()
Define validation schemas for bind arguments. Bind args are additional arguments bound to the action function before the main input.
client.bindArgsSchemas(schemas)Parameters:
Prop
Type
Returns: A new SafeActionClient with typed bind argument inputs.
const action = actionClient
.inputSchema(z.object({ title: z.string() }))
.bindArgsSchemas([z.string().uuid()]) // bind arg: projectId
.action(async ({ parsedInput, bindArgsParsedInputs: [projectId] }) => {
// parsedInput: { title: string }
// projectId: string
});
// In a component:
const boundAction = action.bind(null, projectId);.action()
Define the server-side code for the action. This terminates the builder chain and returns the callable action function.
client.action(serverCodeFn, utils?)Parameters:
Prop
Type
The serverCodeFn receives a single argument object:
Prop
Type
Returns: A SafeActionFn, a callable async function.
.stateAction()
Define a stateful server action for use with the useStateAction hook (deprecated) or React's useActionState.
client.stateAction(serverCodeFn, utils?)Same as .action(), but the serverCodeFn receives a second argument utils with access to prevResult:
Prop
Type
Returns: A SafeStateActionFn, a callable async function compatible with useActionState.
SafeActionUtils (action callbacks)
The optional second argument to .action() and .stateAction():
Prop
Type
export const myAction = actionClient
.inputSchema(schema)
.action(
async ({ parsedInput }) => {
return await doSomething(parsedInput);
},
{
onSuccess: async ({ data, metadata }) => {
console.log("Success:", data);
},
onError: async ({ error }) => {
await reportError(error.serverError);
},
}
);Method chaining order
Methods can be called in any order, with these constraints:
.metadata()must be called before.action()/.stateAction()when a metadata schema is defined.action()or.stateAction()must be the last method, as it terminates the chain
Typical order:
actionClient
.use(middleware) // 1. Add middleware (repeatable)
.metadata(data) // 2. Set metadata (if schema defined)
.inputSchema(schema) // 3. Define input validation
.outputSchema(schema) // 4. Define output validation (optional)
.bindArgsSchemas([...]) // 5. Define bind args (optional)
.action(fn, utils) // 6. Define server code (terminal)See also
- Action Client — conceptual overview and immutability pattern
- Middleware guide — practical patterns for
.use() createSafeActionClient()— creating the client instance- Types reference — all exported TypeScript types