Browse documentation

Add actions

Add row, bulk, drawer, and dashboard operations with typed input and authorization.

An action is a server-side operation triggered from the admin. Row, bulk, drawer, and dashboard actions share the same design: stable key, visible label, optional input and confirmation, authorization, a run function, and a result the UI can apply.

Add a row action

import { resource, rowAction } from "@flowpanel/kit";

type SuspendInput = { reason: string };

const suspend = rowAction<User, SuspendInput>({
  key: "suspend",
  label: "Suspend",
  variant: "destructive",
  confirm: { title: "Suspend this user?", confirmLabel: "Suspend" },
  requireRole: "admin",
  form: [{ name: "reason", type: "textarea", required: true }],
  disabled: (user) => (user.status === "suspended" ? "Already suspended" : false),
  run: async (user, input, ctx) => {
    await suspendUser(ctx.db, user.id, input.reason);
    return { ok: true, message: "User suspended", refresh: true };
  },
});

resource(schema.users, { columns: ["email", "status"], actions: [suspend] });

The helper ties form field names to SuspendInput and types the row. The handler receives your database client, session, scope, audit, publish, and revalidation utilities through ctx.

Choose the placement

  • A row action receives one resolved row.
  • A bulk action receives selected IDs. Requests above the configured hard cap are rejected, never truncated.
  • A drawer action stays beside the row detail while the list remains open.
  • A dashboard action receives input without a row and appears in the dashboard header.

Keep the operation where the user has the context to understand it. Reuse a domain service inside multiple actions instead of sharing a UI-specific definition.

Authorize before input work

The admin and resource gates run before the action gate; input validation runs only after authorization. requireRole is enforced on the POST route as well as in the rendered action list. Use hidden for row-specific availability and disabled when the operator should see why an action cannot run.

Return an actionable result

Return { ok: true } for success and add message, redirect, or refresh only when the next UI state needs them. Return { ok: false, error } for an expected domain failure. Keep internal details out of user-visible messages.

Thrown FlowpanelError values retain their mapped status; unexpected errors become a generic server response and should be captured by application logging.

Verify the operation

Test the handler or route for an authorized success, an unauthorized request, invalid input, and a domain failure. For mutations, also verify the intended audit and realtime effects.

See Actions reference for generated types and Request and mutation lifecycle for execution order.