Back to Blog
Guide

Slack Block Kit Select Menus: Static, Multi, Users and External, Tested

We posted all four common select menus from a Socket Mode app, picked a value in each, logged the block_actions payloads, and timed an external_select options handler from 0 to 5 seconds. Past 3 seconds Slack silently shows the old results.

Slack Green Team
October 3, 2026
October 3, 2026
4 min read
Share:
slack api
developers
block kit

A Block Kit select menu is an element you put in a section's accessory or an actions block. static_select takes up to 100 options you send with the message, multi_static_select lets people pick several, users_select lists the workspace's members, and external_select asks your app for options as the user types. On 3 October 2026 we posted all four from a Bolt for Python Socket Mode app in our own test workspace, picked a value in each, and timed the external options handler. The external menu has a hard 3 second limit, and missing it does not show an error.

The message we posted

def opt(t, v): return {"text": {"type": "plain_text", "text": t}, "value": v}
FRUIT = [opt("Apple", "apple"), opt("Banana", "banana"), opt("Cherry", "cherry")]

blocks = [
  {"type": "section", "block_id": "s_static", "text": {"type": "mrkdwn", "text": "*static_select*"},
   "accessory": {"type": "static_select", "action_id": "pick_static",
                 "placeholder": {"type": "plain_text", "text": "Pick a fruit"}, "options": FRUIT}},
  {"type": "section", "block_id": "s_multi", "text": {"type": "mrkdwn", "text": "*multi_static_select*"},
   "accessory": {"type": "multi_static_select", "action_id": "pick_multi",
                 "placeholder": {"type": "plain_text", "text": "Pick fruits"}, "options": FRUIT, "max_selected_items": 2}},
  {"type": "section", "block_id": "s_users", "text": {"type": "mrkdwn", "text": "*users_select*"},
   "accessory": {"type": "users_select", "action_id": "pick_user",
                 "placeholder": {"type": "plain_text", "text": "Pick a person"}}},
  {"type": "section", "block_id": "s_ext", "text": {"type": "mrkdwn", "text": "*external_select*"},
   "accessory": {"type": "external_select", "action_id": "pick_ext",
                 "placeholder": {"type": "plain_text", "text": "Search tickets"}, "min_query_length": 0}},
]
client.chat_postMessage(channel=channel_id, text="612 select menus", blocks=blocks)

One listener caught every pick:

@app.action(re.compile("pick_.*"))
def picked(ack, body):
    ack()
    print(json.dumps(body["actions"][0]))

The block_actions value for each menu

These are the actions[0] objects Slack sent when we picked a value:

{"type": "static_select", "action_id": "pick_static", "block_id": "s_static", "selected_option": {"text": {"type": "plain_text", "text": "Banana", "emoji": true}, "value": "banana"}, "placeholder": {"type": "plain_text", "text": "Pick a fruit", "emoji": true}, "action_ts": "1790989996.318666"}
{"type": "multi_static_select", "action_id": "pick_multi", "block_id": "s_multi", "selected_options": [{"text": {"type": "plain_text", "text": "Apple", "emoji": true}, "value": "apple"}, {"text": {"type": "plain_text", "text": "Banana", "emoji": true}, "value": "banana"}], "placeholder": {"type": "plain_text", "text": "Pick fruits", "emoji": true}, "action_ts": "1790990058.398004"}
{"type": "users_select", "action_id": "pick_user", "block_id": "s_users", "selected_user": "U0B7L4YK420", "action_ts": "1790990079.293592"}
{"type": "external_select", "action_id": "pick_ext", "block_id": "s_ext", "selected_option": {"text": {"type": "plain_text", "text": "all result 1", "emoji": true}, "value": "all-1"}, "placeholder": {"type": "plain_text", "text": "Search tickets", "emoji": true}, "action_ts": "1790990342.110390"}

Read selected_option.value for single selects, selected_options[].value for multi selects, and selected_user for users_select. The full envelope around these objects is on our interactivity payload page.

Things we saw that the reference does not mention:

  • • users_select listed bots and deactivated apps too: our test app, Slackbot, Slack Security, and four deleted test apps marked "(deactivated) APP". Check selected_user with users.info if you only want people.
  • • In a message, multi_static_select does not open a dropdown. It opens a "Pick fruits" dialog with the line "You can select up to 2 items." and Cancel and Confirm buttons. The pick is sent only on Confirm.
  • • Picking a third item turned the hint into a warning and disabled Confirm:

