next-safe-action
API Reference

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:

  1. .metadata() must be called before .action() / .stateAction() when a metadata schema is defined
  2. .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

On this page