configurator.tsx renders the configuration UI inside the workflow editor when a workspace member adds or edits a block. It runs client-side only, with no server access or side-effects.
Every block needs a configurator.tsx. It has two responsibilities:
- Render inputs for each field in the block’s config schema so workspace members can fill them in.
- Declare outcomes via the
Outcomecomponent so the editor knows what data each outcome branch exposes to downstream steps.
defineConfigurator
CalldefineConfigurator in configurator.tsx, passing the block and a render function. The render function receives the block definition and returns the JSX form.
configurator.tsx
useConfigurator
CallWorkflows.useConfigurator(workflowBlock) inside the render function to get typed input components and the Outcome component bound to the block’s config schema. Pass the workflowBlock from the defineConfigurator render callback argument.
Input components
Each input component corresponds to a schema field type. Thename prop is type-checked against the schema. TypeScript will error if name doesn’t match a field of the correct type.
All input components accept at minimum:
Combobox options
AComboboxInput on a ConfigSchema.string() field needs an options prop that supplies the choices. Pass either a static array of {value, label} options:
search (returns options matching a query) and getOption (resolves a single saved value back to its label). Both call server functions, since the configurator cannot fetch directly.
project-options.ts
<Combobox /> for the full options-provider reference.
Outcome component
Every{type: "outcome"} return value carries an id, a short identifier you choose (e.g. "created", "not-found"). Attio exposes each unique id as a named branch in the workflow editor. Workspace members wire each branch to a different downstream step. Return different id values for different code paths to build multi-branch flows.
The Outcome component declares each id to the editor and describes what data that branch carries. The editor uses this to expose typed variables to downstream steps.
When the block has only one outcome,
label is optional; the editor uses the block’s title as
the branch label. When the block has multiple outcomes, always provide label so workspace
members can tell the branches apart.schema prop uses Workflows.OutcomeSchema.* constructors, a separate namespace from the config schema constructors. Available types mirror the Outcome schema node types.
Watching config values
watch reads the current value of a config field as the workspace member types in the editor. Use it to conditionally show or hide other inputs based on what has been filled in.
watch returns a discriminated union; always check type before reading value. value is fully typed — on a ConfigSchema.stringEnum(["basic", "advanced"]) field, modeConfig.value is "basic" | "advanced", not string. On trigger blocks, type can only ever be "static": dynamic variable references come from previous steps, and a trigger has none.
type: "dynamic" means the field will be resolved at runtime, not in the editor. You cannot read a concrete value from it. When a field is dynamic, either show all dependent inputs (safe default) or hide inputs that require a known value.Loading data
Configurators often need data from a third-party service — the list of projects to pick from, the fields on a selected record, and so on. Declare yourOutcomes up front, independent of anything you load, and let the block suspend while data is loading: the editor keeps the block in a loading state until it stops suspending, then reads its outcomes.
Prefer an options provider for dropdowns
Load combobox choices with an options provider. The provider fetches options lazily as the member searches, so the block renders right away and your outcomes stay fixed.configurator.tsx
Loading other data with useAsyncCache
When you need data that isn’t a set of combobox options — for example, to label or shape the inputs you render — load it withuseAsyncCache. It suspends while loading, so the editor keeps the block in its loading state until the data is ready; you don’t need to add your own loading indicator.
Use a fixed cache key so it loads once when the configurator opens.
configurator.tsx
Example
A complete step configurator with two outcomes and typed data:configurator.tsx
See also
- Config schema: all available schema field types
- Outcome schema: typing the data field in return values
- Block definition: block identity and config schema
- useAsyncCache: load data inside the configurator
<Combobox />: full options-provider reference- Building workflow blocks: full working examples