> ## Documentation Index
> Fetch the complete documentation index at: https://twenty-claude-cool-pascal-5ay683.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Page Layouts

> Customize record detail pages — tabs, widgets, and where front components render — using definePageLayout and definePageLayoutTab.

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

| Use case                                                               | Entity                |
| ---------------------------------------------------------------------- | --------------------- |
| Define the entire layout for a record page on an object you own        | `definePageLayout`    |
| Add one tab to an existing layout (your own object, or a standard one) | `definePageLayoutTab` |

## definePageLayout

Use this when you own the entire detail page — typically for a custom object you defined yourself.

```ts src/page-layouts/example-record-page-layout.ts theme={null}
import { definePageLayout, PageLayoutTabLayoutMode } from 'twenty-sdk/define';
import { EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER } from '../objects/example-object';
import { HELLO_WORLD_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER } from '../front-components/hello-world';

export default definePageLayout({
  universalIdentifier: '203aeb94-6701-46d6-9af1-be2bbcc9e134',
  name: 'Example Record Page',
  type: 'RECORD_PAGE',
  objectUniversalIdentifier: EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER,
  tabs: [
    {
      universalIdentifier: '6ed26b60-a51d-4ad7-86dd-1c04c7f3cac5',
      title: 'Hello World',
      position: 50,
      icon: 'IconWorld',
      layoutMode: PageLayoutTabLayoutMode.VERTICAL_LIST,
      widgets: [
        {
          universalIdentifier: 'aa4234e0-2e5f-4c02-a96a-573449e2351d',
          title: 'Hello World',
          type: 'FRONT_COMPONENT',
          configuration: {
            configurationType: 'FRONT_COMPONENT',
            frontComponentUniversalIdentifier:
              HELLO_WORLD_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER,
          },
        },
      ],
    },
  ],
});
```

### Key points

* `type` is one of `'RECORD_INDEX'`, `'RECORD_PAGE'`, `'DASHBOARD'`, `'STANDALONE_PAGE'` or `'RECORD_FORM'`. Use `'RECORD_PAGE'` to customize the detail view of a specific object. `'RECORD_FORM'` describes the creation form of an object and only accepts `FORM_FIELD` widgets, each rendering a single field; conversely, every other widget type is rejected in a `'RECORD_FORM'` layout. Twenty does not render record forms yet.
* `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 coordinates and can omit `position`; Twenty normalizes it to `{ layoutMode: PageLayoutTabLayoutMode.CANVAS }` from the tab layout mode. If a canvas tab contains multiple widgets, they render at their content height instead of filling the viewport.
* A widget `position` matches its tab layout mode. `VERTICAL_LIST` uses `{ layoutMode: PageLayoutTabLayoutMode.VERTICAL_LIST, index }`, `GRID` uses `{ layoutMode: PageLayoutTabLayoutMode.GRID, row, column, rowSpan, columnSpan }`, and `CANVAS` uses `{ layoutMode: PageLayoutTabLayoutMode.CANVAS }` or omits `position`.
* 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](/developers/extend/apps/layout/front-components), 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 current record. For relation fields, it can also embed a view of records related to the current record.

To embed a view that is not scoped to the current record, use a [record table widget](#record-table-widgets).

```ts theme={null}
{
  universalIdentifier: 'c1c2c3c4-c5c6-4000-8000-000000000003',
  title: 'People → Opportunities',
  type: 'FIELD',
  configuration: {
    configurationType: 'FIELD',
    fieldMetadataId: PEOPLE_FIELD_UNIVERSAL_IDENTIFIER,
    fieldDisplayMode: 'TABLE',
    nestedRelationFieldMetadataId: OPPORTUNITIES_FIELD_UNIVERSAL_IDENTIFIER,
  },
}
```

* `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.
* `isUIEditable` applies to relation fields in `TABLE` display mode. It defaults to `true`; set it to `false` to prevent editing records from the embedded table or kanban.
* `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.

### Record table widgets

A `RECORD_TABLE` widget embeds a saved view on a dashboard, record page, or standalone page. The saved view determines the records, fields, filters, sorts, and layout shown in the widget.

```ts theme={null}
{
  universalIdentifier: 'd1d2d3d4-d5d6-4000-8000-000000000004',
  title: 'Open opportunities',
  type: 'RECORD_TABLE',
  configuration: {
    configurationType: 'RECORD_TABLE',
    viewUniversalIdentifier: OPPORTUNITIES_VIEW_UNIVERSAL_IDENTIFIER,
    recordLimit: 8,
    isUIEditable: false,
  },
}
```

* `viewUniversalIdentifier` identifies the view to display.
* `recordLimit` is optional and caps how many records the widget displays.
* `isUIEditable` applies to table and kanban views. It enables table cell editing or kanban card editing, moving, and creation. It defaults to `true` on `RECORD_PAGE` layouts and `false` on other layouts.
* `isUIEditable` controls UI actions only. Record permissions still apply, and view settings remain editable only in the page layout editor.

Workspace admins can configure the same behavior with **Allow editing** in the [dashboard widget](/user-guide/dashboards/capabilities/widgets#editing-records) and [record page](/user-guide/layout/capabilities/record-pages#editing-related-records) editors.

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

```ts src/page-layouts/example-extra-tab.ts theme={null}
import {
  definePageLayoutTab,
  PageLayoutTabLayoutMode,
  STANDARD_PAGE_LAYOUT_UNIVERSAL_IDENTIFIERS,
} from 'twenty-sdk/define';
import { HELLO_WORLD_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER } from '../front-components/hello-world';

