Files
upstream-mirror e7da97194a
Some checks failed
App Prod-Parity E2E Dispatch / dispatch (push) Failing after 10s
CD Storybook UI / build (push) Failing after 3m25s
CD Storybook UI / dispatch (push) Has been skipped
CD deploy main / website-changed-files (push) Successful in 18m8s
CI Agent Skills / changed-files-check (push) Successful in 18m9s
CI Create App E2E minimal / changed-files-check (push) Successful in 24s
CI Create App / changed-files-check (push) Successful in 26s
CI Docs / changed-files-check (push) Successful in 26s
CI Emails / changed-files-check (push) Successful in 28s
CI Example App Hello World / changed-files-check (push) Successful in 23s
CI Front / changed-files-check (push) Has been skipped
CI Example App Postcard / changed-files-check (push) Successful in 27s
CI UI / changed-files-check (push) Has been skipped
CI Utils Test / changed-files-check (push) Successful in 45s
CI Twenty Apps / discover (push) Successful in 1m3s
CD deploy main / deploy-main (push) Failing after 3s
CI Agent Skills / agent-skills-validate (push) Has been skipped
CI Create App / create-app-test (lint) (push) Has been skipped
CI Emails / emails-test (push) Has been skipped
CI Front / front-task (lingui:compile) (push) Has been skipped
CI Front / front-task (lint) (push) Has been skipped
CI Example App Postcard / example-app-postcard (push) Has been skipped
CI UI / ui-task (lint) (push) Has been skipped
CI UI / ui-task (pack-size) (push) Has been skipped
CI UI / ui-task (size) (push) Has been skipped
CI UI / ui-task (test) (push) Has been skipped
CI UI / ui-task (typecheck) (push) Has been skipped
CI Utils Test / utils-test (push) Has been skipped
CI Twenty Apps / ${{ matrix.app.name }} (push) Has been skipped
CI Emails / ci-emails-status-check (push) Successful in 2s
CI Example App Hello World / ci-example-app-hello-world-status-check (push) Successful in 2s
CI Example App Postcard / ci-example-app-postcard-status-check (push) Successful in 3s
CI UI / ui-sb-test (push) Has been skipped
CI Utils Test / ci-utils-test-status-check (push) Successful in 2s
CI Twenty Apps / ci-twenty-apps-status-check (push) Successful in 2s
CI UI / ci-ui-status-check (push) Successful in 2s
CI Desktop / desktop (push) Failing after 1m8s
Push docs to Crowdin / Push documentation to Crowdin (push) Failing after 55s
Push translations to Crowdin / Extract and upload translations (push) Failing after 5s
Push website translations to Crowdin / Extract and upload website translations (push) Failing after 5s
CI Create App / create-app-test (test) (push) Has been skipped
CI Create App / create-app-test (typecheck) (push) Has been skipped
CI Docs / docs-lint (push) Has been skipped
CI Example App Hello World / example-app-hello-world (push) Has been skipped
CI Front / front-task (lingui:extract) (push) Has been skipped
CI Front / front-task (test) (push) Has been skipped
CI Front / front-task (typecheck) (push) Has been skipped
CI UI / ui-sb-build (push) Failing after 1m32s
CI Twenty Apps / ${{ matrix.app.name }} (${{ matrix.server-source }}) (push) Has been skipped
CI Agent Skills / agent-skills-publish (push) Has been skipped
CI Create App / ci-create-app-status-check (push) Successful in 2s
CI Agent Skills / agent-skills-remote-source (push) Has been skipped
CI Create App E2E minimal / create-app-e2e-minimal (push) Has been cancelled
CI E2E Main / e2e-test (push) Has been cancelled
CI Front / front-sb-build (push) Has been cancelled
CI Front / front-build (push) Has been cancelled
CI Create App E2E minimal / ci-create-app-e2e-minimal-status-check (push) Has been cancelled
CI E2E Main / ci-e2e-main-status-check (push) Has been cancelled
CI E2E Main / notify-main-ci-failure (push) Has been cancelled
CI E2E Main / qa-scout-comment (push) Has been cancelled
CI Front / front-sb-test (push) Has been cancelled
CI Front / front-sb-screenshots (push) Has been cancelled
CI Front / ci-front-status-check (push) Has been cancelled
DPA Sub-Processor Sync / sync-subprocessors (push) Failing after 3s
AI Catalog Sync / sync-catalog (push) Failing after 2s
snapshot of https://github.com/twentyhq/twenty.git (main, 2026-09-17; blobs > 131072B omitted)
2026-09-17 23:28:13 +05:00

