Skip to content

Screen Actions

Summary

Screen actions are server-driven contextual operations that go beyond save and delete. Send an email, print an invoice, change a status, post a journal — your server declares what actions are available, and the client renders a picker, collects input if needed, and posts back to your endpoint.

Actions are different from the actions map (which handles save/delete/create URLs). Screen actions are custom operations defined per screen in the screen_actions array.

How it works

  1. Your server includes screen_actions in the ScreenContract
  2. The client shows Ctrl+A Actions in the bottom bar when actions exist
  3. The user presses Ctrl+A (or F8) to open the action picker
  4. The user selects an action — the client handles confirmation or form input based on the action's kind
  5. The client POSTs an ActionRequest to the action's endpoint
  6. Your server processes it and returns an ActionResponse
  7. The client shows the result in a modal the user must acknowledge

ActionDef

Each action in the screen_actions array is an ActionDef:

json
{
  "id": "send_email",
  "label": "Send as Email",
  "kind": "modal",
  "fields": [],
  "endpoint": "/action/sales_order/SO-1001/send_email",
  "confirm_message": null
}
PropertyTypeRequiredDefaultPurpose
idstringYesUnique identifier for this action
labelstringYesDisplay text in the action picker
kindstringNo"simple"How the client handles this action
fieldsarrayNo[]Form fields (only used when kind is "modal")
endpointstringYesURL the client POSTs to when executing
confirm_messagestringNonullConfirmation prompt (only for "confirm" kind)
iconstringNonullReserved for future use

Action kinds

Simple

Fire-and-forget. The user selects the action, and the client immediately POSTs to the endpoint.

json
{
  "id": "export_csv",
  "label": "Export to CSV",
  "kind": "simple",
  "endpoint": "/action/journal/export"
}

Confirm

Shows a confirmation dialog with Yes/No before executing. Use this for destructive or irreversible operations.

json
{
  "id": "post_journal",
  "label": "Post Journal",
  "kind": "confirm",
  "endpoint": "/action/journal/post",
  "confirm_message": "Post all journal lines? This cannot be undone."
}

If confirm_message is omitted, the client shows "Execute this action?".

Opens a form with input fields. The user fills in the fields and submits. The field values are included in the ActionRequest.

json
{
  "id": "send_email",
  "label": "Send as Email",
  "kind": "modal",
  "endpoint": "/action/sales_order/SO-1001/send_email",
  "fields": [
    {
      "id": "to",
      "label": "To",
      "type": "Email",
      "value": "ap@cannongroup.example",
      "required": true
    },
    {
      "id": "subject",
      "label": "Subject",
      "type": "Text",
      "value": "Sales Order SO-1001"
    },
    {
      "id": "message",
      "label": "Message",
      "type": "Text",
      "value": ""
    }
  ]
}

