Quickback Docs

Actions

Custom actions with auto-generated input forms, access filtering, and side effects warnings.

Actions

Actions are custom operations defined in your feature files (e.g., approve, void, post, applyPayment). The CMS renders them as clickable buttons with auto-generated input forms — no UI code required.

Where Actions Appear

Actions show up in two places:

Row Action Menu (Table Mode)

In Table mode, the three-dot menu on each row includes a section for custom actions. Actions are separated from standard operations (view, edit, delete) by a divider.

Action Bar (Detail View)

In the record detail view, an "Actions" card displays all available actions as buttons. Each button shows the action name, and hovering reveals the description as a tooltip.

Action Dialog

Clicking an action opens a modal dialog with:

  1. Header — Action name with an icon (lightning bolt for standard, warning triangle for destructive, download for file responses)
  2. Description — The action's description text
  3. Side effects warning — If the action has sideEffects: "sync", an amber warning banner appears: "This action has synchronous side effects (e.g., GL entries)."
  4. Confirmation step — If the action has cms.confirm, a confirmation message appears before the input form
  5. Input fields — Auto-generated from the action's inputFields schema
  6. Execute button — Submits the action. Shows "Executing..." while in progress.
  7. Cancel button — Closes the dialog without executing

Input Field Types

The dialog generates the appropriate input control for each field:

Zod TypeInput ControlNotes
stringText inputFree-text entry
numberNumber inputStep 0.01, supports decimals
booleanCheckboxWith label text
array<string>Text inputComma-separated values, split on save

Required fields are marked with a red asterisk. Default values from the schema are pre-filled.

Example

Given this action definition:

actions: {
  applyPayment: {
    description: "Apply a payment to this invoice",
    input: 'z.object({ amount: z.number(), reference: z.string().optional() })',
    access: {
      roles: ['admin', 'owner'],
      record: { status: { equals: 'posted' } },
    },
    sideEffects: "sync",
    cms: {
      label: "Apply Payment",
      icon: "dollar-sign",
      confirm: "This will create GL entries. Continue?",
      category: "payments",
      successMessage: "Payment applied successfully",
      order: 1,
    },
  },
}

The CMS renders a dialog with:

  • A number input for amount (required)
  • A text input for reference (optional)
  • A sync side effects warning
  • A confirmation step with the custom message
  • Only visible to admins and owners
  • Only visible on records where status === "posted"

Access Filtering

Actions are filtered based on two criteria:

Role Check

The action's access.roles array is compared against the current user's role. If the user's role is not in the list, the action is hidden.

Record Condition

The action's access.record conditions are evaluated against the current record's data. Supported conditions:

OperatorExampleMeaning
equals{ status: { equals: 'pending' } }Field must equal value
notEquals{ status: { notEquals: 'void' } }Field must not equal value
in{ status: { in: ['pending', 'draft'] } }Field must be one of values

All conditions must pass for the action to be visible. This means "approve" actions naturally disappear after a record is approved, and "void" actions only appear on voidable records.

CMS Metadata

Actions can include an optional cms property for enhanced CMS rendering. Actions without cms render with default behavior.

PropertyTypeDefaultPurpose
labelstringcamelCase splitDisplay name override
iconstring"zap"Lucide icon name
confirmstring | booleanfalsetrue = generic confirm, string = custom message
destructivebooleanfalseRed styling + requires confirmation
categorystringnoneGroup actions under a header
hiddenbooleanfalseHide from CMS entirely (API-only actions)
successMessagestringnoneToast message after success
onSuccess"refresh" | "redirect:list" | "close""refresh"Behavior after successful execution
ordernumber0Sort priority (lower = first)

Example with CMS metadata

post: {
  description: "Post this invoice to accounts receivable",
  input: z.object({}),
  access: { roles: ["owner", "admin"], record: { status: { equals: "draft" } } },
  handler: "./handlers/post",
  sideEffects: "sync",
  cms: {
    label: "Post Invoice",
    icon: "send",
    confirm: "This will post the invoice to AR and create GL entries. Continue?",
    category: "lifecycle",
    successMessage: "Invoice posted successfully",
    onSuccess: "refresh",
    order: 1,
  },
},

Hiding API-only actions

internalRecalc: {
  description: "Recalculate internal totals",
  input: z.object({}),
  access: { roles: ["owner"] },
  handler: "./handlers/recalc",
  cms: { hidden: true },  // Never shown in CMS
},

Destructive Actions

Actions are styled as destructive when either:

  • The action's cms.destructive is true
  • The action is named void or delete (legacy fallback)

Destructive actions receive:

  • Red text in the row action menu
  • Warning triangle icon (unless overridden by cms.icon)
  • Red execute button in the dialog (using bg-destructive styling)
  • Automatic confirmation step (unless cms.confirm is explicitly false)

This provides a visual cue that the action has permanent consequences.

Standalone Actions

Actions with standalone: true are not tied to a specific record. They appear in a separate section and don't receive a recordId when executed. The CMS passes null as the record ID for standalone actions.

File Response Actions

Actions with responseType: "file" show a download icon instead of the lightning bolt. When executed, the response is treated as a file download rather than a JSON result.

Execution Flow

  1. User clicks action in row menu or action bar
  2. Dialog opens with description, inputs, and warnings
  3. If cms.confirm is set, user sees confirmation step first
  4. User fills in required fields
  5. User clicks "Execute" (or "Confirm" after confirmation step)
  6. CMS calls client.executeAction(table, actionName, recordId, input)
  7. On success: cms.successMessage shown as toast (if set), then cms.onSuccess behavior
  8. On error: error message displayed in dialog

Next Steps

On this page