Writing Slack canvases
A canvas is a document that lives inside Slack. Where a message scrolls away, a canvas stays put and gets edited — which makes it the right output for anything people return to: a runbook, a spec, an incident summary, a rolling status doc.
Daslab writes canvases through the Slack integration. There are two kinds, and the difference matters.
Standalone vs channel canvases
A standalone canvas is a free-floating document. Create it with slack_create_canvas. It belongs to the app that made it and is visible to nobody else until you grant access.
A channel canvas is the canvas tab attached to a channel, next to Messages. Create it with slack_create_channel_canvas. Everyone in the channel can see it, and there is exactly one per channel — a second call returns channel_canvas_already_exists, which means the channel already has a canvas and you should edit that one instead.
Channel canvases require a paid Slack plan. On the free tier, slack_create_channel_canvas returns team_tier_cannot_create_channel_canvases; standalone canvases still work.
Permissions
Canvas tools need the canvases:read and canvases:write scopes. A workspace connected before canvas support shipped does not have them, and every canvas call will fail until you reconnect it from scene settings — the tools say so rather than returning a bare missing_scope.
For a channel canvas, the bot also has to be able to see the channel: public channels work as-is, private channels need the bot invited.
Sharing a standalone canvas
A standalone canvas starts private to the app. To let people read or edit it:
slack_set_canvas_access(canvas_id, access_level: "write", channel_ids: "C0123ABCD")
Grant to channels or to users, not both in one call. read gives view-only; write lets recipients edit. Grant access first, then post the link — slack_create_canvas returns the canvas URL alongside its ID.
Content is markdown, not mrkdwn
Slack messages use mrkdwn — the dialect with bold, _italic_, and <url|link text>. Canvases do not. Canvas bodies take ordinary markdown:
# Deploy runbook
## Before you start
- [ ] Check the channel is quiet
- [ ] Confirm the release tag
| Step | Owner |
| ---- | ----- |
| Bundle | CI |
| Cut over | on-call |
Ping <@U0123ABCD> if the cutover stalls.
Headings, lists, checklists, tables, code blocks, dividers, links, and <@U…> user mentions all work. Writing mrkdwn into a canvas produces literal asterisks.
Editing a canvas
slack_edit_canvas applies one operation per call:
| Operation | Needs | Effect |
|---|---|---|
insert_at_end / insert_at_start | markdown | Append or prepend |
insert_after / insert_before | section_id, markdown | Insert next to a section |
replace | markdown, optional section_id | Replace one section, or the whole canvas if no section is given |
delete | section_id | Remove a section |
rename | title | Retitle the canvas |
The targeted operations need a section ID, which you get from slack_lookup_canvas_sections. Sections are addressed by heading, so filter by level or by text:
slack_lookup_canvas_sections(canvas_id, section_types: "h2", contains_text: "Rollback")
Call it with neither filter to list every section. Section IDs are not stable across edits — look them up in the same pass that uses them, rather than caching them between runs.
For a canvas your agent owns end to end, replace with no section_id is usually simpler than patching sections: regenerate the body and overwrite.
Deleting
slack_delete_canvas is permanent — Slack has no undo and no trash for canvases. Prefer replacing the content of a canvas people already have linked over deleting and recreating it, which breaks every existing link.