Slack dialog titled Pick fruits with Apple, Cherry and Banana selected, a yellow warning "You have selected too many items, you can only go up to 2." and a greyed-out Confirm button

When we closed that dialog with Escape, no event reached the app, yet the button in the message read "3 selected" until we reopened it. Do not trust what the button says; trust the last block_actions you received.

Never appear "away" on Slack again

Cloud-based. No downloads. Works 24/7 even when your laptop is off.

How long the external options handler can take

The handler slept for a set time, then answered with options named after the query, so we could tell old results from new ones:

@app.options("pick_ext")
def ext_options(ack, body):
    time.sleep(DELAY)
    q = body.get("value") or "all"
    ack(options=[opt(f"{q} result {i}", f"{q}-{i}") for i in range(1, 6)])

We typed a new query each time and recorded the menu's text every 50 ms in the browser. Slack showed "Loading results…" under the old list at once. Times are from the last keystroke:

Handler delayWhat the user sawBolt for Python log
0 sNew results at 995 ms(nothing)
1 sNew results at 2,118 ms(nothing)
2.5 sNew results at 3,613 ms(nothing)
2.9 sNew results at 4,015 ms(nothing)
3.1 sSpinner gone at 4,002 ms, old results keptext_options didn't call ack()
3.5 sSpinner gone at 4,100 ms, old results keptext_options didn't call ack()

Even a 0-second handler took about a second to show, because Slack waits for typing to pause before it asks your app. Past 3 seconds there was no error, no "No results", nothing. The menu dropped the spinner and kept the previous query's results under the new query:

Slack external_select with "latequery" typed in the box and a list that still shows "twofive result 1" to "twofive result 5" from the previous query

That is worse than an error, because a user can pick a result that does not match what they typed. Bolt for Python also gives up at 3 seconds and logs Unhandled request ({'type': 'block_suggestion', ...}). To rule out Bolt, we answered the same requests from a bare slack_sdk SocketModeClient with no timeout of its own. Answers at 2.9 s showed up; answers at 3.5 s and 5 s were ignored the same way. The 3 second limit is Slack's.

So keep the options handler under about 2.5 seconds: query an index or a cache, never a slow API, and return fewer, better matches.

Limits Slack enforced

What we sentResponse
static_select with 100 optionsok
static_select with 101 optionsinvalid_blocks, [ERROR] no more than 100 items allowed [json-pointer:/blocks/0/accessory/options]
Option text of 76 charactersinvalid_blocks, [ERROR] must be less than 76 characters [json-pointer:/blocks/0/accessory/options/0/text/text]
Option value of 151 charactersinvalid_blocks, [ERROR] must be less than 151 characters [json-pointer:/blocks/0/accessory/options/0/value]
external_select with an options arrayinvalid_blocks, [ERROR] invalid additional property: options [json-pointer:/blocks/0/accessory]
External handler returning 101 optionsNo error anywhere. The menu kept the previous query's results.
External handler returning 100 optionsShown

So option text tops out at 75 characters and values at 150. The silent failure for 101 external options matches the timeout case: if your search can return more, cut the list to 100 before you ack.

To build these blocks by hand first, our Block Kit Builder guide shows the preview workflow, and the invalid_blocks guide explains how to read the json-pointer in each error.

Always Active

Stop Jiggling Your Mouse.

Join hundreds of remote workers who never worry about their Slack status. Set it up once, stay green forever.

Related Articles

Guide

How to See All Your DMs in Slack, Including the Hidden Ones

The DMs tab lists your direct messages with people. App DMs, Slackbot and DMs with deleted accounts sit elsewhere. We compared the tab with the full list the Slack API returned.

Slack Green Team
Guide

Slack Images Not Showing? 5 Causes We Reproduced

We uploaded images to a Slack test channel and then switched each image setting off, ran /collapse, and posted from a bot that was not in the channel. Five causes, each reproduced.

Slack Green Team
Guide

How to Message Yourself on Slack (and What It Notifies)

Your own name under Direct messages opens a private DM with yourself. We sent it a note, a file, a scheduled message and a reminder, and checked which ones raised a badge.

Slack Green Team