Update workflows documentation (#22356)
## Summary Documentation-only updates to the workflow and logic-function docs: - **Code action ↔ logic functions**: clarify that each Code action is backed by its own logic function, and document how to reuse logic across workflows via `workflowActionTriggerSettings` (Code/User Guide + Logic Functions/Developer docs cross-linked). - **`workflowActionTriggerSettings` example**: add a complete example (`label`, `icon`, `inputSchema`, `outputSchema`) and document the previously-undocumented `outputSchema` field. - **Iterator improvements** (docs for #22031): document the new **"Use the whole item"** (reference the whole current item) and **"Whole list"** (loop over a step's top-level array output) options across the Iterator and array-handling guides. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22356?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. -->
This commit is contained in:
@@ -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.
|
||||
|
||||
<Note>
|
||||
**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.
|
||||
</Note>
|
||||
|
||||
```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.
|
||||
|
||||
<Note>
|
||||
**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.
|
||||
</Note>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
<Note>
|
||||
- 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.
|
||||
</Note>
|
||||
|
||||
- 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.
|
||||
|
||||
<VimeoEmbed videoId="1146577247" title="Video demonstration" />
|
||||
|
||||
@@ -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.
|
||||
<VimeoEmbed videoId="1147281795" title="Video demonstration" />
|
||||
@@ -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.
|
||||
</Tip>
|
||||
|
||||
#### 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.
|
||||
|
||||
<Tip>
|
||||
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`.
|
||||
</Tip>
|
||||
|
||||
### HTTP Request
|
||||
Sends a request to an external API as part of your workflow.
|
||||
<img src="/images/user-guide/workflows/http_action.png" style={{width:'100%'}}/>
|
||||
|
||||
+3
-3
@@ -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.
|
||||
|
||||
<Note>
|
||||
We're actively working on making array handling easier in future updates.
|
||||
</Note>
|
||||
<Tip>
|
||||
**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.
|
||||
</Tip>
|
||||
|
||||
<Tip>
|
||||
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.
|
||||
|
||||
Reference in New Issue
Block a user