Component Catalog
Component Catalog
This page lists every component an agent can reference inside an apps_create_from_spec payload — by exact type string, with the props each one accepts.
If your agent uses a name that isn't on this list, the component is dropped at render time and the user sees a blank panel where it should have appeared. Spelling and case matter.
For how to build the surrounding spec and post it, see the MCP Integration Guide. For automation between apps, see Workflows.
Quick rules
- Component
typestrings are case-sensitive and use kebab-case (text-input, nottextInputorTextInput). - Plugin components use the form
plugin:<plugin-id>/<ComponentName>— the part after the slash is PascalCase and case-sensitive (e.g.plugin:chart-pack/BarChart). - Props not listed below are not accepted; extra props are stripped silently.
- Defaults apply when the prop is omitted.
- Several props take a JSON-encoded string (suffix
…Json) — pass a stringified JSON value, not a JSON object. - Props of type
stringshown with{{expression}}support data binding (see the data-binding note in each section).
The catalog grows as new components ship; this page is the current truth.
Layout (8)
container
Top-level page wrapper with controlled width and padding.
| Prop | Type | Default | Notes |
|---|---|---|---|
maxWidth | "sm" | "md" | "lg" | "xl" | "full" | "lg" | |
padding | "none" | "sm" | "md" | "lg" | "md" |
card
Grouped panel with optional title and accent border.
| Prop | Type | Default | Notes |
|---|---|---|---|
title | string | — | Optional. |
bordered | boolean | true | |
shadow | boolean | true | |
accentColor | string | — | Hex color (e.g. "#3b82f6") for a colored top border. |
column
Vertical stack of children with gap control.
| Prop | Type | Default |
|---|---|---|
gap | "none" | "sm" | "md" | "lg" | "md" |
row
Horizontal row of children.
| Prop | Type | Default | Notes |
|---|---|---|---|
gap | "none" | "sm" | "md" | "lg" | "md" | |
align | "start" | "center" | "end" | "stretch" | "stretch" | |
wrap | boolean | true | Wrap onto multiple lines on narrow screens. |
tabs
Tabbed container — children are tab panels in labels order.
| Prop | Type | Default | Notes |
|---|---|---|---|
defaultTab | number | 0 | Index of the initially active tab. |
labels | string[] or Array<{label, public?}> | required (min 1) | See visibility note below. |
labels accepts two shapes:
- Legacy string array —
["Overview", "Reports"]. All tabs are visible to everyone. - Object array with optional public flag —
[{"label": "Menu", "public": true}, {"label": "Admin"}]. On a public-shared app page, anonymous visitors see only items marked"public": true. Items without the flag are hidden from anonymous viewers (the owner viewing their own workspace always sees every tab). Use this to expose customer-facing tabs and keep admin tabs private on the same shared page.
grid
CSS Grid layout. Children may set gridColumnSpan (1–4) and gridRowSpan (1–3) to span cells.
| Prop | Type | Default |
|---|---|---|
columns | number | 3 |
gap | "none" | "sm" | "md" | "lg" | "md" |
sidebar-layout
Left-sidebar navigation shell with switchable page panels. Use as the root component of an app.
| Prop | Type | Default | Notes |
|---|---|---|---|
appTitle | string | required | Shown at the top of the sidebar. |
appIcon | string | — | Optional emoji shown next to the title. |
navItemsJson | string (JSON) | required | See shape below. |
width | "narrow" | "default" | "wide" | "default" | |
activeIndex | number | 0 | Index of the initially selected nav item. |
navItemsJson is a JSON-encoded array of {label, icon?, public?}:
"[{\"label\":\"Menu\",\"icon\":\"🍔\",\"public\":true},{\"label\":\"Orders\",\"icon\":\"📦\"},{\"label\":\"Settings\",\"icon\":\"⚙️\"}]"Each child of sidebar-layout is one panel, in the same order as navItemsJson. The optional public flag works the same way as on tabs: items marked "public": true are visible to anonymous visitors of a publicly shared app; items without the flag are hidden from public visitors (and their child panel does not render). The owner workspace always sees every item. Mark customer-facing nav (menu, ordering, booking) "public": true; leave admin nav (dashboards, settings) without the flag.
accordion
Collapsible panels — children are panel contents, in itemsJson order.
| Prop | Type | Default | Notes |
|---|---|---|---|
itemsJson | string (JSON) | required | Array of {title, icon?}. |
allowMultiple | boolean | false | If true, multiple panels can be open at once. |
defaultOpen | number | 0 | Index of the panel open on load. |
"[{\"title\":\"FAQ\",\"icon\":\"❓\"},{\"title\":\"Contact\"}]"Input (13)
Many input components share two optional URL-binding props — when set, the field's initial value is read once from the URL query string on mount:
bindFromQuery(string, max 64, must match/^[a-zA-Z][a-zA-Z0-9_]*$/) — the URL parameter name to read. Empty values are not seeded.bindReadOnly(boolean) — whentrueand a value was seeded, the input is locked. Useful for QR-code, referral, or pre-filled deep-link flows.
These appear on text-input, number-input, select, checkbox, and date-picker.
text-input
Single-line text input.
| Prop | Type | Default | Notes |
|---|---|---|---|
label | string | required | |
field | string | required | Form field name. |
placeholder | string | — | |
required | boolean | false | |
bindFromQuery | string | — | URL param name. See note above. |
bindReadOnly | boolean | — | Lock the field when seeded. |
number-input
Numeric input.
| Prop | Type | Default | Notes |
|---|---|---|---|
label | string | required | |
field | string | required | |
placeholder | string | — | |
min | number | — | |
max | number | — | |
step | number | — | |
bindFromQuery | string | — | Non-numeric / missing URL values are ignored. |
bindReadOnly | boolean | — |
select
Dropdown with predefined options.
| Prop | Type | Default | Notes |
|---|---|---|---|
label | string | required | |
field | string | required | |
options | Array<{label, value}> | optional | When omitted, options are auto-derived from the schema field if its type is "enum". See "enum field auto-derive" below. |
placeholder | string | — | |
bindFromQuery | string | — | |
bindReadOnly | boolean | — |
Enum field auto-derive. When the bound field matches a schema field with type: "enum" and options: [...], you may omit the component-level options prop. The renderer derives the dropdown options from the schema and humanises labels ("on_hold" → "On Hold"). When multiple schemas share the same field name with conflicting option lists, the renderer picks the first match and emits console.warn — pass explicit options JSON to disambiguate.
checkbox
Boolean checkbox.
| Prop | Type | Default | Notes |
|---|---|---|---|
label | string | required | |
field | string | required | |
bindFromQuery | string | — | URL values "1", "true", "yes" map to checked; anything else maps to unchecked. |
bindReadOnly | boolean | — |
date-picker
Date / datetime / time picker (HTML5 native — <input type="date|datetime-local|time">).
| Prop | Type | Default | Notes |
|---|---|---|---|
label | string | required | |
field | string | required | |
mode | "date" | "datetime" | "time" | optional | Explicit input-type override. When omitted, the renderer auto-derives the right HTML5 type from the bound schema field's type (datetime → datetime-local, date → date, time → time). See "datetime / date / time field auto-render" below. |
includeTime | boolean | false | Legacy boolean (kept for back-compat). Superseded by mode. When mode is set, includeTime is ignored. |
bindFromQuery | string | — | URL value must match YYYY-MM-DD, YYYY-MM-DDTHH:MM[:SS], or HH:MM[:SS]. |
bindReadOnly | boolean | — |
Datetime / date / time field auto-render. When the bound field matches a schema field with type: "datetime" | "date" | "time", you may omit mode — the renderer picks the right HTML5 input automatically (mirrors the enum auto-derive pattern). Explicit mode always wins; includeTime wins over auto-derive but loses to mode. Resolution order: mode > includeTime > schema field-type auto-derive > "date" fallback.
Storage convention. Datetime values are stored exactly as the user typed them — wall-clock by default, NOT auto-converted to UTC. Apps that need UTC must include the Z suffix in the stored value (e.g. "2026-06-15T14:30Z"). For cross-timezone scheduling apps, document this convention in the UI. validateRecord rejects malformed strings with HTTP 400 (e.g. "tmrw 3pm" → 400, "2026-13-99" → 400, "25:99" → 400). Empty / null / undefined pass through unchanged.
Booking & availability auto-fetch. When the bound datetime field has availabilityCheck config in its schema, the picker:
- Auto-fetches booked slots from
/api/{public,apps}/[appId]/availabilityon mount. - Refetches when the configured
partitionFieldvalue changes (e.g. switching tutor). - Surfaces booked slots via an HTML
<datalist>hint plus a small visual feedback line below the input ("Already booked: 10:00, 14:00"). - Sets the input's
min/maxHTML attributes fromminLeadTimeMinutes/maxLeadTimeDaysso the browser blocks selecting out-of-range datetimes. - Server-side conflict and lead-time checks are the load-bearing rails — UI greying is UX layer only. A user who races past the dropdown gets a clean 400 from the server with
slot_datetime: already booked(public mode) or a verbose owner-mode equivalent.
Use the optional availabilityFromField / availabilityPartition props only as escape hatches when the picker is bound to a different field name than the availabilityCheck owner field (rare). Auto-derive from the bound field is the preferred path.
button
Action button.
| Prop | Type | Default |
|---|---|---|
label | string | required |
variant | "primary" | "secondary" | "danger" | "ghost" | "primary" |
size | "sm" | "md" | "lg" | "md" |
disabled | boolean | false |
payment-link
External payment button — opens an https:// URL in a new tab. Suitable for Stripe Payment Links, PayPal, PayMe, FPS, etc.
| Prop | Type | Default | Notes |
|---|---|---|---|
label | string | required | Button text. |
url | string | required | Must start with https://. |
amount | string | — | Display amount, e.g. "HK$500", "£29.99". |
icon | string | — | Emoji icon, e.g. "💳". |
variant | "primary" | "secondary" | "primary" | |
size | "sm" | "md" | "lg" | "md" |
textarea
Multi-line text input.
| Prop | Type | Default |
|---|---|---|
label | string | required |
field | string | required |
placeholder | string | — |
rows | number | 4 |
toggle
On/off switch.
| Prop | Type | Default |
|---|---|---|
label | string | required |
field | string | required |
size | "sm" | "md" | "lg" | "md" |
radio-group
Radio button group.
| Prop | Type | Default | Notes |
|---|---|---|---|
label | string | required | |
field | string | required | |
optionsJson | string (JSON) | required | Array of {label, value}. |
layout | "horizontal" | "vertical" | "vertical" |
"[{\"label\":\"Yes\",\"value\":\"y\"},{\"label\":\"No\",\"value\":\"n\"}]"slider
Range slider.
| Prop | Type | Default |
|---|---|---|
label | string | required |
field | string | required |
min | number | 0 |
max | number | 100 |
step | number | 1 |
showValue | boolean | true |
barcode-scanner
Camera-based barcode/QR scanner. Falls back to manual text input when the camera is unavailable.
| Prop | Type | Default | Notes |
|---|---|---|---|
label | string | required | |
field | string | required | Form field that receives the decoded value. |
format | enum (see below) | "all" | |
scannerHeight | string | — | CSS length, e.g. "200px", "40vh". Pattern: ^\d+(\.\d+)?(px|vh|vw|rem|em|%)$. |
placeholder | string | — | |
beepOnScan | boolean | true | |
autoSubmit | boolean | false |
format accepts: "all", "qr_code", "ean_13", "ean_8", "code_128", "code_39", "upc_a", "upc_e".
image-upload
Image upload with drag-drop, preview, and progress. Stores the resulting URL in field. Pair with an image field type in your data schema. Suitable for photos, receipts, and documentation — not for confidential documents (the URL is reachable by anyone with the link).
| Prop | Type | Default |
|---|---|---|
label | string | required |
field | string | required |
placeholder | string | — |
Display (14)
heading
Heading text (h1–h4).
| Prop | Type | Default | Notes |
|---|---|---|---|
text | string | required | |
level | "1" | "2" | "3" | "4" | "2" | String, not number. |
icon | string | — | Optional emoji rendered before the text on h1. |
text
Paragraph or inline text.
| Prop | Type | Default |
|---|---|---|
content | string | required |
size | "sm" | "md" | "lg" | "md" |
color | "default" | "muted" | "success" | "warning" | "error" | "default" |
badge
Small colored tag.
| Prop | Type | Default |
|---|---|---|
text | string | required |
variant | "default" | "success" | "warning" | "error" | "info" | "default" |
image
Image with src and alt text.
| Prop | Type | Default |
|---|---|---|
src | string | required |
alt | string | required |
width | number | — |
height | number | — |
rounded | boolean | false |
stat-card
Metric card with label, value, and optional change indicator.
| Prop | Type | Default | Notes |
|---|---|---|---|
label | string | required | |
value | string | required | Supports {{expression}} data binding. |
change | string | — | Optional change text, e.g. "+12%". |
changeType | "positive" | "negative" | "neutral" | — | |
icon | string | — | Emoji, e.g. "📊". |
accentColor | string | — | Hex color for the left border. |
prefix | string | — | Prefix before the value, e.g. "$". |
Server-side aggregate delegation: when value is exactly an aggregate formula (SUM, COUNT, AVG, MIN, MAX, COUNTIF, SUMIF) AND the bound schema exceeds 1000 records, the value is computed on the server instead of shipped to the browser. Below the threshold the formula is computed client-side for instant render. The same delegation applies to heading.text. Caveat: delegation only fires when the bound expression occupies the entire prop value (e.g. value: "{{SUM(orders.total)}}"). Mixed content like value: "Total: {{SUM(orders.total)}}" always interpolates client-side regardless of dataset size — bind aggregates to the full prop to opt into server-side computation on large datasets. On public pages, the same row-scope (customerScoped) and publicHiddenFields rules that protect the underlying records also apply to the aggregate — formulas referencing a hidden field render a "—" placeholder rather than leaking the value.
Time-window aggregates: COUNTIF and SUMIF accept comparison operators =, >, <, >=, <= and UTC magic tokens for time windows:
| Token | Meaning (UTC) |
|---|---|
$now | Current epoch ms |
$today | Midnight at the start of today |
$thisWeek | Monday 00:00 of the current ISO 8601 week |
$thisMonth | Day 1 00:00 of the current month |
$thisYear | Jan 1 00:00 of the current year |
Use these for "Today's …" / "This week's …" / "This month's …" stat-cards instead of all-time aggregates:
{ "type": "stat-card", "props": { "label": "Today's revenue", "value": "{{SUMIF(orders.created_at>=$today, total)}}", "prefix": "$" } }
{ "type": "stat-card", "props": { "label": "Tasks done this week", "value": "{{COUNTIF(tasks.completed_at>=$thisWeek)}}" } }Magic tokens always evaluate on the server (UTC), so dashboards stay honest regardless of who's viewing or what timezone their browser thinks it's in. All formulas containing a magic token are auto-delegated to the server even when the dataset is below the 1000-row threshold. Unsupported operators (BETWEEN, IN, LIKE, IS NULL) return a clear 400 error naming the operator.
divider
Horizontal divider line, optionally labelled.
| Prop | Type | Default |
|---|---|---|
label | string | — |
spacing | "sm" | "md" | "lg" | "md" |
avatar
User avatar circle with initials or image, optional status dot.
| Prop | Type | Default |
|---|---|---|
name | string | required |
src | string | — |
size | "sm" | "md" | "lg" | "md" |
status | "online" | "offline" | "busy" | "away" | — |
progress-ring
Circular progress indicator.
| Prop | Type | Default | Notes |
|---|---|---|---|
value | string | required | Number, or {{expression}} binding. |
max | number | 100 | |
label | string | — | |
size | "sm" | "md" | "lg" | "md" | |
color | string | — | Hex color. |
empty-state
Placeholder for empty data views.
| Prop | Type | Default |
|---|---|---|
icon | string | — |
title | string | required |
description | string | — |
alert
Colored alert banner.
| Prop | Type | Default |
|---|---|---|
title | string | required |
description | string | — |
variant | "info" | "success" | "warning" | "error" | "info" |
icon | string | — |
progress-bar
Horizontal progress bar.
| Prop | Type | Default | Notes |
|---|---|---|---|
value | string | required | Number, or {{expression}} binding. |
max | number | 100 | |
label | string | — | |
color | string | — | Hex color. |
size | "sm" | "md" | "lg" | "md" |
list
Static list of items.
| Prop | Type | Default | Notes |
|---|---|---|---|
itemsJson | string (JSON) | required | Array of {text, icon?}. |
variant | "plain" | "bordered" | "striped" | "bordered" |
"[{\"text\":\"Step 1: Sign up\",\"icon\":\"✅\"},{\"text\":\"Step 2: Invite team\"}]"rating
Star rating display.
| Prop | Type | Default | Notes |
|---|---|---|---|
value | string | required | Number, or {{expression}} binding. |
max | number | 5 | |
size | "sm" | "md" | "lg" | "md" | |
color | string | — | Hex color for filled stars. |
tag-list
Horizontal row of colored tags.
| Prop | Type | Default | Notes |
|---|---|---|---|
tagsJson | string (JSON) | required | Array of {text, color?}. |
size | "sm" | "md" | "sm" |
"[{\"text\":\"React\",\"color\":\"#3b82f6\"},{\"text\":\"TypeScript\"}]"Schema field types
The components above bind to data tables (schemas) you define in your spec. Each schema is a named list of fields, and every field declares a type. The type drives three things: which HTML5 control a bound input renders, how values are validated on write, and how the renderer auto-derives behaviour (enum dropdowns, datetime pickers, etc.).
There are 12 field types. Pick the most specific one — it buys you free validation and the right input control.
type | Stores | Validated on write | Notes |
|---|---|---|---|
string | Short single-line text | Stored as-is | The default catch-all for names, labels, references. |
text | Long multi-line text | Stored as-is | Use for descriptions, notes, addresses. Pairs with textarea. |
number | Numeric value | Must parse as a number (else 400) | Numeric strings are coerced to numbers. |
boolean | True / false | "true" / "1" coerce to true | Pairs with checkbox / toggle. |
email | Email address | Must contain @ (else 400) | Lightweight format check only. |
url | URL | Stored as-is | No scheme enforcement on plain url fields. |
image | Image URL | Must be https:// and ≤ 2048 chars (else 400) | Pairs with image-upload. Image is reachable by anyone with the link — not for confidential files. |
date | Calendar date | Must match YYYY-MM-DD and be a real date (else 400) | Pairs with date-picker (mode: "date"). |
datetime | Date + time | Must match YYYY-MM-DD or YYYY-MM-DDTHH:MM[:SS][Z|±HH:MM] (else 400) | Pairs with date-picker (mode: "datetime"). Stored as typed — wall-clock unless you append Z. Enables availabilityCheck (booking slots). |
time | Time of day | Must match HH:MM[:SS] in range 00–23:00–59[:00–59] (else 400) | Pairs with date-picker (mode: "time"). |
relation | Reference to another schema's record | Stored as-is | Set relationTo: "<other schema name>" to declare the target. |
enum | One value from a fixed set | Must be one of options (else 400) | Requires a non-empty options array. Pairs with select / radio-group. Auto-derives dropdown options. |
For all date / datetime / time types, an empty / null / undefined value passes through unchanged — only non-empty malformed strings are rejected. Storage is exactly as typed (no automatic UTC conversion); apps needing UTC must append Z to the stored value.
Field options
Every field is an object. Beyond name and type, these keys apply:
| Key | Applies to | Type | Default | Meaning |
|---|---|---|---|---|
name | all | string | required | The field key used in records and component bindings. |
type | all | one of the 12 above | required | See table above. |
required | all | boolean | true | Whether the field is mandatory. Defaults to true — set false explicitly for optional fields. |
options | enum | string[] | required for enum | The allowed value set. Must be non-empty; each value unique; up to 50 options, each ≤ 64 chars. |
relationTo | relation | string | — | Name of the schema this field references. |
Field definition example (inside a schema in your spec):
{
"name": "bookings",
"fields": [
{ "name": "customer_name", "type": "string" },
{ "name": "notes", "type": "text", "required": false },
{ "name": "party_size", "type": "number" },
{ "name": "contact_email", "type": "email" },
{ "name": "slot", "type": "datetime" },
{ "name": "status", "type": "enum", "options": ["pending", "confirmed", "cancelled"] },
{ "name": "table_id", "type": "relation", "relationTo": "tables" }
]
}Aliases. Common synonyms are accepted and normalised automatically — e.g. integer / float / decimal → number, timestamp / date-time → datetime, textarea / longtext → text, bool → boolean, uri / link → url, photo / picture / img → image. Prefer the canonical 12 names; the aliases exist so a near-miss is silently corrected rather than rejected.
Data (8)
Components in this category bind to a data table you defined in your spec via dataBinding: "{{schema_name}}" (the binding mechanism is shared with charts; see the Plugin: Chart Pack section).
data-list
Rich list or grid of records with title, subtitle, badge, and avatar.
| Prop | Type | Default | Notes |
|---|---|---|---|
titleField | string | required | Field used as the main title. |
subtitleField | string | — | Optional secondary text. |
badgeField | string | — | Field shown as a badge. |
avatarField | string | — | Field used for avatar initials. |
layout | "list" | "grid" | "list" |
timeline
Vertical timeline displaying records as chronological events. Sorts by dateField.
| Prop | Type | Default | Notes |
|---|---|---|---|
titleField | string | required | |
descriptionField | string | — | |
dateField | string | — | |
iconField | string | — | Per-item emoji icon. |
colorField | string | — | Per-item hex accent color. |
kanban-board
Groups records by statusField into columns. Click a card to advance status to the next column.
| Prop | Type | Default | Notes |
|---|---|---|---|
columnsJson | string (JSON) | required | Array of {value, label}. |
statusField | string | required | The schema field used to group. |
titleField | string | required | |
subtitleField | string | — | |
accentColorsJson | string (JSON) | — | Object mapping status value → hex color. |
// columnsJson
"[{\"value\":\"todo\",\"label\":\"To Do\"},{\"value\":\"in_progress\",\"label\":\"In Progress\"},{\"value\":\"done\",\"label\":\"Done\"}]"
// accentColorsJson
"{\"todo\":\"#3b82f6\",\"in_progress\":\"#f59e0b\",\"done\":\"#10b981\"}"data-table
Tabular display with columns, pagination, search, and row actions.
| Prop | Type | Default | Notes |
|---|---|---|---|
columns | Array<{key, label, sortable?, width?}> | required (min 1) | sortable defaults false. |
pageSize | number | 10 | |
searchable | boolean | false | |
actions | Array<"edit" | "delete" | "view"> | — | Built-in row-action buttons. |
defaultSort | "newest-first" | "oldest-first" | "none" | "newest-first" | Applied when the user hasn't clicked a column header. Uses createdAt desc when present, falls back to id desc (CUIDs are k-sorted by time). User column-click sort always wins; datasource-level orderBy continues to take precedence over this default. |
rowActions | Array<{label, actionId, paramsTemplate, condition?, confirm?}> | — | Custom inline action buttons rendered per row (max 5). See Row actions below. |
Row actions: per-row buttons that invoke a custom action with row data. Use for one-tap state changes — "Mark done", "Cancel booking", "Approve", "Archive". For multi-field edits use the auto-rendered Edit button (when update_<schema> is in publicAllowedActions).
{
"component": "data-table",
"dataBinding": "{{tasks}}",
"props": {
"columns": "[{\"key\":\"title\",\"label\":\"Title\"},{\"key\":\"status\",\"label\":\"Status\"}]",
"rowActions": "[{\"label\":\"Mark done\",\"actionId\":\"update_task\",\"paramsTemplate\":\"{\\\"id\\\":\\\"$row.id\\\",\\\"status\\\":\\\"done\\\",\\\"completed_at\\\":\\\"$now\\\"}\",\"condition\":\"status!=done\",\"confirm\":false}]"
}
}| Field | Type | Notes |
|---|---|---|
label | string (1-50 chars) | Button text. |
actionId | string | Action to invoke. For public apps must also be in app.publicAllowedActions. |
paramsTemplate | string (JSON, max 2000 chars) | Object literal whose values can be $row.<field> (any field on the row), $now (ISO timestamp), $today (YYYY-MM-DD), or hard-coded primitives (numbers, booleans, strings). Server still validates resolved fields against publicEditableFields for public callers. |
condition | string (max 200 chars, optional) | field op value expression — operators =, !=, >, <, >=, <=. Button only renders for rows where this is true. Omit to always show. Bad expressions default to "always show" (defensive). |
confirm | boolean (optional) | When true, click shows browser window.confirm() dialog before submitting. |
Server is the source of truth. Client-side condition is render-time UI sugar — a public user with DevTools can craft any $row.<field> value or click a hidden button. Real authorisation lives at the public route gates (publicAllowedActions + publicEditableFields + customerScoped row scope). A row-action that targets another customer's row id always returns 404 from the server, regardless of what the client UI shows.
search-bar
Search input that filters the bound data.
| Prop | Type | Default |
|---|---|---|
placeholder | string | "Search..." |
field | string | required |
cart
Shopping-cart-style accumulator over a source schema. Renders one card per source record with title / subtitle / price / image and +/– quantity controls. Items are kept in client-side session storage, so the cart survives page navigation. Use for restaurant ordering, e-commerce checkout, or batch RSVP.
submitActionId MUST point to a multiRecord action. The cart submits to either POST /api/public/{appId}/action (anonymous public page) or POST /api/apps/{appId}/proxy (owner workspace) with this exact envelope:
{
"actionId": "<multiRecord action id>",
"params": {
"parent": { "<sibling-input-field-name>": "<value>", ... },
"children": [{ "source_id": "...", "quantity": 1, "<title>": "...", "<price>": 0 }],
"total_count": 2,
"subtotal": 60
}
}Top-level keys other than actionId and params are rejected with 400 "Invalid envelope". The cart auto-injects total_count (always) and subtotal (only when priceField is configured). Pair cart with sibling text-input / select components — their values become entries on params.parent.
Computing service charge / tax / grand total: dataMapping values on the multiRecord action accept arithmetic expressions over numeric placeholders. Example mapping that writes subtotal, 10% service charge, and grand total:
{
"subtotal_hkd": "$subtotal",
"service_charge_hkd": "$subtotal * 0.1",
"total_hkd": "$subtotal * 1.1",
"item_count": "$total_count"
}Supports + - * /, parentheses, unary minus, numeric literals, and $key / $parent.field / $item.field placeholders. If any placeholder is missing or non-numeric the result is undefined and the field stores NULL.
String templates and timestamps: dataMapping values can also mix literal text with $placeholder references. Three magic tokens are auto-filled at submit time: $now (ISO timestamp string, e.g. "2026-05-05T20:11:44.859Z"), $today (date string "2026-05-05"), $timestamp (Unix milliseconds number).
{
"order_no": "WALKIN-$now",
"ref": "Order #$parent.id",
"created_at": "$now",
"order_date": "$today"
}The same missing-placeholder rule applies — if any $placeholder in a template can't be resolved, the field stores NULL rather than producing a partial-string output. A caller-supplied arg with the same name as a magic token (e.g. you submit now yourself) wins over the auto-injection.
Inline schema lookups ($lookup): dataMapping values can read from another schema at submit time using $lookup(<schema>, <whereField>=<placeholder>).<readField>. Returns the first matching row's read-field value, or null when no row matches (the field stores explicit NULL — no fallback to whatever value happened to be in the form payload).
{
"order_id": "$parent.id",
"menu_item_id": "$item.id",
"category": "$lookup(menu_items, id=$item.id).category",
"line_total": "$lookup(menu_items, id=$item.id).price * $item.quantity"
}Works with both string reads (denormalising a category) and numeric reads (combining with arithmetic for line totals or tax calculations). The same syntax can auto-link a foreign key from form data: "employee_id": "$lookup(employees, email=$customer_email).id".
Lookups are server-side and per-action cached — every unique target schema is read once per action call regardless of how many children iterate over it. There is no N+1 cost for a 100-line cart that does two lookups per child.
Public-action security: a lookup against a schema marked customerScoped: true is rejected when adding the action to publicAllowedActions, unless the schema is also in unsafePublicReadSchemas (use this opt-in only for catalog-style data — menu items, product listings — never for per-customer rows). A lookup that reads a publicHiddenFields-listed field is also rejected, because copying a redacted field into another schema would bypass the response-time redaction.
Quick reference — what works in dataMapping:
| Pattern | Example | Result |
|---|---|---|
| Single placeholder | "$parent.id" | Direct value (any type) |
| Arithmetic | "$subtotal * 1.1" | Numeric result; NULL if any operand non-numeric |
| String template | "WALKIN-$now" | Interpolated string; NULL if any placeholder missing |
| Magic token | "$now", "$today", "$timestamp" | Server-injected timestamp |
| Inline lookup | "$lookup(menu_items, id=$item.id).category" | First-match read; explicit NULL on no-match |
| Lookup + arithmetic | "$lookup(menu_items, id=$item.id).price * $item.quantity" | Lookup result feeds the arithmetic engine |
| Literal | "pending" | Stored as-is |
| Prop | Type | Default | Notes |
|---|---|---|---|
source | string | required | Schema name to read source records from, e.g. "menu_items". |
titleField | string | "name" | Field on the source record displayed as the item title. |
subtitleField | string | — | Optional secondary field shown beneath the title. |
priceField | string | — | Numeric field used for line subtotal and grand total. |
imageField | string | — | Field containing an image URL. |
submitActionId | string | required | Action id of a multiRecord action. |
submitLabel | string | "Submit Order" | |
emptyLabel | string | "No items available." | |
currencyPrefix | string | "$" | |
layout | "list" | "grid" | "grid" |
Minimal cart example (within the parent spec):
{
"type": "cart",
"props": {
"source": "menu_items",
"titleField": "name",
"subtitleField": "description",
"priceField": "price",
"imageField": "photo",
"submitActionId": "place_order",
"submitLabel": "Place Order",
"currencyPrefix": "$",
"layout": "grid"
}
}form-multi-line
Manual multi-line form for invoice-line / shopping-list use cases. Each line is an inline form built from fieldsJson. An "Add line" button appends a new blank line; a remove icon deletes one. Lines are persisted in session storage.
submitActionId MUST point to a multiRecord action — submit posts { parent: <sibling form fields>, children: [<line objects>] }. dataMapping arithmetic expressions (e.g. "line_total": "$item.price * $item.quantity") work the same way as in cart — see the cart section above.
| Prop | Type | Default | Notes |
|---|---|---|---|
fieldsJson | string (JSON) | required | Array of {name, label, type} where type is "text", "number", or "date". |
submitActionId | string | required | Action id of a multiRecord action. |
submitLabel | string | "Submit" | |
addLabel | string | "Add line" | |
minLines | number | 1 | Guarantees a starting row. |
// fieldsJson
"[{\"name\":\"item\",\"label\":\"Item\",\"type\":\"text\"},{\"name\":\"qty\",\"label\":\"Qty\",\"type\":\"number\"},{\"name\":\"due\",\"label\":\"Due\",\"type\":\"date\"}]"workflow-status
Live status badge for cross-app workflow executions. Polls the per-app workflow-execution endpoint at 2s intervals and shows the current status (queued, processing, succeeded, failed, skipped). Stops polling once the execution reaches a terminal state or after 5 minutes. Use on customer-facing order-status pages so submitters can see whether their order actually reached the kitchen / inventory / fulfilment app.
To make this component readable on a public page, the host app must:
- Set
App.publicVisibleWorkflowStatus = true(defaultfalse— opt-in). - Bind
sourceRecordIdto the customer's order id, typically via URL query (bindFromQuery) or row context.
| Prop | Type | Default | Notes |
|---|---|---|---|
sourceRecordId | string | required | The id of the source record whose workflows we're tracking. Supports {{...}} data binding. |
pollIntervalMs | number | 2000 | Min 1000. |
compact | boolean | false | Renders a smaller pill instead of the default badge + label. |
showError | boolean | true | Whether to render the (sanitized) error message under the badge for failed status. |
label | string | "Status" | Heading shown above the badge. |
targetSchemaName | string | — | Optional filter — only show executions whose target schema matches. |
Error messages on the public endpoint are sanitized to a small allowlist (Condition not met, Rate limit exceeded, Recipient unsubscribed, Workflow no longer exists) or Execution failed. Internal infrastructure errors are never exposed to public visitors.
Plugin: Calendar (3)
All calendar components bind to a data schema via dataBinding: "{{schema_name}}". They are read-only visualisations — pair them with a sibling data-table over the same schema if users need to create or edit records.
plugin:calendar/MonthView
Month-view calendar. Days with events show a colored dot and a count badge for multiple events.
| Prop | Type | Default | Notes |
|---|---|---|---|
dataBinding | string | — | "{{schema_name}}". |
dateField | string | — | Field holding the event date. |
titleField | string | — | Field shown as event title. |
color | string | — | Hex color for dots. |
height | number | — | Pixel height. |
plugin:calendar/DayView
Day schedule with hourly time slots. Best for salon, fitness, or single-day booking apps.
| Prop | Type | Default | Notes |
|---|---|---|---|
dataBinding | string | — | "{{schema_name}}". |
dateField | string | — | Date field, format YYYY-MM-DD. |
timeField | string | — | Start time, format HH:MM. |
titleField | string | — | Slot label. |
statusField | string | — | Field used for slot coloring. |
endTimeField | string | — | Optional end time, HH:MM. |
durationField | string | — | Optional duration in minutes. |
startHour | number | — | First hour shown (0–23). |
endHour | number | — | Last hour shown (0–23). |
statusColorsJson | string (JSON) | — | Object: status value → hex color. |
defaultDuration | number | — | Minutes used when neither end nor duration field is set. |
color | string | — | Default slot color. |
height | number | — | Pixel height. |
// statusColorsJson
"{\"Available\":\"#22c55e\",\"Booked\":\"#ef4444\"}"plugin:calendar/WeekView
Week grid with 7 day columns and the same time axis as DayView. Compact slot blocks per day. Best for salon, fitness, or weekly booking apps.
| Prop | Type | Default | Notes |
|---|---|---|---|
dataBinding | string | — | "{{schema_name}}". |
dateField | string | — | Date field, format YYYY-MM-DD. |
timeField | string | — | Start time, format HH:MM. |
titleField | string | — | Slot label. |
statusField | string | — | Field used for slot coloring. |
endTimeField | string | — | Optional end time, HH:MM. |
durationField | string | — | Optional duration in minutes. |
startHour | number | — | First hour shown (0–23). |
endHour | number | — | Last hour shown (0–23). |
statusColorsJson | string (JSON) | — | Object: status value → hex color. |
defaultDuration | number | — | Minutes used when neither end nor duration field is set. |
color | string | — | Default slot color. |
height | number | — | Pixel height. |
Plugin: Chart Pack (8)
All chart components bind to data via dataBinding: "{{schema_name}}". For breakdowns by category (e.g. tasks per status, sales per region), set groupBy to the grouping field and aggregate to one of "sum", "count", or "avg". Without groupBy, charts plot raw records and only render correctly when each record already has explicit label/value fields.
For time-series charts (revenue over days, signups over months) use bucket instead of groupBy. Setting bucket: "day" | "week" | "month" truncates the xField date to a UTC boundary so each bar represents a meaningful period rather than a millisecond.
plugin:chart-pack/BarChart
Vertical bar chart for comparing values across categories.
| Prop | Type | Default | Notes |
|---|---|---|---|
dataBinding | string | — | "{{schema_name}}". |
xField | string | — | Field for X axis (categories — or date when bucket is set). |
yField | string | — | Field for Y axis (values). |
groupBy | string | — | Optional categorical grouping field. |
aggregate | "sum" | "count" | "avg" | — | Required when groupBy or bucket is set. |
bucket | "day" | "week" | "month" | — | Time-bucket the date xField (UTC). Use this for time-series — never groupBy: "created_at". |
color | string | "#3b82f6" | Hex color. |
height | number | 300 | Pixels. |
Example — daily revenue:
{ "component": "plugin:chart-pack/BarChart", "props": { "dataBinding": "{{orders}}", "xField": "created_at", "yField": "total", "bucket": "day", "aggregate": "sum" } }plugin:chart-pack/PieChart
Pie or donut chart for proportions.
| Prop | Type | Default | Notes |
|---|---|---|---|
dataBinding | string | — | |
labelField | string | — | Field for slice labels. |
valueField | string | — | Field for slice values. |
groupBy | string | — | |
aggregate | "sum" | "count" | "avg" | — | |
donut | boolean | false | Render as a donut. |
height | number | 300 |
plugin:chart-pack/LineChart
Line chart for trends over a continuous axis.
Same prop shape as BarChart (dataBinding, xField, yField, groupBy, aggregate, bucket, color, height). Use bucket: "day" | "week" | "month" to time-bucket a date xField (UTC).
plugin:chart-pack/AreaChart
Filled area chart for trends. Same prop shape as BarChart.
plugin:chart-pack/GaugeChart
Semicircular gauge displaying the first record's value as a 0–100 reading. Use for KPI tiles.
| Prop | Type | Default | Notes |
|---|---|---|---|
dataBinding | string | — | |
valueField | string | "value" | Numeric field, expected 0–100. |
color | string | — | |
height | number | 200 | Pixels. |
plugin:chart-pack/ScatterChart
Scatter plot for showing correlation between two numeric fields.
| Prop | Type | Default | Notes |
|---|---|---|---|
dataBinding | string | — | |
xField | string | — | |
yField | string | — | |
color | string | — | |
height | number | — |
plugin:chart-pack/FunnelChart
Funnel for pipelines and conversion stages.
| Prop | Type | Default | Notes |
|---|---|---|---|
dataBinding | string | — | |
labelField | string | — | Stage label. |
valueField | string | — | Stage value. |
groupBy | string | — | |
aggregate | "sum" | "count" | "avg" | — | |
height | number | — |
plugin:chart-pack/CandlestickChart
Candlestick chart for financial / OHLC data. The bound records must have open, high, low, and close fields.
| Prop | Type | Default | Notes |
|---|---|---|---|
dataBinding | string | — | |
xField | string | — | Date field. |
height | number | — |
Minimal valid example
A small spec fragment using a heading, a stat card, and a data table. Use it as a starting point and replace the schema names / fields with your own.
{
"type": "container",
"props": { "maxWidth": "lg", "padding": "md" },
"children": [
{ "type": "heading", "props": { "text": "Orders", "level": "1", "icon": "📦" } },
{
"type": "stat-card",
"props": {
"label": "Open Orders",
"value": "{{orders.count}}",
"icon": "🧾",
"accentColor": "#3b82f6"
}
},
{
"type": "data-table",
"props": {
"columns": [
{ "key": "id", "label": "ID", "sortable": true },
{ "key": "customer", "label": "Customer" },
{ "key": "total", "label": "Total", "sortable": true }
],
"pageSize": 20,
"searchable": true,
"actions": ["view", "edit"]
},
"dataBinding": "{{orders}}"
}
]
}Related
| Topic | Document |
|---|---|
| Build apps via MCP | MCP Integration Guide |
| 5-minute quick start | MCP Quick Start |
| Cross-app automation | Workflows |
| Receiving external data | Webhooks |
| External API connections | API Connectors |