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

6.2 KiB

Front Components

Use this reference for Twenty app front component source files, registration, data access, Twenty UI imports, runtime imports, and browser verification.

Use layout.md for placing a front component in a page layout. Use standalone-pages.md for full-page custom UI rendered through a front component widget. Use ../design/front-component-ui.md for visual design, spacing, states, and polish. Use app-structure.md for the develop-app validation checklist and ../manage-app/cli-and-sync.md for command behavior or troubleshooting.

Source And Registration

By convention, keep front components under src/front-components/, often as <name>.front-component.tsx.

Register the component with defineFrontComponent:

import { defineFrontComponent } from 'twenty-sdk/define';

const MyFrontComponent = () => {
  return <div />;
};

export default defineFrontComponent({
  universalIdentifier: '<front-component-uuid>',
  name: '<front-component-name>',
  description: '<short description>',
  component: MyFrontComponent,
});

Do not import or call react-dom/client in the component source. The SDK renderer mounts front components.

Runtime Imports

Keep imports narrow:

  • Use twenty-sdk/front-component for front component hooks and host APIs.
  • Use twenty-client-sdk/core or twenty-client-sdk/metadata for data access.
  • Install twenty-ui from npm at the same version as twenty-sdk and twenty-client-sdk and import Twenty UI components, icons, and theme tokens from its subpaths (twenty-ui/primitives/input, twenty-ui/primitives/data-display, twenty-ui/icon, twenty-ui/primitives/typography, twenty-ui/theme-constants, and others) before adding external UI libraries.

The front component renderer provides a Twenty ThemeProvider around the remote root. For isolated examples or local story-style verification, wrapping the component in ThemeProvider from twenty-ui/theme-constants is also acceptable.

The canonical Twenty UI front component example is packages/twenty-front-component-renderer/src/__stories__/showcase/twenty-ui-example.front-component.tsx. It imports Button from twenty-ui/primitives/input; Chip, Status, and Tag from twenty-ui/primitives/data-display; Section from twenty-ui/components; and ThemeProvider from twenty-ui/theme-constants.

Read theme tokens with the useTheme() hook from twenty-ui/theme-constants inside the component body. The same tokens are also exported as the themeCssVariables constant, but the SDK mocks twenty-ui during manifest extraction, so a module-level constant that dereferences themeCssVariables can fail before the component renders — prefer useTheme().

Prefer Twenty UI icons from twenty-ui/icon when one exists. Use inline SVG only for app-specific marks or icons that are not available from twenty-ui/icon.

Record Context And Data

For record-page components, read selection from front component context:

  • Use useSelectedRecordIds() for single, bulk, or empty selection; derive one id only when the array length is 1.

Use the generated or core Twenty client for reads and writes. Keep loading, empty, error, disabled, and saving states explicit so runtime failures are visible and recoverable.

Headless Actions And DRY Helpers

Headless front components should be thin action shells. The component file should mostly read SDK hooks, return the Command helper, and delegate reusable behavior to helpers.

Common headless action flow:

  1. Read selected record IDs.
  2. Load the selected records.
  3. Build a payload for one logic-function call.
  4. Execute the logic function.
  5. Parse the result summary.
  6. Show a snackbar.
  7. Let Command unmount the component.

Do not duplicate this orchestration across sibling front components. When two components share command execution flow, extract it before copying:

  • Pure helpers go in src/utils/<name>.util.ts.
  • Front-component runtime helpers go in src/front-components/utils/<name>.util.ts.
  • Payload builders, result parsers, selected-record validators, and summary formatters should be small testable functions with sibling specs.

Each extracted helper and type lives in its own file: one export per file. A local, non-exported type may stay with the util, but an exported type moves to its own file. See app-structure.md.

Prefer configuration-driven helpers when creating parallel actions for people, companies, tasks, or other objects. Each front component should provide only object-specific configuration, such as the front component universal identifier, logic function universal identifier, record query, payload builder, labels, and snackbar copy.

Bulk Logic Function Calls

When a front component triggers a logic function for selected records, always prefer a bulk payload shape unless the user explicitly says the logic function is only for one record. The front component should call the logic function once with all selected records, not loop and execute the same logic function once per record.

Default payload shape:

{
  records: Array<{
    id: string;
    // object-specific fields used by the logic function
  }>;
}

Inside records, prefer id for the Twenty record ID because the array name already establishes record context. Do not add flat recordId payloads unless the user explicitly asks for a single-record action.

Logic functions that return per-record outcomes should mirror the same naming:

{
  ok: boolean;
  enrichedCount: number;
  noMatchCount: number;
  failedCount: number;
  results: Array<{
    id: string;
    status: 'ENRICHED' | 'NO_MATCH' | 'FAILED';
    error?: string;
  }>;
}

If an existing logic function accepts only a flat record ID, prefer upgrading it to accept records and remove the old flat input unless the user explicitly asks to preserve it.

Runtime Verification

A clean typecheck and sync is not runtime verification. After the standard validation in app-structure.md, open the relevant Twenty surface and confirm:

  • The widget mounts without a FrontComponent error in the widget body or toast.
  • The component renders loading, empty, and error states correctly.
  • The main user action works against a real record.
  • The page still renders after a hard refresh.