Web-Nothing Docs
Guides

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 type strings are case-sensitive and use kebab-case (text-input, not textInput or TextInput).
  • 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 string shown 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.

PropTypeDefaultNotes
maxWidth"sm" | "md" | "lg" | "xl" | "full""lg"
padding"none" | "sm" | "md" | "lg""md"

card

Grouped panel with optional title and accent border.

PropTypeDefaultNotes
titlestring—Optional.
borderedbooleantrue
shadowbooleantrue
accentColorstring—Hex color (e.g. "#3b82f6") for a colored top border.

column

Vertical stack of children with gap control.

PropTypeDefault
gap"none" | "sm" | "md" | "lg""md"

row

Horizontal row of children.

PropTypeDefaultNotes
gap"none" | "sm" | "md" | "lg""md"
align"start" | "center" | "end" | "stretch""stretch"
wrapbooleantrueWrap onto multiple lines on narrow screens.

tabs

Tabbed container — children are tab panels in labels order.

PropTypeDefaultNotes
defaultTabnumber0Index of the initially active tab.
labelsstring[] 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.

PropTypeDefault
columnsnumber3
gap"none" | "sm" | "md" | "lg""md"

Left-sidebar navigation shell with switchable page panels. Use as the root component of an app.

PropTypeDefaultNotes
appTitlestringrequiredShown at the top of the sidebar.
appIconstring—Optional emoji shown next to the title.
navItemsJsonstring (JSON)requiredSee shape below.
width"narrow" | "default" | "wide""default"
activeIndexnumber0Index 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.

PropTypeDefaultNotes
itemsJsonstring (JSON)requiredArray of {title, icon?}.
allowMultiplebooleanfalseIf true, multiple panels can be open at once.
defaultOpennumber0Index 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) — when true and 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.

PropTypeDefaultNotes
labelstringrequired
fieldstringrequiredForm field name.
placeholderstring—
requiredbooleanfalse
bindFromQuerystring—URL param name. See note above.
bindReadOnlyboolean—Lock the field when seeded.

number-input

Numeric input.

PropTypeDefaultNotes
labelstringrequired
fieldstringrequired
placeholderstring—
minnumber—
maxnumber—
stepnumber—
bindFromQuerystring—Non-numeric / missing URL values are ignored.
bindReadOnlyboolean—

select

Dropdown with predefined options.

PropTypeDefaultNotes
labelstringrequired
fieldstringrequired
optionsArray<{label, value}>optionalWhen omitted, options are auto-derived from the schema field if its type is "enum". See "enum field auto-derive" below.
placeholderstring—
bindFromQuerystring—
bindReadOnlyboolean—

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.

PropTypeDefaultNotes
labelstringrequired
fieldstringrequired
bindFromQuerystring—URL values "1", "true", "yes" map to checked; anything else maps to unchecked.
bindReadOnlyboolean—

date-picker

Date / datetime / time picker (HTML5 native — <input type="date|datetime-local|time">).

PropTypeDefaultNotes
labelstringrequired
fieldstringrequired
mode"date" | "datetime" | "time"optionalExplicit 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.
includeTimebooleanfalseLegacy boolean (kept for back-compat). Superseded by mode. When mode is set, includeTime is ignored.
bindFromQuerystring—URL value must match YYYY-MM-DD, YYYY-MM-DDTHH:MM[:SS], or HH:MM[:SS].
bindReadOnlyboolean—

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]/availability on mount.
  • Refetches when the configured partitionField value 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 / max HTML attributes from minLeadTimeMinutes / maxLeadTimeDays so 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.

PropTypeDefault
labelstringrequired
variant"primary" | "secondary" | "danger" | "ghost""primary"
size"sm" | "md" | "lg""md"
disabledbooleanfalse

External payment button — opens an https:// URL in a new tab. Suitable for Stripe Payment Links, PayPal, PayMe, FPS, etc.

PropTypeDefaultNotes
labelstringrequiredButton text.
urlstringrequiredMust start with https://.
amountstring—Display amount, e.g. "HK$500", "£29.99".
iconstring—Emoji icon, e.g. "💳".
variant"primary" | "secondary""primary"
size"sm" | "md" | "lg""md"

