> ## Documentation Index
> Fetch the complete documentation index at: https://docs.attio.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Configurator

> Render config inputs and declare outcomes in the workflow editor

`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:

1. **Render inputs** for each field in the block's config schema so workspace members can fill them in.
2. **Declare outcomes** via the `Outcome` component so the editor knows what data each outcome branch exposes to downstream steps.

## defineConfigurator

Call `defineConfigurator` in `configurator.tsx`, passing the block and a render function. The render function receives the block definition and returns the JSX form.

```tsx configurator.tsx theme={"system"}
import {Workflows} from "attio/client"
import block from "./block"

export default Workflows.defineConfigurator(block, (workflowBlock) => {
  const {TextInput, Outcome} = Workflows.useConfigurator(workflowBlock)
  return (
    <>
      <TextInput name="project_id" label="Project ID" />
      <Outcome
        id="created"
        label="Task created"
        schema={{task_id: Workflows.OutcomeSchema.string()}}
      />
      <Outcome id="not-found" label="Project not found" schema={null} />
    </>
  )
})
```

## useConfigurator

Call `Workflows.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. The `name` prop is type-checked against the schema. TypeScript will error if `name` doesn't match a field of the correct type.

| Component             | Schema type                                                  | Description                                               |
| --------------------- | ------------------------------------------------------------ | --------------------------------------------------------- |
| `TextInput`           | `ConfigSchema.string()`                                      | Single-line text                                          |
| `RichTextInput`       | `ConfigSchema.richText()`                                    | Formatted text                                            |
| `NumberInput`         | `ConfigSchema.number()`                                      | Numeric value                                             |
| `CheckboxInput`       | `ConfigSchema.boolean()`                                     | True/false toggle                                         |
| `ComboboxInput`       | `ConfigSchema.stringEnum(values)` or `ConfigSchema.string()` | Dropdown — a fixed list, or searchable options you supply |
| `DateInput`           | `ConfigSchema.date()`                                        | Calendar date                                             |
| `TimestampInput`      | `ConfigSchema.timestamp()`                                   | Date and time                                             |
| `DurationInput`       | `ConfigSchema.duration()`                                    | Length of time                                            |
| `EmailAddressInput`   | `ConfigSchema.emailAddress()`                                | Email address                                             |
| `PhoneNumberInput`    | `ConfigSchema.phoneNumber()`                                 | Phone number                                              |
| `PersonalNameInput`   | `ConfigSchema.personalName()`                                | First and last name                                       |
| `DomainInput`         | `ConfigSchema.domain()`                                      | Web domain                                                |
| `LocationInput`       | `ConfigSchema.location()`                                    | Geographic location                                       |
| `CurrencyInput`       | `ConfigSchema.currency()`                                    | Monetary value                                            |
| `AttioRecordInput`    | `ConfigSchema.attioRecord()`                                 | Attio record                                              |
| `AttioObjectInput`    | `ConfigSchema.attioObject()`                                 | Attio object type                                         |
| `AttioListInput`      | `ConfigSchema.attioList()`                                   | Attio list                                                |
| `AttioActorInput`     | `ConfigSchema.attioActor()`                                  | Attio actor (user or workspace member)                    |
| `AttioAttributeInput` | `ConfigSchema.attioAttribute()`                              | Attio attribute                                           |
| `AttioSelectInput`    | `ConfigSchema.attioSelect()`                                 | Select value from an Attio attribute                      |
| `AttioSequenceInput`  | `ConfigSchema.attioSequence()`                               | Attio sequence                                            |
| `CollectionInput`     | `ConfigSchema.array(element)`                                | Repeating list of values                                  |

All input components accept at minimum:

| Prop          | Type     | Required | Description                                               |
| ------------- | -------- | -------- | --------------------------------------------------------- |
| `name`        | `string` | Yes      | Path to the schema field, type-checked against the schema |
| `label`       | `string` | Yes      | Human-readable label shown above the input                |
| `help`        | `string` | No       | Help text shown below the input                           |
| `tooltip`     | `string` | No       | Tooltip shown on hover                                    |
| `placeholder` | `string` | No       | Placeholder text                                          |

### Combobox options

A `ComboboxInput` on a `ConfigSchema.string()` field needs an `options` prop that supplies the choices. Pass either a static array of `{value, label}` options:

```tsx theme={"system"}
<ComboboxInput
  name="priority"
  label="Priority"
  options={[
    {value: "low", label: "Low"},
    {value: "high", label: "High"},
  ]}
/>
```

Or an **options provider** that loads them from a third party as the member types. A provider is an object with `search` (returns options matching a query) and `getOption` (resolves a single saved value back to its label). Both call [server functions](/sdk/server/server-functions), since the configurator cannot `fetch` directly.

```ts project-options.ts theme={"system"}
import type {PlainComboboxOptionsProvider} from "attio/client"
import getProject from "./get-project.server"
import listProjects from "./list-projects.server"

export const projectOptions: PlainComboboxOptionsProvider = {
  async search(query) {
    const projects = await listProjects(query)
    return projects.map(({id, name}) => ({value: id, label: name}))
  },
  async getOption(value) {
    const project = await getProject(value)
    return project ? {label: project.name} : undefined
  },
}
```

```tsx theme={"system"}
<ComboboxInput name="project_id" label="Project" options={projectOptions} />
```

