> ## Documentation Index
> Fetch the complete documentation index at: https://docs.get-clara.tech/llms.txt
> Use this file to discover all available pages before exploring further.

# Frontend-Komponenten

> Build React components that render inside Twenty's UI with sandboxed isolation.

Front-Komponenten sind React-Komponenten, die direkt innerhalb der Twenty-UI gerendert werden. Sie laufen in einem **isolierten Web Worker** unter Verwendung von Remote DOM — Ihr Code wird in einer Sandbox ausgeführt, rendert jedoch nativ auf der Seite, nicht in einem iframe.

## Wo Front-Komponenten verwendet werden können

Front-Komponenten können an zwei Stellen innerhalb von Twenty gerendert werden:

* **Seitenpanel** — Nicht-Headless-Front-Komponenten werden im rechten Seitenpanel geöffnet. Dies ist das Standardverhalten, wenn eine Front-Komponente über das Befehlsmenü ausgelöst wird.
* **Widgets (Dashboards und Datensatzseiten)** — Front-Komponenten können als Widgets in Seitenlayouts eingebettet werden. Beim Konfigurieren eines Dashboards oder eines Datensatzseiten-Layouts können Benutzer ein Front-Komponenten-Widget hinzufügen.

## Einfaches Beispiel

Der schnellste Weg, eine Front-Komponente in Aktion zu sehen, ist, sie als Befehl zu registrieren. Das Hinzufügen eines `command`-Felds mit `isPinned: true` lässt sie als Schnellaktionsschaltfläche oben rechts auf der Seite erscheinen — kein Seitenlayout erforderlich:

```tsx src/front-components/hello-world.tsx theme={null}
import { defineFrontComponent } from 'twenty-sdk/define';

const HelloWorld = () => {
  return (
    <div style={{ padding: '20px', fontFamily: 'sans-serif' }}>
      <h1>Hello from my app!</h1>
      <p>This component renders inside Twenty.</p>
    </div>
  );
};

export default defineFrontComponent({
  universalIdentifier: '74c526eb-cb68-4cf7-b05c-0dd8c288d948',
  name: 'hello-world',
  description: 'A simple front component',
  component: HelloWorld,
  command: {
    universalIdentifier: 'd4e5f6a7-b8c9-0123-defa-456789012345',
    shortLabel: 'Hello',
    label: 'Hello World',
    icon: 'IconBolt',
    isPinned: true,
    availabilityType: 'GLOBAL',
  },
});
```

Nach dem Synchronisieren mit `yarn twenty dev` (oder durch einmaliges Ausführen von `yarn twenty dev --once`) erscheint die Schnellaktion oben rechts auf der Seite:

<div style={{textAlign: 'center'}}>
  <img src="https://mintcdn.com/clara-1b8b12e1/qLc_oPoqnIeP4kYN/images/docs/developers/extends/apps/quick-action.png?fit=max&auto=format&n=qLc_oPoqnIeP4kYN&q=85&s=e718e660bb389e74285aeefd2837d787" alt="Schnellaktionsschaltfläche oben rechts" width="3024" height="1502" data-path="images/docs/developers/extends/apps/quick-action.png" />
</div>

Klicken Sie darauf, um die Komponente inline zu rendern.

## Konfigurationsfelder