textarea

Multi-line text input.

PropTypeDefault
labelstringrequired
fieldstringrequired
placeholderstring—
rowsnumber4

toggle

On/off switch.

PropTypeDefault
labelstringrequired
fieldstringrequired
size"sm" | "md" | "lg""md"

radio-group

Radio button group.

PropTypeDefaultNotes
labelstringrequired
fieldstringrequired
optionsJsonstring (JSON)requiredArray of {label, value}.
layout"horizontal" | "vertical""vertical"
"[{\"label\":\"Yes\",\"value\":\"y\"},{\"label\":\"No\",\"value\":\"n\"}]"

slider

Range slider.

PropTypeDefault
labelstringrequired
fieldstringrequired
minnumber0
maxnumber100
stepnumber1
showValuebooleantrue

barcode-scanner

Camera-based barcode/QR scanner. Falls back to manual text input when the camera is unavailable.

PropTypeDefaultNotes
labelstringrequired
fieldstringrequiredForm field that receives the decoded value.
formatenum (see below)"all"
scannerHeightstring—CSS length, e.g. "200px", "40vh". Pattern: ^\d+(\.\d+)?(px|vh|vw|rem|em|%)$.
placeholderstring—
beepOnScanbooleantrue
autoSubmitbooleanfalse

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).

PropTypeDefault
labelstringrequired
fieldstringrequired
placeholderstring—

Display (14)

heading

Heading text (h1–h4).

PropTypeDefaultNotes
textstringrequired
level"1" | "2" | "3" | "4""2"String, not number.
iconstring—Optional emoji rendered before the text on h1.

text

Paragraph or inline text.

PropTypeDefault
contentstringrequired
size"sm" | "md" | "lg""md"
color"default" | "muted" | "success" | "warning" | "error""default"

badge

Small colored tag.

PropTypeDefault
textstringrequired
variant"default" | "success" | "warning" | "error" | "info""default"

image

Image with src and alt text.

PropTypeDefault
srcstringrequired
altstringrequired
widthnumber—
heightnumber—
roundedbooleanfalse

stat-card

Metric card with label, value, and optional change indicator.

PropTypeDefaultNotes
labelstringrequired
valuestringrequiredSupports {{expression}} data binding.
changestring—Optional change text, e.g. "+12%".
changeType"positive" | "negative" | "neutral"—
iconstring—Emoji, e.g. "📊".
accentColorstring—Hex color for the left border.
prefixstring—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:

TokenMeaning (UTC)
$nowCurrent epoch ms
$todayMidnight at the start of today
$thisWeekMonday 00:00 of the current ISO 8601 week
$thisMonthDay 1 00:00 of the current month
$thisYearJan 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.

PropTypeDefault
labelstring—
spacing"sm" | "md" | "lg""md"

avatar

User avatar circle with initials or image, optional status dot.

PropTypeDefault
namestringrequired
srcstring—
size"sm" | "md" | "lg""md"
status"online" | "offline" | "busy" | "away"—

progress-ring

Circular progress indicator.

PropTypeDefaultNotes
valuestringrequiredNumber, or {{expression}} binding.
maxnumber100
labelstring—
size"sm" | "md" | "lg""md"
colorstring—Hex color.

empty-state

Placeholder for empty data views.

PropTypeDefault
iconstring—
titlestringrequired
descriptionstring—

alert

Colored alert banner.

PropTypeDefault
titlestringrequired
descriptionstring—
variant"info" | "success" | "warning" | "error""info"
iconstring—

progress-bar

Horizontal progress bar.

PropTypeDefaultNotes
valuestringrequiredNumber, or {{expression}} binding.
maxnumber100
labelstring—
colorstring—Hex color.
size"sm" | "md" | "lg""md"

list

Static list of items.

PropTypeDefaultNotes
itemsJsonstring (JSON)requiredArray of {text, icon?}.
variant"plain" | "bordered" | "striped""bordered"
"[{\"text\":\"Step 1: Sign up\",\"icon\":\"✅\"},{\"text\":\"Step 2: Invite team\"}]"

