Skip to content

Block Kit actions

Slack lets you build interactivity into your Slack app using Block Kit. Block Kit is a UI framework that lets you add interactive elements, such as buttons, input fields, datepickers etc. to surfaces like messages, modals and the App Home tab.

Slack Machine makes it easy to listen to actions triggered by these interactive elements.

Defining actions

When you're defining blocks for your interactive surfaces - either by providing a dict or by leveraging the models of the Slack SDK for Python - each of these blocks can be given a block_id. Within certain blocks, you can place block elements that are interactive. These interactive elements can be given an action_id. Given that one block can contain multiple action elements, each block_id can be linked to multiple action_ids.

Whenever the user interacts with these elements, an event is sent to Slack Machine that contains the block_id and action_id corresponding to the block and element in which an action happened.

Listening to actions

With the @action decorator you can define which plugin methods should be called when a certain action is triggered. The decorator takes 2 arguments: the block_id and the action_id that you want to listen to. Both arguments are optional, but one of them always needs to be set. Both arguments accept a str or re.Pattern. When a string is provided, the handler only fires upon an exact match, whereas with a regex pattern, you can have the handler fired for multiple matching block_ids or action_ids. This is convenient when you want one handler to process multiple actions within a block, for example.

If only action_id or block_id is provided, the other defaults to None, which means it always matches.

Parameters of your action handler

Your block action handler will be called with a BlockAction object that contains useful information about the action that was triggered and the message or other surface in which the action was triggered.

You can optionally add the logger argument to your handler get a logger that was enriched by Slack Machine

The BlockAction contains various useful fields and properties about the action that was triggered and the context in which that happened. The user property corresponds to the user that triggered the action (e.g. clicked a button) and the channel property corresponds to the channel in which the message was posted where the action was triggered. This property is None when the action happened in a modal or the App Home tab. The triggered_action field holds information on the action that triggered the handler, including any value that was the result of the triggered action - such as the value of the button that was clicked. Lastly, the payload holds the complete payload the was received by Slack Machine when the action was triggered. Among other things, it holds the complete state of the interactive blocks within the message or modal where the action was triggered. This is especially useful when dealing with a submit button that was triggered, where you want to collect all the information in a form for example.

Example

Let's imagine you're building a plugin for your Slack Machine bot that allows users to vote for what to have for lunch. You designed the following interaction:

block-kit-example

Each lunch option has a vote button. Due to the way Block Kit works, to represent each option like this, they should be in their own section. Each section will have the description of the lunch option, the emoji and a button to vote. Sections are blocks, so we want to listen for actions within different blocks.

This is what the handler could look like:

@action(action_id=None, block_id=re.compile(r"lunch.*", re.IGNORECASE))
async def lunch_action(self, action: BlockAction, logger: BoundLogger):
    logger.info("Action triggered", triggered_action=action.triggered_action)
    food_block = [block for block in action.payload.message.blocks if block.block_id == action.triggered_action.block_id][0]
    food_block_section = cast(blocks.SectionBlock, food_block)
    food_description = str(food_block_section.text.text)
    msg = f"{action.user.fmt_mention()} has voted for '{food_description}'"
    await action.say(msg, ephemeral=False)

As you can see, we only care about the block_id here and not about the action_id. In the blocks that show the lunch options, block_ids would be set like lunch_ramen, lunch_hamburger etc.

Responding to an action

As you can see in the example, if you want to send a message to the user after an action was triggered, you can do so by calling the say() method on the action object your handler received from Slack Machine. This works just like any other way Slack provides for sending messages. You can include just text, but also rich content using Block Kit

Info

The response_url property is used by the say() method to send messages to a channel after receiving a command. It does so by invoking a Webhook using this response_url This is different from how message.say() works - which uses the Slack Web API.

The reason for this is to keep consistency with how Slack recommends interacting with a user. For block actions, using the response_url is the recommended way

Warning

The response_url is only available when the action was triggered in a message - as opposed to in a modal or the App Home tab. The reason is of course that in the other two cases there is no channel to send the message to.