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:
- Header — Action name with an icon (lightning bolt for standard, warning triangle for destructive, download for file responses)
- Description — The action's description text
- Side effects warning — If the action has
sideEffects: "sync", an amber warning banner appears: "This action has synchronous side effects (e.g., GL entries)." - Confirmation step — If the action has
cms.confirm, a confirmation message appears before the input form - Input fields — Auto-generated from the action's
inputFieldsschema - Execute button — Submits the action. Shows "Executing..." while in progress.
- Cancel button — Closes the dialog without executing
Input Field Types
The dialog generates the appropriate input control for each field:
| Zod Type | Input Control | Notes |
|---|---|---|
string | Text input | Free-text entry |
number | Number input | Step 0.01, supports decimals |
boolean | Checkbox | With label text |
array<string> | Text input | Comma-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:
| Operator | Example | Meaning |
|---|---|---|
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.
| Property | Type | Default | Purpose |
|---|---|---|---|
label | string | camelCase split | Display name override |
icon | string | "zap" | Lucide icon name |
confirm | string | boolean | false | true = generic confirm, string = custom message |
destructive | boolean | false | Red styling + requires confirmation |
category | string | none | Group actions under a header |
hidden | boolean | false | Hide from CMS entirely (API-only actions) |
successMessage | string | none | Toast message after success |
onSuccess | "refresh" | "redirect:list" | "close" | "refresh" | Behavior after successful execution |
order | number | 0 | Sort 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.destructiveistrue - The action is named
voidordelete(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-destructivestyling) - Automatic confirmation step (unless
cms.confirmis explicitlyfalse)
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
- User clicks action in row menu or action bar
- Dialog opens with description, inputs, and warnings
- If
cms.confirmis set, user sees confirmation step first - User fills in required fields
- User clicks "Execute" (or "Confirm" after confirmation step)
- CMS calls
client.executeAction(table, actionName, recordId, input) - On success:
cms.successMessageshown as toast (if set), thencms.onSuccessbehavior - On error: error message displayed in dialog
Next Steps
- Security — Access conditions and role-based access
- Table Views — Where actions appear in the UI
- Schema Format Reference — ActionMeta type definition