rating

Star rating display.

PropTypeDefaultNotes
valuestringrequiredNumber, or {{expression}} binding.
maxnumber5
size"sm" | "md" | "lg""md"
colorstring—Hex color for filled stars.

tag-list

Horizontal row of colored tags.

PropTypeDefaultNotes
tagsJsonstring (JSON)requiredArray 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.

typeStoresValidated on writeNotes
stringShort single-line textStored as-isThe default catch-all for names, labels, references.
textLong multi-line textStored as-isUse for descriptions, notes, addresses. Pairs with textarea.
numberNumeric valueMust parse as a number (else 400)Numeric strings are coerced to numbers.
booleanTrue / false"true" / "1" coerce to truePairs with checkbox / toggle.
emailEmail addressMust contain @ (else 400)Lightweight format check only.
urlURLStored as-isNo scheme enforcement on plain url fields.
imageImage URLMust be https:// and ≤ 2048 chars (else 400)Pairs with image-upload. Image is reachable by anyone with the link — not for confidential files.
dateCalendar dateMust match YYYY-MM-DD and be a real date (else 400)Pairs with date-picker (mode: "date").
datetimeDate + timeMust 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).
timeTime of dayMust match HH:MM[:SS] in range 00–23:00–59[:00–59] (else 400)Pairs with date-picker (mode: "time").
relationReference to another schema's recordStored as-isSet relationTo: "<other schema name>" to declare the target.
enumOne value from a fixed setMust 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:

KeyApplies toTypeDefaultMeaning
nameallstringrequiredThe field key used in records and component bindings.
typeallone of the 12 aboverequiredSee table above.
requiredallbooleantrueWhether the field is mandatory. Defaults to true — set false explicitly for optional fields.
optionsenumstring[]required for enumThe allowed value set. Must be non-empty; each value unique; up to 50 options, each ≤ 64 chars.
relationTorelationstring—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.

PropTypeDefaultNotes
titleFieldstringrequiredField used as the main title.
subtitleFieldstring—Optional secondary text.
badgeFieldstring—Field shown as a badge.
avatarFieldstring—Field used for avatar initials.
layout"list" | "grid""list"

timeline

Vertical timeline displaying records as chronological events. Sorts by dateField.

PropTypeDefaultNotes
titleFieldstringrequired
descriptionFieldstring—
dateFieldstring—
iconFieldstring—Per-item emoji icon.
colorFieldstring—Per-item hex accent color.

kanban-board

Groups records by statusField into columns. Click a card to advance status to the next column.

