These docs describe an outmoded approach to composing messages and interactive experiences. Learn how to more effectively communicate and help folks get work done by our new documentation and transitioning to "blocks".

Legacy: Attaching interactive message menus

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.

Triforce Project PM

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.

Prerequisite knowledge and experience required

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.

Interactive message invocation pattern

There's more to it than this, but here's a recap of how interactive messages work.

  1. Post a message containing one or more attachments containing one or more interactive elements
  2. Users click a button or select an option from a menu
  3. A request is sent to your registered Request URL containing all the context you need to understand: who clicked it, which option they clicked, which message callback_id was associated with the message, and the original message inciting the selection
  4. You respond to your Request URL's invocation with a message to replace the original, and/or a new ephemeral message, and/or you utilize the invocation's response_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.

Building basic menus

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.

Attach a drop down of predetermined actions

A short list of selectable options

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.

Request URL response

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.

Invoked message menu actions
  • 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.

A list of members to select from

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"
                }
            ]
        }
    ]
}

A list of channels

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"
                }
            ]
        }
    ]
}

Conversation, channel, and user Request URL invocations

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.

Options Load URL

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.

Grouping options

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 include
  • options - an array of option objects

In 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.

Preselecting menu 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:

Static and dynamic menus

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"
    }
]

Channels and conversations

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"
    }
]

Users

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"
    }
]

Best practices are on the menu

These message menu best practices should be considered with our message guidelines.

  • If your message only needs to provide one or two interaction options, use message buttons to make those choices distinct and encourage decisiveness.
  • You can mix message buttons and message menus within the same message by including multiple attachments. Wow your friends and colleagues with ancient inputs made new again.
  • Respond quickly! We'll timeout a request to your Request URL or Option Load URL after 3 seconds. If it takes you longer to build a response or decide what to do, send a HTTP 200 OK and use other means to progress the interaction.
Bits and bobs for the Deno developer
These TypeScript code snippets help you work with canvases, authentication, forms, connectors, and more.