A page layout controls how a record’s detail page is arranged: which tabs appear and what widgets they contain. Use definePageLayout() to declare a layout for an object you own, or definePageLayoutTab() to add a single tab to a layout that already exists (yours or a standard Twenty one).

definePageLayout

Use this when you own the entire detail page — typically for a custom object you defined yourself.
src/page-layouts/example-record-page-layout.ts

Key points

  • type is one of 'RECORD_INDEX', 'RECORD_PAGE', 'DASHBOARD' or 'STANDALONE_PAGE'. Use 'RECORD_PAGE' to customize the detail view of a specific object.
  • objectUniversalIdentifier specifies which object this layout applies to.
  • Each tab defines a section of the page with a title, position, and layoutMode: VERTICAL_LIST for record pages and standalone pages, GRID for dashboards, and CANVAS for a single widget that should fill the tab viewport. A VERTICAL_LIST tab stacks widgets vertically. Built-in widgets that manage their own scrolling, such as timelines, files, notes, tasks, and workflows, fill one viewport; fields, front components, graphs, and other fit-content widgets render at their content or configured height. A GRID tab always lays its widgets out as cards on a 12-column grid. A CANVAS widget has no explicit position; if a canvas tab contains multiple widgets, they render at their content height instead of filling the viewport.
  • Set layoutMode explicitly. Omitting it gives you VERTICAL_LIST on a STANDALONE_PAGE and GRID everywhere else, which is rarely what you want on a record page.
  • Each widget inside a tab can render a front component, a relation list, or other built-in widget types.
  • A FRONT_COMPONENT widget can set headerCommandMenuItemUniversalIdentifiers to an ordered array of command menu item universal identifiers from the same app. These actions appear as icon buttons in the widget card header and keep their command-level availability and permission checks. Identifiers must be unique and must resolve when the app is installed.
  • position on tabs controls their order. Use higher values (e.g., 50) to place custom tabs after built-in ones.

Field widgets

A FIELD widget renders one field of the record. For relation fields it can also embed a list of related records:
  • fieldMetadataId takes the universal identifier of a field on the layout’s object.
  • fieldDisplayMode is one of 'FIELD', 'CARD', 'EDITOR', 'VIEW' or 'TABLE'. TABLE embeds a view listing the records of a one-to-many relation field.
  • nestedRelationFieldMetadataId is optional and takes the universal identifier of a one-to-many relation field on the relation target object, to list records two relation hops away (e.g. a Company page listing the opportunities of the company’s people, or a Person page listing the opportunities of the person’s company). The first hop can be a one-to-many or a many-to-one relation field, the second must be one-to-many (junction relations are not supported), and it requires fieldDisplayMode: 'TABLE' — combining it with any other display mode is a validation error, since a nested widget always renders as an embedded view.

definePageLayoutTab

Use this when you only want to add a tab to an existing layout — for example, an analytics tab on the standard Company page, or an AI summary tab attached to your own object’s layout.
src/page-layouts/example-extra-tab.ts

Key points

  • pageLayoutUniversalIdentifier is required and must point to a page layout that already exists at install time — either a standard Twenty layout or one defined by your own app. Cross-app references to layouts owned by another installed app are not supported today. When the parent layout is missing, installation fails with a clear validation error.
  • For standard Twenty layouts, import identifiers from twenty-sdk/define:
    Each layout entry also exposes its tabs and their widgets, so you can reference any level:
    A short alias STANDARD_PAGE_LAYOUT is also available:
  • widgets are scoped to this tab only — they reference front components, views, etc. exactly like widgets defined inline in definePageLayout.
  • position controls ordering against existing tabs on the targeted layout. Pick a value that places your tab where you want it relative to built-in tabs.
  • Use this instead of definePageLayout when you only want to add to an existing layout. Use definePageLayout when you own the entire layout.