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
typeis one of'RECORD_INDEX','RECORD_PAGE','DASHBOARD'or'STANDALONE_PAGE'. Use'RECORD_PAGE'to customize the detail view of a specific object.objectUniversalIdentifierspecifies which object this layout applies to.- Each
tabdefines a section of the page with atitle,position, andlayoutMode:VERTICAL_LISTfor record pages and standalone pages,GRIDfor dashboards, andCANVASfor a single widget that should fill the tab viewport. AVERTICAL_LISTtab 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. AGRIDtab always lays its widgets out as cards on a 12-column grid. ACANVASwidget has no explicit position; if a canvas tab contains multiple widgets, they render at their content height instead of filling the viewport. - Set
layoutModeexplicitly. Omitting it gives youVERTICAL_LISTon aSTANDALONE_PAGEandGRIDeverywhere else, which is rarely what you want on a record page. - Each
widgetinside a tab can render a front component, a relation list, or other built-in widget types. - A
FRONT_COMPONENTwidget can setheaderCommandMenuItemUniversalIdentifiersto 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. positionon tabs controls their order. Use higher values (e.g., 50) to place custom tabs after built-in ones.
Field widgets
AFIELD widget renders one field of the record. For relation fields it can also embed a list of related records:
fieldMetadataIdtakes the universal identifier of a field on the layout’s object.fieldDisplayModeis one of'FIELD','CARD','EDITOR','VIEW'or'TABLE'.TABLEembeds a view listing the records of a one-to-many relation field.nestedRelationFieldMetadataIdis 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 requiresfieldDisplayMode: '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
-
pageLayoutUniversalIdentifieris 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 itstabsand theirwidgets, so you can reference any level:A short aliasSTANDARD_PAGE_LAYOUTis also available: -
widgetsare scoped to this tab only — they reference front components, views, etc. exactly like widgets defined inline indefinePageLayout. -
positioncontrols 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
definePageLayoutwhen you only want to add to an existing layout. UsedefinePageLayoutwhen you own the entire layout.