AsyncSelect

A form field whose dropdown options are fetched from a remote endpoint as the user types.

Overview

AsyncSelect is a form field whose options are fetched from a remote endpoint when the field renders, rather than being fixed up front. Reach for it when the choices live in an API, are too many to list by hand, or change often, for example a project, user, or channel picker with type-ahead search.

Because the options aren't known when you author the field, an AsyncSelect has no default value.

Choosing between AsyncSelect and Select

UseWhen
Select with allowed_values.valuesA fixed set you can write down at author time.
Select with allowed_values.variableOptions come from a List in your account or user data.
AsyncSelectOptions are fetched from a remote endpoint, the list is large, or it changes often. Supports type-ahead search.

See Configuring data for how to set allowed_values on a Select.

A fixed list belongs in a Select

Don't point an AsyncSelect at a hardcoded list. If the choices never change, use a Select with allowed_values; it shows them instantly with no network round-trip.

Configuration

AsyncSelect options are configured under the field's data:

KeyRequiredMeaning
target_urlyesThe GET endpoint that returns the options.
object_keynoThe key (or path) to the options array in the response. Omit it if the response is the array itself.
label_keynoThe response key to show as each option's label.
value_keynoThe response key to store as each option's value.
fetch_modenoontype (default) or prefetch. See below.
allow_multiplenoAllow selecting more than one option.
filtersnoServer-side filters applied to each result. See Searching.
paginationnoCursor pagination config. See Pagination.
headersnoRequest headers sent to target_url.
paramsnoStatic query params sent to target_url.
1
2
3
4
5
6
7
8
9
10
11
- name: form__project
  type: AsyncSelect
  label: Project
  data:
    target_url: https://api.example.com/projects
    object_key: data        # the array is at response.data
    label_key: name
    value_key: id
    fetch_mode: ontype
    filters:
      - { type: contains_var, field: name, value: "field.query" }
Always set label_key and value_key

Set both explicitly. If you omit them, the label and value can come back swapped, so the dropdown shows the wrong text or stores the wrong value.

target_url, headers, and params accept placeholders, so you can build the request from runtime data. Account and user data are referenced flat by name ({{ my_var }}, not namespaced). You can also use the user's typed input ({{ field.query }}), the current form values ({{ action_data.* }}), and a connected app ({{ app_connection.* }}).

A common pattern is an auth header built from an API key in your account data:

1
2
3
4
5
6
data:
  target_url: https://api.example.com/projects
  headers:
    Authorization: "Bearer {{ crm_api_key }}"
  label_key: name
  value_key: id

When the source uses OAuth, wire the field to an app connection and reference its token, so refresh is handled for you:

1
2
3
data:
  headers:
    Authorization: "Bearer {{ app_connection.access_token }}"

The options request can't read a connection's secrets, so OAuth client credentials aren't available here.

Fetch modes

  • ontype (default): options are fetched as the user types (the input is debounced), and the endpoint filters per query. Best for large lists where you can't load everything up front.
  • prefetch: all options are fetched once when the field opens, cached, and filtered in the browser. A refresh control clears the cache. Best for smaller lists that don't change mid-session.

Searching with field.query

filters narrow the results server-side. field reads each fetched item's own property, and value is what to compare against. To compare against what the user typed, use a _var operator and set value to field.query:

1
2
filters:
  - { type: contains_var, field: name, value: "field.query" }

This keeps only results whose name contains the typed text.

Use a _var operator to read the query

Only the _var and _field operators resolve value as a reference. A plain operator treats value as a literal, so { type: contains, value: "field.query" } looks for the literal text field.query. Use contains_var (and the other _var variants) whenever value is field.query or another variable.

Filters use the same operators as workflow conditions: contains, excludes, equal, not_equal, present, empty, the numeric lt / gt / lte / gte family, and their _var / _field variants. Match mode is all or any.

Pagination

pagination is cursor-based. Set type: cursor and tell the fetcher where the cursor lives:

KeyMeaning
cursor_paramThe request param that carries the cursor to the next page.
cursor_pathWhere to read the next cursor from in the response body.
cursor_headerWhere to read the next cursor from in a response header (use instead of cursor_path).
max_itemsHow many options to gather, capped at 2000.

Fetching stops at max_items, at 50 pages, or when there's no next cursor. Without a cursor config, only the first page is fetched.

Troubleshooting

An empty dropdown means the fetch returned nothing

If the picker shows no options, the request failed or came back empty; it isn't surfaced as an error. Check that target_url is reachable, that object_key points at the array, that any headers or params are correct, and that label_key / value_key match the response.