
Safe Action Advanced
Extend typed Next.js server actions with bind args, metadata, framework errors, and server-side success/error callbacks.
Overview
safe-action-advanced is an agent skill for the Build phase that documents next-safe-action bind arguments, metadata, framework errors, type utilities, and server-level action callbacks.
Install
npx skills add https://github.com/next-safe-action/skills --skill safe-action-advancedWhat is this skill?
- Bind extra arguments via `.bind()` (e.g. resource IDs) into safe actions
- Attach typed metadata for middleware and infer inputs/results with InferSafeActionFn utilities
- Handle redirect, notFound, forbidden, and unauthorized as framework errors inside actions
- Server-level onSuccess, onError, and onSettled callbacks on `.action()` (not client hooks)
- Overview table maps bind arguments, metadata, framework errors, and type utilities to use cases
- Four advanced feature areas: bind arguments, metadata, framework errors, type utilities
- Three server-level callbacks documented: onSuccess, onError, onSettled
Adoption & trust: 853 installs on skills.sh; 3/3 security scanners passed (skills.sh audits).
What problem does it solve?
Your server actions work at a basic level but you lack patterns for bound IDs, middleware metadata, navigation errors, and server-only lifecycle hooks.
Who is it for?
Next.js App Router developers standardizing advanced next-safe-action usage across auth-wrapped action clients.
Skip if: Teams not using next-safe-action or only needing introductory safe-action setup without bind/metadata or framework error edge cases.
When should I use this skill?
Working with bind arguments, metadata schemas, framework errors (redirect/notFound/forbidden/unauthorized), type inference utilities, or server-level action callbacks.
What do I get? / Deliverables
After applying the skill, actions use typed bind/metadata, correct framework error handling, inferred types, and onSuccess/onError/onSettled callbacks on the server.
- Server actions using bind/metadata, framework error handling, and server callbacks per skill patterns
Recommended Skills
Journey fit
Advanced next-safe-action patterns belong on the build shelf because they shape how server actions are authored and wired before ship. Backend is canonical—bind arguments, middleware metadata, and server callbacks are server-action implementation concerns, not frontend UI.
How it compares
Complements baseline next-safe-action skills with advanced server patterns— not a replacement for choosing a validation library or RPC framework.
Common Questions / FAQ
Who is safe-action-advanced for?
Solo and indie builders shipping Next.js apps who already use next-safe-action and need bind args, metadata, framework errors, and server callbacks documented in one place.
When should I use safe-action-advanced?
Use it during Build when wiring auth action clients, passing resource IDs via bind, handling redirect/notFound in mutations, or adding server-side invalidation and logging after actions complete.
Is safe-action-advanced safe to install?
Treat it as documentation guidance for your codebase; review the Security Audits panel on this Prism page before trusting third-party skill sources.
SKILL.md
READMESKILL.md - Safe Action Advanced
# next-safe-action Advanced Features ## Overview | Feature | Use Case | |---|---| | [Bind arguments](./bind-arguments.md) | Pass extra args to actions via `.bind()` (e.g., resource IDs) | | [Metadata](./metadata.md) | Attach typed metadata to actions for use in middleware | | [Framework errors](./framework-errors.md) | Handle redirect, notFound, forbidden, unauthorized in actions | | [Type utilities](./type-utilities.md) | Infer types from action functions and middleware | ## Server-Level Action Callbacks The second argument to `.action()` accepts callbacks that run **on the server** (not client-side hooks): ```ts export const createPost = authActionClient .inputSchema(schema) .action( async ({ parsedInput, ctx }) => { const post = await db.post.create(parsedInput); return post; }, { onSuccess: async ({ data, parsedInput, ctx, metadata, clientInput }) => { // Runs on the server after successful execution await invalidateCache("posts"); }, onError: async ({ error, metadata, ctx, clientInput, bindArgsClientInputs }) => { // error: { serverError?, validationErrors? } await logError(error); }, onSettled: async ({ result }) => { // Always runs await recordMetrics(result); }, onNavigation: async ({ navigationKind }) => { // Runs when a framework error (redirect, notFound, etc.) occurs console.log("Navigation:", navigationKind); }, } ); ``` These are distinct from hook callbacks (`useAction({ onSuccess })`) — server callbacks run in the Node.js runtime, hook callbacks run in the browser. ## throwServerError Re-throw server errors instead of returning them as `result.serverError`: ```ts export const myAction = actionClient .inputSchema(schema) .action(serverCodeFn, { throwServerError: true, // The handled server error (return of handleServerError) is thrown }); ``` # Metadata > **Note:** Action files require a `"use server"` directive — omitted from examples below for brevity. ## What Is Metadata? Metadata is typed data attached to each action, accessible in middleware and server callbacks. Common uses: action names for logging, feature flags, permission requirements. ## Define a Metadata Schema ```ts import { createSafeActionClient } from "next-safe-action"; import { z } from "zod"; export const actionClient = createSafeActionClient({ defineMetadataSchema: () => z.object({ actionName: z.string(), }), }); ``` When `defineMetadataSchema` is set, every action **must** call `.metadata()` before `.action()` — TypeScript enforces this. ## Set Metadata Per Action ```ts export const createUser = actionClient .metadata({ actionName: "createUser" }) .inputSchema(z.object({ name: z.string() })) .action(async ({ parsedInput, metadata }) => { // metadata.actionName === "createUser" return { name: parsedInput.name }; }); ``` ## Access Metadata in Middleware ```ts export const actionClient = createSafeActionClient({ defineMetadataSchema: () => z.object({ actionName: z.string(), requiresAuth: z.boolean().default(false), }), }).use(async ({ next, metadata }) => { // metadata is fully typed: { actionName: string; requiresAuth: boolean } if (metadata.requiresAuth) { const session = await getSession(); if (!session) throw new Error("Unauthorized"); return next({ ctx: { userId: session.user.id } }); } return next({ ctx: {} }); }); ``` ```ts // Public action export const getPublicData = actionClient .metadata({ actionName: "getPublicData", requiresAuth: false }) .action(async () => ({ data: "public" })); // Protected action export const getUser