> ## 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)` | Dropdown from a fixed list             |
| `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                                          |

## 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>

## 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
* [Building workflow blocks](/guides/building-workflow-blocks): full working examples
