Files
twenty/packages/twenty-sdk/docs/logic-function-inputs.md
T
Raphaël Bosi 3675f264f1 Infer record pickers for record-typed logic function workflow inputs (#21494)
## Context

Logic functions can declare workflow inputs typed as records or arrays
of records (e.g. the People Data Labs enrichment functions), but the
workflow builder rendered those as a plain text input with a variable
picker, which is not usable.

## What this does

- Adds an `objectUniversalIdentifier` link on input schema properties,
so a record-typed input is tied to a workspace object.
- The SDK build infers it from a
`TwentyRecord<'objectUniversalIdentifier'>` marker type in the handler
signature, reading the object's universal identifier straight from the
source; explicit input schemas can still set the field directly.
- The workflow builder renders these inputs as a single record picker or
a record multi-select with the variable picker on the right. Selected
records are stored as record ids; `TwentyRecord<UID>` is a branded
`string`, so the handler signature reflects that it receives ids (a
bound variable resolves to whatever the referenced step produced).
- The multi-select collapses overflowing chips into a `+N` badge
(reusing `ExpandableList`) and its variable picker offers both record
objects and fields.
- Updates the People Data Labs enrichment inputs as the reference
implementation.

<img width="802" height="824" alt="CleanShot 2026-06-12 at 16 54 10@2x"
src="https://github.com/user-attachments/assets/a0896d74-0aab-49bd-a173-14c578a2e533"
/>


<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/21494?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. -->
2026-06-19 09:10:01 +02:00

67 lines
2.6 KiB
Markdown

# Logic function inputs
When a logic function opts into the workflow action or AI tool surface but does
not declare an explicit `inputSchema`, the SDK infers one from the handler's
parameter type during the manifest build. The workflow builder uses that schema
to render an input form, and record-typed inputs render as record pickers.
## How inference works
Inference reads the handler's single `params` object type and maps each property:
- `string` / `number` / `boolean` map to the matching scalar input.
- String literal unions (`'a' | 'b'`) map to a select input.
- `T[]` / `Array<T>` map to array inputs.
- `TwentyRecord<'objectUniversalIdentifier'>` maps to a record input (see below).
Inference runs only when the trigger settings omit `inputSchema`. Providing an
explicit `inputSchema` disables inference for that surface entirely — this is the
escape hatch when a handler type cannot be expressed inline.
## Record-typed inputs
To bind an input to a workspace object, type it with `TwentyRecord`, passing the
object's universal identifier as a string literal:
```ts
import { defineLogicFunction, type TwentyRecord } from 'twenty-sdk/define';
const handler = async (params: {
companyId: TwentyRecord<'20202020-b374-4779-a561-80086cb2e17f'>;
postCardIds: TwentyRecord<'54b589ca-eeed-4950-a176-358418b85c05'>[];
}) => {
return {
companyId: params.companyId,
postCardCount: params.postCardIds.length,
};
};
```
The universal identifier is the source of truth and is read directly from the
literal — there is no name matching, so an unrelated type can never be mistaken
for a record.
- **Standard objects**: get the identifier from `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS`
(exported from `twenty-sdk/define`), e.g.
`STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.universalIdentifier`.
- **App objects**: use the `universalIdentifier` you set on the object's
`defineObject(...)`.
Only a string-literal argument resolves. `TwentyRecord` with no argument, or with
a non-literal argument, is treated as an unknown input.
## What the handler receives
`TwentyRecord<TUid>` is a branded `string`: `companyId` is a record id, and
`postCardIds` is an array of record ids. This matches what the runtime delivers —
the workflow action passes the selected record ids (or the value a bound
`{{variable}}` resolves to) straight to the handler. Handlers must therefore
accept ids. The People Data Labs functions model this:
```ts
export type RecordInput = string | { id?: string | null };
```
and normalize the input with an `extractRecordIds` helper before use. If a
handler needs full records, it fetches them by id with the Core API client.