export default definePageLayoutTab({
  universalIdentifier: 'b1b2b3b4-b5b6-4000-8000-000000000001',
  pageLayoutUniversalIdentifier:
    STANDARD_PAGE_LAYOUT_UNIVERSAL_IDENTIFIERS.companyRecordPage
      .universalIdentifier,
  title: 'Hello World',
  position: 1000,
  icon: 'IconWorld',
  layoutMode: PageLayoutTabLayoutMode.VERTICAL_LIST,
  widgets: [
    {
      universalIdentifier: 'b1b2b3b4-b5b6-4000-8000-000000000002',
      title: 'Hello World',
      type: 'FRONT_COMPONENT',
      configuration: {
        configurationType: 'FRONT_COMPONENT',
        frontComponentUniversalIdentifier:
          HELLO_WORLD_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER,
      },
    },
  ],
});
```

### 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`:

  ```ts theme={null}
  import { STANDARD_PAGE_LAYOUT_UNIVERSAL_IDENTIFIERS } from 'twenty-sdk/define';

  // STANDARD_PAGE_LAYOUT_UNIVERSAL_IDENTIFIERS.companyRecordPage.universalIdentifier
  // STANDARD_PAGE_LAYOUT_UNIVERSAL_IDENTIFIERS.personRecordPage.universalIdentifier
  // STANDARD_PAGE_LAYOUT_UNIVERSAL_IDENTIFIERS.taskRecordPage.universalIdentifier
  // STANDARD_PAGE_LAYOUT_UNIVERSAL_IDENTIFIERS.opportunityRecordPage.universalIdentifier
  // STANDARD_PAGE_LAYOUT_UNIVERSAL_IDENTIFIERS.noteRecordPage.universalIdentifier
  // …
  ```

  Each layout entry also exposes its `tabs` and their `widgets`, so you can reference any level:

  ```ts theme={null}
  STANDARD_PAGE_LAYOUT_UNIVERSAL_IDENTIFIERS.taskRecordPage.tabs.home
    .universalIdentifier;
  STANDARD_PAGE_LAYOUT_UNIVERSAL_IDENTIFIERS.taskRecordPage.tabs.home.widgets
    .fields.universalIdentifier;
  ```

  A short alias `STANDARD_PAGE_LAYOUT` is also available:

  ```ts theme={null}
  import { STANDARD_PAGE_LAYOUT } from 'twenty-sdk/define';

  STANDARD_PAGE_LAYOUT.companyRecordPage.universalIdentifier;
  ```

* `widgets` are scoped to this tab only — they reference [front components](/developers/extend/apps/layout/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.