See [`<Combobox />`](/sdk/components/forms/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.

```tsx theme={"system"}
<>
  <Outcome
    id="created"
    label="Task created"
    schema={{
      task_id: Workflows.OutcomeSchema.string(),
      task_url: Workflows.OutcomeSchema.string(),
    }}
  />
  <Outcome id="not-found" label="Project not found" schema={null} />
</>
```

| Prop     | Type             | Description                                                                                                     |
| -------- | ---------------- | --------------------------------------------------------------------------------------------------------------- |
| `id`     | `string`         | Matches the `id` returned by your handler                                                                       |
| `label`  | `string`         | Human-readable name shown in the editor as the branch label                                                     |
| `schema` | `object \| null` | Shape of the outcome data, built with `Workflows.OutcomeSchema.*` nodes. Pass `null` for outcomes with no data. |

<Note>
  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.
</Note>

The `schema` prop uses `Workflows.OutcomeSchema.*` constructors, a separate namespace from the config schema constructors. Available types mirror the [Outcome schema](./outcome-schema) node types.

```ts theme={"system"}
Workflows.OutcomeSchema.string()
Workflows.OutcomeSchema.number()
Workflows.OutcomeSchema.boolean()
Workflows.OutcomeSchema.date()
Workflows.OutcomeSchema.timestamp()
Workflows.OutcomeSchema.emailAddress()
Workflows.OutcomeSchema.array(Workflows.OutcomeSchema.string())
Workflows.OutcomeSchema.struct({key: Workflows.OutcomeSchema.string()})
// ...and all other 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.

| Shape                        | Meaning                                                                                               |
| ---------------------------- | ----------------------------------------------------------------------------------------------------- |
| `undefined`                  | Field not yet filled in                                                                               |
| `{type: "static", value: T}` | User entered a literal value; `value` is typed against the schema field                               |
| `{type: "dynamic"}`          | User wired a variable reference (e.g. output from a previous step); value is not known at config time |

```tsx theme={"system"}
export default Workflows.defineConfigurator(block, (workflowBlock) => {
  const {ComboboxInput, TextInput, NumberInput, Outcome, watch} =
    Workflows.useConfigurator(workflowBlock)

  // "mode" is defined as ConfigSchema.stringEnum(["basic", "advanced"]) in the block
  const modeConfig = watch("mode") // undefined if not yet filled in

  // when type is "static", .value is typed as "basic" | "advanced" — not just string
  const mode = modeConfig?.type === "static" ? modeConfig.value : undefined

  return (
    <>
      <ComboboxInput name="mode" label="Mode" />
      {mode === "advanced" && <TextInput name="custom_endpoint" label="Custom endpoint" />}
      {mode === "advanced" && <NumberInput name="timeout_ms" label="Timeout (ms)" />}
      <Outcome id="done" schema={null} />
    </>
  )
})
```

<Note>
  `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.
</Note>

## 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 your `Outcome`s 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](#combobox-options). The provider fetches options lazily as the member searches, so the block renders right away and your outcomes stay fixed.

```tsx configurator.tsx theme={"system"}
import {Workflows} from "attio/client"
import {projectOptions} from "./project-options"
import block from "./block"

export default Workflows.defineConfigurator(block, () => {
  const {ComboboxInput, TextInput, Outcome} = Workflows.useConfigurator(block)
  return (
    <>
      <ComboboxInput name="project_id" label="Project" options={projectOptions} />
      <TextInput name="content" label="Task name" />
      <Outcome
        id="created"
        label="Task created"
        schema={{task_id: Workflows.OutcomeSchema.string()}}
      />
      <Outcome id="project-not-found" label="Project not found" schema={null} />
    </>
  )
})
```

### 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 with [`useAsyncCache`](/sdk/data-fetching/use-async-cache). 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.

```tsx configurator.tsx theme={"system"}
import {useAsyncCache, Workflows} from "attio/client"
import loadAccount from "./load-account.server"
import block from "./block"

// Loads once when the configurator opens.
function TaskNameInput() {
  const {TextInput} = Workflows.useConfigurator(block)
  const {values} = useAsyncCache({account: loadAccount})
  return (
    <TextInput
      name="content"
      label="Task name"
      help={`Adding to the ${values.account.workspace_name} workspace.`}
    />
  )
}

export default Workflows.defineConfigurator(block, () => {
  const {TextInput, Outcome} = Workflows.useConfigurator(block)
  return (
    <>
      <TextInput name="project_id" label="Project ID" />
      <TaskNameInput />
      <Outcome
        id="created"
        label="Task created"
        schema={{task_id: Workflows.OutcomeSchema.string()}}
      />
      <Outcome id="project-not-found" label="Project not found" schema={null} />
    </>
  )
})
```

## Example

A complete step configurator with two outcomes and typed data:

```tsx configurator.tsx theme={"system"}
import {Workflows} from "attio/client"
import block from "./block"

export default Workflows.defineConfigurator(block, (workflowBlock) => {
  const {TextInput, DateInput, Outcome} = Workflows.useConfigurator(workflowBlock)
  return (
    <>
      <TextInput name="project_id" label="Project ID" />
      <TextInput name="content" label="Task name" />
      <DateInput name="due_date" label="Due date" />
      <Outcome
        id="created"
        label="Task created"
        schema={{
          task_id: Workflows.OutcomeSchema.string(),
          task_url: Workflows.OutcomeSchema.string(),
        }}
      />
      <Outcome id="project-not-found" label="Project not found" schema={null} />
    </>
  )
})
```

## See also

* [Config schema](./config-schema): all available schema field types
* [Outcome schema](./outcome-schema): typing the data field in return values
* [Block definition](./define-workflow-block): block identity and config schema
* [useAsyncCache](/sdk/data-fetching/use-async-cache): load data inside the configurator
* [`<Combobox />`](/sdk/components/forms/combobox): full options-provider reference
* [Building workflow blocks](/guides/building-workflow-blocks): full working examples
