From 57118a868f2e656d6020d0817fb1a2777473c129 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Bosi?= <71827178+bosiraphael@users.noreply.github.com> Date: Fri, 29 May 2026 16:04:19 +0200 Subject: [PATCH] Docs update: Calling a logic function from a front component (#21057) Documents how a headless front component calls a server-side logic function over HTTP via the /s/ route, so AI agents have a clear reference for implementing this pattern. --- .../extend/apps/layout/front-components.mdx | 88 +++++++++++++++++++ .../extend/apps/logic/logic-functions.mdx | 4 + 2 files changed, 92 insertions(+) diff --git a/packages/twenty-docs/developers/extend/apps/layout/front-components.mdx b/packages/twenty-docs/developers/extend/apps/layout/front-components.mdx index d64fb16916..7c0fb95b84 100644 --- a/packages/twenty-docs/developers/extend/apps/layout/front-components.mdx +++ b/packages/twenty-docs/developers/extend/apps/layout/front-components.mdx @@ -196,6 +196,94 @@ export default defineFrontComponent({ }); ``` +## Calling a logic function + +Front components run browser-side in a sandboxed Web Worker, while [logic functions](/developers/extend/apps/logic/logic-functions) run server-side. There is no direct in-process call between the two — instead, a front component reaches a logic function over HTTP. + +A logic function declared with `httpRouteTriggerSettings` is exposed under the `/s/` endpoint at `${TWENTY_API_URL}/s`. Your front component calls that route with `fetch`, authenticating with the `TWENTY_APP_ACCESS_TOKEN` that Twenty injects into the worker. + +A small reusable helper keeps the call sites clean: + +```ts src/shared/call-app-route.ts +export async function callAppRoute( + path: string, + body: Record, +): Promise { + const apiUrl = process.env.TWENTY_API_URL ?? ''; + const token = process.env.TWENTY_APP_ACCESS_TOKEN; + + const res = await fetch(`${apiUrl}/s${path}`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + ...(token ? { Authorization: `Bearer ${token}` } : {}), + }, + body: JSON.stringify(body), + }); + + if (!res.ok) { + throw new Error(`Logic function failed (${res.status})`); + } + + return res.json(); +} +``` + +A headless front component can run the call on mount via the `Command` component, then unmount automatically: + +```tsx src/front-components/sync-prs.tsx +import { defineFrontComponent } from 'twenty-sdk/define'; +import { Command } from 'twenty-sdk/command'; +import { callAppRoute } from 'src/shared/call-app-route'; + +const SyncPrs = () => { + const execute = async () => { + await callAppRoute('/github/fetch-prs', { + owner: 'twentyhq', + repo: 'twenty', + }); + }; + + return ; +}; + +export default defineFrontComponent({ + universalIdentifier: '...', + name: 'sync-prs', + description: 'Triggers the fetch-prs logic function', + isHeadless: true, + component: SyncPrs, +}); +``` + +The `path` passed to `callAppRoute` must match the logic function's `httpRouteTriggerSettings.path` (the `/s` prefix is added by the helper): + +```ts src/logic-functions/fetch-prs.logic-function.ts +import { defineLogicFunction } from 'twenty-sdk/define'; +import type { RoutePayload } from 'twenty-sdk/logic-function'; + +const handler = async (event: RoutePayload) => { + const { owner, repo } = (event.body ?? {}) as { owner: string; repo: string }; + // ...fetch from GitHub and persist records... + return { ok: true }; +}; + +export default defineLogicFunction({ + universalIdentifier: '...', + name: 'fetch-prs', + handler, + httpRouteTriggerSettings: { + path: '/github/fetch-prs', + httpMethod: 'POST', + isAuthRequired: true, + }, +}); +``` + + +`TWENTY_API_URL` and `TWENTY_APP_ACCESS_TOKEN` are injected automatically — see [Application variables](#application-variables). Because secret application variables are never exposed to front components, keep API keys and other sensitive logic in the logic function, not in the front component. + + ## Accessing runtime context Inside your component, use SDK hooks to access the current user, record, and component instance: diff --git a/packages/twenty-docs/developers/extend/apps/logic/logic-functions.mdx b/packages/twenty-docs/developers/extend/apps/logic/logic-functions.mdx index 5503fbffe7..af06862da7 100644 --- a/packages/twenty-docs/developers/extend/apps/logic/logic-functions.mdx +++ b/packages/twenty-docs/developers/extend/apps/logic/logic-functions.mdx @@ -53,6 +53,10 @@ export default defineLogicFunction({ Available trigger types: - **httpRoute**: Exposes your function on an HTTP path and method **under the `/s/` endpoint**: > e.g. `path: '/post-card/create'` is callable at `https://your-twenty-server.com/s/post-card/create` + + +To invoke a route-triggered logic function from a (headless) front component, see [Calling a logic function](/developers/extend/apps/layout/front-components#calling-a-logic-function). + - **cron**: Runs your function on a schedule using a CRON expression. - **databaseEvent**: Runs on workspace object lifecycle events. When the event operation is `updated`, specific fields to listen to can be specified in the `updatedFields` array. If left undefined or empty, any update will trigger the function. > e.g. `person.updated`, `*.created`, `company.*`