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

5.1 KiB

Logic

Use this reference for Twenty app logic functions, skills, agents, and connection providers.

Logic Functions

A logic function file should contain trigger registration, input validation, the call out to the work, and the writes back — nothing else. Everything else lives next to it (see app-structure.md):

  • External API calls → src/<service>-client/<name>.ts.
  • Response parsing and mapping → src/utils/<name>.util.ts.
  • Response and DTO types → src/types/<name>.ts.
  • Shared structure across object types → one mapper factory parameterized by object kind, not parallel functions.

Kebab-case filenames, one export per file. Response/DTO types go in src/types/<name>.ts and parsing/mapping helpers in src/utils/<name>.util.ts — never export both a type and a util from the same file (a local, non-exported type may stay with the util), and never put multiple function exports in one file. See app-structure.md.

Other rules:

  • Validate required fields before writes or remote calls.
  • Prefer bulk inputs for record actions. If a logic function can be triggered from selected records, the canonical input should be records: Array<{ id: string; ...fields }> unless the user explicitly says the function is only for one record.
  • Use id inside a records array for the Twenty record ID. Do not add recordId, object-specific IDs such as companyId, or flat single-record payloads unless the user explicitly requests a single-record contract.
  • Return a bulk summary with per-record results for multi-record actions, including counts for success, no match, and failed records.
  • Prefer idempotent behavior for jobs and repeated invocations.
  • Read secrets through the application-config helper, not raw process.env.
  • Do not hide customer-impacting side effects behind UI-only actions.

Soft cap: a *.logic-function.ts or *.post-install.ts file over 200 lines is a refactor signal.

Bulk Record Actions

Bulk is the default logic-function contract for actions that may run from front component selection. The front component should gather selected records and invoke the function once. Do not make the front component loop over selected records and execute the same logic function repeatedly unless there is an explicit single-record requirement.

Preferred input:

type BulkInput<TRecord extends { id: string }> = {
  records: TRecord[];
};

Preferred output:

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

For existing single-record functions that are being upgraded to selected-record actions, replace the old flat input with records: Array<{ id: string; ...fields }> unless the user explicitly asks for backward compatibility.

Extract and test:

  • input normalization for the canonical bulk shape;
  • per-record validation;
  • external API payload mapping;
  • per-record result mapping;
  • summary count aggregation;
  • error message normalization.

Skills And Agents

Skills and agents should describe when they apply, what context they need, and what output is expected.

When adding AI behavior:

  • Make trigger rules concrete.
  • Keep instructions grounded in available app data and tools.
  • State when the agent should ask for missing workspace or record context.
  • Avoid exposing raw IDs, timestamps, or nested API output to end users when a readable answer is possible.

Connection Providers

For third-party connections:

  • Keep secrets out of source and public assets.
  • Document required OAuth or API setup in the app README or listing.
  • Verify failure states for expired or missing credentials.

Post-Install Hooks

Use definePostInstallLogicFunction for records that must exist on install — default workflows, views, roles, or seeded reference data. Do not implement this as runtime first-run code.

Post-install hook files live alongside other logic functions (typically src/logic-functions/<name>.post-install.ts). Kebab-case filename, one export per file.

Hooks must be idempotent: find by stable identifier before creating, update if it exists, never duplicate. Treat a not-found from a single-record query as "needs create."

Do not write fields Twenty computes elsewhere (workflow statuses is computed from version status — see workflows.md).

Dev sync skips install hooks. Invoke locally:

yarn twenty dev:function:exec

Run again after rebuilding to verify idempotency.

Uninstall Hook

Use defineUninstallLogicFunction for best-effort cleanup of external resources when the app is uninstalled (deprovision API resources, delete remaining bots, revoke webhooks). Failures are logged and never block the uninstall.

Uninstall hook files live alongside other logic functions (typically src/logic-functions/uninstall.ts). Kebab-case filename, one export per file.

The hook runs before the app's metadata, data, and code are removed, so handlers can still query the app's objects and records. Handlers receive UninstallPayload ({ version?: string }).