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
| Use | When |
|---|---|
Select with allowed_values.values | A fixed set you can write down at author time. |
Select with allowed_values.variable | Options come from a List in your account or user data. |
AsyncSelect | Options 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.
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:
| Key | Required | Meaning |
|---|---|---|
target_url | yes | The GET endpoint that returns the options. |
object_key | no | The key (or path) to the options array in the response. Omit it if the response is the array itself. |
label_key | no | The response key to show as each option's label. |
value_key | no | The response key to store as each option's value. |
fetch_mode | no | ontype (default) or prefetch. See below. |
allow_multiple | no | Allow selecting more than one option. |
filters | no | Server-side filters applied to each result. See Searching. |
pagination | no | Cursor pagination config. See Pagination. |
headers | no | Request headers sent to target_url. |
params | no | Static 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" }
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 6data: 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 3data: 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 2filters: - { type: contains_var, field: name, value: "field.query" }
This keeps only results whose name contains the typed text.
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:
| Key | Meaning |
|---|---|
cursor_param | The request param that carries the cursor to the next page. |
cursor_path | Where to read the next cursor from in the response body. |
cursor_header | Where to read the next cursor from in a response header (use instead of cursor_path). |
max_items | How 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
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.
Related
- Authoring a Configuration: where form fields fit in a config
- Placeholders: the
{{ ... }}syntax used intarget_url,headers, andparams