PropTypeDefaultNotes
columnsJsonstring (JSON)requiredArray of {value, label}.
statusFieldstringrequiredThe schema field used to group.
titleFieldstringrequired
subtitleFieldstring—
accentColorsJsonstring (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.

PropTypeDefaultNotes
columnsArray<{key, label, sortable?, width?}>required (min 1)sortable defaults false.
pageSizenumber10
searchablebooleanfalse
actionsArray<"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.
rowActionsArray<{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}]"
  }
}
FieldTypeNotes
labelstring (1-50 chars)Button text.
actionIdstringAction to invoke. For public apps must also be in app.publicAllowedActions.
paramsTemplatestring (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.
conditionstring (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).
confirmboolean (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 input that filters the bound data.

PropTypeDefault
placeholderstring"Search..."
fieldstringrequired

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:

PatternExampleResult
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
PropTypeDefaultNotes
sourcestringrequiredSchema name to read source records from, e.g. "menu_items".
titleFieldstring"name"Field on the source record displayed as the item title.
subtitleFieldstring—Optional secondary field shown beneath the title.
priceFieldstring—Numeric field used for line subtotal and grand total.
imageFieldstring—Field containing an image URL.
submitActionIdstringrequiredAction id of a multiRecord action.
submitLabelstring"Submit Order"
emptyLabelstring"No items available."
currencyPrefixstring"$"
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.

PropTypeDefaultNotes
fieldsJsonstring (JSON)requiredArray of {name, label, type} where type is "text", "number", or "date".
submitActionIdstringrequiredAction id of a multiRecord action.
submitLabelstring"Submit"
addLabelstring"Add line"
minLinesnumber1Guarantees 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:

  1. Set App.publicVisibleWorkflowStatus = true (default false — opt-in).
  2. Bind sourceRecordId to the customer's order id, typically via URL query (bindFromQuery) or row context.
PropTypeDefaultNotes
sourceRecordIdstringrequiredThe id of the source record whose workflows we're tracking. Supports {{...}} data binding.
pollIntervalMsnumber2000Min 1000.
compactbooleanfalseRenders a smaller pill instead of the default badge + label.
showErrorbooleantrueWhether to render the (sanitized) error message under the badge for failed status.
labelstring"Status"Heading shown above the badge.
targetSchemaNamestring—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.

PropTypeDefaultNotes
dataBindingstring—"{{schema_name}}".
dateFieldstring—Field holding the event date.
titleFieldstring—Field shown as event title.
colorstring—Hex color for dots.
heightnumber—Pixel height.

plugin:calendar/DayView

Day schedule with hourly time slots. Best for salon, fitness, or single-day booking apps.

PropTypeDefaultNotes
dataBindingstring—"{{schema_name}}".
dateFieldstring—Date field, format YYYY-MM-DD.
timeFieldstring—Start time, format HH:MM.
titleFieldstring—Slot label.
statusFieldstring—Field used for slot coloring.
endTimeFieldstring—Optional end time, HH:MM.
durationFieldstring—Optional duration in minutes.
startHournumber—First hour shown (0–23).
endHournumber—Last hour shown (0–23).
statusColorsJsonstring (JSON)—Object: status value → hex color.
defaultDurationnumber—Minutes used when neither end nor duration field is set.
colorstring—Default slot color.
heightnumber—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.

PropTypeDefaultNotes
dataBindingstring—"{{schema_name}}".
dateFieldstring—Date field, format YYYY-MM-DD.
timeFieldstring—Start time, format HH:MM.
titleFieldstring—Slot label.
statusFieldstring—Field used for slot coloring.
endTimeFieldstring—Optional end time, HH:MM.
durationFieldstring—Optional duration in minutes.
startHournumber—First hour shown (0–23).
endHournumber—Last hour shown (0–23).
statusColorsJsonstring (JSON)—Object: status value → hex color.
defaultDurationnumber—Minutes used when neither end nor duration field is set.
colorstring—Default slot color.
heightnumber—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.

PropTypeDefaultNotes
dataBindingstring—"{{schema_name}}".
xFieldstring—Field for X axis (categories — or date when bucket is set).
yFieldstring—Field for Y axis (values).
groupBystring—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".
colorstring"#3b82f6"Hex color.
heightnumber300Pixels.

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.

PropTypeDefaultNotes
dataBindingstring—
labelFieldstring—Field for slice labels.
valueFieldstring—Field for slice values.
groupBystring—
aggregate"sum" | "count" | "avg"—
donutbooleanfalseRender as a donut.
heightnumber300

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.

PropTypeDefaultNotes
dataBindingstring—
valueFieldstring"value"Numeric field, expected 0–100.
colorstring—
heightnumber200Pixels.

plugin:chart-pack/ScatterChart

Scatter plot for showing correlation between two numeric fields.

PropTypeDefaultNotes
dataBindingstring—
xFieldstring—
yFieldstring—
colorstring—
heightnumber—

plugin:chart-pack/FunnelChart

Funnel for pipelines and conversion stages.

PropTypeDefaultNotes
dataBindingstring—
labelFieldstring—Stage label.
valueFieldstring—Stage value.
groupBystring—
aggregate"sum" | "count" | "avg"—
heightnumber—

plugin:chart-pack/CandlestickChart

Candlestick chart for financial / OHLC data. The bound records must have open, high, low, and close fields.

PropTypeDefaultNotes
dataBindingstring—
xFieldstring—Date field.
heightnumber—

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}}"
    }
  ]
}

TopicDocument
Build apps via MCPMCP Integration Guide
5-minute quick startMCP Quick Start
Cross-app automationWorkflows
Receiving external dataWebhooks
External API connectionsAPI Connectors

On this page