The form uses the same field rendering as card fields — full editing with cursor, selection, and field navigation. Pre-fill the value property with defaults from your data (e.g., the customer's email address).

ActionField

Fields in a modal action form:

PropertyTypeRequiredDefaultPurpose
idstringYesField identifier (sent in ActionRequest)
labelstringYesDisplay label
typestringYesField type (same as card field types)
valuestringNo""Pre-filled value
requiredboolNofalseWhether the field must have a value
optionsarrayNonullOptions for Option type fields
placeholderstringNonullPlaceholder text
validationobjectNonullValidation rules (same as card field validation)

The type property accepts the same values as card fields: Text, Email, Decimal, Integer, Date, Phone, URL, Boolean, Option, Password, TextArea, Time.

Option fields cycle through values with Space, just like on cards.

ActionRequest

When the user executes an action, the client POSTs:

json
{
  "action_id": "send_email",
  "screen_title": "Sales Order - SO-1001",
  "record_id": "SO-1001",
  "fields": {
    "to": "ap@cannongroup.example",
    "subject": "Sales Order SO-1001",
    "message": "Please review the attached order."
  }
}
PropertyTypePurpose
action_idstringThe id from the ActionDef
screen_titlestringThe human-readable screen title (e.g. "Sales Order - SO-1001")
record_idstringFrom ScreenContract.record_id, or null if empty
fieldsobjectField values from the form (empty for Simple and Confirm)

ActionResponse

Your server returns:

json
{
  "success": true,
  "message": "Email sent to ap@cannongroup.example.",
  "screen": null,
  "error": null
}
PropertyTypePurpose
successboolWhether the action completed successfully
messagestringSuccess message shown to the user
errorstringError message shown to the user (on failure)
redirect_urlstringClears navigation history and navigates to this URL after dismissal
push_urlstringPushes this URL onto the navigation stack after dismissal
screenScreenContractReplaces the current screen inline (lower priority than URL fields)

The client shows the message or error in a result modal that the user must dismiss with Enter.

Only set one navigation field per response. If multiple are set, the client applies the highest-priority one: redirect_urlpush_urlscreen. On failure (success: false), all navigation fields are ignored.

Success

Return success: true with a message:

json
{
  "success": true,
  "message": "Email sent to ap@cannongroup.example."
}

Success with screen update

If the action changes data, return an updated ScreenContract to refresh the display:

json
{
  "success": true,
  "message": "Journal posted. 3 entries created.",
  "screen": {
    "layout": "Grid",
    "title": "Banking Journal",
    "lines": { "columns": [], "rows": [] }
  }
}

Success with redirect

Use redirect_url when an action transforms the current record into something else — posting an invoice, converting a quote to an order. The original record no longer exists, so the client clears navigation history and opens the new screen. Escape from there follows the destination screen's parent_url (see ScreenContract parent_url).

json
{
  "success": true,
  "message": "Invoice posted.",
  "redirect_url": "/screen/posted_invoice/42"
}

If redirect_url is an external URL (starts with http:// or https:// pointing to a different host), the client opens it in the system default browser instead of navigating to it.

Success with push

Use push_url when an action opens a related screen without destroying the current one — a print preview, a report, an analytics view. The destination is pushed onto the navigation stack. Escape returns to the screen the action was triggered from.

json
{
  "success": true,
  "message": "Report ready.",
  "push_url": "/screen/invoice_report/42"
}

Applied in priority order. Only set one per response.

PriorityResponse fieldClient behaviorEscape from destination
1redirect_urlClear history, navigate to URLFollows destination's parent_url
2push_urlPush URL onto stackReturns to current screen
3screenReplace current screen inlineUnchanged
(none)Refresh current URL (404 → pop back to parent)Unchanged

Without any navigation field, the client refreshes the current URL after a successful action. If the record returns 404 (deleted or transformed), the client automatically pops back to the previous list.

Error

Return success: false with an error:

json
{
  "success": false,
  "error": "A valid email address is required."
}

The client shows the error in the result modal. The user can retry the action.

Adding actions to a screen

Add screen_actions to your ScreenContract response:

json
{
  "layout": "HeaderLines",
  "title": "Sales Order - SO-1001",
  "sections": [],
  "lines": {},
  "actions": {
    "save": "/screen/sales_order/SO-1001/save",
    "delete": "/screen/sales_order/SO-1001/delete"
  },
  "screen_actions": [
    {
      "id": "send_email",
      "label": "Send as Email",
      "kind": "modal",
      "endpoint": "/action/sales_order/SO-1001/send_email",
      "fields": [
        {
          "id": "to",
          "label": "To",
          "type": "Email",
          "value": "ap@cannongroup.example"
        }
      ]
    }
  ]
}

The Ctrl+A Actions hint appears in the bottom bar only when screen_actions is non-empty.

Actions work on all screen types: Card, List, HeaderLines, and Grid. On HeaderLines screens, Ctrl+A works from both the card header and the lines overlay.

Action endpoint routes

Action endpoints follow the same URL architecture as other routes. The client resolves the endpoint path by prepending the scheme and host.

PatternMethodRequest bodyResponse
/action/{resource}/{id}/{action_id}POSTActionRequestActionResponse
/action/{resource}/{action_id}POSTActionRequestActionResponse

These patterns are conventions. Use any URL structure you want — the client posts to whatever endpoint you provide in the ActionDef.

For complete worked examples, see Sales Order (modal email action) and Banking Journal (confirm posting action).