159 lines
9.6 KiB
Plaintext
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
title: Seitenlayouts
description: Passen Sie Detailseiten von Datensätzen an – Tabs, Widgets und die Stellen, an denen Frontend-Komponenten gerendert werden – mithilfe von `definePageLayout` und `definePageLayoutTab`.
icon: table-columns
---
Ein **Seitenlayout** steuert, wie die Detailseite eines Datensatzes angeordnet ist: welche Tabs angezeigt werden und welche Widgets sie enthalten. Verwenden Sie `definePageLayout()`, um ein Layout für ein Objekt zu deklarieren, das Sie besitzen, oder `definePageLayoutTab()`, um einen einzelnen Tab zu einem Layout hinzuzufügen, das bereits existiert (Ihr eigenes oder ein standardmäßiges Twenty-Layout).
| Anwendungsfall | Entität |
| ----------------------------------------------------------------------------------------------- | --------------------- |
| Definieren Sie das gesamte Layout für die Datensatzseite eines Objekts, das Sie besitzen | `definePageLayout` |
| Fügen Sie einem vorhandenen Layout einen Tab hinzu (Ihr eigenes Objekt oder ein Standardlayout) | `definePageLayoutTab` |
## definePageLayout
Verwenden Sie dies, wenn Sie die gesamte Detailseite besitzen – typischerweise für ein benutzerdefiniertes Objekt, das Sie selbst definiert haben.
```ts src/page-layouts/example-record-page-layout.ts
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,
},
},
],
},
],
});
```
### Hauptpunkte
* `type` ist eines von `'RECORD_INDEX'`, `'RECORD_PAGE'`, `'DASHBOARD'` oder `'STANDALONE_PAGE'`. Verwenden Sie `'RECORD_PAGE'`, um die Detailansicht eines bestimmten Objekts anzupassen.
* `objectUniversalIdentifier` gibt an, auf welches Objekt dieses Layout angewendet wird.
* Jeder `tab` definiert einen Abschnitt der Seite mit `title`, `position` und `layoutMode`: `VERTICAL_LIST` für Datensatzseiten und eigenständige Seiten, `GRID` für Dashboards und `CANVAS` für ein einzelnes Widget, das den Ansichtsbereich des Tabs ausfüllen soll. Ein `VERTICAL_LIST`-Tab ordnet Widgets vertikal an. Integrierte Widgets, die ihr eigenes Scrollen verwalten, wie etwa Timelines, Dateien, Notizen, Aufgaben und Workflows, füllen einen Ansichtsbereich aus; Felder, Front-Komponenten, Diagramme und andere Widgets mit `fit-content` werden mit ihrer Inhalts- oder konfigurierten Höhe dargestellt. Ein `GRID`-Tab ordnet seine Widgets immer als Karten in einem Raster mit 12 Spalten an. Ein `CANVAS`-Widget hat keine explizite Position; wenn ein Canvas-Tab mehrere Widgets enthält, werden sie in ihrer Inhaltshöhe gerendert, anstatt den Viewport auszufüllen.
* Setze `layoutMode` explizit. Wenn du es weglässt, erhältst du `VERTICAL_LIST` auf einer `STANDALONE_PAGE` und `GRID` überall sonst, was auf einer Datensatzseite selten gewünscht ist.
* Jedes `widget` innerhalb eines Tabs kann eine [Frontend-Komponente](/l/de/developers/extend/apps/layout/front-components), eine Relationenliste oder andere eingebaute Widget-Typen rendern.
* Ein `FRONT_COMPONENT`-Widget kann `headerCommandMenuItemUniversalIdentifiers` auf ein geordnetes Array universeller Kennungen von Befehlsmenüelementen aus derselben App festlegen. Diese Aktionen werden als Symbolschaltflächen im Kopf der Widget-Karte angezeigt und behalten ihre Verfügbarkeits- und Berechtigungsprüfungen auf Befehlsebene bei. Kennungen müssen eindeutig sein und bei installierter App aufgelöst werden können.
* `position` auf Tabs steuert deren Reihenfolge. Verwenden Sie höhere Werte (z. B. 50), um benutzerdefinierte Tabs hinter den integrierten zu platzieren.
### Feld-Widgets
Ein `FIELD`-Widget rendert ein Feld des Datensatzes. Bei Relationsfeldern kann es auch eine Liste verwandter Datensätze einbetten:
```ts
{
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` nimmt den universellen Bezeichner eines Feldes am Objekt des Layouts.
* `fieldDisplayMode` ist eines von `'FIELD'`, `'CARD'`, `'EDITOR'`, `'VIEW'` oder `'TABLE'`. `TABLE` bettet eine Ansicht ein, die die Datensätze eines 1:n-Relationsfeldes auflistet.
* `nestedRelationFieldMetadataId` ist optional und nimmt den universellen Bezeichner eines Eins-zu-viele-Beziehungsfelds auf dem Beziehungszielobjekt entgegen, um Datensätze aufzulisten, die zwei Beziehungssprünge entfernt sind (z. B. eine Company-Seite, die die Opportunities der Personen des Unternehmens auflistet, oder eine Person-Seite, die die Opportunities des Unternehmens der Person auflistet). Der erste Sprung kann ein Eins-zu-viele- oder ein Viele-zu-eins-Beziehungsfeld sein, der zweite muss Eins-zu-viele sein (Junction-Beziehungen werden nicht unterstützt), und es erfordert `fieldDisplayMode: 'TABLE'` — die Kombination mit jedem anderen Anzeigemodus ist ein Validierungsfehler, da ein verschachteltes Widget immer als eingebettete Ansicht gerendert wird.
## definePageLayoutTab
Verwenden Sie dies, wenn Sie nur einen Tab zu einem vorhandenen Layout **hinzufügen** möchten – zum Beispiel einen Analytics-Tab auf der standardmäßigen Company-Seite oder einen KI-Zusammenfassungs-Tab, der an das Layout Ihres eigenen Objekts angehängt ist.
```ts src/page-layouts/example-extra-tab.ts
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,
},
},
],
});
```
### Hauptpunkte
* `pageLayoutUniversalIdentifier` ist **erforderlich** und muss auf ein Seitenlayout verweisen, das zum Installationszeitpunkt bereits existiert – entweder ein standardmäßiges Twenty-Layout oder eines, das von Ihrer eigenen App definiert wurde. App-übergreifende Verweise auf Layouts, die einer anderen installierten App gehören, werden derzeit nicht unterstützt. Wenn das übergeordnete Layout fehlt, schlägt die Installation mit einem eindeutigen Validierungsfehler fehl.
* Für Standard-Twenty-Layouts importieren Sie die Bezeichner aus `twenty-sdk/define`:
```ts
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
// …
```
Jeder Layout-Eintrag stellt außerdem seine `tabs` und deren `widgets` zur Verfügung, sodass Sie auf jede Ebene verweisen können:
```ts
STANDARD_PAGE_LAYOUT_UNIVERSAL_IDENTIFIERS.taskRecordPage.tabs.home
.universalIdentifier;
STANDARD_PAGE_LAYOUT_UNIVERSAL_IDENTIFIERS.taskRecordPage.tabs.home.widgets
.fields.universalIdentifier;
```
Eine kurze Alias-Variable `STANDARD_PAGE_LAYOUT` ist ebenfalls verfügbar:
```ts
import { STANDARD_PAGE_LAYOUT } from 'twenty-sdk/define';
STANDARD_PAGE_LAYOUT.companyRecordPage.universalIdentifier;
```
* `widgets` sind ausschließlich auf diesen Tab beschränkt – sie verweisen auf [Frontend-Komponenten](/l/de/developers/extend/apps/layout/front-components), Ansichten usw., genau wie Widgets, die inline in `definePageLayout` definiert sind.
* `position` steuert die Reihenfolge im Zielseitenlayout relativ zu den vorhandenen Registerkarten. Wählen Sie einen Wert, der Ihre Registerkarte relativ zu integrierten Registerkarten an die gewünschte Position bringt.
* Verwenden Sie dies anstelle von `definePageLayout`, wenn Sie einem vorhandenen Layout nur etwas hinzufügen möchten. Verwenden Sie `definePageLayout`, wenn Sie das gesamte Layout besitzen.