SalesOS

Page builder

Compose record pages, app pages and home pages from fields, related lists, charts, list views, text, flows and installed apps — no code, versioned, assigned per profile.

The page builder composes whole pages out of components. Org owners and admins (or a system admin) use it under Settings → Page builder; it is part of app_platform (Professional). An organization that hasn't assigned a builder page sees every page exactly as SalesOS ships it.

There are two kinds of page:

  • Record pages — the page for one object: LEAD, CONTACT, ACCOUNT, OPPORTUNITY or custom:<apiName>. Regions: header (above the tabs), tabs (at least one; only the open tab is mounted) and sidebar (the rail beside the tabs).
  • App pages — your own pages at /dashboard/p/<slug>, for dashboards, team hubs and launch pads. Regions: header, main and sidebar. An app page can be listed in the navigation and can replace /dashboard as the home page for chosen profiles.

Components

TypePagesWhat it shows
field_sectionrecordChosen fields in one or two columns, read-only or editable in place
metricsrecordUp to six headline figures from the record
record_briefrecordA short summary block from up to four fields (not AI)
related_listrecordContacts, deals, tasks, cases, quotes or orders of the record, with columns, a filter, sort and a row limit
custom_related_listrecordCustom object records that look up the record
activity_timelinerecordActivities, notes and field changes (standard objects)
builtin_tabrecordOne of the page's own tabs, exactly as today
builtin_railrecord (sidebar)The page's own field rail, with inline editing
rich_textanyFormatted text: paragraphs, emphasis, headings, lists, links
report_chartanyA widget from a dashboard shared with the org or a team
list_viewanyA few rows of a list view (system or shared saved view) with a link to the rest
flow_launcheranyStarts a screen flow, as a button or inline
quick_actionsanyButtons: edit the record, run a custom action, start a flow
app_slotanyAn installed app's extension: record.tab / record.sidePanel on record pages, dashboard.widget on app pages

A new record page starts as the page it replaces — every built-in tab as a builtin_tab and the rail as builtin_rail, or the arrangement of an existing page layout — so nothing changes until you change it.

Visibility

Every component and tab can carry visibility:

  • profiles — profile ids; none means everyone.
  • formFactor — desktop, mobile or all (the md breakpoint, 768px).
  • conditions — record pages only: { field, operator, value } with eq, neq, contains, gt, lt, is_empty, is_not_empty, combined by match: all | any.

Profile rules are applied on the server: a viewer never receives components meant for other profiles. Form factor and conditions are evaluated in the browser against the record as the viewer received it. A component whose condition names a field the viewer can't read is removed on the server, so a condition can't reveal a hidden value.

Field-level security

A page can't reveal a field. When a page resolves, every field the viewer's profile can't read is taken out of sections, tiles, briefs and list columns, and the records themselves still arrive with those fields stripped by the API.

The editor's preview renders with the admin's own access and flags each placed field that some profile can't read ("Hidden from Sales: Annual revenue"), so you can see what those people will miss.

Which page a person gets

Record pages are assigned per object × profile, optionally × record type: choose a picklist field of the object to act as the record type, then assign pages to its values. For a viewer, the first match wins:

  1. each of the viewer's profiles, in assignment order: with the record's type, then for any type;
  2. the organization default (no profile): with the record's type, then any;
  3. otherwise the page as it is today — the existing page layout, if any, and installed apps.

The record type is read only when the viewer can see the record and read the field. Assignments take effect once the page has an active version.

Versions

A page has at most one open draft and one active version. Saving validates the whole definition against your organization (fields, relations, profiles, charts, views, installed extensions) and rejects anything that doesn't resolve. Activate publishes the draft and archives the previous version; Revert copies an archived version into a new active one. History keeps every version.

The builder

Everything is operable from the keyboard: choose a region with Add here, add components from the palette, select one to edit its properties, and use Move up / Move down / Move to… to rearrange — each move is announced, and focus stays on the moved component. Undo and Redo (Ctrl/⌘ + Z, Ctrl + Y or ⌘ + Shift + Z) cover every change since the page was opened. The Preview tab renders the page for a sample record at desktop or phone width.

Definition format

{
  "version": 1,
  "regions": {
    "header": [
      { "id": "kpis", "type": "metrics", "props": { "tiles": [{ "field": "annualRevenue", "format": "currency" }] } }
    ],
    "tabs": [
      {
        "id": "overview",
        "label": "Overview",
        "components": [
          { "id": "details", "type": "field_section", "props": { "title": "Details", "fields": ["industry", "cf:tier"], "columns": 2, "mode": "edit" } },
          { "id": "deals", "type": "related_list", "props": { "relation": "opportunities", "columns": ["name", "stage", "amount"], "filters": [{ "field": "open", "value": "true" }], "limit": 10 } },
          { "id": "tab-overview", "type": "builtin_tab", "props": { "tabId": "overview" } }
        ]
      }
    ],
    "sidebar": [{ "id": "rail", "type": "builtin_rail", "props": {} }],
    "main": []
  }
}

Custom fields on standard objects are cf:<apiName>. Limits: 60 components per page, 20 per region, 12 per tab, 12 tabs, 40 fields per section, 8 columns, 3 filters, 5 conditions, 20,000 characters of rich text. Rich text is rebuilt from an allow-list on save (links keep only an http(s), mailto: or relative href) and sanitised again when shown.

API

Session only (not API keys or OAuth tokens); use the metadata API to manage pages programmatically.

MethodPathWho
GET/v1/page-builder/resolve/record/:objectType?recordId=any member
GET/v1/page-builder/resolve/app/:slugthe page's audience, org owners/admins
GET/v1/page-builder/resolve/homeany member
GET/v1/page-builder/navany member
GET/v1/page-builder/meta?objectType=org OWNER/ADMIN, system ADMIN
POST/v1/page-builder/previeworg OWNER/ADMIN, system ADMIN
GET POST/v1/page-builder/pagesorg OWNER/ADMIN, system ADMIN
GET PATCH DELETE/v1/page-builder/pages/:idorg OWNER/ADMIN, system ADMIN
PUT DELETE/v1/page-builder/pages/:id/draftorg OWNER/ADMIN, system ADMIN
GET/v1/page-builder/pages/:id/versionsorg OWNER/ADMIN, system ADMIN
PUT/v1/page-builder/pages/:id/assignmentsorg OWNER/ADMIN, system ADMIN
GET/v1/page-builder/versions/:idorg OWNER/ADMIN, system ADMIN
POST/v1/page-builder/versions/:id/activate · /revertorg OWNER/ADMIN, system ADMIN
PUT/v1/page-builder/objects/:objectType/settingsorg OWNER/ADMIN, system ADMIN

A resolve with no assigned page answers { "page": null, "source": "default" }. recordTypeSensitive: true means the answer depends on the record, so ask again with recordId.

Metadata

Builder pages travel in change sets and the metadata source format:

  • PAGE — pages/<apiName>.page.json: apiName, label, description, objectType, recordTypeField, assignments ({ profile, recordType }, null for everyone / any) and definition.
  • APP_PAGE — app-pages/<apiName>.apppage.json: apiName, label, description, slug, icon, showInNav, navOrder, audience and home (profile names; null for everyone else) and definition.

In source, app extensions are written app/<appSlug>:<key>, profiles by name and custom actions by apiName, so a page resolves in the target org. A deploy creates a new active version only when the definition changed; extensions the target hasn't installed and custom actions it lacks are left out with a warning. PAGE_LAYOUT components keep working as before.

On this page