Skip to main content

Pages

ui/pages.yaml declares the pages of your solution UI. A page binds a title, a layout and an ordered list of widget placements. The portal's layout engine and widget registry render it — there is no page-level code.

Page definitions

ui/pages.yaml
- id: dashboard
title: Dashboard
layout: dashboard-grid
widgets:
- { widget: active_orders, span: 3 }
- { widget: revenue_chart, span: 9 }
- { widget: order_table, span: 12 }

- id: orders
title: Orders
layout: split-grid
widgets:
- { widget: order_table, span: 8 }
- { widget: orders_timeline, span: 4 }
FieldTypeRequiredDescription
idstringYesPage id; referenced by navigation entries and routes.
titlestringYesPage header and tab label.
layoutstring or objectYesLayout preset id from layouts.yaml, or an inline layout.
widgetslistYesOrdered widget placements (at least one).

Widget placements

FieldTypeRequiredDescription
widgetstringYesWidget id declared in widgets.yaml.
spannumberNoGrid columns occupied (1–12). Defaults to full width.

Placements render in declaration order, wrapping left-to-right, top-to-bottom. restaurant-pro's dashboard places the active_orders metric (3 columns) beside the revenue_chart line chart (9 columns), with the full-width order_table underneath.

Referencing layouts

Prefer named presets from layouts.yaml:

layout: dashboard-grid

Inline layouts are accepted for one-off pages:

layout:
type: grid
columns: 12

Both forms enforce the same rules: the layout type must be grid, and columns must be between 1 and 12.

Validation and clamping

  • At publish time: unknown widget ids, spans larger than the layout's column count, non-grid layouts and out-of-range column counts are rejected. See Validation.
  • At render time: the layout engine defensively clamps spans into range and coerces shapes, so a degraded definition renders as a scoped error card instead of crashing the portal.

Responsive behavior

On narrow screens every placement stacks into a single column in declaration order; at ≥ 1024 px the grid uses the declared column count. Design dashboards so the stacked order still tells a story (headline metrics first, details last).

Data wiring

Pages never fetch data. Every widget references a source from sources.yaml, and each fetch is capability-gated: a widget whose capability is not granted never fires a request. The page itself renders immediately with loading states.

Host pages (CRM and Automation)

Marketplace Apps can open platform hosts instead of a widget layout. From restaurant-pro-runtime / real-estate-runtime:

- id: contacts
title: Contacts
host: contacts
- id: automations
title: Automations
host: automations
hostSurface
contactsPerson CRM (not an app-local contacts table)
automationsCRM Automation (e.g. reservation.created → Send WhatsApp)

These are not package folders. See Events.

Guidelines

  • One page per operational question ("what's happening now?", "what did we sell?") rather than per entity.
  • Keep dashboards under 8 placements; split detail views into separate pages.
  • Reuse widgets across pages — restaurant-pro-runtime uses the same reservations_table on Today and Reservations with different spans.
  • Every page referenced by navigation.yaml must exist; every page should be reachable from navigation.

Restaurant Pro Runtime page list

PageLayoutPlacements
Todaydashboard-gridintro + reservations table
Reservationssplit-gridform + table
Tables / Menusplit-gridentity tables
ContactshostPerson CRM
AutomationshostCRM Automation

The full definitions are in the restaurant-pro-runtime example. SDK takeaway pages remain in restaurant-pro.