Beyond Legacy Attachments: Engineering Slack Messages with Block Kit
Stop using legacy Slack attachments that fragment notifications. Learn how to implement Block Kit for consolidated, interactive messages, including a /poll example and payload limit strategies.
10 Aug 2025, 07:41 UTC

The Fragmentation Problem in Slack Notifications
If you are maintaining a Slack integration built several years ago, you likely rely on legacy attachments. These often lead to "message fragmentation," where a single notification spawns multiple messages—one for the header, another for fields, and a third for buttons. This forces users to scroll through a thread to piece together context. Furthermore, Slack has signaled the deprecation of legacy attachment formats, meaning these integrations risk inconsistent rendering across desktop and mobile clients.
Block Kit solves this by consolidating content, layout, and interactivity into a single JSON payload. Instead of disparate attachments, you define a sequence of blocks that render predictably across all devices.
Core Block Types for Technical Workflows
Most engineering tools only require a few specific block types to create a professional interface. Understanding these schemas prevents the common validation errors that occur during deployment:
- section: The primary content container. It supports
mrkdwn(Slack's flavor of Markdown) and can include anaccessory, such as a button or a static select menu, placed to the right of the text. - divider: A simple horizontal rule used to visually separate different logical groups of information.
- image: Used for screenshots or logos. It requires an
image_urlandalt_textfor accessibility. - actions: A dedicated container for interactive elements like buttons, date pickers, or checkboxes. Each element must have a unique
action_idthat your backend uses to identify the user's intent.
Slack enforces a strict limit of 50 blocks per message and a total payload size of 30 KB. Exceeding these limits typically results in a validation error from the API.
Worked Example: Building a /poll Slash Command
A common use case for Block Kit is a poll. In this scenario, a user triggers a slash command, and the app responds with a structured message containing voting buttons.
1. The Request Payload
When a user runs /poll "Lunch?" | "Tacos" | "Sushi", your server receives a POST request. You must parse the text field to extract the question and the options.
2. Constructing the Block Array
Run this logic on your application server to build the JSON payload. Note how the accessory is used within a section to keep the layout compact:
const blocks = [
{
type: "section",
text: { type: "mrkdwn", text: "*Poll:* Lunch options?\n*Created by:* <@U12345>" }
},
{ type: "divider" },
// Map options to section blocks with buttons
{
type: "section",
text: { type: "mrkdwn", text: "Tacos" },
accessory: {
type: "button",
text: { type: "plain_text", text: "Vote" },
action_id: "vote_tacos",
value: "tacos"
}
},
{
type: "section",
text: { type: "mrkdwn", text: "Sushi" },
accessory: {
type: "button",
text: { type: "plain_text", text: "Vote" },
action_id: "vote_sushi",
value: "sushi"
}
}
];
3. Sending the Message
Use the chat.postMessage method with a bot token (requiring the chat:write scope). Avoid using the response_url for the final message if you intend to update the poll results later, as chat.update requires a permanent message timestamp (ts).
await slackClient.chat.postMessage({
channel: "C12345",
blocks: blocks,
text: "New Poll: Lunch options?" // Fallback text for notifications
});
4. Handling the Interaction
When a user clicks "Vote," Slack sends a payload to your Request URL. Your handler should check payload.actions[0].action_id. To reflect the vote, call chat.update to replace the button with a checkmark or update the text to show the current tally.
Migration Trade-offs and Constraints
Moving from attachments to blocks is not a 1:1 swap. The most significant hurdle is the payload size limit.
| Feature | Legacy Attachments | Block Kit |
|---|---|---|
| Payload Limit | ~40 KB per attachment | 30 KB total per message |
| Visual Layout | Fixed fields/colors | Flexible vertical stacking |
| Interactivity | Basic buttons | Rich menus, date pickers, modals |
| API Method | Incoming Webhooks | Bot Tokens (chat.postMessage) |
If your legacy notifications are verbose, you may hit the 30 KB limit. The practical solution is to move detailed data into a modal using views.open. Instead of listing 20 logs in a message, list the top 5 and provide a "View All" button that opens a modal window.
Verification and Testing
Before deploying to production, verify your implementation using these steps:
- Schema Validation: Paste your JSON into the Slack Block Kit Builder to ensure there are no missing required fields.
- Cross-Client Check: Post the message to a private test channel and verify it on both the Slack Desktop app and the mobile app (iOS/Android) to ensure the
accessoryelements don't displace text awkwardly. - Payload Stress Test: Generate a message with the maximum expected amount of data to ensure you are comfortably under the 30 KB limit.
Closing Action
Identify one high-traffic notification in your stack—such as a CI/CD failure alert—that currently uses legacy attachments. Rewrite the payload using Block Kit and switch from a webhook to a bot token. This removes the risk of deprecation and improves the signal-to-noise ratio for your team.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.