Help users make clear, concise decisions by providing a menu of options within messages.
Message menus build on our interactive message framework allowing your Slack app to provide more expansive and evolutionary selections than previously possible.
Message buttons empower limited and precise workflows within Slack conversations. Every one wants to click those tender buttons.
There's a new way to promote more nuanced decision-making.
Instead of buttons, users encounter drop downs, each containing a series of options: perhaps a list of fellow members, a list of Slack channels, or a list of actions you provide while creating the message. Message menus can even be dynamically populated with options based on your server's response.
To create messages with message menus and respond to user selections, you'll need a Slack app set up with a functional interactive message Request URL.
This guide assumes that you're already familiar with posting messages to Slack, whether via chat.postMessage
, chat.postEphemeral
, incoming webhooks, or in response to slash command invocations.
We also assume you're already familiar with adding message formatting and message attachments to your messages.
Building message menus and responding to user interaction builds on patterns already established with interactive messages and message buttons. These three documents form a voltronic gestalt.
There's more to it than this, but here's a recap of how interactive messages work.
attachments
containing one or more interactive elementscallback_id
was associated with the message, and the original message inciting the selectionresponse_url
to update the original message out of band for a limited time.This pattern doesn't occur in a vacuum — often you will coalesce the simultaneous option selection of multiple members interacting with one or (possibly many) more messages.
Interactive messages are an evolving narrative, where one message may be all it takes to complete a workflow. Or that one message may become a chain of many messages, progressively evolving to suit the goal. Or that one message may be continuously destroyed and rebuilt again anew, with new options, new goals, new ways to discover the self.
The most basic kind of message menu is one where your app provides a static set of options to select from.
For a comprehensive accounting of all the fields related to interactive messages, please consult the dedicated field guide.
If providing three or more pre-determined options, this is the best approach to making your message interactive.
For example, this message presents users with a menu of possible (but increasingly dangerous) games to play:
{
"text": "Would you like to play a game?",
"response_type": "in_channel",
"attachments": [
{
"text": "Choose a game to play",
"fallback": "If you could read this message, you'd be choosing something fun to do right now.",
"color": "#3AA3E3",
"attachment_type": "default",
"callback_id": "game_selection",
"actions": [
{
"name": "games_list",
"text": "Pick a game...",
"type": "select",
"options": [
{
"text": "Hearts",
"value": "hearts"
},
{
"text": "Bridge",
"value": "bridge"
},
{
"text": "Checkers",
"value": "checkers"
},
{
"text": "Chess",
"value": "chess"
},
{
"text": "Poker",
"value": "poker"
},
{
"text": "Falken's Maze",
"value": "maze"
},
{
"text": "Global Thermonuclear War",
"value": "war"
}
]
}
]
}
]
}
This message structure is very similar to those used in message buttons.
They key difference is that instead of each button being a distinct action, a message menu is represented as a single action with multiple options.
As users select options from the provided drop down, your Request URL will receive invocations similar to those found in message buttons. You'll receive a HTTP POST with a payload
body parameter containing a string-encoded JSON object. Decode the JSON to your programming language's native hash/object data structure to evaluate the action.
Continuing with the above example, if a user had selected the Falken's Maze
menu option, Slack would issue a request to your configured Request URL indicating that the games_list
select dialog was used to choose the maze
option.
Here's example JSON sent in this scenario, already decoded from the payload
parameter:
{
"type": "interactive_message",
"actions": [
{
"name": "games_list",
"selected_options": [
{
"value": "maze"
}
]
}
],
"callback_id": "game_selection",
"team": {
"id": "T012AB0A1",
"domain": "pocket-calculator"
},
"channel": {
"id": "C012AB3CD",
"name": "general"
},
"user": {
"id": "U012A1BCD",
"name": "muzik"
},
"action_ts": "1481579588.685999",
"message_ts": "1481579582.000003",
"attachment_id": "1",
"token": "verification_token_string",
"original_message": {
"text": "Pick a game...",
"bot_id": "B08BCU62D",
"attachments": [
{
"callback_id": "game_selection",
"fallback": "Upgrade your Slack client to use messages like these.",
"id": 1,
"color": "3AA3E3",
"actions": [
{
"name": "games_list",
"text": "Pick a game...",
"type": "select",
"options": [
{
"text": "Chess",
"value": "chess"
},
{
"text": "Falken's Maze",
"value": "maze"
},
{
"text": "Global Thermonuclear War",
"value": "war"
}
]
}
]
}
],
"type": "message",
"subtype": "bot_message",
"ts": "1481579582.000003"
},
"response_url": "https://hooks.slack.com/actions/T012AB0A1/1234567890/JpmK0yzoZ5eRiqfeduTBYXWQ",
"trigger_id": "13345224609.738474920.8088930838d88f008e0"
}
We have exhaustive detail on these fields in the field guide, but here are some highlights:
You'll find an original_message
attached for those of you that like to re-use message bodies and don't keep them in memory. original_message
is not included when referring to an ephemeral message, and only contains attachment data when working with a link unfurl interactive message.
There's also the callback_id
, helping identify this specific instance of interaction, along with context around the team
and channel
this happened in and the user
invoking this action. You know to use the token
value to validate this inbound request comes from Slack.
So let's focus on what's new and relevant in the top-level actions
array.
name
- the string you provided as the name of this message menu. Like games_list
used above.selected_options
- an array of option value hashes selected by the user from this message menu. The example above shows a single select option value
set to maze
, but it could have been war
or chess
. At this time only a single option can be selected by the user or delivered to your app.After receiving a Request URL invocation, your response pattern remains the same as message buttons: respond directly to the message with another (adding a new message, replacing the original, or delivering something more ephemeral) and/or use the response_url
and perhaps chat.update
to deliver further interactions.
It's easy to populate a message menu with a list of a members to select from. Slack will even populate the user list client-side, so your app doesn't even need access to a related OAuth scope.
When users make selections, your Request URL receives any selected user's User ID.
The magic begins by specifying users
as your action's data_source
:
Here's a demonstrative example:
{
"text": "I hope the tour went well, Mr. Wonka.",
"response_type": "in_channel",
"attachments": [
{
"text": "Who wins the lifetime supply of chocolate?",
"fallback": "You could be telling the computer exactly what it can do with a lifetime supply of chocolate.",
"color": "#3AA3E3",
"attachment_type": "default",
"callback_id": "select_simple_1234",
"actions": [
{
"name": "winners_list",
"text": "Who should win?",
"type": "select",
"data_source": "users"
}
]
}
]
}
You can also provide a message menu of channels. You don't need the associated scopes to use this approach. Your Request URL will receive only the selected channel's ID.
Users will only be able to select from public channels on their workspace.
Specify channels
as your action's data_source
like so:
{
"text": "It's time to nominate the channel of the week",
"response_type": "in_channel",
"attachments": [
{
"fallback": "Upgrade your Slack client to use messages like these.",
"color": "#3AA3E3",
"attachment_type": "default",
"callback_id": "select_simple_1234",
"actions": [
{
"name": "channels_list",
"text": "Which channel changed your life this week?",
"type": "select",
"data_source": "channels"
}
]
}
]
}
Chatter occurs in more that just channels. A user might want to choose from the full breadth of their productive conversations
Show a list of conversations, tailored to each user seeing it by providing conversations
as your action's data_source
:
{
"text": "Let's get a productive conversation going",
"response_type": "in_channel",
"attachments": [
{
"fallback": "Upgrade your Slack client to use messages like these.",
"color": "#3AA3E3",
"attachment_type": "default",
"callback_id": "conversations_123",
"actions": [
{
"name": "conversations_list",
"text": "Who did you talk to last?",
"type": "select",
"data_source": "conversations"
}
]
}
]
}
When a workspace member selects an option from the conversations
, channels
, or users
dropdown, your Request URL will receive the familiar Request URL invocation. Populated once more with selected_options
field, containing an array of selected values.
In the example below, the user selected a specific workspace's #general
channel. But you won't know that just from the action response. You'll only have the channel's ID — if you need more info about it, use conversations.info
if you have the proper permissions.
{
"type": "interactive_message",
"actions": [
{
"name": "channels_list",
"selected_options": [
{
"value": "C012AB3CD"
}
]
}
],
"callback_id": "select_simple_1234",
"team": {
"id": "T012AB0A1",
"domain": "pocket-calculator"
},
"channel": {
"id": "C012AB3CD",
"name": "general"
},
"user": {
"id": "U012A1BCD",
"name": "musik"
},
"action_ts": "1481579588.685999",
"message_ts": "1481579582.000003",
"attachment_id": "1",
"token": "iUeRJkkRC9RMMvSRTd8gdq2m",
"original_message": {
"text": "It's time to nominate the channel of the week",
"bot_id": "B08BCU62D",
"attachments": [
{
"callback_id": "select_simple_1234",
"fallback": "Upgrade your Slack client to use messages like these.",
"id": 1,
"color": "3AA3E3",
"actions": [
{
"id": "1",
"name": "channels_list",
"text": "Which channel changed your life this week?",
"type": "select",
"data_source": "channels"
}
]
}
],
"type": "message",
"subtype": "bot_message",
"ts": "1481579582.000003"
},
"response_url": "https://hooks.slack.com/actions/T012AB0A1/123456789/JpmK0yzoZDeRiqfeduTBYXWQ",
"trigger_id": "13345224609.738474920.8088930838d88f008e0"
}
As with predetermined option selection, look to the actions
array for a single named selection. Inside its selected_options
array you'll find the value
field containing the relevant conversation, channel, or user.
Sometimes you don't want to provide a list of static options but want them to change dynamically based on the user, channel, or a previous interaction. Perhaps you want to offer the latest possible values, or personalize every little option for the engaging user. You can do all these things with dynamic message menus.
Once you've configured your External Suggestions URL in your app's interactive message settings, create a message with an attachment action set with a data_source
set to external
.
When the posted message's drop down is opened, we'll send a request to your specified URL, expecting a HTTP 200 OK response back along with an application/json
post body.
When users enter a externally-loaded menu's typeahead mode, requests will be sent to your External Suggestions URL for each character or change. If you prefer fewer requests or more fully ideated queries, use the min_query_length
attribute to tell Slack the fewest number of typed characters required before dispatch. Yes, this is one way to provide guided text-entry — you'll receive a value
field with the user's current query.
Include an options
or option_groups
array attribute as described in the field guide. These can be formatted and configured just like pre-populated options in the static example above.
A maximum of 100 options may be included.
Here's a menu to help you find some bugs.
{
"text": "What's bugging you?",
"response_type": "in_channel",
"attachments": [
{
"fallback": "Upgrade your Slack client to use messages like these.",
"color": "3AA3E3",
"attachment_type": "default",
"callback_id": "select_remote_1234",
"actions": [
{
"name": "bugs_list",
"text": "Which random bug do you want to resolve?",
"type": "select",
"data_source": "external",
"min_query_length": 3,
}
]
}
]
}
We've set up our external URL to return a list of options containing a few bugs to choose from. Options returned here will be passed to the user, so you can do some intelligent filtering and ordering on your side.
{
"options": [
{
"text": "Unexpected sentience",
"value": "AI-2323"
},
{
"text": "Bot biased toward other bots",
"value": "SUPPORT-42"
},
{
"text": "Bot broke my toaster",
"value": "IOT-75"
}
]
}
Send that applications/json
response when your External Suggestions URL receives a request similar to this one, giving you needed context to customize your response:
Your list of options may also include the selected_options
structure detailed above. Selected options do not automatically persist, so if your receive another options load request, you'll want to include selected_options
each time.
When Slack sends requests to your Options Load URL, it sends you context about the workspace, channel, and user. Use this data to customize the response directly for the user interacting with the menu. If the workspace member uses typeahead, you'll also receive a value
attribute with the current query.
{
"name": "bugs_list",
"value": "bot",
"callback_id": "select_remote_1234",
"type": "interactive_message",
"team": {
"id": "T012AB0A1",
"domain": "pocket-calculator"
},
"channel": {
"id": "C012AB3CD",
"name": "general"
},
"user": {
"id": "U012A1BCJ",
"name": "bugcatcher"
},
"action_ts": "1481670445.010908",
"message_ts": "1481670439.000007",
"attachment_id": "1",
"token": "verification_token_string"
}
For a comprehensive accounting of all the fields related to interactive messages, please consult the dedicated field guide.
As with all of these interactions, your server's response time is important. With dynamically loaded menus, this is even more true — you don't want to make users wait to order from your message menu and there's no chance to use response_url
.
Whether you're pre-populating options or loading them remotely, you can nestle them categorically to make navigation easier and typeahead more expressive. Instead of providing an options
field, provide option_groups
.
You'll find option groups in the field guide but really there are only two attributes:
text
- the words used to identify the "category". Can even includeoptions
- an array of option objectsIn the example below, we nest bugs by category.
{
"option_groups": [
{
"text": "Doggone bot antics",
"options": [
{
"text": "Unexpected sentience",
"value": "AI-2323"
},
{
"text": "Bot biased toward other bots",
"value": "SUPPORT-42"
},
{
"text": "Bot broke my toaster",
"value": "IOT-75"
}
]
},
{
"text": "Human error",
"options": [
{
"text": "Not Penny's boat",
"value": "LOST-7172"
},
{
"text": "We built our own CMS",
"value": "OOPS-1"
}
]
}
]
}
Nested options are still limited to a total of 100 options.
If you have a suggested choice for your particular user, make use of the selected_options
field to pre-populate the dropdown with what you have in mind while still allowing them to peruse the full menu. This is available for all data_source
types. At this time, only one selection may be made.
In this example, we preselect maze
for the games_list
.
{
"attachments": [
{
"callback_id": "select_simple_1234",
"actions": [
{
"name": "games_list",
"text": "Pick a game...",
"type": "select",
"options": [
{
"text": "Chess",
"value": "chess"
},
{
"text": "Falken's Maze",
"value": "maze"
},
{
"text": "Thermonuclear War",
"value": "war"
}
],
"selected_options": [
{
"text": "Falken's Maze",
"value": "maze"
}
]
}
]
}
]
}
The provided value
must correspond to a value contained within the menu options itself. Here's how that plays out with each menu type:
Just make sure the value
and text
match an actual option you've provided.
"selected_options": [
{
"text": "Yes! I am a long way from home",
"value": "mogwai"
}
]
The provided value
should be the channel, MPIM, or direct message's ID. Don't worry about providing text
but if you do make it sensible.
"selected_options": [
{
"text": "#melding",
"value": "C123456"
}
]
Provide a user's ID as the value
— they begin with U
or W
. We take both. You don't need to provide a text
value.
"selected_options": [
{
"text": "Mr. Book",
"value": "W123456"
}
]
These message menu best practices should be considered with our message guidelines.