Skip to content
Block Kit for React
Esc
↑↓navigate↵open⌘Jpreview
On this page

External data

Fill an external_select's options from your own data with onOptions and block_suggestion.

A static select ships its options inline. An external_select (and multi_external_select) asks your app for them instead, as the user types. Slack calls this a block_suggestion request. @nkootstra/block-kit emulates the same request/response cycle in the browser through onOptions.

Your AppAPP
Pick a fruit
onActionInteract with the preview.

onOptions

Pass onOptions to <BlockKitProvider>. It receives the block_suggestion payload and returns options the same way an app’s options-load URL would reply:

import { BlockKitProvider, Message, type OptionsResponse } from "@nkootstra/block-kit";

const FRUIT = ["Apple", "Banana", "Cherry", "Grape", "Mango", "Orange", "Peach", "Pear"];

function onOptions({ value }: { value: string }): OptionsResponse {
  const query = value.toLowerCase();
  return {
    options: FRUIT.filter((name) => name.toLowerCase().includes(query)).map((name) => ({
      text: { type: "plain_text", text: name },
      value: name.toLowerCase(),
    })),
  };
}

export function App() {
  return (
    <BlockKitProvider onOptions={onOptions}>
      <Message
        blocks={[
          {
            type: "section",
            block_id: "fruit",
            text: { type: "mrkdwn", text: "Pick a fruit" },
            accessory: {
              type: "external_select",
              action_id: "value",
              placeholder: { type: "plain_text", text: "Search fruit" },
              min_query_length: 1,
            },
          },
        ]}
      />
    </BlockKitProvider>
  );
}

onOptions can also return a promise, so it’s fine to call a real API from it.

Without an onOptions handler, an external_select still lets the user type a value and press Enter, but it just never offers suggestions, since there’s nothing to ask.

The block_suggestion payload

onOptions receives the exact shape Slack sends to an options-load URL:

PropType
type?"block_suggestion"
Type"block_suggestion"
action_id?string
Typestring
block_id?string
Typestring
value?string

What the user has typed into the search box so far.

Typestring
container?Container

The message or view the select is in: { type: "message", ... } or { type: "view", ... }.

TypeContainer
view?ViewLike

Present when the select is inside a modal or Home tab.

TypeViewLike
team?PayloadTeam | null
TypePayloadTeam | null
user?PayloadUser
TypePayloadUser

Use value to filter your results, and block_id / action_id if the same handler serves more than one external select.

Answering with options or option_groups

PropType
options?SuggestionOption[]

Flat list. Each is { text: { type: "plain_text", text }, value, description?, url? }.

TypeSuggestionOption[]
option_groups?Array<{ label, options }>

Labelled groups, each with its own options array, same option shape as above.

TypeArray<{ label, options }>
function onOptions({ value }: { value: string }): OptionsResponse {
  return {
    option_groups: [
      {
        label: { type: "plain_text", text: "Citrus" },
        options: [{ text: { type: "plain_text", text: "Orange" }, value: "orange" }],
      },
      {
        label: { type: "plain_text", text: "Stone fruit" },
        options: [{ text: { type: "plain_text", text: "Peach" }, value: "peach" }],
      },
    ],
  };
}

Slack allows up to 100 options total across options/option_groups in a single response.

min_query_length and debouncing

An external_select waits for min_query_length characters (Slack’s default is 3) before it asks onOptions at all, and debounces keystrokes after that so it doesn’t fire on every character. Set min_query_length on the element to change the threshold. The preview above uses 1 so it responds immediately:

{ type: "external_select", action_id: "value", min_query_length: 1 }

Inside a modal

onOptions works the same way whether the select is in a message, a modal, or the Home tab: the payload’s container/view tells you which. This is what lets an external_select inside a modal load real data:

Your AppAPP

Was this page helpful?