| Feld                  | Erforderlich | Beschreibung                                                                             |
| --------------------- | ------------ | ---------------------------------------------------------------------------------------- |
| `universalIdentifier` | Ja           | Stabile eindeutige ID für diese Komponente                                               |
| `component`           | Ja           | Eine React-Komponentenfunktion                                                           |
| `name`                | Nein         | Anzeigename                                                                              |
| `description`         | Nein         | Beschreibung dessen, was die Komponente macht                                            |
| `isHeadless`          | Nein         | Auf `true` setzen, wenn die Komponente keine sichtbare UI hat (siehe unten)              |
| `command`             | Nein         | Die Komponente als Befehl registrieren (siehe unten [Befehlsoptionen](#command-options)) |

## Eine Front-Komponente auf einer Seite platzieren

Über Befehle hinaus können Sie eine Front-Komponente direkt in eine Datensatzseite einbetten, indem Sie sie als Widget in einem **Seitenlayout** hinzufügen. Details finden Sie im Abschnitt [definePageLayout](/l/de/developers/extend/apps/skills-and-agents#definepagelayout).

## Headless vs. Nicht-Headless

Front-Komponenten gibt es in zwei Rendering-Modi, die durch die Option `isHeadless` gesteuert werden:

**Nicht-Headless (Standard)** — Die Komponente rendert eine sichtbare UI. Wird sie über das Befehlsmenü ausgelöst, öffnet sie sich im Seitenpanel. Dies ist das Standardverhalten, wenn `isHeadless` `false` ist oder weggelassen wird.

**Headless (`isHeadless: true`)** — Die Komponente wird unsichtbar im Hintergrund gemountet. Sie öffnet das Seitenpanel nicht. Headless-Komponenten sind für Aktionen konzipiert, die Logik ausführen und sich anschließend selbst unmounten — zum Beispiel das Ausführen einer asynchronen Aufgabe, das Navigieren zu einer Seite oder das Anzeigen eines Bestätigungsdialogs. Sie lassen sich gut mit den unten beschriebenen SDK-Command-Komponenten kombinieren.

```tsx src/front-components/sync-tracker.tsx theme={null}
import { defineFrontComponent } from 'twenty-sdk/define';
import { useRecordId, enqueueSnackbar } from 'twenty-sdk/front-component';
import { useEffect } from 'react';

const SyncTracker = () => {
  const recordId = useRecordId();

  useEffect(() => {
    enqueueSnackbar({ message: `Tracking record ${recordId}`, variant: 'info' });
  }, [recordId]);

  return null;
};

export default defineFrontComponent({
  universalIdentifier: '...',
  name: 'sync-tracker',
  description: 'Tracks record views silently',
  isHeadless: true,
  component: SyncTracker,
});
```

Da die Komponente `null` zurückgibt, überspringt Twenty das Rendern eines Containers dafür — im Layout entsteht kein Leerraum. Die Komponente hat dennoch Zugriff auf alle Hooks und die Host-Kommunikations-API.

## SDK-Command-Komponenten

Das Paket `twenty-sdk` stellt vier Command-Hilfskomponenten bereit, die für Headless-Front-Komponenten ausgelegt sind. Jede Komponente führt beim Mounten eine Aktion aus, behandelt Fehler durch Anzeige einer Snackbar-Benachrichtigung und unmountet die Front-Komponente nach Abschluss automatisch.

Importieren Sie sie aus `twenty-sdk/command`:

* **`Command`** — Führt einen asynchronen Callback über das Prop `execute` aus.
* **`CommandLink`** — Navigiert zu einem App-Pfad. Props: `to`, `params`, `queryParams`, `options`.
* **`CommandModal`** — Öffnet einen Bestätigungsdialog. Bestätigt der Benutzer, wird der Callback `execute` ausgeführt. Props: `title`, `subtitle`, `execute`, `confirmButtonText`, `confirmButtonAccent`.
* **`CommandOpenSidePanelPage`** — Öffnet eine bestimmte Seite im Seitenpanel. Props: `page`, `pageTitle`, `pageIcon`.

Hier ist ein vollständiges Beispiel einer Headless-Front-Komponente, die `Command` verwendet, um eine Aktion aus dem Befehlsmenü auszuführen:

```tsx src/front-components/run-action.tsx theme={null}
import { defineFrontComponent } from 'twenty-sdk/define';
import { Command } from 'twenty-sdk/command';
import { CoreApiClient } from 'twenty-sdk/clients';

const RunAction = () => {
  const execute = async () => {
    const client = new CoreApiClient();

    await client.mutation({
      createTask: {
        __args: { data: { title: 'Created by my app' } },
        id: true,
      },
    });
  };

  return <Command execute={execute} />;
};

export default defineFrontComponent({
  universalIdentifier: 'e5f6a7b8-c9d0-1234-efab-345678901234',
  name: 'run-action',
  description: 'Creates a task from the command menu',
  component: RunAction,
  isHeadless: true,
  command: {
    universalIdentifier: 'f6a7b8c9-d0e1-2345-fabc-456789012345',
    label: 'Run my action',
    icon: 'IconPlayerPlay',
  },
});
```

Und ein Beispiel, das `CommandModal` verwendet, um vor der Ausführung um Bestätigung zu bitten:

```tsx src/front-components/delete-draft.tsx theme={null}
import { defineFrontComponent } from 'twenty-sdk/define';
import { CommandModal } from 'twenty-sdk/command';

const DeleteDraft = () => {
  const execute = async () => {
    // perform the deletion
  };

  return (
    <CommandModal
      title="Delete draft?"
      subtitle="This action cannot be undone."
      execute={execute}
      confirmButtonText="Delete"
      confirmButtonAccent="danger"
    />
  );
};

export default defineFrontComponent({
  universalIdentifier: 'a7b8c9d0-e1f2-3456-abcd-567890123456',
  name: 'delete-draft',
  description: 'Deletes a draft with confirmation',
  component: DeleteDraft,
  isHeadless: true,
  command: {
    universalIdentifier: 'b8c9d0e1-f2a3-4567-bcde-678901234567',
    label: 'Delete draft',
    icon: 'IconTrash',
  },
});
```

## Zugriff auf den Laufzeitkontext

Verwenden Sie innerhalb Ihrer Komponente SDK-Hooks, um auf den aktuellen Benutzer, den Datensatz und die Komponenteninstanz zuzugreifen:

```tsx src/front-components/record-info.tsx theme={null}
import { defineFrontComponent } from 'twenty-sdk/define';
import {
  useUserId,
  useRecordId,
  useFrontComponentId,
} from 'twenty-sdk/front-component';

const RecordInfo = () => {
  const userId = useUserId();
  const recordId = useRecordId();
  const componentId = useFrontComponentId();

  return (
    <div>
      <p>User: {userId}</p>
      <p>Record: {recordId ?? 'No record context'}</p>
      <p>Component: {componentId}</p>
    </div>
  );
};

export default defineFrontComponent({
  universalIdentifier: 'b2c3d4e5-f6a7-8901-bcde-f23456789012',
  name: 'record-info',
  component: RecordInfo,
});
```

Verfügbare Hooks:

| Hook                                          | Gibt zurück          | Beschreibung                                                                |
| --------------------------------------------- | -------------------- | --------------------------------------------------------------------------- |
| `useUserId()`                                 | `string` oder `null` | Die ID des aktuellen Benutzers                                              |
| `useRecordId()`                               | `string` oder `null` | Die ID des aktuellen Datensatzes (wenn auf einer Datensatzseite platziert)  |
| `useFrontComponentId()`                       | `string`             | Die ID dieser Komponenteninstanz                                            |
| `useFrontComponentExecutionContext(selector)` | variiert             | Zugriff auf den vollständigen Ausführungskontext mit einer Selektorfunktion |

## Host-Kommunikations-API

Front-Komponenten können Navigation, Modals und Benachrichtigungen mittels Funktionen aus `twenty-sdk` auslösen:

| Funktion                                        | Beschreibung                              |
| ----------------------------------------------- | ----------------------------------------- |
| `navigate(to, params?, queryParams?, options?)` | Zu einer Seite in der App navigieren      |
| `openSidePanelPage(params)`                     | Ein Seitenpanel öffnen                    |
| `closeSidePanel()`                              | Seitenpanel schließen                     |
| `openCommandConfirmationModal(params)`          | Einen Bestätigungsdialog anzeigen         |
| `enqueueSnackbar(params)`                       | Eine Toast-Benachrichtigung anzeigen      |
| `unmountFrontComponent()`                       | Die Komponente entfernen                  |
| `updateProgress(progress)`                      | Einen Fortschrittsindikator aktualisieren |

Hier ist ein Beispiel, das die Host-API verwendet, um nach Abschluss einer Aktion eine Snackbar anzuzeigen und das Seitenpanel zu schließen:

```tsx src/front-components/archive-record.tsx theme={null}
import { defineFrontComponent } from 'twenty-sdk/define';
import { useRecordId } from 'twenty-sdk/front-component';
import { enqueueSnackbar, closeSidePanel } from 'twenty-sdk/front-component';
import { CoreApiClient } from 'twenty-sdk/clients';

const ArchiveRecord = () => {
  const recordId = useRecordId();

  const handleArchive = async () => {
    const client = new CoreApiClient();

    await client.mutation({
      updateTask: {
        __args: { id: recordId, data: { status: 'ARCHIVED' } },
        id: true,
      },
    });

    await enqueueSnackbar({
      message: 'Record archived',
      variant: 'success',
    });

    await closeSidePanel();
  };

  return (
    <div style={{ padding: '20px' }}>
      <p>Archive this record?</p>
      <button onClick={handleArchive}>Archive</button>
    </div>
  );
};

export default defineFrontComponent({
  universalIdentifier: 'c9d0e1f2-a3b4-5678-cdef-789012345678',
  name: 'archive-record',
  description: 'Archives the current record',
  component: ArchiveRecord,
});
```

## Befehlsoptionen

Das Hinzufügen eines `command`-Felds zu `defineFrontComponent` registriert die Komponente im Befehlsmenü (Cmd+K). Wenn `isPinned` `true` ist, erscheint sie außerdem als Schnellaktionsschaltfläche oben rechts auf der Seite.

| Feld                                    | Erforderlich | Beschreibung                                                                                                                                                                                |
| --------------------------------------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `universalIdentifier`                   | Ja           | Stabile eindeutige ID für den Befehl                                                                                                                                                        |
| `label`                                 | Ja           | Vollständiges Label, das im Befehlsmenü (Cmd+K) angezeigt wird                                                                                                                              |
| `shortLabel`                            | Nein         | Kürzeres Label, das auf der angehefteten Schnellaktionsschaltfläche angezeigt wird                                                                                                          |
| `icon`                                  | Nein         | Neben dem Label angezeigter Icon-Name (z. B. 'IconBolt', 'IconSend')                                                                                                                        |
| `isPinned`                              | Nein         | Bei `true` wird der Befehl als Schnellaktionsschaltfläche oben rechts auf der Seite angezeigt                                                                                               |
| `availabilityType`                      | Nein         | Steuert, wo der Befehl erscheint: 'GLOBAL' (immer verfügbar), 'RECORD\_SELECTION' (nur wenn Datensätze ausgewählt sind) oder 'FALLBACK' (wird angezeigt, wenn keine anderen Befehle passen) |
| `availabilityObjectUniversalIdentifier` | Nein         | Beschränken Sie den Befehl auf Seiten eines bestimmten Objekttyps (z. B. nur bei Company-Datensätzen)                                                                                       |
| `conditionalAvailabilityExpression`     | Nein         | Ein boolescher Ausdruck, um dynamisch zu steuern, ob der Befehl sichtbar ist (siehe unten)                                                                                                  |

## Bedingte Verfügbarkeitsausdrücke

Mit dem Feld `conditionalAvailabilityExpression` können Sie basierend auf dem aktuellen Seitenkontext steuern, wann ein Befehl sichtbar ist. Importieren Sie typisierte Variablen und Operatoren aus `twenty-sdk`, um Ausdrücke zu erstellen:

```tsx theme={null}
import { defineFrontComponent } from 'twenty-sdk/define';
import {
  pageType,
  numberOfSelectedRecords,
  objectPermissions,
  everyEquals,
  isDefined,
} from 'twenty-sdk/front-component';

export default defineFrontComponent({
  universalIdentifier: '...',
  name: 'bulk-action',
  component: BulkAction,
  command: {
    universalIdentifier: '...',
    label: 'Bulk Update',
    availabilityType: 'RECORD_SELECTION',
    conditionalAvailabilityExpression: everyEquals(
      objectPermissions,
      'canUpdateObjectRecords',
      true,
    ),
  },
});
```

**Kontextvariablen** — sie repräsentieren den aktuellen Zustand der Seite:

| Variable                       | Typ       | Beschreibung                                                    |
| ------------------------------ | --------- | --------------------------------------------------------------- |
| `pageType`                     | `string`  | Aktueller Seitentyp (z. B. 'RecordIndexPage', 'RecordShowPage') |
| `isInSidePanel`                | `boolean` | Ob die Komponente in einem Seitenpanel gerendert wird           |
| `numberOfSelectedRecords`      | `number`  | Anzahl der aktuell ausgewählten Datensätze                      |
| `isSelectAll`                  | `boolean` | Ob „Alle auswählen“ aktiv ist                                   |
| `selectedRecords`              | `array`   | Die ausgewählten Datensatzobjekte                               |
| `favoriteRecordIds`            | `array`   | IDs der favorisierten Datensätze                                |
| `objectPermissions`            | `object`  | Berechtigungen für den aktuellen Objekttyp                      |
| `targetObjectReadPermissions`  | `object`  | Leseberechtigungen für das Zielobjekt                           |
| `targetObjectWritePermissions` | `object`  | Schreibberechtigungen für das Zielobjekt                        |
| `featureFlags`                 | `object`  | Aktive Feature-Flags                                            |
| `objectMetadataItem`           | `object`  | Metadaten des aktuellen Objekttyps                              |
| `hasAnySoftDeleteFilterOnView` | `boolean` | Ob die aktuelle Ansicht einen Soft-Delete-Filter hat            |

**Operatoren** — Variablen zu booleschen Ausdrücken kombinieren:

| Operator                            | Beschreibung                                                                                |
| ----------------------------------- | ------------------------------------------------------------------------------------------- |
| `isDefined(value)`                  | `true`, wenn der Wert nicht null/undefined ist                                              |
| `isNonEmptyString(value)`           | `true`, wenn der Wert eine nicht leere Zeichenfolge ist                                     |
| `includes(array, value)`            | `true`, wenn das Array den Wert enthält                                                     |
| `includesEvery(array, prop, value)` | `true`, wenn die Eigenschaft jedes Elements den Wert enthält                                |
| `every(array, prop)`                | `true`, wenn die Eigenschaft bei jedem Element truthy ist                                   |
| `everyDefined(array, prop)`         | `true`, wenn die Eigenschaft bei jedem Element definiert ist                                |
| `everyEquals(array, prop, value)`   | `true`, wenn die Eigenschaft bei jedem Element dem Wert entspricht                          |
| `some(array, prop)`                 | `true`, wenn die Eigenschaft bei mindestens einem Element truthy ist                        |
| `someDefined(array, prop)`          | `true`, wenn die Eigenschaft bei mindestens einem Element definiert ist                     |
| `someEquals(array, prop, value)`    | `true`, wenn die Eigenschaft bei mindestens einem Element dem Wert entspricht               |
| `someNonEmptyString(array, prop)`   | `true`, wenn die Eigenschaft bei mindestens einem Element eine nicht leere Zeichenfolge ist |
| `none(array, prop)`                 | `true`, wenn die Eigenschaft bei jedem Element falsy ist                                    |
| `noneDefined(array, prop)`          | `true`, wenn die Eigenschaft bei jedem Element undefined ist                                |
| `noneEquals(array, prop, value)`    | `true`, wenn die Eigenschaft bei keinem Element dem Wert entspricht                         |

## Öffentliche Assets

Front-Komponenten können mit `getPublicAssetUrl` auf Dateien aus dem `public/`-Verzeichnis der App zugreifen:

```tsx theme={null}
import { defineFrontComponent, getPublicAssetUrl } from 'twenty-sdk/define';

const Logo = () => <img src={getPublicAssetUrl('logo.png')} alt="Logo" />;

export default defineFrontComponent({
  universalIdentifier: '...',
  name: 'logo',
  component: Logo,
});
```

Details finden Sie im Abschnitt [Öffentliche Assets](/l/de/developers/extend/apps/cli-and-testing#public-assets-public-folder).

## Styling

Front-Komponenten unterstützen mehrere Styling-Ansätze. Sie können verwenden:

* **Inline-Styles** — `style={{ color: 'red' }}`
* **Twenty-UI-Komponenten** — Import aus `twenty-sdk/ui` (Button, Tag, Status, Chip, Avatar und mehr)
* **Emotion** — CSS-in-JS mit `@emotion/react`
* **Styled-components** — `styled.div`-Muster
* **Tailwind CSS** — Utility-Klassen
* **Beliebige CSS-in-JS-Bibliothek**, die mit React kompatibel ist

```tsx theme={null}
import { defineFrontComponent } from 'twenty-sdk/define';
import { Button, Tag, Status } from 'twenty-sdk/ui';

const StyledWidget = () => {
  return (
    <div style={{ padding: '16px', display: 'flex', gap: '8px' }}>
      <Button title="Click me" onClick={() => alert('Clicked!')} />
      <Tag text="Active" color="green" />
      <Status color="green" text="Online" />
    </div>
  );
};

export default defineFrontComponent({
  universalIdentifier: 'e5f6a7b8-c9d0-1234-efab-567890123456',
  name: 'styled-widget',
  component: StyledWidget,
});
```
