Mainstream messaging
Slack charts, tables, and approvals
The Block Kit surfaces OpenClaw renders natively in Slack.
Native charts
Slack's public data_visualization Block Kit block
renders line, bar, area, and pie charts in messages. OpenClaw maps the portable
presentation chart block to that native shape; no additional OAuth scope,
file upload, image renderer, or Slack configuration is required beyond normal
chat:write message access.
{ "blocks": [ { "type": "chart", "chartType": "bar", "title": "Quarterly revenue", "categories": ["Q1", "Q2"], "series": [{ "name": "Revenue", "values": [120, 145] }], "xLabel": "Quarter" } ]}Slack's limits are enforced before native rendering:
- title and optional axis labels: 50 characters
- pie: 1-12 positive segments
- line/bar/area: 1-12 uniquely named series and 1-20 shared categories
- segment, category, and series labels: 20 characters
- every series must contain one finite value for every category; non-pie values may be negative
Every native chart also carries a top-level text representation for screen
readers, notifications, session mirroring, and clients that cannot render the
block. Standard presentation sends to other OpenClaw channels receive that same
deterministic chart data as text unless they advertise native chart support. If
Slack rejects the chart with invalid_blocks during a phased rollout, OpenClaw
removes the rejected native data blocks, keeps any sibling controls, and sends
the complete chart representation as visible text.
Slack currently accepts up to two data_visualization blocks per message. When
a presentation contains more than two valid charts, OpenClaw keeps their order
and continues native rendering in follow-up messages, with no more than two
charts in each message.
Slack's developer launch documents the block as an app-facing Block Kit feature and publishes no paid plan restriction. The Business+/Enterprise eligibility language applies to Slackbot's automatic AI chart generation, which is separate from an app sending an already-structured Block Kit chart. Charts are message-only blocks, not App Home, modal, or Canvas content.
Native tables
Slack's current data_table Block Kit block
renders structured rows and columns in messages. OpenClaw maps an explicit
portable presentation table block to data_table; it does not use Slack's
legacy table block.
No additional OAuth scope or Slack configuration is required beyond normal
chat:write message access.
{ "blocks": [ { "type": "table", "caption": "Open pipeline", "headers": ["Account", "Stage", "ARR"], "rows": [ ["Acme", "Won", 125000], ["Globex", "Review", 82000] ], "rowHeaderColumnIndex": 0 } ]}OpenClaw maps header and string cells to Slack raw_text cells. Numeric cells
map to raw_number, with the finite numeric value preserved for native sorting
and filtering. rowHeaderColumnIndex, when present, marks that zero-based
column as Slack row headers.
Slack's published data_table limits are enforced before native rendering:
- 1-20 columns
- 1-100 data rows, plus the header row
- the same number of cells in every row
- at most 10,000 aggregate characters across all table cells in one message
Multiple valid table blocks can render natively while the message remains within the aggregate character limit. A table that cannot render within the native envelope becomes complete deterministic text instead of losing rows or cells. If that text exceeds one Slack message, sends and slash responses use ordered text chunks. Table edits fail with an explicit size error instead of silently truncating rows from an existing message.
Every native table produced from portable presentation also carries a top-level
text representation for screen readers, notifications, session mirroring, and
clients that cannot render the block. Raw chart and table values stay literal
in the fallback, so cell data such as <@U123> does not become a Slack mention.
If Slack rejects native chart or table blocks with invalid_blocks, OpenClaw
removes every native data block in one bounded recovery step, retains valid
sibling blocks such as buttons and selects, and sends complete visible chart
and table text with Slack formatting disabled. Slash-command delivery
tracks Slack's five-call response_url budget across the command. Before each
reply batch, it selects a complete plan that fits the remaining calls or fails
before posting that batch.
Only explicit presentation table blocks are promoted to native tables.
Markdown pipe tables remain authored text; OpenClaw does not guess at table
structure or cell types. Existing trusted Slack-native producers can continue
to pass raw blocks through channelData.slack.blocks; OpenClaw derives fallback
text from valid raw data_table cells, while malformed custom blocks may
degrade to their caption or general Block Kit fallback. Portable agent, CLI,
and plugin output should use presentation.
Slack clients can also deliver pasted spreadsheet content as a legacy table
block in the message's top-level blocks or attachments. OpenClaw renders those
inbound cells as delimiter-safe TSV for live agent input, thread context, and
Slack read actions. Only native table blocks are admitted from ordinary
attachments; link-unfurl and other non-forwarded attachment text remains
excluded.
Plugin-owned modal submissions
Slack plugins that register an interactive handler can also receive modal
view_submission and view_closed lifecycle events before OpenClaw compacts
the payload for the agent-visible system event. Use one of these routing
patterns when opening a Slack modal:
- Set
callback_idtoopenclaw:<namespace>:<payload>. - Or keep an existing
callback_idand putpluginInteractiveData: "<namespace>:<payload>"in the modalprivate_metadata.
The handler receives ctx.interaction.kind as view_submission or
view_closed, normalized inputs, and the full raw stateValues object from
Slack. Callback-id-only routing is enough to invoke the plugin handler; include
the existing modal private_metadata user/session routing fields when the
modal should also produce an agent-visible system event. The agent receives a
compact, redacted Slack interaction: ... system event. If the handler returns
systemEvent.summary, systemEvent.reference, or systemEvent.data, those
fields are included in that compact event so the agent can reference
plugin-owned storage without seeing the complete form payload.
Native approvals in Slack
Slack can act as a native approval client with interactive buttons and interactions, instead of falling back to the Web UI or terminal.
- Exec and plugin approvals can render as Slack-native Block Kit prompts.
channels.slack.execApprovals.*remains the native exec approval client enablement and DM/channel routing config.- Exec approval DMs use
channels.slack.execApprovals.approversorcommands.ownerAllowFrom. - Plugin approvals use Slack-native buttons when Slack is enabled as a native approval client for the originating session, or when
approvals.pluginroutes to the originating Slack session or a Slack target. - Plugin approval DMs use Slack plugin approvers from
channels.slack.allowFrom, named-accountallowFrom, or the account default route. - Approver authorization is still enforced: exec-only approvers cannot approve plugin requests unless they are also plugin approvers.
For Enterprise Grid org installs, the originating event's validated workspace is retained for the approval prompt, approver DM, button callback, and final message update. Approval delivery fails closed when an org-installed account does not have that event-owned workspace scope.
This uses the same shared approval button surface as other channels. When interactivity is enabled in your Slack app settings, approval prompts render as Block Kit buttons directly in the conversation.
When those buttons are present, they are the primary approval UX; OpenClaw
should only include a manual /approve command when the tool result says chat
approvals are unavailable or manual approval is the only path.
Config path:
channels.slack.execApprovals.enabledchannels.slack.execApprovals.approvers(optional; falls back tocommands.ownerAllowFromwhen possible)channels.slack.execApprovals.target(dm|channel|both, default:dm)agentFilter,sessionFilter
Slack native exec approvals require enabled: true or "auto" and at least one
resolved exec approver. Leaving enabled unset or setting it to false disables
native exec approval delivery. Slack can also handle native plugin approvals
through this native-client path when Slack plugin approvers resolve and the
request matches its filters. Disabling Slack exec approvals does not disable
native plugin approval delivery enabled through approvals.plugin, which uses
Slack plugin approvers instead.
Minimal Slack-native configuration using command owners as approvers:
{ channels: { slack: { execApprovals: { enabled: "auto" }, }, }, commands: { ownerAllowFrom: ["slack:U12345678"], },}To override approvers, add filters, or opt into origin-chat delivery:
{ channels: { slack: { execApprovals: { enabled: true, approvers: ["U12345678"], target: "both", }, }, },}Shared approvals.exec forwarding is separate. Use it only when exec approval prompts must also
route to other chats or explicit out-of-band targets. Shared approvals.plugin forwarding is also
separate; Slack native delivery suppresses that fallback only when Slack can handle the plugin
approval request natively.
Same-chat /approve also works in Slack channels and DMs that already support commands. See Exec approvals for the full approval forwarding model.