ScreenContract
Summary
Every JSON response from your server (except ValidateResponse and AuthResponse) is a ScreenContract. This is the universal page format.
See protocol.md for the backwards compatibility policy.
The structure
json
{
"layout": "Card",
"title": "Customer Card - 10000",
"screen_id": "customer_card",
"sections": [],
"lines": null,
"menu": null,
"status": null,
"work_date": "10-03-2026",
"locale": {
"date_format": "DD-MM-YYYY",
"decimal_separator": ",",
"thousand_separator": "."
},
"ui_strings": null,
"auth_action": null,
"user_display_name": "Erik Y.",
"actions": {
"save": "/screen/customer_card/10000/save",
"delete": "/screen/customer_card/10000/delete",
"create": "/screen/customer_card/new"
},
"lines_overlay_pct": 50,
"screen_actions": []
}All fields
| Field | Type | Required | Default | Purpose |
|---|---|---|---|---|
layout | string | Yes | — | How the client renders this screen: "Card", "List", "HeaderLines", "Grid", "Menu" |
title | string | Yes | — | Screen title shown in the title bar |
screen_id | string | Yes | — | Machine identifier for this screen (e.g. "customer_card"). Echoed back in SaveChangeset and DeleteRequest. Must be stable snake_case. Use "" for screens with no record identity (login, menus). |
sections | array | No | [] | Field groups for Card and HeaderLines layouts |
lines | object | No | null | Table data for List, HeaderLines, Grid, and lookup screens |
menu | object | No | null | Menu structure for Menu layout |
status | string | No | null | Message shown in the bottom status bar |
work_date | string | No | null | Work date for the session (used by date field shortcuts). Format follows locale.date_format. |
locale | object | No | null | Date/number formatting settings |
ui_strings | object | No | null | Server-driven UI text for localization |
auth_action | string | No | null | When set, the form submits as an AuthRequest to this URL |
user_display_name | string | No | null | Logged-in user's name (shown in the top-right corner on all screens) |
actions | object | No | {} | Action URLs keyed by name. Values are always URLs. |
record_id | string | No | "" | The current record's machine identifier (e.g. "10000"). Included in ActionRequest when the user executes a screen action. Leave empty for screens with no record identity (menus, journals). |
lines_overlay_pct | integer | No | 50 | Lines overlay height percentage (0–100). Only used on HeaderLines screens. |
lines_open | bool | No | false | When true, the lines overlay opens automatically on HeaderLines screens. |
totals | array | No | [] | Summary label+value pairs shown in a footer row below the grid |
screen_actions | array | No | [] | Contextual actions (send email, print, post, etc.). See Screen Actions |
parent_url | string | No | null | The natural parent of this screen. See Navigation and parent_url. |
Layout types
| Layout | Use for | Key content |
|---|---|---|
Card | Single-record form (Customer Card, Item Card) | sections with fields |
List | Table view (Customer List, Lookup List) | lines with columns and rows |
HeaderLines | Document with header + line items (Sales Order) | sections + lines + lines_overlay_pct |
Grid | Full-screen editable grid (journals, batch entry) | lines with columns and rows |
Menu | Navigation menu | menu with tabs and items |
The actions map
Provide URLs for save, delete, and create operations:
json
{
"actions": {
"save": "/screen/customer_card/10000/save",
"delete": "/screen/customer_card/10000/delete",
"create": "/screen/customer_card/new"
}
}The client uses these URLs when the user presses Ctrl+S (save), Ctrl+D (delete), or Ctrl+N (new). Your server controls all routing — the client never constructs action URLs.
Include only the actions that are valid for this screen. If a record cannot be deleted, omit delete. All values in actions are URLs.
The screen_actions array
For custom operations beyond save/delete (send email, print invoice, post journal, change status), use screen_actions:
json
{
"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 client shows Ctrl+A Actions in the bottom bar when this array is non-empty. See Screen Actions for the full reference.
The status field
Use status to show messages after operations:
json
{
"status": "Saved."
}For save errors that are business logic (not infrastructure), return HTTP 200 with the error in status:
json
{
"layout": "Card",
"title": "Customer Card - 10000",
"status": "Cannot save: Name is required.",
"sections": []
}The client shows the status text in the bottom bar. This keeps the card open so the user can see what went wrong.
Minimal examples
Minimal Card
json
{
"layout": "Card",
"title": "Customer Card",
"screen_id": "customer_card",
"sections": [
{
"id": "general",
"label": "General",
"fields": [
{
"id": "name",
"label": "Name",
"type": "Text",
"value": "Acme Corp"
}
]
}
],
"actions": {
"save": "/screen/customer_card/1/save"
}
}Minimal List
json
{
"layout": "List",
"title": "Customers",
"screen_id": "customer_list",
"lines": {
"columns": [
{
"id": "no",
"label": "No.",
"width": 10
},
{
"id": "name",
"label": "Name",
"width": "fill"
}
],
"rows": [
{
"index": 0,
"values": [
"10000",
"Acme Corp"
]
}
],
"selectable": true,
"on_select": "/screen/customer_card/{0}"
}
}Minimal Grid
json
{
"layout": "Grid",
"title": "Payment Journal",
"screen_id": "payment_journal",
"sections": [],
"lines": {
"columns": [
{
"id": "date",
"label": "Date",
"type": "Date",
"width": 12,
"editable": true
},
{
"id": "description",
"label": "Description",
"type": "Text",
"width": "fill",
"editable": true
},
{
"id": "amount",
"label": "Amount",
"type": "Decimal",
"width": 14,
"align": "right",
"editable": true
}
],
"rows": [],
"editable": true
},
"totals": [
{
"label": "Balance:",
"value": "0.00"
}
],
"actions": {
"save": "/screen/journal/save"
}
}Minimal Menu
json
{
"layout": "Menu",
"title": "Main Menu",
"screen_id": "",
"menu": {
"panel_title": "Main Menu",
"tabs": [
{
"label": "Sales",
"items": [
{
"label": "Customers",
"action": {
"type": "open_screen",
"url": "/screen/customer_list"
}
}
]
}
]
}
}Navigation and parent_url
parent_url is the screen the user would naturally expect to go back to from this screen.
The client uses it as a fallback when there is no navigation history — the most common case being after an action redirect_url lands the user on a new screen. During normal user navigation the stack is always populated and parent_url is ignored.
json
{
"layout": "Card",
"title": "Posted Sales Invoice - PSI-01018",
"parent_url": "/screen/posted_invoice_list",
...
}When to set it:
| Screen type | Typical parent_url |
|---|---|
| Card | The list for this resource |
| HeaderLines | The list for this resource |
| List | null (top-level) or a sub-menu URL |
| Grid | null or menu URL |
| Menu / Login | null — always |
How it chains: If the client follows parent_url to a list screen, and that list screen also declares a parent_url, Escape from the list follows that too. The full back-chain is built from the server's declarations, not from client history.
In the Laravel plugin, parent_url is set automatically — Card and HeaderLines screens default to the resource's list URL, lists default to null. Override parentUrl() on resources where the default does not fit.