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 2d0799f07d..ee1a8cc6c7 100644 --- a/packages/twenty-docs/developers/extend/apps/logic/logic-functions.mdx +++ b/packages/twenty-docs/developers/extend/apps/logic/logic-functions.mdx @@ -412,6 +412,10 @@ Logic functions can be exposed on two surfaces, each with its own trigger: A function can opt into one, the other, or both. They sit alongside `cronTriggerSettings`, `databaseEventTriggerSettings`, and `httpRouteTriggerSettings` — same pattern, same shape. + +**Relationship to the workflow Code action.** The built-in **Code** action in the workflow builder is itself a logic function — Twenty creates one per Code step and exposes its editor inline. `workflowActionTriggerSettings` is how you turn that one-off, inline code into a **reusable** action: define the function once in your app and it becomes selectable in any workflow, instead of being copy-pasted into each Code step. See the [Code action](/user-guide/workflows/capabilities/workflow-actions#code) in the user guide for the end-user view. + + ```ts src/logic-functions/enrich-company.logic-function.ts import { defineLogicFunction } from 'twenty-sdk/define'; import { CoreApiClient } from 'twenty-client-sdk/core'; @@ -497,6 +501,77 @@ export default defineLogicFunction({ }); ``` +##### A complete workflow action example + +`workflowActionTriggerSettings` accepts four fields: + +| Field | Purpose | +|-------|---------| +| `label` | Name shown for the action in the workflow builder's step picker. Defaults to the function `name`. | +| `icon` | Icon shown next to the action (a `tabler-icons` name, e.g. `IconBuilding`). | +| `inputSchema` | Twenty's rich `InputSchema` — what the builder renders as configurable fields (with variable pickers). Optional; inferred from the handler when omitted. | +| `outputSchema` | Declares the shape the handler returns, so **subsequent steps can map to its output fields**. Optional; without it, the output is exposed as a single opaque value. | + +Putting it together — a function exposed as a workflow action, with a declared output so later steps can reference `taskId`: + +```ts src/logic-functions/enrich-company.logic-function.ts +import { defineLogicFunction } from 'twenty-sdk/define'; +import { jsonSchemaToInputSchema, type InputJsonSchema } from 'twenty-sdk/logic-function'; +import { CoreApiClient } from 'twenty-client-sdk/core'; + +const inputSchema: InputJsonSchema = { + type: 'object', + properties: { + companyName: { type: 'string', label: 'Company name' }, + domain: { type: 'string', label: 'Domain' }, + }, + required: ['companyName'], +}; + +const handler = async (params: { companyName: string; domain?: string }) => { + const client = new CoreApiClient(); + + const result = await client.mutation({ + createTask: { + __args: { + data: { + title: `Enrich data for ${params.companyName}`, + body: `Domain: ${params.domain ?? 'unknown'}`, + }, + }, + id: true, + }, + }); + + // The keys returned here should match the `outputSchema` properties below. + return { taskId: result.createTask.id, enriched: true }; +}; + +export default defineLogicFunction({ + universalIdentifier: 'f47ac10b-58cc-4372-a567-0e02b2c3d479', + name: 'enrich-company', + description: 'Enrich a company record with external data', + timeoutSeconds: 10, + handler, + workflowActionTriggerSettings: { + label: 'Enrich Company', + icon: 'IconBuilding', + inputSchema: jsonSchemaToInputSchema(inputSchema), + outputSchema: [ + { + type: 'object', + properties: { + taskId: { type: 'string' }, + enriched: { type: 'boolean' }, + }, + }, + ], + }, +}); +``` + +Once the app is installed, **Enrich Company** appears in the workflow builder's action picker. The builder renders `companyName` and `domain` as input fields (each able to pull values from previous steps), and downstream steps can reference the step's `taskId` and `enriched` outputs. + **Write a good `description`.** AI agents rely on the function's `description` field to decide when to use the tool. Be specific about what the tool does and when it should be called. diff --git a/packages/twenty-docs/user-guide/workflows/capabilities/use-iterator.mdx b/packages/twenty-docs/user-guide/workflows/capabilities/use-iterator.mdx index 8365b4a2fa..9d83151b55 100644 --- a/packages/twenty-docs/user-guide/workflows/capabilities/use-iterator.mdx +++ b/packages/twenty-docs/user-guide/workflows/capabilities/use-iterator.mdx @@ -23,6 +23,12 @@ Iterator expects an **array** as input. It then: 3. Moves to the next item 4. Repeats until all items are processed +### Selecting the array to loop over + +Most array sources (Search Records, a webhook array field, a Bulk manual trigger) can be selected directly in the variable picker. + +When a **Code** or **Logic Function** step returns a *top-level array*, its output appears in the variable picker as indexed entries (`0`, `1`, `2`, …). To loop over the array as a whole, select the **Whole list** option for that step — the Iterator then infers the shape of each item from the list automatically. + ## Basic Setup ### Example: Email Everyone in Search Results @@ -76,6 +82,8 @@ Inside Iterator, use `{{iterator.currentItem}}` to access the current record: | `{{iterator.currentItem.company.name}}` | Related company name | | `{{iterator.index}}` | Current position in array (0-based) | +In the variable picker, you can either drill into a specific field of the current item, or pick **Use the whole item** to reference the entire current item (`{{iterator.currentItem}}`). Selecting the whole item is handy when you want to pass a full record straight into a downstream step rather than rebuilding it field by field. + ## Common Patterns ### Update Multiple Records diff --git a/packages/twenty-docs/user-guide/workflows/capabilities/workflow-actions.mdx b/packages/twenty-docs/user-guide/workflows/capabilities/workflow-actions.mdx index 0459776ef4..b10d999b17 100644 --- a/packages/twenty-docs/user-guide/workflows/capabilities/workflow-actions.mdx +++ b/packages/twenty-docs/user-guide/workflows/capabilities/workflow-actions.mdx @@ -93,14 +93,14 @@ Creates a new record or updates an existing one based on matching criteria. This **Loops through an array of records** returned from a previous step, allowing you to perform actions on each record individually. **Configuration**: -- Select the array of records from a previous step (e.g., results from Search Records, from a Manual trigger with Bulk availability, from a code node) +- Select the array of records from a previous step (e.g., results from Search Records, from a Manual trigger with Bulk availability, from a code node). When a Code or Logic Function step returns a top-level array, select its **Whole list** option to loop over the entire output. - Define the actions to perform on each record in the loop. - You can add several actions within an iterator. - When using branches inside an iterator, make sure the last step of each branch connects back to the iterator to close the loop. -- Access `Current Item` Fields: to use fields from the record currently being processed, click on the **Iterator** step, then select **Current item**. The list of available fields from that record will be displayed and can be selected for use in subsequent actions. +- Access `Current Item` Fields: to use fields from the record currently being processed, click on the **Iterator** step, then select **Current item**. The list of available fields from that record will be displayed and can be selected for use in subsequent actions. You can also select **Use the whole item** to pass the entire current item into a downstream step. @@ -207,6 +207,8 @@ The fields cannot be made mandatory. ### Code Runs custom JavaScript within your workflow. +Behind the scenes, each Code action is backed by its own **logic function** — a server-side TypeScript function that runs on the Twenty platform. When you add a Code action, Twenty creates a dedicated logic function for that step and exposes its editor inline, so the code you write lives with the workflow. + **Configuration**: - Access variables from previous steps. You can edit the variables names dynamically. @@ -226,6 +228,25 @@ If you need to use external API keys in your code, you must input them directly Click the square icon at the top right of the code editor to display it in full screen — helpful since the default editor width is limited. +#### Reusing a logic function across workflows + +The inline Code action is great for one-off logic, but the code it holds belongs to that single step. When you want to share the same logic across several workflows — or maintain it as versioned source in an app — define a **reusable logic function** instead of copy-pasting code into each Code action. + +A logic function is defined once in an app (using the SDK's `defineLogicFunction`) and exposed to the workflow builder by adding `workflowActionTriggerSettings`. Once your app is installed, that function appears as its own action in the workflow builder, alongside the built-in actions. Selecting it renders the input fields you declared (with variable pickers, just like other actions) and runs your shared code — no inline JavaScript required. + +| | Code action | Reusable logic function | +|---|---|---| +| **Where the code lives** | Inline editor, tied to one workflow step | Defined in an app, versioned in source control | +| **Reuse** | Copy-pasted per step | Added as an action in any workflow | +| **Inputs** | Variables wired in the editor | Fields rendered from your declared input schema | +| **Best for** | Quick, workflow-specific logic | Shared logic used across multiple workflows | + +Both run on the same logic function runtime, so a Code action you've prototyped inline can later be promoted into a reusable logic function with minimal changes. + + +For the developer-facing reference on defining logic functions and exposing them to the workflow builder, see [Logic Functions](/developers/extend/apps/logic/logic-functions) — in particular the **Exposing a function as an AI tool or workflow action** section and `workflowActionTriggerSettings`. + + ### HTTP Request Sends a request to an external API as part of your workflow. diff --git a/packages/twenty-docs/user-guide/workflows/how-tos/advanced-configurations/handle-arrays-in-code-actions.mdx b/packages/twenty-docs/user-guide/workflows/how-tos/advanced-configurations/handle-arrays-in-code-actions.mdx index cc285a9762..298e0a2570 100644 --- a/packages/twenty-docs/user-guide/workflows/how-tos/advanced-configurations/handle-arrays-in-code-actions.mdx +++ b/packages/twenty-docs/user-guide/workflows/how-tos/advanced-configurations/handle-arrays-in-code-actions.mdx @@ -71,9 +71,9 @@ export const main = async (params: { The Code node returns a structured object instead of an array. In subsequent steps, you can now select individual fields like `answer.firstname` and `answer.name` from the variable picker. - -We're actively working on making array handling easier in future updates. - + +**Want to loop over the array instead of extracting fields?** When a Code or Logic Function step returns a top-level array, you can feed it straight into an [Iterator](/user-guide/workflows/capabilities/use-iterator): select the step's **Whole list** option as the Iterator's input, then reference each element with `{{iterator.currentItem}}` inside the loop. In that case you don't need to restructure the array into an object. + Click the square icon at the top right of the code editor to display it in full screen — helpful since the default editor width is limited.