
Slack Block Kit
- 106 installs
- 57 repo stars
- Updated July 7, 2026
- ccheney/robust-skills
Builds Slack Block Kit payloads for rich message layouts, modals, App Home views, and interactive elements, including AI streaming responses.
About
Guidance for constructing Slack Block Kit payloads for messages, modals, App Home views, and interactive elements. Developers use it when generating blocks arrays, building modals, adding interactive elements, or streaming agent output in Slack.
- Covers messages, modals, App Home, interactive elements
- Clarifies mrkdwn vs standard Markdown in blocks
Slack Block Kit by the numbers
- 106 all-time installs (skills.sh)
- Ranked #770 of 2,715 Automation & Workflows skills by installs in the Skillselion catalog
- Data as of Aug 3, 2026 (Skillselion catalog sync)
npx skills add https://github.com/ccheney/robust-skills --skill slack-block-kitAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 106 |
|---|---|
| repo stars | ★ 57 |
| Last updated | July 7, 2026 |
| Repository | ccheney/robust-skills ↗ |
What it does
Builds Slack Block Kit payloads for rich message layouts, modals, App Home views, and interactive elements, including AI streaming responses.
Files
Slack Block Kit
UI framework for building rich, interactive layouts in Slack messages, modals, and App Home.
CRITICAL: Two Markup Systems
Text inside Block Kit text objects uses Slack mrkdwn syntax (*bold*, <url|text>), NOT standard Markdown. The only exception is the markdown block which uses standard Markdown.
Quick Decision Trees
"Should I use blocks?"
Response type?
├─ Conversational reply, short answer, <3 lines → text only (no blocks)
├─ Multi-section summary, report, dashboard → blocks
├─ Two-column key-value data → blocks (section fields)
├─ Tabular data → blocks (table)
├─ Code with heading or surrounding context → blocks
├─ Visual separation needed between topics → blocks
└─ Feedback buttons or interactive elements → blocks"Which block type?"
What am I rendering?
├─ Large section title → header (plain_text, 150 chars max)
├─ Body text or key-value pairs → section (text + fields + accessory)
├─ Small metadata or secondary info → context (images + text, 10 max)
├─ Horizontal separator → divider
├─ Buttons, menus, date pickers → actions (25 elements max)
├─ Status, warning, success callout → alert (severity + text)
├─ Compact entity or summary preview → card (optional image/actions)
├─ Multiple comparable cards/options → carousel (1-10 cards)
├─ Standalone image → image (image_url or slack_file)
├─ Formatted text with lists, quotes → rich_text (nested sub-elements)
├─ Tabular data → table (100 rows, 20 cols, 1 per msg)
├─ LLM-generated markdown content → markdown (standard MD, messages only)
├─ Embedded video player → video (requires links.embed:write)
├─ Remote file reference → file (read-only, source: "remote")
├─ Feedback thumbs up/down → context_actions (messages only)
├─ Collecting user input (modals) → input (label + element)
├─ AI agent task steps → plan (sequential tasks, messages only)
└─ Single task with status → task_card (inside plan or standalone)"mrkdwn or markdown block?"
Content source?
├─ Short formatted text, labels, fields → mrkdwn in section/context
├─ Long-form LLM-generated content → markdown block (standard MD)
├─ LLM-generated tables/task lists/code → markdown block
├─ Programmatic tabular data → table block
├─ Need headings → markdown block or header blocks
└─ Mixed: structured layout + prose → section/header blocks + markdown blockBlock Types Overview
header
Large bold text for section titles. plain_text only. Max 150 chars.
{ "type": "header", "text": { "type": "plain_text", "text": "Section Title", "emoji": true } }section
Primary content block. Supports text, two-column fields, and one accessory element.
{
"type": "section",
"text": { "type": "mrkdwn", "text": "*Project Status*\nAll systems operational." }
}Two-column fields layout:
{
"type": "section",
"fields": [
{ "type": "mrkdwn", "text": "*Status:*\nActive" },
{ "type": "mrkdwn", "text": "*Owner:*\nChris" },
{ "type": "mrkdwn", "text": "*Priority:*\nHigh" },
{ "type": "mrkdwn", "text": "*Due:*\nFriday" }
]
}Either text or fields required (or both). Text max 3000 chars. Fields max 10 items, each max 2000 chars. Set expand: true to force full text display without "see more" truncation.
Compatible accessories: button, overflow, datepicker, timepicker, select menus, multi-select menus, checkboxes, radio buttons, image.
divider
{ "type": "divider" }context
Small, muted text for metadata. Elements: mrkdwn text objects or image elements. Max 10 elements.
{
"type": "context",
"elements": [
{ "type": "mrkdwn", "text": "Last updated: Feb 9, 2026" },
{ "type": "mrkdwn", "text": "Source: deploy-bot" }
]
}actions
Interactive elements: buttons, select menus, overflow menus, date pickers. Max 25 elements.
{
"type": "actions",
"elements": [
{
"type": "button",
"text": { "type": "plain_text", "text": "Approve", "emoji": true },
"style": "primary",
"action_id": "approve_action",
"value": "approved"
}
]
}Button styles: primary (green), danger (red), or omit for default. Use primary sparingly — one per set. Action IDs must be unique within the message.
alert
Callout for status, risk, confirmation, or urgency. Text accepts plain_text or mrkdwn. level: default, info, warning, error, or success (defaults to default).
{
"type": "alert",
"text": { "type": "mrkdwn", "text": "*Dependency conflict detected* before deploy." },
"level": "warning"
}card
Compact, scannable preview for entities, summaries, records, or agent results. At least one of hero_image, title, actions, or body is required. There is currently no size attribute.
{
"type": "card",
"title": { "type": "mrkdwn", "text": "Daily Standup Reminder" },
"subtitle": { "type": "mrkdwn", "text": "Runs every weekday at *9:00 AM*" },
"body": { "type": "mrkdwn", "text": "Last run: Today at 9:00 AM. Status: Success" },
"actions": [
{
"type": "button",
"text": { "type": "plain_text", "text": "View Logs" },
"action_id": "view_logs"
}
]
}Fields: icon and hero_image are image objects; title, subtitle, and body are text objects; actions is an array of button elements. Title/subtitle max 150 chars. Body max 200 chars.
carousel
Horizontal, scrollable group of card blocks for options, recommendations, search results, or next steps. Must contain 1-10 cards.
{
"type": "carousel",
"elements": [
{ "type": "card", "title": { "type": "mrkdwn", "text": "Option A" } },
{ "type": "card", "title": { "type": "mrkdwn", "text": "Option B" } }
]
}image
Standalone image with alt text. Provide either image_url (public, max 3000 chars) or slack_file object. Formats: png, jpg, jpeg, gif.
{
"type": "image",
"image_url": "https://example.com/chart.png",
"alt_text": "Deployment success rate chart",
"title": { "type": "plain_text", "text": "Deploy Metrics" }
}rich_text
Advanced formatted text with nested elements. Supports styled text, lists, code blocks, and quotes. See references/RICH-TEXT.md for deep dive.
{
"type": "rich_text",
"elements": [
{
"type": "rich_text_section",
"elements": [
{ "type": "text", "text": "Key findings:", "style": { "bold": true } }
]
},
{
"type": "rich_text_list",
"style": "bullet",
"elements": [
{ "type": "rich_text_section", "elements": [{ "type": "text", "text": "Latency reduced by 40%" }] },
{ "type": "rich_text_section", "elements": [{ "type": "text", "text": "Error rate under 0.1%" }] }
]
}
]
}Sub-element types: rich_text_section (paragraph), rich_text_list (style: "bullet" or "ordered", with indent, offset, border), rich_text_preformatted (code block), rich_text_quote (blockquote).
Inline element types within sections: text (with optional style: { bold, italic, strike, code, underline }), link, emoji, user, channel, usergroup, broadcast, date, color.
table
Tabular data. One table per message (appended as attachment at bottom).
{
"type": "table",
"rows": [
[
{ "type": "raw_text", "text": "Service" },
{ "type": "raw_text", "text": "Status" },
{ "type": "raw_text", "text": "Latency" }
],
[
{ "type": "raw_text", "text": "API" },
{ "type": "raw_text", "text": "Healthy" },
{ "type": "raw_text", "text": "12ms" }
]
],
"column_settings": [
{ "align": "left" },
{ "align": "center" },
{ "align": "right" }
]
}Each row is an array of cell objects (NOT an object with a cells property). Cell types: raw_text or rich_text. There is no columns property — the first row is the header. Max 100 rows, 20 columns. Multiple tables trigger invalid_attachments error.
markdown
Standard Markdown rendering for AI app output. Messages only.
{ "type": "markdown", "text": "**Bold**, *italic*, [link](https://example.com)\n\n## Heading\n\n- List item" }Supports: bold, italic, strikethrough, links, headers, ordered/unordered lists, inline code, code blocks with optional syntax highlighting, block quotes, horizontal rules/dividers, tables, task lists, and character escaping. Images render as hyperlink text. Cumulative 12,000 char limit per payload. block_id is ignored. A single markdown block may translate into multiple Slack blocks.
context_actions
Feedback and icon buttons for message-level actions. Messages only. Max 5 elements. Compatible elements: feedback_buttons, icon_button.
video
Embedded video player. Requires links.embed:write scope, publicly accessible URL in app's unfurl domains.
input
Collects user data in modals, messages, and Home tabs. Requires label (plain_text, 2000 chars) and one compatible element. See references/ELEMENTS.md for all input element types.
plan
Container for sequential task cards, designed for AI agent output. Messages only.
{
"type": "plan",
"title": "Thinking completed",
"tasks": [
{ "task_id": "t1", "title": "Fetched data", "status": "complete" },
{ "task_id": "t2", "title": "Generating report", "status": "in_progress" }
]
}Task status values: pending, in_progress, complete, error. Each task is a task_card block with optional details, output (rich_text), and sources (url elements).
streaming agent output
Use chat.startStream, chat.appendStream, and chat.stopStream for live AI responses. Streamed messages should be replies to a user request (thread_ts required on start) and require chat:write.
chunks can include:
markdown_textchunks for standard Markdown texttask_updatechunks for timeline-style task progressplan_updatechunks for updating a plan titleblockschunks for arrays of Block Kit blocks
Set task_display_mode on chat.startStream:
timeline(default): tasks appear individually in sequenceplan: tasks appear grouped in one plan, with the first task placement determining the plan placement
chat.stopStream can add final blocks rendered after streamed chunks or markdown_text. It has a separate 50-block limit from streamed blocks chunks, allowing up to 100 total finalized blocks.
file
Remote file reference. Read-only. Cannot be directly added to messages by apps.
Composition Objects
Option Object
Used in select menus, overflow, checkboxes, radio buttons:
{
"text": { "type": "plain_text", "text": "Option 1" },
"value": "opt_1",
"description": { "type": "plain_text", "text": "Detailed description" }
}Text max 75 chars. value max 150 chars. description optional, max 75 chars.
Confirmation Dialog
{
"title": { "type": "plain_text", "text": "Are you sure?" },
"text": { "type": "plain_text", "text": "This action cannot be undone." },
"confirm": { "type": "plain_text", "text": "Yes, do it" },
"deny": { "type": "plain_text", "text": "Cancel" },
"style": "danger"
}Conversation Filter
Filters conversation select menus. include: im, mpim, private, public.
Dispatch Action Configuration
Controls when input elements trigger block_actions: on_enter_pressed, on_character_entered.
See references/COMPOSITION.md for full property tables.
Limits
| Constraint | Limit |
|---|---|
| Blocks per message | 50 |
| Blocks per modal/Home tab | 100 |
| Section text | 3000 chars |
| Section fields | 10 items, 2000 chars each |
| Header text | 150 chars |
| Context elements | 10 |
| Actions elements | 25 |
| Context actions elements | 5 |
| Alert levels | default, info, warning, error, success |
| Card title/subtitle | 150 chars |
| Card body | 200 chars |
| Carousel cards | 1-10 |
| Table rows | 100 |
| Table columns | 20 |
| Tables per message | 1 |
| Markdown block text | 12,000 chars cumulative per payload |
| Modal title | 24 chars |
| Modal submit/close text | 24 chars |
| Modal views in stack | 3 |
| Modal private_metadata | 3000 chars |
| Button text | 75 chars (displays ~30) |
| Button value | 2000 chars |
| action_id / block_id | 255 chars |
| Overflow options | 5 |
| Select options | 100 |
| Option text | 75 chars |
| Placeholder text | 150 chars |
| File input max file size | 10MB per file |
Anti-Patterns
| Anti-Pattern | Problem | Fix |
|---|---|---|
Blocks without text fallback | Empty notifications, no accessibility fallback | Always provide text in chat.postMessage |
text and blocks diverge | Confusing: notification says one thing, chat shows another | Keep semantically aligned |
| Blocks for simple replies | Visual noise for short responses | Use text only for simple replies |
| 2+ tables in one message | invalid_attachments error | One table per message |
Using blog-nested card/alert payloads | Invalid against current reference docs | Put title, body, text, and level directly on the block |
mrkdwn in header text | Ignored — headers only accept plain_text | Use plain_text type |
| Long header text | Silently truncated at 150 chars | Keep under 150 |
Missing alt_text on images | Accessibility failure, API may reject | Always include alt_text |
Best Practices
Use blocks when:
- The response has multiple distinct sections (summaries, reports, dashboards)
- Two-column key-value layouts improve readability (metadata, config summaries)
- A table presents data more clearly than prose
- Visual separation between topics helps comprehension
- Code needs a header or surrounding context
- Interactive elements (buttons, menus, feedback) are needed
Don't use blocks when:
- The response is conversational ("sure, done", "hey, good morning")
- The response is under ~3 lines of text
- The content is a simple answer to a direct question
Always:
- Provide a complete
textfield as the accessible fallback (notifications, threads, search, screen readers) - Keep the
textandblockssemantically aligned - Use mrkdwn syntax in text objects, not standard Markdown (except in
markdownblocks) - Escape
&,<,>in user-generated content
Surfaces Overview
| Surface | Max Blocks | Key Methods | Notes |
|---|---|---|---|
| Messages | 50 | chat.postMessage, chat.update | Primary output surface |
| Modals | 100 | views.open, views.update, views.push | Requires trigger_id (3s expiry), up to 3 stacked views |
| App Home | 100 | views.publish | Private per-user view, Home/Messages/About tabs |
| Canvases | N/A | canvases.create, canvases.edit | Markdown only — no Block Kit support |
| Lists | N/A | lists.* API methods | Task tracking and project management |
| Split View | N/A | Agents & AI Apps config | AI chat surface with Chat + History tabs |
Modals collect input via input blocks, return view_submission payloads. They chain up to 3 views with push/update/clear response actions. private_metadata (3000 chars) persists context between views.
Work Objects
Work Objects render rich entity previews when links are shared in Slack. They extend link unfurling with structured data, flexpane details, editable fields, and actions.
Entity Types
| Type | Entity ID | Purpose |
|---|---|---|
| File | slack#/entities/file | Documents, spreadsheets, images |
| Task | slack#/entities/task | Tickets, to-dos, work items |
| Incident | slack#/entities/incident | Service interruptions, outages |
| Content Item | slack#/entities/content_item | Articles, pages, wiki entries |
| Item | slack#/entities/item | General-purpose entity |
Work Objects use chat.unfurl with a metadata parameter containing entity type, external reference, and entity payload. See references/WORK-OBJECTS.md for full implementation details.
Reference Documentation
| File | Purpose |
|---|---|
| references/CHEATSHEET.md | Quick reference: all blocks, elements, limits at a glance |
| references/BLOCKS.md | All 18 block types with full property tables and constraints |
| references/ELEMENTS.md | All 20 interactive elements with properties and constraints |
| references/COMPOSITION.md | Composition objects: text, option, confirmation, filters |
| references/RICH-TEXT.md | Rich text block deep dive: sub-elements, inline types, styles |
| references/SURFACES.md | Modals, App Home, canvases, lists, split view |
| references/WORK-OBJECTS.md | Entity types, chat.unfurl, flexpane, editable fields, actions |
Sources
- Block Kit Reference — Slack
- Block Kit Blocks — Slack
- Alert Block — Slack
- Card Block — Slack
- Carousel Block — Slack
- Block Kit Elements — Slack
- Block Kit Composition Objects — Slack
- Streaming Messages — Slack
- Work Objects — Slack
- Surfaces — Slack
- Modals — Slack
- App Home — Slack
- Canvases — Slack
- Lists — Slack
- Split View — Slack
- App Design — Slack
Block Types — Complete Property Reference
Sources:
- Block Kit Blocks — Slack
- Block Kit Reference — Slack
- Alert Block — Slack
- Card Block — Slack
- Carousel Block — Slack
All 18 block types with full property tables, constraints, and surface compatibility.
---
1. Header Block
Large, bold text for section titles.
| Property | Type | Required | Constraints |
|---|---|---|---|
type | string | Yes | Must be "header" |
text | text object | Yes | plain_text only, max 150 chars |
block_id | string | No | Max 255 chars, unique per message |
Surfaces: Messages, Modals, Home tabs
{
"type": "header",
"text": { "type": "plain_text", "text": "Section Title", "emoji": true }
}---
2. Section Block
Primary content block with text, fields, and accessory.
| Property | Type | Required | Constraints |
|---|---|---|---|
type | string | Yes | Must be "section" |
text | text object | Preferred | Max 3000 chars. Not required if fields provided |
fields | text object[] | No | Max 10 items, each max 2000 chars. Can replace or supplement text |
accessory | element | No | One compatible element |
expand | boolean | No | Forces full display without "see more" |
block_id | string | No | Max 255 chars |
Surfaces: Messages, Modals, Home tabs
Compatible accessories: button, overflow, datepicker, timepicker, select menus, multi-select menus, checkboxes, radio_buttons, image.
{
"type": "section",
"text": { "type": "mrkdwn", "text": "*Status:* Active" },
"accessory": {
"type": "button",
"text": { "type": "plain_text", "text": "View" },
"action_id": "view_btn",
"value": "view"
}
}Fields layout renders as two columns:
{
"type": "section",
"fields": [
{ "type": "mrkdwn", "text": "*Status:*\nActive" },
{ "type": "mrkdwn", "text": "*Owner:*\nChris" }
]
}---
3. Divider Block
Horizontal rule separator.
| Property | Type | Required | Constraints |
|---|---|---|---|
type | string | Yes | Must be "divider" |
block_id | string | No | Max 255 chars |
Surfaces: Messages, Modals, Home tabs
{ "type": "divider" }---
4. Context Block
Small, muted metadata.
| Property | Type | Required | Constraints |
|---|---|---|---|
type | string | Yes | Must be "context" |
elements | (text object \ | image element)[] | Yes |
block_id | string | No | Max 255 chars |
Surfaces: Messages, Modals, Home tabs
{
"type": "context",
"elements": [
{ "type": "image", "image_url": "https://example.com/pin.png", "alt_text": "pin" },
{ "type": "mrkdwn", "text": "Location: *Dogpatch*" }
]
}---
5. Actions Block
Container for interactive elements.
| Property | Type | Required | Constraints |
|---|---|---|---|
type | string | Yes | Must be "actions" |
elements | element[] | Yes | Max 25 elements |
block_id | string | No | Max 255 chars |
Surfaces: Messages, Modals, Home tabs
Compatible elements: button, select menus, multi-select menus, overflow, datepicker, datetimepicker, timepicker, checkboxes, radio_buttons, workflow_button.
{
"type": "actions",
"elements": [
{
"type": "datepicker",
"action_id": "date_pick",
"initial_date": "2026-02-09",
"placeholder": { "type": "plain_text", "text": "Select date" }
},
{
"type": "button",
"text": { "type": "plain_text", "text": "Submit" },
"style": "primary",
"action_id": "submit_btn"
}
]
}---
6. Alert Block
Callout for status, risk, confirmation, or urgency.
| Property | Type | Required | Constraints |
|---|---|---|---|
type | string | Yes | Must be "alert" |
text | text object | Yes | plain_text or mrkdwn |
level | string | No | "default", "info", "warning", "error", or "success"; defaults to "default" |
block_id | string | No | Max 255 chars |
Surfaces: Messages
{
"type": "alert",
"text": {
"type": "mrkdwn",
"text": "*Dependency conflict detected* before deploy."
},
"level": "warning"
}---
7. Card Block
Compact, scannable preview for entities, records, summaries, or agent results.
| Property | Type | Required | Constraints |
|---|---|---|---|
type | string | Yes | Must be "card" |
icon | image object | No | Small image next to title/subtitle |
hero_image | image object | No | Top image |
title | text object | No | Max 150 chars |
subtitle | text object | No | Max 150 chars |
body | text object | No | Max 200 chars |
actions | button[] | No | Action buttons shown at bottom |
block_id | string | No | Max 255 chars |
Surfaces: Messages
At least one of hero_image, title, actions, or body is required. There is currently no size attribute.
{
"type": "card",
"icon": {
"type": "image",
"image_url": "https://example.com/icon.png",
"alt_text": "Icon"
},
"title": {
"type": "mrkdwn",
"text": "Daily Standup Reminder"
},
"subtitle": {
"type": "mrkdwn",
"text": "Runs every weekday at *9:00 AM*"
},
"body": {
"type": "mrkdwn",
"text": "Last run: Today at 9:00 AM. Status: Success"
},
"actions": [
{
"type": "button",
"text": { "type": "plain_text", "text": "View Logs" },
"action_id": "view_logs"
}
]
}---
8. Carousel Block
Horizontal group of cards for options, recommendations, search results, or next steps.
| Property | Type | Required | Constraints |
|---|---|---|---|
type | string | Yes | Must be "carousel" |
elements | card[] | Yes | Minimum 1 card, maximum 10 cards |
block_id | string | No | Max 255 chars |
Surfaces: Messages
{
"type": "carousel",
"elements": [
{ "type": "card", "title": { "type": "mrkdwn", "text": "Option A" } },
{ "type": "card", "title": { "type": "mrkdwn", "text": "Option B" } }
]
}---
9. Image Block
Standalone image.
| Property | Type | Required | Constraints |
|---|---|---|---|
type | string | Yes | Must be "image" |
alt_text | string | Yes | Max 2000 chars |
image_url | string | No | Max 3000 chars, publicly hosted. Must provide either image_url or slack_file |
slack_file | object | No | { url } or { id }. Must provide either image_url or slack_file |
title | text object | No | plain_text only, max 2000 chars |
block_id | string | No | Max 255 chars |
Surfaces: Messages, Modals, Home tabs
Formats: png, jpg, jpeg, gif
{
"type": "image",
"image_url": "https://example.com/chart.png",
"alt_text": "Sales chart Q4 2025",
"title": { "type": "plain_text", "text": "Q4 Sales" }
}Using Slack file:
{
"type": "image",
"slack_file": { "id": "F0123ABC" },
"alt_text": "Uploaded screenshot"
}---
10. Rich Text Block
Formatted text with nested structure. See RICH-TEXT.md for deep dive.
| Property | Type | Required | Constraints |
|---|---|---|---|
type | string | Yes | Must be "rich_text" |
elements | sub-element[] | Yes | Array of section/list/preformatted/quote |
block_id | string | No | Max 255 chars |
Surfaces: Messages, Modals, Home tabs
---
11. Table Block
Tabular data display.
| Property | Type | Required | Constraints |
|---|---|---|---|
type | string | Yes | Must be "table" |
rows | cell[][] | Yes | Max 100 rows. Each row is an array of cell objects (max 20 cells). First row = header |
column_settings | setting[] | No | Max 20, with align and is_wrapped |
block_id | string | No | Max 255 chars |
Surfaces: Messages only
There is no `columns` property. The first row in rows acts as the header row.
Row structure: Each row is a flat array of cell objects — NOT an object with a cells property.
Cell types: { "type": "raw_text", "text": "..." } for plain text, or { "type": "rich_text", "elements": [...] } for formatted content (links, mentions, emoji, bold).
Column settings: align (left/center/right, default left), is_wrapped (boolean, default false).
Limit: One table per message. Multiple tables cause invalid_attachments error.
{
"type": "table",
"rows": [
[
{ "type": "raw_text", "text": "Service" },
{ "type": "raw_text", "text": "Status" },
{ "type": "raw_text", "text": "Latency" }
],
[
{ "type": "raw_text", "text": "API" },
{ "type": "raw_text", "text": "Healthy" },
{ "type": "raw_text", "text": "12ms" }
],
[
{ "type": "raw_text", "text": "Worker" },
{ "type": "raw_text", "text": "Degraded" },
{ "type": "raw_text", "text": "340ms" }
]
],
"column_settings": [
{ "align": "left" },
{ "align": "center" },
{ "align": "right", "is_wrapped": true }
]
}---
12. Markdown Block
Standard Markdown rendering, designed for AI app output.
| Property | Type | Required | Constraints |
|---|---|---|---|
type | string | Yes | Must be "markdown" |
text | string | Yes | Standard Markdown, 12,000 chars cumulative per payload |
block_id | string | No | Ignored and not retained |
Surfaces: Messages only
Supports: bold, italic, strikethrough, links, headers (all header levels render at the same size), ordered/unordered lists, inline code, code blocks with optional syntax highlighting, block quotes, horizontal rules/dividers, tables, task lists, images (as hyperlinks), and character escaping.
Note: A single markdown block may translate into multiple Slack blocks after rendering.
Escaping: Use backslash to render special characters literally. Supported: \, ` `, *, _, {, }, [, ], (, ), #, +, -, ., !, &`.
{ "type": "markdown", "text": "## Status Report\n\n**API** is healthy.\n\n- Latency: 12ms\n- Error rate: 0.01%" }---
13. Context Actions Block
Message-level feedback and action buttons.
| Property | Type | Required | Constraints |
|---|---|---|---|
type | string | Yes | Must be "context_actions" |
elements | element[] | Yes | Max 5 elements |
block_id | string | No | Max 255 chars |
Surfaces: Messages only
Compatible elements: feedback_buttons, icon_button
{
"type": "context_actions",
"elements": [
{
"type": "feedback_buttons",
"action_id": "feedback_1",
"positive_button": {
"text": { "type": "plain_text", "text": "Helpful" },
"value": "positive"
},
"negative_button": {
"text": { "type": "plain_text", "text": "Not helpful" },
"value": "negative"
}
},
{
"type": "icon_button",
"icon": "trash",
"text": { "type": "plain_text", "text": "Delete" },
"action_id": "delete_msg",
"value": "delete"
}
]
}---
14. Input Block
Collects user data via form elements.
| Property | Type | Required | Constraints |
|---|---|---|---|
type | string | Yes | Must be "input" |
label | text object | Yes | plain_text only, max 2000 chars |
element | element | Yes | One compatible element |
block_id | string | No | Max 255 chars |
dispatch_action | boolean | No | Default false. Cannot be true with file_input |
hint | text object | No | plain_text only, max 2000 chars |
optional | boolean | No | Default false. Allows empty submission when true |
Surfaces: Modals, Messages, Home tabs
Compatible elements: plain_text_input, number_input, email_text_input, url_text_input, rich_text_input, static_select, external_select, users_select, conversations_select, channels_select, multi_static_select, multi_external_select, multi_users_select, multi_conversations_select, multi_channels_select, datepicker, datetimepicker, timepicker, checkboxes, radio_buttons, file_input.
---
15. Video Block
Embedded video player.
| Property | Type | Required | Constraints |
|---|---|---|---|
type | string | Yes | Must be "video" |
alt_text | string | Yes | Tooltip for accessibility |
title | text object | Yes | plain_text only, max 200 chars |
video_url | string | Yes | HTTPS, must be in app's unfurl domains |
thumbnail_url | string | Yes | Thumbnail image URL |
title_url | string | Preferred | Non-embeddable URL for the video, HTTPS |
description | text object | Preferred | plain_text only, max 200 chars |
author_name | string | No | Max 50 chars |
provider_name | string | No | Originating domain (e.g., "YouTube") |
provider_icon_url | string | No | Provider icon |
block_id | string | No | Max 255 chars |
Surfaces: Messages, Modals, Home tabs
Requirements: links.embed:write scope, iFrame-embeddable, publicly accessible, cannot point to Slack domains.
---
16. File Block
Remote file reference. Read-only — appears when retrieving messages containing remote files.
| Property | Type | Required | Constraints |
|---|---|---|---|
type | string | Yes | Must be "file" |
external_id | string | Yes | External unique ID |
source | string | Yes | Must be "remote" |
block_id | string | No | Max 255 chars |
Surfaces: Messages only
Cannot be directly added to messages by apps. Shows up when retrieving messages with remote files.
{ "type": "file", "external_id": "ABCD1", "source": "remote" }---
17. Plan Block
Container for displaying sequential tasks or workflow steps, designed for AI agent output.
| Property | Type | Required | Constraints |
|---|---|---|---|
type | string | Yes | Must be "plan" |
title | string | Yes | Plan title, max 255 chars |
tasks | task_card[] | No | Array of task card blocks |
block_id | string | No | Max 255 chars |
Surfaces: Messages
{
"type": "plan",
"title": "Thinking completed",
"tasks": [
{
"task_id": "call_001",
"title": "Fetched user profile",
"status": "complete"
},
{
"task_id": "call_002",
"title": "Generating report",
"status": "in_progress",
"details": {
"type": "rich_text",
"elements": [{ "type": "rich_text_section", "elements": [{ "type": "text", "text": "Processing data..." }] }]
}
}
]
}---
18. Task Card Block
Displays a single task with title, status, optional details/output, and source URLs. Used standalone or inside a plan block.
| Property | Type | Required | Constraints |
|---|---|---|---|
type | string | Yes | Must be "task_card" |
task_id | string | Yes | Unique task identifier |
title | string | Yes | Task title, plain text |
status | string | No | "pending", "in_progress", "complete", or "error" |
details | rich_text object | No | Task details (single rich_text entity) |
output | rich_text object | No | Task output/results (single rich_text entity) |
sources | url element[] | No | Array of url source elements (references used to generate response) |
block_id | string | No | Max 255 chars, use unique per message/iteration |
Surfaces: Messages
{
"type": "task_card",
"task_id": "task_1",
"title": "Fetching weather data",
"status": "complete",
"output": {
"type": "rich_text",
"elements": [
{ "type": "rich_text_section", "elements": [{ "type": "text", "text": "Found weather data from 2 sources" }] }
]
},
"sources": [
{ "type": "url", "url": "https://weather.com/", "text": "weather.com" },
{ "type": "url", "url": "https://accuweather.com/", "text": "accuweather.com" }
]
}Slack Block Kit Quick Reference
Sources:
- Block Kit Reference — Slack
---
Block Types
| Block | Type String | Surfaces | Key Limits |
|---|---|---|---|
| Header | header | Msg, Modal, Home | 150 chars, plain_text only |
| Section | section | Msg, Modal, Home | 3000 chars text, 10 fields (2000 each), 1 accessory |
| Divider | divider | Msg, Modal, Home | No fields |
| Context | context | Msg, Modal, Home | 10 elements (text + image) |
| Actions | actions | Msg, Modal, Home | 25 elements |
| Alert | alert | Msg | Status/risk callout. Levels: default/info/warning/error/success |
| Card | card | Msg | Compact preview. Title/subtitle 150 chars, body 200 chars |
| Carousel | carousel | Msg | 1-10 card elements |
| Image | image | Msg, Modal, Home | alt_text required, png/jpg/gif |
| Rich Text | rich_text | Msg, Modal, Home | Nested sub-elements |
| Table | table | Msg only | 100 rows, 20 cols, 1/msg. First row = header. Rows are arrays of raw_text/rich_text cells. No columns prop |
| Markdown | markdown | Msg only | 12K chars cumulative, standard MD incl. tables, task lists, dividers, syntax-highlighted code |
| Context Actions | context_actions | Msg only | 5 elements |
| Input | input | Modal, Msg, Home | label required, many element types |
| Video | video | Msg, Modal, Home | links.embed:write scope |
| Plan | plan | Msg only | Sequential task cards for AI agent output |
| Task Card | task_card | Msg only | Single task with status, details, output, sources |
| File | file | Msg only | Read-only, source: "remote" |
---
Interactive Elements
| Element | Type String | Compatible Blocks |
|---|---|---|
| Button | button | section, actions, card |
| Overflow Menu | overflow | section, actions |
| Select Menu | static_select / external_select / users_select / conversations_select / channels_select | section, actions, input |
| Multi-Select | multi_static_select / multi_external_select / multi_users_select / multi_conversations_select / multi_channels_select | section, actions, input |
| Date Picker | datepicker | section, actions, input |
| Time Picker | timepicker | section, actions, input |
| Datetime Picker | datetimepicker | actions, input |
| Checkboxes | checkboxes | section, actions, input |
| Radio Buttons | radio_buttons | section, actions, input |
| Plain Text Input | plain_text_input | input |
| Number Input | number_input | input |
| Email Input | email_text_input | input |
| URL Input | url_text_input | input |
| Rich Text Input | rich_text_input | input |
| File Input | file_input | input |
| Feedback Buttons | feedback_buttons | context_actions |
| Icon Button | icon_button | context_actions |
| Image | image | section (accessory), context |
| Workflow Button | workflow_button | section, actions |
| URL Source | url | task_card (sources array) |
---
Composition Objects
| Object | Used In |
|---|---|
Text (mrkdwn / plain_text) | Most blocks and elements |
| Option | Select menus, overflow, checkboxes, radio buttons |
| Option Group | Select menus (grouped options) |
| Confirmation Dialog | Any interactive element (via confirm property) |
| Conversation Filter | Conversation select menus (via filter) |
| Dispatch Action Config | plain_text_input, rich_text_input |
| Slack File | Image block/element (via slack_file) |
| Trigger | Workflow button (via workflow.trigger) |
| Workflow | Workflow button (wraps trigger object) |
---
Rich Text Sub-Elements
| Sub-Element | Purpose | Key Properties |
|---|---|---|
rich_text_section | Paragraph | elements (inline array) |
rich_text_list | Bullet/ordered list | style, indent, offset, border |
rich_text_preformatted | Code block | elements, border |
rich_text_quote | Blockquote | elements, border |
Inline Elements (within sections)
| Type | Key Properties |
|---|---|
text | text, style: { bold, italic, strike, code, underline, highlight, client_highlight, unlink } |
link | url, text, style |
emoji | name |
user | user_id |
channel | channel_id |
usergroup | usergroup_id |
broadcast | range (here/channel/everyone) |
date | timestamp, format, fallback |
color | value (hex) |
---
Button Styles
| Style | Color | Usage |
|---|---|---|
| (default) | Gray | Standard actions |
primary | Green | Affirmation — use sparingly, one per set |
danger | Red | Destructive — use with confirmation dialog |
---
Limits at a Glance
| What | Limit |
|---|---|
| Blocks per message | 50 |
| Blocks per modal/Home | 100 |
| Section text | 3000 chars |
| Section fields | 10 items |
| Header text | 150 chars |
| Context elements | 10 |
| Actions elements | 25 |
| Card title / subtitle / body | 150 / 150 / 200 chars |
| Carousel cards | 1-10 |
| Table rows / cols | 100 / 20 |
| Tables per message | 1 |
| Modal title | 24 chars |
| Modal views stack | 3 |
| Button text | 75 chars |
| action_id / block_id | 255 chars |
| Select options | 100 |
| Overflow options | 5 |
| Placeholder text | 150 chars |
| File input max size | 10MB per file |
---
Surfaces
| Surface | Block Kit | Key Method |
|---|---|---|
| Messages | Yes (50 blocks) | chat.postMessage |
| Modals | Yes (100 blocks) | views.open |
| App Home | Yes (100 blocks) | views.publish |
| Canvases | No (markdown only) | canvases.create |
| Lists | No | lists.* |
| Split View | Config-based | Agents & AI Apps |
---
Work Object Entity Types
| Type | Entity ID |
|---|---|
| File | slack#/entities/file |
| Task | slack#/entities/task |
| Incident | slack#/entities/incident |
| Content Item | slack#/entities/content_item |
| Item | slack#/entities/item |
Composition Objects — Complete Property Reference
Sources:
- Block Kit Composition Objects — Slack
Composition objects are reusable JSON patterns used inside blocks and elements.
---
Text Object
The most common composition object. Appears in nearly every block and element.
| Property | Type | Required | Constraints |
|---|---|---|---|
type | string | Yes | "mrkdwn" or "plain_text" |
text | string | Yes | Min 1 char, max 3000 chars |
emoji | boolean | No | plain_text only. Converts :emoji: to rendered emoji |
verbatim | boolean | No | mrkdwn only. Default false. When true, disables auto-link conversion and mention parsing |
Type Rules
| Context | Allowed Types |
|---|---|
| Header block text | plain_text only |
| Section text / fields | mrkdwn or plain_text |
| Context elements | mrkdwn or plain_text |
| Button text | plain_text only |
| Placeholder | plain_text only |
| Input label | plain_text only |
| Input hint | plain_text only |
| Modal title / submit / close | plain_text only |
| Confirmation dialog (title/confirm/deny) | plain_text only |
| Confirmation dialog (text) | plain_text only |
| Option text (select/overflow) | plain_text only |
| Option text (checkboxes/radio) | mrkdwn or plain_text |
| Option description | plain_text (or mrkdwn for checkboxes/radio buttons) |
Verbatim Behavior
When verbatim: false (default):
- URLs auto-convert to clickable links
- Channel names auto-convert to channel links
- Mentions auto-parse
When verbatim: true:
- Markdown formatting still processes
- No auto-linking or mention parsing
- Useful for displaying raw URLs or text containing
@or#that aren't mentions
{ "type": "mrkdwn", "text": "Check the log at http://example.com/debug", "verbatim": true }---
Option Object
Represents a single selectable item.
| Property | Type | Required | Constraints |
|---|---|---|---|
text | text object | Yes | plain_text for select/overflow menus; mrkdwn allowed for checkboxes/radio buttons. Max 75 chars |
value | string | Yes | Unique identifier, max 150 chars |
description | text object | No | plain_text (or mrkdwn for checkboxes/radio buttons only), max 75 chars |
url | string | No | Overflow menus only, max 3000 chars |
Used in: static_select, multi_static_select, external_select, multi_external_select, overflow, checkboxes, radio_buttons.
{
"text": { "type": "plain_text", "text": "High Priority" },
"value": "high",
"description": { "type": "mrkdwn", "text": "Critical issues only" }
}---
Option Group Object
Groups related options in select menus for visual organization.
| Property | Type | Required | Constraints |
|---|---|---|---|
label | text object | Yes | plain_text only, max 75 chars |
options | option[] | Yes | Max 100 options per group |
Used in: static_select, multi_static_select, external_select, multi_external_select.
Max 100 option groups per menu.
{
"label": { "type": "plain_text", "text": "Engineering" },
"options": [
{ "text": { "type": "plain_text", "text": "Backend" }, "value": "backend" },
{ "text": { "type": "plain_text", "text": "Frontend" }, "value": "frontend" }
]
}---
Confirmation Dialog Object
Adds a confirmation step to any interactive element.
| Property | Type | Required | Constraints |
|---|---|---|---|
title | text object | Yes | plain_text only, max 100 chars |
text | text object | Yes | plain_text only, max 300 chars |
confirm | text object | Yes | plain_text only, max 30 chars |
deny | text object | Yes | plain_text only, max 30 chars |
style | string | No | "primary" (green) or "danger" (red). Default "primary" |
Desktop: danger = red background, primary = green background. Mobile: danger = red text, primary = blue text.
{
"title": { "type": "plain_text", "text": "Delete item?" },
"text": { "type": "plain_text", "text": "This cannot be undone." },
"confirm": { "type": "plain_text", "text": "Delete" },
"deny": { "type": "plain_text", "text": "Keep" },
"style": "danger"
}Used in: Any element that accepts a confirm property (buttons, select menus, overflow, datepicker, timepicker, checkboxes, radio buttons).
---
Conversation Filter Object
Filters the options in conversation select menus.
| Property | Type | Required | Constraints |
|---|---|---|---|
include | string[] | No | "im", "mpim", "private", "public". Array cannot be empty |
exclude_external_shared_channels | boolean | No | Default false |
exclude_bot_users | boolean | No | Default false |
At least one property must be supplied.
{
"include": ["public", "private"],
"exclude_bot_users": true
}Used in: conversations_select, multi_conversations_select (via filter property).
Known issues: iOS shows "0 selected" instead of placeholder text when nothing is selected. iOS also has UI inconsistencies when users interact with multi-select menu items.
---
Dispatch Action Configuration Object
Controls when plain_text_input or rich_text_input elements return block_actions payloads during input.
| Property | Type | Required | Constraints |
|---|---|---|---|
trigger_actions_on | string[] | No | One or both of the values below |
Trigger values:
| Value | Behavior |
|---|---|
on_enter_pressed | Dispatches when user presses Enter. Shows hint text prompting the user |
on_character_entered | Dispatches on every character add/remove |
Requires dispatch_action: true on the parent input block.
{
"type": "input",
"dispatch_action": true,
"element": {
"type": "plain_text_input",
"action_id": "search_input",
"dispatch_action_config": {
"trigger_actions_on": ["on_character_entered"]
}
},
"label": { "type": "plain_text", "text": "Search" }
}---
Slack File Object
References a Slack-hosted file for use in image blocks or image elements.
Provide either url or id (not both):
{ "url": "https://files.slack.com/files-pri/T0123-F0123/image.png" }{ "id": "F0123ABC456" }Used in: image block (slack_file property), image element (slack_file property).
---
Trigger Object
Contains trigger information for workflow buttons.
| Property | Type | Required | Constraints |
|---|---|---|---|
url | string | Yes | Trigger URL |
customizable_input_parameters | param[] | No | Input parameters for the workflow |
Each parameter: { name: string, value: string }.
{
"trigger": {
"url": "https://slack.com/shortcuts/Ft0123ABC/launch",
"customizable_input_parameters": [
{ "name": "input_param_a", "value": "Value for param A" }
]
}
}---
Workflow Object
Wraps a trigger object for workflow buttons.
| Property | Type | Required | Constraints |
|---|---|---|---|
trigger | trigger object | Yes | See trigger object above |
Used in: workflow_button element (workflow property).
Block Elements — Complete Property Reference
Sources:
- Block Kit Elements — Slack
All 20 interactive element types with properties, constraints, and compatible blocks.
---
Button
| Property | Type | Required | Constraints |
|---|---|---|---|
type | string | Yes | "button" |
text | text object | Yes | plain_text only, max 75 chars (displays ~30) |
action_id | string | No | Max 255 chars |
url | string | No | Max 3000 chars, opens in browser |
value | string | No | Max 2000 chars, sent in payload |
style | string | No | "primary" (green) or "danger" (red) |
confirm | confirm object | No | Confirmation dialog |
accessibility_label | string | No | Screen reader text, max 75 chars |
Blocks: section (accessory), actions, card (actions array)
---
Overflow Menu
| Property | Type | Required | Constraints |
|---|---|---|---|
type | string | Yes | "overflow" |
options | option[] | Yes | Max 5 options |
action_id | string | No | Max 255 chars |
confirm | confirm object | No | Confirmation dialog |
Blocks: section (accessory), actions
---
Select Menus (5 types)
All share: action_id (255 chars), confirm, focus_on_load (boolean), placeholder (plain_text, 150 chars).
Static Select (static_select)
options: option[] — max 100. Required unlessoption_groupsprovidedoption_groups: option group[] — max 100 groupsinitial_option: option object — pre-selected
External Select (external_select)
min_query_length: integer — chars before typeahead (default 3)initial_option: option object- Requires Options Load URL configured in app settings
Users Select (users_select)
initial_user: string — user ID
Conversations Select (conversations_select)
initial_conversation: string — conversation IDdefault_to_current_conversation: boolean (default false)filter: conversation filter objectresponse_url_enabled: boolean — for modals
Channels Select (channels_select)
initial_channel: string — public channel IDresponse_url_enabled: boolean — for modals
Blocks: section (accessory), actions, input
---
Multi-Select Menus (5 types)
All share: action_id (255 chars), confirm, focus_on_load, placeholder (150 chars), max_selected_items (integer, min 1).
Multi Static Select (multi_static_select)
options: option[] — max 100. Each option max 76 charsoption_groups: option group[] — max 100initial_options: option[]
Multi External Select (multi_external_select)
min_query_length: integer (default 3)initial_options: option[]
Multi Users Select (multi_users_select)
initial_users: string[] — user IDs
Multi Conversations Select (multi_conversations_select)
initial_conversations: string[] — conversation IDsdefault_to_current_conversation: booleanfilter: conversation filter object
Multi Channels Select (multi_channels_select)
initial_channels: string[] — channel IDs
Blocks: section (accessory), actions, input
---
Date Picker
| Property | Type | Required | Constraints |
|---|---|---|---|
type | string | Yes | "datepicker" |
action_id | string | No | Max 255 chars |
initial_date | string | No | YYYY-MM-DD format |
confirm | confirm object | No | |
focus_on_load | boolean | No | Default false |
placeholder | text object | No | plain_text, max 150 chars |
Blocks: section (accessory), actions, input
---
Time Picker
| Property | Type | Required | Constraints |
|---|---|---|---|
type | string | Yes | "timepicker" |
action_id | string | No | Max 255 chars |
initial_time | string | No | HH:mm (24-hour) |
timezone | string | No | IANA timezone (e.g., "America/Los_Angeles") |
confirm | confirm object | No | |
focus_on_load | boolean | No | Default false |
placeholder | text object | No | plain_text, max 150 chars |
Blocks: section (accessory), actions, input
---
Datetime Picker
| Property | Type | Required | Constraints |
|---|---|---|---|
type | string | Yes | "datetimepicker" |
action_id | string | No | Max 255 chars |
initial_date_time | integer | No | Unix timestamp |
confirm | confirm object | No | |
focus_on_load | boolean | No | Default false |
Blocks: actions, input
---
Checkboxes
| Property | Type | Required | Constraints |
|---|---|---|---|
type | string | Yes | "checkboxes" |
options | option[] | Yes | Max 10 options |
action_id | string | No | Max 255 chars |
initial_options | option[] | No | Must match items in options |
confirm | confirm object | No | |
focus_on_load | boolean | No | Default false |
Blocks: section (accessory), actions, input
---
Radio Buttons
| Property | Type | Required | Constraints |
|---|---|---|---|
type | string | Yes | "radio_buttons" |
options | option[] | Yes | Max 10 options |
action_id | string | No | Max 255 chars |
initial_option | option | No | Must match one item in options |
confirm | confirm object | No | |
focus_on_load | boolean | No | Default false |
Blocks: section (accessory), actions, input
---
Plain Text Input
| Property | Type | Required | Constraints |
|---|---|---|---|
type | string | Yes | "plain_text_input" |
action_id | string | No | Max 255 chars |
initial_value | string | No | Pre-populated text |
multiline | boolean | No | Default false. True = textarea |
min_length | integer | No | 0-3000 |
max_length | integer | No | 1-3000 |
dispatch_action_config | object | No | trigger_actions_on array |
focus_on_load | boolean | No | Default false |
placeholder | text object | No | plain_text, max 150 chars |
Blocks: input
---
Number Input
| Property | Type | Required | Constraints |
|---|---|---|---|
type | string | Yes | "number_input" |
is_decimal_allowed | boolean | Yes | Allow decimal numbers |
action_id | string | No | Max 255 chars |
initial_value | string | No | Initial number as string |
min_value | string | No | Minimum allowed |
max_value | string | No | Maximum allowed |
dispatch_action_config | object | No | |
focus_on_load | boolean | No | Default false |
placeholder | text object | No | plain_text, max 150 chars |
Blocks: input
---
Email Input
| Property | Type | Required | Constraints |
|---|---|---|---|
type | string | Yes | "email_text_input" |
action_id | string | No | Max 255 chars |
initial_value | string | No | Pre-populated email |
dispatch_action_config | object | No | |
focus_on_load | boolean | No | Default false |
placeholder | text object | No | plain_text, max 150 chars |
Blocks: input
---
URL Input
| Property | Type | Required | Constraints |
|---|---|---|---|
type | string | Yes | "url_text_input" |
action_id | string | No | Max 255 chars |
initial_value | string | No | Pre-populated URL |
dispatch_action_config | object | No | |
focus_on_load | boolean | No | Default false |
placeholder | text object | No | plain_text, max 150 chars |
Blocks: input
---
Rich Text Input
| Property | Type | Required | Constraints |
|---|---|---|---|
type | string | Yes | "rich_text_input" |
action_id | string | Yes | Max 255 chars |
initial_value | rich text object | No | Pre-populated rich text |
dispatch_action_config | object | No | |
focus_on_load | boolean | No | Default false |
placeholder | text object | No | plain_text, max 150 chars |
Blocks: input
---
File Input
| Property | Type | Required | Constraints |
|---|---|---|---|
type | string | Yes | "file_input" |
action_id | string | No | Max 255 chars |
filetypes | string[] | No | Allowed extensions (e.g., ["jpg", "png"]). Client-side only — perform server-side validation too |
max_files | integer | No | Max uploadable files (1-10, default 10) |
Blocks: input. Note: dispatch_action: true is incompatible with file_input.
Requirements: App must have files:read scope. Max 10MB per file.
---
Feedback Buttons
| Property | Type | Required | Constraints |
|---|---|---|---|
type | string | Yes | "feedback_buttons" |
positive_button | button object | Yes | text (plain_text, 75 chars), value (2000 chars) |
negative_button | button object | Yes | Same as positive |
action_id | string | No | Max 255 chars |
Both buttons support accessibility_label (75 chars).
Blocks: context_actions
---
Icon Button
| Property | Type | Required | Constraints |
|---|---|---|---|
type | string | Yes | "icon_button" |
icon | string | Yes | Only "trash" available |
text | text object | Yes | plain_text only |
action_id | string | No | Max 255 chars |
value | string | No | Max 2000 chars |
confirm | confirm object | No | |
accessibility_label | string | No | Max 75 chars |
visible_to_user_ids | string[] | No | Only these users see the button |
Blocks: context_actions
---
Image Element
| Property | Type | Required | Constraints |
|---|---|---|---|
type | string | Yes | "image" |
alt_text | string | Yes | Max 2000 chars |
image_url | string | Conditional | Max 3000 chars. Required unless slack_file |
slack_file | object | Conditional | { url } or { id } |
Blocks: section (accessory), context (element)
---
Workflow Button
| Property | Type | Required | Constraints |
|---|---|---|---|
type | string | Yes | "workflow_button" |
text | text object | Yes | plain_text only, max 75 chars |
workflow | workflow object | Yes | Contains trigger URL + input parameters |
action_id | string | Yes | Max 255 chars, unique within block |
style | string | No | "primary" or "danger" |
accessibility_label | string | No | Max 75 chars |
Blocks: section, actions
---
URL Source Element
| Property | Type | Required | Constraints |
|---|---|---|---|
type | string | Yes | "url" |
url | string | Yes | Target URL reference |
text | string | Yes | Display text shown to users |
Blocks: task_card (sources array only)
{ "type": "url", "url": "https://docs.slack.dev/", "text": "Slack API docs" }Rich Text Block — Deep Dive
Sources:
- Rich Text Block — Slack
The rich text block is Slack's most expressive formatting system. It's the format Slack's WYSIWYG composer produces and supports deep nesting, styled text, lists, code blocks, quotes, and specialized inline elements.
Surfaces: Messages, Modals, Home tabs
---
Structure
rich_text
└── elements[] (sub-elements)
├── rich_text_section → elements[] (inline elements)
├── rich_text_list → elements[] (rich_text_section items)
├── rich_text_preformatted → elements[] (inline elements), optional language
└── rich_text_quote → elements[] (inline elements)---
Sub-Element Types
rich_text_section
Container for a paragraph of inline elements.
| Property | Type | Required |
|---|---|---|
type | string | Yes — "rich_text_section" |
elements | inline element[] | Yes |
{
"type": "rich_text_section",
"elements": [
{ "type": "text", "text": "Hello ", "style": { "bold": true } },
{ "type": "user", "user_id": "U0123ABC" },
{ "type": "text", "text": ", welcome!" }
]
}rich_text_list
Ordered or unordered list. Each item is a rich_text_section.
| Property | Type | Required | Details |
|---|---|---|---|
type | string | Yes | "rich_text_list" |
style | string | Yes | "bullet" or "ordered" |
elements | rich_text_section[] | Yes | One section per list item |
indent | integer | No | 0-based nesting level (pixels) |
offset | integer | No | Starting number for ordered lists |
border | integer | No | Border thickness in pixels |
{
"type": "rich_text_list",
"style": "ordered",
"offset": 1,
"elements": [
{
"type": "rich_text_section",
"elements": [{ "type": "text", "text": "First item" }]
},
{
"type": "rich_text_section",
"elements": [{ "type": "text", "text": "Second item" }]
}
]
}Nested lists use multiple rich_text_list elements with increasing indent values:
{
"type": "rich_text",
"elements": [
{
"type": "rich_text_list",
"style": "bullet",
"indent": 0,
"elements": [
{ "type": "rich_text_section", "elements": [{ "type": "text", "text": "Top level" }] }
]
},
{
"type": "rich_text_list",
"style": "bullet",
"indent": 1,
"elements": [
{ "type": "rich_text_section", "elements": [{ "type": "text", "text": "Nested item" }] }
]
}
]
}rich_text_preformatted
Code block with monospace font and grey background. Set language for syntax highlighting when the language is known.
| Property | Type | Required | Details |
|---|---|---|---|
type | string | Yes | "rich_text_preformatted" |
elements | inline element[] | Yes | Text content (styling ignored) |
border | integer | No | Border thickness in pixels |
language | string | No | Language hint for syntax highlighting, e.g. "python", "javascript", "json" |
{
"type": "rich_text_preformatted",
"elements": [
{ "type": "text", "text": "const x = 42;\nconsole.log(x);" }
],
"language": "javascript"
}rich_text_quote
Indented quote block with left border.
| Property | Type | Required | Details |
|---|---|---|---|
type | string | Yes | "rich_text_quote" |
elements | inline element[] | Yes | |
border | integer | No | Border thickness in pixels |
{
"type": "rich_text_quote",
"elements": [
{ "type": "text", "text": "The best way to predict the future is to invent it.", "style": { "italic": true } }
]
}---
Inline Element Types
These go inside rich_text_section, rich_text_preformatted, and rich_text_quote elements arrays.
text
Basic text with optional styling.
| Property | Type | Required |
|---|---|---|
type | string | Yes — "text" |
text | string | Yes |
style | style object | No |
link
Hyperlink with optional display text.
| Property | Type | Required |
|---|---|---|
type | string | Yes — "link" |
url | string | Yes |
text | string | No — defaults to URL |
unsafe | boolean | No |
style | style object | No |
emoji
Standard or custom emoji.
| Property | Type | Required |
|---|---|---|
type | string | Yes — "emoji" |
name | string | Yes — emoji name without colons (e.g., "wave" or "wave::skin-tone-2") |
unicode | string | No — unicode codepoint |
user
User mention.
| Property | Type | Required |
|---|---|---|
type | string | Yes — "user" |
user_id | string | Yes |
style | style object | No |
channel
Channel mention.
| Property | Type | Required |
|---|---|---|
type | string | Yes — "channel" |
channel_id | string | Yes |
style | style object | No |
usergroup
User group mention.
| Property | Type | Required |
|---|---|---|
type | string | Yes — "usergroup" |
usergroup_id | string | Yes |
style | style object | No |
broadcast
@here, @channel, or @everyone mention.
| Property | Type | Required |
|---|---|---|
type | string | Yes — "broadcast" |
range | string | Yes — "here", "channel", or "everyone" |
style | style object | No |
date
Localized date display.
| Property | Type | Required |
|---|---|---|
type | string | Yes — "date" |
timestamp | integer | Yes — Unix timestamp |
format | string | Yes — Token string (e.g., "{date} at {time}") |
fallback | string | No |
url | string | No |
style | style object | No |
Supports 12+ format tokens: {date}, {date_num}, {date_short}, {date_long}, {date_pretty}, {date_short_pretty}, {date_long_pretty}, {time}, {time_secs}, {ago}.
color
Color swatch display.
| Property | Type | Required |
|---|---|---|
type | string | Yes — "color" |
value | string | Yes — hex color (e.g., "#FF5733") |
style | style object | No |
---
Style Object
Available on most inline elements. All properties are boolean, default false. Not all properties are available on every element type.
| Property | Effect | Available On |
|---|---|---|
bold | Bold text | All elements |
italic | Italic text | All elements |
strike | Strikethrough | All elements |
code | Inline code (monospace) | text, link only |
underline | Underlined text | All elements |
highlight | Highlighted background | user, usergroup, channel, color |
client_highlight | Client-managed highlight | user, usergroup, channel, color |
unlink | Removes link styling | user, usergroup, channel |
Styles can be combined:
{ "type": "text", "text": "Important note", "style": { "bold": true, "italic": true } }---
Complete Example
A rich text block with heading, paragraph, bulleted list, code block, and quote:
{
"type": "rich_text",
"elements": [
{
"type": "rich_text_section",
"elements": [
{ "type": "text", "text": "Deploy Summary", "style": { "bold": true } },
{ "type": "text", "text": "\nVersion 2.4.1 deployed to production.\n\n" }
]
},
{
"type": "rich_text_list",
"style": "bullet",
"elements": [
{
"type": "rich_text_section",
"elements": [
{ "type": "text", "text": "3 new features" }
]
},
{
"type": "rich_text_section",
"elements": [
{ "type": "text", "text": "12 bug fixes" }
]
},
{
"type": "rich_text_section",
"elements": [
{ "type": "text", "text": "0 breaking changes ", "style": { "bold": true } },
{ "type": "emoji", "name": "tada" }
]
}
]
},
{
"type": "rich_text_section",
"elements": [
{ "type": "text", "text": "\nKey migration command:\n" }
]
},
{
"type": "rich_text_preformatted",
"elements": [
{ "type": "text", "text": "bun run db:migrate --env production" }
]
},
{
"type": "rich_text_quote",
"elements": [
{ "type": "text", "text": "All smoke tests passing. Monitoring dashboards look clean. — " },
{ "type": "user", "user_id": "U0123ABC" }
]
}
]
}Slack Surfaces — Modals, App Home, Canvases, Lists, Split View
Sources:
- Surfaces — Slack
- Modals — Slack
- App Home — Slack
- Canvases — Slack
- Lists — Slack
- Split View — Slack
- App Design — Slack
---
Overview
| Surface | Block Kit | Max Blocks | Persistence | Primary Method |
|---|---|---|---|---|
| Messages | Yes | 50 | Permanent (until deleted) | chat.postMessage |
| Modals | Yes | 100 | Temporary (until dismissed) | views.open |
| App Home | Yes | 100 | Until app updates | views.publish |
| Canvases | No (markdown) | N/A | Permanent | canvases.create |
| Lists | No | N/A | Permanent | lists.* |
| Split View | Config | N/A | Session-based | Agents & AI Apps |
---
1. Messages
The primary output surface. Apps send messages via:
| Method | Use Case |
|---|---|
chat.postMessage | Standard messages |
chat.postEphemeral | Temporary, visible to one user only |
chat.update | Modify existing messages |
chat.delete | Remove messages |
| Incoming Webhooks | Simple message posting via URL |
response_url | Reply to interactions |
Message Types
Standard: Visible to all conversation members. Persist until deleted.
Ephemeral: Visible to one user only. Do not persist across reloads. Cannot be retrieved via API. Only send in response to user actions, never unsolicited.
Threaded: Replies under a parent message. Use thread_ts parameter. Set reply_broadcast: true to also show in channel.
Payload Structure
{
"channel": "C0123ABC",
"text": "Fallback text for notifications",
"blocks": [ /* Block Kit blocks */ ],
"thread_ts": "1234567890.123456",
"mrkdwn": true
}text is required — it serves as the notification preview and accessibility fallback, even when blocks are present.
---
2. Modals
Focused popup dialogs that capture user attention. Requires a trigger_id from a user interaction (expires in 3 seconds).
Lifecycle
User interaction → trigger_id → views.open → [views.push] → view_submission → responseView Object
| Property | Type | Required | Constraints |
|---|---|---|---|
type | string | Yes | "modal" |
title | text object | Yes | plain_text, max 24 chars |
blocks | block[] | Yes | Max 100 blocks |
callback_id | string | No | Max 255 chars, identifies interactions |
submit | text object | Conditional | Required if input blocks exist. plain_text, max 24 chars |
close | text object | No | plain_text, max 24 chars |
private_metadata | string | No | Max 3000 chars, persists between views |
clear_on_close | boolean | No | Clears entire view stack on close |
notify_on_close | boolean | No | Sends view_closed event |
external_id | string | No | Unique per team |
submit_disabled | boolean | No | Disables submit until inputs completed |
API Methods
| Method | Purpose |
|---|---|
views.open | Open initial modal (requires trigger_id) |
views.update | Update any view in stack (requires view_id) |
views.push | Add new view to stack (max 3 total) |
Response Actions (within 3s of view_submission)
| Action | Effect |
|---|---|
| Empty 200 response | Close current view |
{ "response_action": "update", "view": {...} } | Replace current view |
{ "response_action": "push", "view": {...} } | Add view to stack |
{ "response_action": "clear" } | Close all views |
{ "response_action": "errors", "errors": {...} } | Show validation errors |
Error Handling
{
"response_action": "errors",
"errors": {
"block_id_for_input": "Please enter a valid email address"
}
}Race Condition Prevention
Use the hash parameter in views.update calls. Included in block_actions payloads. API rejects outdated hashes.
Input Data Retrieval
After view_submission, input values are in:
view.state.values[block_id][action_id].value # text inputs
view.state.values[block_id][action_id].selected_option # single select
view.state.values[block_id][action_id].selected_options # multi-select, checkboxes
view.state.values[block_id][action_id].selected_date # datepicker
view.state.values[block_id][action_id].selected_time # timepicker
view.state.values[block_id][action_id].selected_date_time # datetimepicker
view.state.values[block_id][action_id].selected_user # users_select
view.state.values[block_id][action_id].selected_users # multi_users_select
view.state.values[block_id][action_id].selected_conversation # conversations_select
view.state.values[block_id][action_id].selected_conversations # multi_conversations_select
view.state.values[block_id][action_id].selected_channel # channels_select
view.state.values[block_id][action_id].selected_channels # multi_channels_selectTip: When updating views, preserve user-entered data by keeping identical block_id and action_id values in replacement input blocks.
Payload Types
| Event | When | Key Data |
|---|---|---|
block_actions | User interacts with non-input elements | actions[], view.hash |
view_submission | User clicks submit button | view.state.values |
view_closed | User dismisses modal (requires notify_on_close: true) | view |
Response URLs from Modals
For conversations_select or channels_select with response_url_enabled: true, the view_submission payload includes response_urls for posting messages to selected conversations.
---
3. App Home
Private, per-user space with three tabs: Home, Messages, About.
Home Tab
Published via views.publish. Supports full Block Kit (100 blocks).
{
"type": "home",
"blocks": [ /* Block Kit blocks */ ],
"private_metadata": "optional context string",
"callback_id": "home_view",
"external_id": "unique_per_team"
}API: POST views.publish with user_id and view parameters.
Update: Call views.publish again with updated blocks. Replaces existing view.
Messages Tab
Direct messages between user and app bot. Requires chat:write scope. Optionally im:history for reading messages.
Subscribe to message.im event to receive user messages.
About Tab
Auto-populated from app's Display Information settings. Not programmatically customizable.
Events
app_home_opened fires when users open App Home. Payload includes user, channel, tab (home/messages/about), and view.
Deep Linking
slack://app?team={TEAM_ID}&id={APP_ID}&tab={home|messages|about}Design Best Practices
- Prioritize relevant content at top
- Expose settings behind buttons, not prominently
- Limit call-to-action density
- Consider overflow menus for secondary actions
- Trigger modals from interactive components for data collection
---
4. Canvases
Built-in documents attached to channels or standalone. Use markdown formatting (NOT Block Kit).
API Methods
| Method | Purpose |
|---|---|
canvases.create | Create new canvas |
canvases.edit | Modify existing canvas |
files.list | Search canvases (filter by type "canvas") |
Content Format
{
"document_content": {
"type": "markdown",
"markdown": "# Title\n\nContent with **bold** and [links](https://example.com)"
}
}Supported Markdown
Text styling (bold, italic, strikethrough, code), lists (bulleted, ordered, checklists), headings (h1-h3), code blocks, quote blocks, dividers, emoji (standard + custom), tables (max 300 cells), links, user/channel mentions.
Mention Syntax (Canvas-specific)
User: 
Channel: Image References
Images can be referenced using public URLs or Slack-hosted permalinks (obtained via files.info after uploading):
Limitations
- No Block Kit support
- Tables limited to 300 cells
- Markdown is the only supported content type
---
5. Lists
Work management surfaces for organizing, collaborating, and tracking projects within Slack.
Use Cases
- Feedback management in channels
- Cross-functional project tracking
- Task assignment and status tracking
API Access
Lists are managed through the lists.* family of Slack Web API methods. Apps can extract data from Lists and post summaries to channels.
Advantages Over Alternatives
- Custom views within Slack
- Direct @-mentions for stakeholder notification
- Native integration without external tools
---
6. Split View
AI chat surface providing side-by-side layout for AI-powered apps.
Configuration
1. Navigate to "Agents & AI Apps" in app settings 2. Toggle split view on
Effects
- Top bar entry point appears for app access
- App Home messages tab is replaced with Chat and History tabs
- Chat tab: primary AI conversation interface
- History tab: previous interaction history
Complementary Features
- Suggested prompts for user guidance
- Loading states for processing indicators
- App threads for organized conversations
---
App Design Best Practices
Communication Guidelines
| Principle | Guidance |
|---|---|
| Message frequency | Offer digests over individual alerts |
| Mentions | Avoid @channel, @here, @everyone except for critical outages |
| Channel selection | Segment by type, avoid #general default |
| Ephemeral vs channel | Use ephemeral for single-user responses |
| DMs | Only when user-initiated or confidential |
| Actionable messages | Include interactive elements for immediate action |
Localization
| Context | Use |
|---|---|
| Public content | Channel locale |
| Private interactions | User locale |
| Locale retrieval | conversations.info / users.info with include_locale: true |
Bot Design
- Descriptive, memorable names (lowercase, <22 chars)
- Brand colors for attachment highlights
- Icons designed for 512x512px scaling down to 36x36px
- Illustrations over photography, minimal text in icons
- Don't round corners — Slack handles this
Tone
- Brief: every word should facilitate an interaction
- Clear: no jargon, no culturally specific references
- Empathetic: gender-neutral pronouns, diverse emoji skin tones
- Actionable: specific button labels in active voice
Work Objects — Entity Previews in Slack
Sources:
- Work Objects — Slack
Work Objects standardize how external entities (files, tasks, incidents) appear within Slack conversations. They extend link unfurling with rich, interactive entity previews.
---
Architecture
User shares link → link_shared event → app calls chat.unfurl with metadata
→ Unfurl component renders in message
User clicks unfurl → entity_details_requested event → app calls entity.presentDetails
→ Flexpane opens with full detailsUnfurl component: Rich entity preview visible to all participants in the conversation.
Flexpane component: Side panel with full details, edit capability, actions, and related conversations.
---
Entity Types
| Type | Entity ID | Use Case |
|---|---|---|
| File | slack#/entities/file | Documents, spreadsheets, images, PDFs |
| Task | slack#/entities/task | Tickets, to-dos, work items, issues |
| Incident | slack#/entities/incident | Service interruptions, outages, alerts |
| Content Item | slack#/entities/content_item | Articles, wiki pages, documentation |
| Item | slack#/entities/item | General-purpose entity (anything else) |
---
Setup
1. Go to app settings at api.slack.com/apps 2. Navigate to "Work Object Previews" in the sidebar 3. Enable the toggle 4. Select desired entity types 5. Save
---
Unfurl Implementation
chat.unfurl with Metadata
The metadata parameter (URL-encoded) contains the entity definition:
{
"metadata": {
"app_unfurl_url": "https://example.com/task/123",
"entities": [
{
"url": "https://example.com/task/123",
"entity_type": "slack#/entities/task",
"external_ref": { "id": "task-123", "type": "task" },
"entity_payload": {
"attributes": { /* header info */ },
"fields": { /* entity-specific fields */ },
"custom_fields": [ /* optional custom fields */ ],
"display_order": [ /* field display order */ ]
}
}
]
}
}Key Properties
| Property | Required | Purpose |
|---|---|---|
app_unfurl_url | Yes | The URL the user posted |
url | Yes | Canonical resource URL in external system |
entity_type | Yes | One of the 5 entity types |
external_ref | Yes | Object with id (required, must never change) and type (optional) identifying the entity |
entity_payload.attributes | Yes | Header info including title |
entity_payload.fields | Yes | Object of typed field entries (required even if empty {}) |
Slack silently drops the entire metadata payload if any required field is missing — no error, no card, just the bare text message.
Important: external_ref.id must remain stable across the lifetime of the entity — changing it breaks related conversation tracking.
Direct Posting (Without Link Unfurl)
chat.postMessage also accepts the metadata parameter for posting Work Objects directly:
{
"channel": "C0123ABC",
"text": "New task created",
"metadata": { /* same structure as unfurl */ }
}---
Entity Payload Schema
{
"entity_payload": {
"attributes": {
"title": "Bug: Login page 500 error",
"subtitle": "Project Alpha",
"display_id": "PROJ-123",
"display_type": "Bug",
"product_name": "Jira",
"product_icon": { "type": "image", "url": "https://example.com/jira-icon.png" },
"icon": { "type": "image", "url": "https://example.com/icon.png" },
"full_size_preview": { "preview_url": "https://example.com/preview.pdf" },
"metadata_last_modified": 1708000000
},
"fields": {
"status": {
"type": "string",
"value": "In Progress",
"display_name": "Status"
},
"assignee": {
"type": "slack#/types/user",
"value": "U0123ABC",
"display_name": "Assignee"
},
"priority": {
"type": "string",
"value": "High",
"display_name": "Priority"
},
"due_date": {
"type": "slack#/types/date",
"value": "2026-02-15",
"display_name": "Due Date"
}
},
"custom_fields": [
{
"type": "string",
"key": "sprint",
"value": "Sprint 14",
"display_name": "Sprint"
}
],
"display_order": ["status", "assignee", "priority", "due_date"]
}
}Attributes Reference
| Property | Required | Purpose |
|---|---|---|
title | Yes | Entity name |
subtitle | No | Secondary text below title |
display_id | No | User-friendly identifier (e.g., "PROJ-123") |
display_type | No | Resource classification label (defaults to entity type name) |
product_name | No | External system name (e.g., "Jira", "GitHub") |
product_icon | No | Custom icon ({ type: "image", url } or { type: "image", slack_file: { id } }) |
icon | No | Entity icon |
full_size_preview | No | Image/PDF preview config (see Full-Size Preview section) |
metadata_last_modified | No | Unix timestamp — Slack compares to previous value and skips refresh if unchanged |
Entity-Type-Specific Fields
Each entity type has standard fields. All are optional and use the data types below.
File (slack#/entities/file): preview (image), created_by (user), date_created (timestamp), date_updated (timestamp), last_modified_by (user), file_size (string), mime_type (string).
Task (slack#/entities/task): description (markdown), created_by (user), assignee (user), date_created (timestamp), date_updated (timestamp), status (string, supports tag_color and link), due_date (date or timestamp), priority (string, supports icon and link).
Incident (slack#/entities/incident): status (string), severity (string), created_by (user), assigned_to (user), date_created (timestamp), date_updated (timestamp), description (string), service (string).
Content Item (slack#/entities/content_item): preview (image), description (string), created_by (user), last_modified_by (user), date_created (timestamp), date_updated (timestamp).
Item (slack#/entities/item): No predefined fields — fully custom via fields and custom_fields.
---
Data Types
| Type | Format |
|---|---|
string | Plain text |
integer | Whole number |
boolean | true / false |
array | Array of values |
slack#/types/user | Slack user ID |
slack#/types/channel_id | Slack channel ID |
slack#/types/timestamp | Unix timestamp |
slack#/types/date | Date string |
slack#/types/image | Image URL |
slack#/types/link | Hyperlink |
slack#/types/email | Email address |
slack#/types/entity_ref | Reference to another Work Object |
User Type Properties
When using slack#/types/user, the value can include: user_id (Slack user ID), text (display name), email, url (external profile link), icon (avatar image).
Boolean Display Modes
Boolean fields support two display modes via a boolean property:
- Checkbox:
{ "type": "checkbox", "text": "Enable notifications" } - Text (custom labels):
{ "type": "text", "true_text": "Public", "false_text": "Private" }
Entity Reference Properties
When using slack#/types/entity_ref, the value includes: entity_url (canonical URL), external_ref ({ id, type }), title, display_type, icon.
---
Flexpane
Handling Requests
When users open a flexpane or refresh it, Slack sends an entity_details_requested event:
{
"type": "event_callback",
"event": {
"type": "entity_details_requested",
"user": "U0123ABC",
"external_ref": { "id": "task-123", "type": "task" },
"entity_url": "https://example.com/task/123",
"app_unfurl_url": "https://example.com/task/123",
"trigger_id": "12345.67890",
"user_locale": "en-US",
"channel": "C0123ABC",
"message_ts": "1234567890.123456",
"thread_ts": "1234567890.123456"
}
}Note: external_ref is not guaranteed to be set in all cases, such as entities from Enterprise Search results.
Content Refresh TTL: First open always sends the event. Within 10 minutes, only manual refresh triggers it. After 10 minutes, reopening, tab switching, or manual refresh triggers it. A red dot indicator appears on stale content.
Responding
Use entity.presentDetails with the trigger_id from the event to respond with metadata (same schema as chat.unfurl minus entities array and app_unfurl_url):
{
"trigger_id": "12345.67890",
"metadata": {
"entity_type": "slack#/entities/task",
"external_ref": { "id": "task-123", "type": "task" },
"entity_payload": { /* same structure as unfurl */ }
}
}Custom Partial Views
For restricted access or custom error states, respond with an error object instead:
{
"trigger_id": "12345.67890",
"error": {
"status": "custom_partial_view",
"custom_title": "Access Restricted",
"custom_message": "Request access using the button below",
"message_format": "markdown",
"actions": [{ "text": "Request Access", "action_id": "request_access", "style": "primary" }]
}
}---
Editable Fields
Fields can be made editable by adding an edit property:
{
"description": {
"type": "string",
"value": "Original text",
"display_name": "Description",
"edit": {
"enabled": true,
"text": {
"max_length": 500,
"min_length": 1
}
}
}
}Edit Property Configuration
| Property | Purpose |
|---|---|
enabled | Boolean — activate editing |
placeholder | Input hint text |
hint | Descriptive text below input |
optional | Allow blank values |
select | Dropdown/multi-select options config |
number | { min_value, max_value } constraints |
text | { min_length, max_length } constraints (0-3000 chars) |
boolean | Input type config (checkbox, radio, or select) |
Supported Edit Field Types
| Field Type | Block Kit Element | Validation Options |
|---|---|---|
| text | plain_text_input | min_length, max_length |
| number | number_input | min_value, max_value |
| date | datepicker | — |
| datetime | datetimepicker | — |
| email_text_input | — | |
| boolean | checkbox/radio/select | Input type configurable |
| select | static_select | Static options list |
| multi-select | multi_static_select | Static options list |
Dynamic Options
Use fetch_options_dynamically: true for select/multi-select fields that load options from your server.
Submission Handling
When users submit edits, a view_submission event is sent with changes in view.state.values.
Validation
Three levels:
1. Client-side: edit property constraints prevent submission 2. Server-side field-level: Respond to view_submission with field-specific errors 3. Server-side form-level: Use entity.presentDetails with edit_error status
---
Actions
Apps can add interactive buttons to Work Object previews.
Primary Actions (max 2)
{
"actions": {
"primary_actions": [
{
"text": "View Details",
"action_id": "view_details",
"style": "primary",
"value": "task-123"
},
{
"text": "Mark Complete",
"action_id": "mark_complete",
"style": "danger",
"value": "task-123",
"url": "https://example.com/task/123/complete"
}
]
}
}Overflow Actions (max 5)
Additional actions in an overflow menu alongside primary actions:
{
"actions": {
"primary_actions": [{ "text": "View", "action_id": "view", "style": "primary" }],
"overflow_actions": [
{ "text": "Edit", "action_id": "edit", "value": "task-123" },
{ "text": "Delete", "action_id": "delete", "value": "task-123" }
]
}
}Action Properties
| Property | Required | Constraints |
|---|---|---|
text | Yes | Button label |
action_id | Yes | Interaction identifier |
value | No | Max 2000 chars |
style | No | "primary" or "danger" |
url | No | External link destination |
accessibility_label | No | Max 75 chars |
Interaction Handling
When users click action buttons, block_actions events are dispatched.
Unfurl actions: container.type: "message_attachment" with app_unfurl_url, entity_url, external_ref, message_ts, thread_ts, channel_id.
Flexpane actions: container.type: "entity_detail" with the same fields.
Response options: Open a modal, post a thread reply, send a DM, call chat.unfurl to refresh the unfurl, or call entity.presentDetails to refresh the flexpane.
---
Authentication
For sensitive data requiring user authentication:
{
"user_auth_required": true,
"user_auth_url": "https://example.com/auth/slack"
}The flexpane shows a sign-in prompt. After authentication, the flexpane refreshes.
---
Full-Size Preview
For PDFs and images, provide a preview URL in attributes:
{
"attributes": {
"full_size_preview": {
"is_supported": true,
"preview_url": "https://example.com/document.pdf",
"mime_type": "application/pdf"
}
}
}| Property | Required | Purpose |
|---|---|---|
preview_url | Yes | URL of the preview file |
is_supported | No | Boolean — set to false to disable preview |
mime_type | No | MIME type of the preview file |
error | No | { code, message } for unsupported files (e.g., "file_not_supported") |
Requirement: Must include CORS header access-control-allow-origin: https://app.slack.com.
Unfurl thumbnail: Display a thumbnail in the unfurl via fields.preview:
{ "fields": { "preview": { "image_url": "https://example.com/thumb.png", "alt_text": "Preview" } } }---
Automatic Refresh
Unfurls auto-refresh when:
- User opens, edits, or refreshes flexpane (via
entity.presentDetailsresponse) - User clicks an action button (after processing delay)
Optimization: Include metadata_last_modified Unix timestamp in attributes. Slack compares to the previous value and skips the refresh if unchanged.
---
View Submission Handling
When users save edits in the flexpane, a view_submission event is sent with view.type: "entity_detail":
{
"type": "view_submission",
"view": {
"type": "entity_detail",
"state": {
"values": {
"description": {
"description.input": { "type": "plain_text_input", "value": "Updated text" }
}
}
},
"external_ref": { "id": "task-123" },
"entity_url": "https://example.com/task/123"
}
}Respond within 3 seconds:
- Success: Call
entity.presentDetailswith updated metadata - Field errors: Return
{ "response_action": "errors", "errors": { "field_block_id": "Error message" } } - Form errors: Use
entity.presentDetailswithedit_errorstatus
---
Related Conversations
The flexpane automatically aggregates conversations where the Work Object resource was mentioned, providing cross-conversation context.
---
Enterprise Search Integration
Apps supporting Enterprise Search must subscribe to entity_details_requested and respond with entity.presentDetails. Define entity types in Work Object Previews settings. Supports search results, traditional results, and AI answer citations.