Launched:self-serve migrations intoSuperhuman Docs (Coda)
Try it now
01Agent-first
Runs where you already work
Plug it into Claude, ChatGPT or Cursor. Describe the move in plain English; the agent runs it.
02Engineer-led
Our production engine, unlocked
The pipeline our engineers use on managed enterprise migrations — the same code, now something you can drive yourself.
03Pricing
Try 10 pages free, then $1 a page
Credit-based, pay-as-you-go. No scoping call, no quote — sample it on your own docs before you spend anything.
04Sources
NotionSlabConfluenceSoonGoogle DocsSoon
Skip to content

Slack Canvas Markdown: What Renders, What Fails, What Drops

A technical reference on what Slack Canvas markdown renders, what it quietly drops, and what causes hard API failures during document migration.

Raajshekhar Rajan Raajshekhar Rajan · · 19 min read
Slack Canvas Markdown: What Renders, What Fails, What Drops
TALK TO AN ENGINEER

Planning a migration?

Get a free 30-min call with our engineers. We'll review your setup and map out a custom migration plan — no obligation.

Schedule a free call
  • 1,500+ migrations completed
  • Zero downtime guaranteed
  • Transparent, fixed pricing
  • Project success responsibility
  • Post-migration support included

Slack Canvas Markdown: What Renders, What Fails, What Drops

Slack Canvas accepts markdown through the document_content object in its API, but only a specific subset actually renders. The type field must be "markdown" — it is the only supported content type for the Web API. If you are converting documents from Quip, Confluence, Notion, or any other source, the gap between what Canvas supports and what it silently ignores (or outright rejects) determines whether your migrated content arrives intact.

This reference covers the exact supported set, the hard failures, the silent drops, and the converter policies that prevent data loss at scale.

Last verified: June 2025 against Slack API documentation for canvases.create, canvases.edit, conversations.canvases.create, and canvases.getContent.

Scope: This reference covers the Web API document_content.markdown path. If you're creating canvases through the Deno Slack SDK's built-in functions, Slack uses an expanded_rich_text payload instead of the markdown string covered here. (docs.slack.dev)

Syntax note: Code examples throughout this document show Canvas API payload syntax. The callout and column fenced-div blocks (::: {.callout}, ::: {.column}) are the syntax submitted in API payloads. Styled callout boxes in this blog post are rendered by the blog's own CMS and are not Canvas API syntax.


What markdown does Slack Canvas actually support?

Slack Canvas markdown is the subset of markdown Slack accepts in the document_content.markdown field when you create or edit a canvas. Anything not on the list below is either silently dropped or triggers a hard API failure. (docs.slack.dev)

Category Supported Elements
Text formatting Bold, italic, strikethrough, inline code span
Headings h1, h2, h3 only — nothing below h3
Lists Bulleted lists, ordered lists, checklists (task lists)
Block-level Blockquote, callout, code block, divider (horizontal rule), paragraph
Layout Column layout — max 3 columns
Tables Markdown tables — max 300 cells per table
Links & mentions Inline links, link references, @mentions for users and channels
Unfurls Canvas unfurl, file unfurl, message unfurl, user unfurl, website unfurl
Other Emojis (standard and custom), hard line breaks

This list comes from Slack's developer documentation for canvases.create, canvases.edit, and conversations.canvases.create. If your source documents contain HTML blocks, footnotes, definition lists, or any standard markdown elements outside this set, they will not render. You must intercept them during conversion and rewrite them into supported equivalents — or mark them explicitly as unsupported.

Payload size limit: The document_content payload is capped at 1 MiB (1,048,576 characters) per call. For canvases.edit, this limit applies to each individual change object in the changes array, not the total canvas size. The changes array is currently limited to one operation per API call. Documents exceeding 1 MiB must be split across multiple API calls. (docs.slack.dev)

A minimal safe subset for an automated converter:

# Runbook
## Owners
![](@U123ABCDEFG)
 
### Checklist
- [ ] Export source
- [ ] Upload images
- [x] Validate links
 
> Freeze window starts at 18:00 UTC.
 
| Item | Owner |
| --- | --- |
| Prod cutover | ![](@U123ABCDEFG) |
| Notes | **Read** `/ops/runbook` first |
 
![](https://yourworkspace.slack.com/docs/T123ABC456/F123ABC456)

Why h4, h5, and h6 headings don't exist in Canvas

Slack Canvas supports headings h1 through h3 only. The API does not return an error for deeper heading levels — the text is inserted but renders as a plain paragraph with no semantic heading role. For a single document, that is a minor annoyance. Across ten thousand converted documents, it destroys navigational structure.

The canvases.sections.lookup API confirms this constraint: you can filter sections by h1, h2, h3, or any_header. No deeper heading type exists in the Canvas section taxonomy. (docs.slack.dev)

Roundtrip behavior: If you write an h4 element and read it back with canvases.getContent, the API returns it as a plain paragraph — the heading markup is gone. This means roundtrip comparison (write → read → diff) will flag h4+ headings as conversion loss, which is the correct behavior: the content that went in is not the content that came out.

Options for flattening a deep heading tree

When a source document has four or five heading levels, you have several realistic strategies:

Strategy Mechanism Best for
Collapse to three levels Map h1→h1, h2→h2, h3/h4/h5/h6→h3 Documents where deeper levels rarely stand alone
Promote and bold Map top three levels to h1–h3, convert h4+ to bold paragraph text Enterprise migrations; most reliable default
Split into multiple canvases Break document at h3 boundaries; link via canvas unfurls Documents genuinely requiring 5 levels; operationally expensive
Convert to tables If h4 sections contain keyed values (owner, status, date), use a two-column table Reference docs with structured sub-sections
Convert to callouts Replace emphasis-as-hierarchy with native callout blocks Deep headings standing in for emphasis rather than structure

The wrong option is to pass the markdown through unmodified. An h4 rendered as body text is invisible to anyone scanning by headings, and Canvas provides no table-of-contents or outline view that would surface the problem.

Warning: If legal, regulatory, or SOP content depends on a deep outline tree, Slack Canvas is a lossy target. Make that determination before the migration, not during the final import pass.


What fails hard in Slack Canvas markdown?

A hard failure is content Slack rejects outright instead of silently simplifying. The distinction matters operationally: silent drops let the API call succeed while losing content; hard failures reject the entire payload, blocking canvas creation for every document containing the offending structure.

Block Kit is not supported inside canvases

Slack's documentation states this directly: "Note that currently, Block Kit is not supported in canvases." This is a hard boundary, not a soft limitation. (docs.slack.dev)

Block Kit powers interactive components in Slack messages — buttons, select menus, date pickers, overflow menus, and input blocks. None of these survive conversion into a canvas. The Canvas markdown format has zero representation for interactive UI beyond checklists.

This matters for migrations from tools like Quip, where spreadsheet-backed live components, Salesforce record links, and interactive widgets are embedded in documents. Attempting to inject Block Kit JSON into a Canvas payload results in a hard API failure. These elements must be converted to static text, markdown tables, or links to the external system — or flagged for human review.

If the source depends on interactivity, remap it before conversion. Convert the interaction to plain text instructions, move it to a workflow or modal, or fail the document for manual review. Do not pretend a button became content just because some label text made it through.

Nested structures that Canvas rejects outright

Some markdown structures that are valid in CommonMark or GFM will cause the Canvas API to reject the entire payload. The most documented failure is nesting a list inside a blockquote.

The error returned by both canvases.create and canvases.edit:

{
    "ok": false,
    "error": "canvas_editing_failed",
    "detail": "'content' error: line 28: Unsupported block type (List) within block quote"
}

This is not a silent drop — the entire API call fails, not just the offending line. A single deeply-nested list in a blockquote blocks creation of the entire canvas.

Nesting rules, based on both official documentation and observed behavior:

Container Allowed inside Not allowed inside
Blockquote Plain text paragraphs, inline formatting (bold, italic, inline code, links) Headings, lists, code blocks, nested quotes
List items Plain text, inline formatting Code blocks, blockquotes, headings
Code blocks Top-level only Inside blockquotes or list items
Callouts Plain text, inline formatting Tables, nested structural elements
Column layouts Plain text, inline formatting Tables, callouts; max 3 columns

A converter must pre-process the markdown to flatten nested structures before submitting to the API. The safest rewrites:

  • Keep the quote, but collapse the list to plain quoted lines (manually prepending • characters).
  • Keep the list, but hoist it outside the quote.
  • Turn the quote into a callout, then follow it with a normal list.

Leaving this to Slack's parser is how you end up with rejected payloads in the middle of a bulk run.


The canvases.edit change object structure

The blog's earlier sections mention the changes array limit, but the structure of a change object is necessary to build against the edit API. A change object in the changes array has the following shape:

{
  "operation": "insert",
  "document_content": {
    "type": "markdown",
    "markdown": "## New Section\n\nContent here."
  }
}

Valid operation values are insert, replace, and delete. For replace and delete, a section_id must be provided to target a specific section. Section IDs are returned by canvases.sections.lookup.

{
  "operation": "replace",
  "section_id": "temp:C:abc123def456",
  "document_content": {
    "type": "markdown",
    "markdown": "## Updated Section\n\nReplaced content."
  }
}

One operation per API call is the current constraint. A pipeline that needs to make five edits to a single canvas must make five sequential canvases.edit calls. At Tier 3 limits (50+ requests/minute), that is manageable — but the one-at-a-time constraint rules out batching changes for a single document in a single request.


Canvas access model: workspace-level vs. channel-linked canvases

Canvas access depends on how the canvas was created — a distinction that determines who can read and edit it.

  • canvases.create (no channel_id): Creates a standalone canvas owned by the authenticated user. Access is private by default. The canvas must be explicitly shared or linked to a channel before other users can see it. On free Slack plans, canvases.create requires a channel_id — standalone creation is a paid-plan feature.
  • conversations.canvases.create (channel_id required): Creates a canvas linked to a specific channel. All members of that channel can view the canvas. Channel settings (private vs. public) determine who is a member.
  • Edit permissions: By default, any channel member can edit a channel-linked canvas. The canvas owner can restrict editing. API-created canvases inherit these defaults.

Migration implication: If you migrate 10,000 documents using canvases.create without channel linkage, every canvas is private by default. Users will not be able to find their content. Decide channel-linking strategy before the migration run, not after — retroactively linking canvases to channels requires additional API calls and may not reproduce the original document's sharing model accurately.


How images work in Canvas markdown

Canvas images are URL references, not binary payloads. The standard markdown image syntax ! [alt](url) works, but the URL must resolve to either a publicly reachable image URL or a Slack-hosted permalink. There is no place in the markdown payload to embed base64 or raw image bytes. (docs.slack.dev)

If the source image already has a stable public URL, use normal markdown syntax:

![Network diagram](https://example.com/network-diagram.png)

For private or migrated images, the required upload flow is:

  1. Call files.getUploadURLExternal to request a secure upload destination.
  2. POST the binary file data to the returned upload URL.
  3. Call files.completeUploadExternal to finalize the file in the Slack workspace.
  4. Call files.info using the returned file ID to extract the permalink.
  5. Insert the permalink into your Canvas markdown as ! [alt text](permalink).
# Step 1: Request upload URL
url_resp = client.files_getUploadURLExternal(
    filename="diagram.png",
    length=file_size
)
 
# Step 2: POST binary data to the returned upload URL
# (upload to url_resp["upload_url"] via HTTP PUT/POST)
 
# Step 3: Finalize the upload
complete_resp = client.files_completeUploadExternal(
    files=[{"id": url_resp["file_id"]}]
)
 
# Step 4: Read back the permalink
info_resp = client.files_info(file=url_resp["file_id"])
permalink = info_resp["file"]["permalink"]
 
# Step 5: Use in canvas markdown
markdown = f"![Diagram]({permalink})"

Access nuance: If you call files.completeUploadExternal without a channel_id, the file is uploaded but remains private — it has not been shared anywhere. The permalink is a Slack-authenticated URL; users without an active session matching the file context will see a broken image. Validate file visibility deliberately instead of assuming every Slack-hosted image resolves for every viewer. (docs.slack.dev)

Roundtrip behavior for images: If you write ! [alt](permalink) and read back with canvases.getContent, the image markdown is returned intact. Images do not get silently dropped on write — they either succeed (if the URL is reachable) or render as broken images in the canvas UI.

Scale planning: Every embedded image requires a separate upload-then-reference cycle. Tier 2 methods run at 20+ requests/minute. A document set with 5,000 images requires at minimum ~250 minutes for image uploads alone, before a single canvas is created. Plan the image pipeline as a separate, parallelized step with its own retry budget.


Canvas table constraints

Markdown tables in Slack Canvas are limited to 300 cells total per table, where cells = rows × columns.

Columns Max rows
3 100
5 60
10 30
15 20

Table cells support inline formatting: bold, italic, strikethrough, inline code, links, mentions, checkboxes, ordered and unordered lists, and unfurls. They do not support block-level elements like code blocks or nested tables.

Roundtrip behavior for oversized tables: Submitting a table exceeding 300 cells results in a hard API failure, not a silent truncation. The error message specifies the cell count violation. If your source documents contain data tables with hundreds of rows, they must be split or converted (linked spreadsheet, attached CSV) before migration.


Callout and column layout syntax

Callouts and column layouts use a fenced-div syntax specific to Slack Canvas. This syntax is submitted in the document_content.markdown field of API payloads. It is not part of standard CommonMark or GFM.

Callout syntax (API payload):

::: {.callout}
This is important information.
:::

Callouts render as colored blocks in the Canvas UI. They cannot contain tables and cannot be nested within other structural elements. The exact specification for color variants and icon customization is not fully documented in the Canvas API reference — color and icon behavior varies by Slack client version.

Column layout syntax (API payload):

::: {.column}
First column content
:::
 
::: {.column}
Second column content
:::

Maximum 3 columns per layout. Tables and callouts are not supported inside column layouts.

What canvases.getContent returns for these elements: Callouts and columns are returned using the same fenced-div syntax on readback, making them roundtrip-safe. This is confirmed by the Slack documentation statement that getContent returns markdown in the same format accepted by create and edit. (docs.slack.dev)

Columns are layout, not document hierarchy. Use them for side-by-side content, not as a substitute for missing heading levels. (slack.com)


Unfurls: canvas, file, message, user, and website

Unfurls are Slack Canvas's way of embedding rich previews of other Slack objects inline. The five supported unfurl types:

Unfurl type Syntax Render behavior
Canvas unfurl ! [](canvas-url) Embedded canvas preview
File unfurl ! [](slack-file-url) File metadata card
Message unfurl ! [](slack-message-url) Message content preview
User unfurl (card) ! [](@U123ABCDEFG) on its own line Profile card block
User mention (inline) ! [](@U123ABCDEFG) within text Styled inline chip
Channel mention ! [](#C123ABC456) Channel link chip
Website unfurl External URL Open Graph preview when resolvable

The unfurl behavior is contextual — a user reference on its own line renders as a card; the same reference inline renders as styled text. The canvases.sections.lookup API exposes unfurl section types including canvas_unfurl, file_unfurl, message_unfurl, user_unfurl, and user_mention, confirming these are first-class constructs in the Canvas data model.


What should a converter do when there is no Canvas equivalent?

Every document migration hits constructs the target platform does not support. The question is what happens at that moment. There are three approaches, and the choice matters enormously at scale.

Decision table: converter policy by construct type

Construct Hard failure? Recommended policy Output
Block Kit interactivity Yes Fail loudly Pipeline error + human review queue
Nested list in blockquote Yes Fail loudly or restructure Hoist list; log transformation
h4+ headings No (silent paragraph) Degrade visibly Bold text + migration marker
Images without stable URL No (broken render) Degrade visibly Placeholder + upload task logged
Tables > 300 cells Yes Fail loudly or split Split table + migration marker
Embedded spreadsheets No equivalent Degrade visibly Link + migration marker
HTML blocks No (silent drop) Degrade visibly Plain text + migration marker
Footnotes No (silent drop) Degrade visibly Inline text + migration marker

Fail loudly

Stop processing the document and log the unsupported construct with its location. This forces human review. Use this when the lost construct changes meaning: Block Kit interactivity, nested block structures that cause API rejections, or source embeds that are the only place important data lives.

Degrade with a visible marker

Replace the unsupported construct with its plain-text content, wrapped in a visible marker:

::: {.callout}
⚠️ MIGRATION NOTE: This document contained an interactive approval button that cannot be represented in Slack Canvas. Original label: "Approve Request"
:::

This keeps the pipeline moving while leaving a searchable breadcrumb. After migration, search all canvases for MIGRATION NOTE and resolve them manually. This is the standard approach for enterprise migrations at scale — visible markers maintain user trust, explicitly state the boundaries of the migration, and provide a clear audit trail.

Silent degradation (the worst option)

Drop the unsupported element or convert it to unmarked plain text. The canvas looks fine at a glance, but the interactive widget, the embedded spreadsheet, the five-level heading tree — all gone without a trace.

Silent degradation is the default in most off-the-shelf conversion tools. At 50 documents, you might notice. At 10,000, you will not. You discover the missing content months later when someone searches for a specific approval workflow that no longer exists. By then, the source system may be decommissioned.

Build your converter to be loud about what it can't do. Every unsupported construct should produce either a pipeline error or a visible marker in the output.


How to validate a Canvas conversion pipeline

Validation means proving Slack stored what you intended, not just that the API returned ok: true. Create or edit the canvas, then immediately read it back with canvases.getContent as markdown and diff the returned content against your expected normalized output.

What canvases.getContent roundtrip actually tells you:

Element written What getContent returns Lossless?
h1, h2, h3 h1, h2, h3 Yes
h4, h5, h6 Plain paragraph No — heading markup stripped
Callout (::: {.callout}) ::: {.callout} Yes
Column layout (::: {.column}) ::: {.column} Yes
Markdown table (≤300 cells) Markdown table Yes
Image with valid permalink Image markdown Yes
Nested list in blockquote API rejection (no canvas created) N/A
Bold, italic, inline code Bold, italic, inline code Yes
Block Kit JSON API rejection N/A

Roundtrip validation is not a complete QA strategy on its own. It catches structural losses (h4 → paragraph) and API rejections, but it does not validate semantic correctness — whether the migrated content means the same thing it meant in the source system. Pair roundtrip diffing with human spot-checks on a representative sample (typically 2–5% of documents) and automated checks for migration markers.

Round-trip every new mapping rule. If you add a transform for callouts, columns, deep headings, or image handling, push a sample canvas and read it back with canvases.getContent before rolling that rule into production.


API rate limits for Canvas operations

Canvas API methods are rate-limited per Slack's standard tier system. These are nominal limits; Slack's rate limiter uses a burst-and-throttle model, and sustained traffic above the nominal rate will receive HTTP 429 responses with a Retry-After header specifying the wait in seconds.

Method Tier Nominal rate
canvases.create Tier 2 ~20 requests/minute
conversations.canvases.create Tier 2 ~20 requests/minute
canvases.edit Tier 3 ~50 requests/minute
canvases.sections.lookup Tier 3 ~50 requests/minute
canvases.getContent Tier 3 ~50 requests/minute
files.getUploadURLExternal Tier 4 ~100 requests/minute
files.completeUploadExternal Tier 4 ~100 requests/minute

Retry strategy for 429 responses: Read the Retry-After header value (in seconds) and wait that duration before retrying. Do not implement fixed-delay retries — they will not match Slack's dynamic throttle windows. Exponential backoff without the Retry-After header will over-wait and under-utilize your rate limit budget.

Scale math for a 1,000-canvas migration:

  • Canvas creation at ~20/min: minimum 50 minutes for document creation
  • 5,000 images at ~20/min (files.getUploadURLExternal is Tier 4, but canvases.create is Tier 2): image uploads are not the bottleneck — canvas creation is
  • Post-write validation at ~50/min (canvases.getContent): minimum 20 minutes for validation pass
  • Total minimum wall time for 1,000 documents with 5 images each: ~70 minutes at full rate limit, with zero retries and zero errors

Real migrations run at 40–60% of theoretical throughput due to errors, retries, payload splitting, and pipeline coordination overhead. Plan for 2–3× the theoretical minimum.


The sfdc_record_mention section type

The canvases.sections.lookup API exposes a section type called sfdc_record_mention that does not appear in the document_content supported elements list. Based on available documentation and observed behavior:

  • sfdc_record_mention sections appear in the Canvas data model when a canvas contains embedded Salesforce record references.
  • These are listed on docs.slack.dev in the context of Slack for Salesforce integration, separate from standard Canvas API documentation.
  • Creating an sfdc_record_mention via the public Canvas API is not documented. It is likely generated by Slack's Salesforce integration layer rather than by direct API payload construction.
  • Workspaces without the Slack for Salesforce integration installed cannot create or meaningfully render these sections.

Migration implication for Quip documents: Quip documents that contain embedded Salesforce record links (CRM records, opportunity links) will not have a direct Canvas API equivalent for the embedded record card. The conservative approach is to convert these to plain hyperlinks pointing to the Salesforce record URL and add a migration marker. Do not assume the sfdc_record_mention section type is writable via canvases.create without testing against your specific workspace's integration state.


Quip-to-Canvas element mapping

Quip Element Canvas Equivalent Fidelity Notes
Headings h1–h3 h1–h3 Full Direct mapping
Headings h4+ None (paragraph) Loss Flatten or restructure; roundtrip drops heading
Bold, italic, strikethrough Bold, italic, strikethrough Full Direct mapping
Bullet/numbered lists Bullet/ordered lists Full Direct mapping
Checklists Checklists Full Direct mapping
Code blocks Code blocks Full Top-level only; cannot be nested
Images Image via URL Partial Requires upload + permalink cycle
Spreadsheets (small) Markdown table (≤300 cells) Partial Oversized sheets need splitting
Spreadsheets (large) Linked file or CSV Loss No native equivalent
@mentions @mentions Partial Syntax differs; IDs must be remapped
Embedded Salesforce records sfdc record unfurl (integration-dependent) Partial Requires Slack for Salesforce; fallback to hyperlink
Interactive buttons/forms None Loss No Canvas equivalent; fail loudly
Embedded Quip docs Canvas unfurl (if migrated) Partial Requires all referenced docs migrated first
HTML blocks None Loss Silently dropped; degrade with marker
Footnotes None Loss Silently dropped; convert to inline text

Where documentation ends and observed behavior begins

Slack's official documentation for canvases.create and canvases.edit lists the supported markdown elements explicitly. The callout and column layout fenced-div syntax (::: {.callout}, ::: {.column}) is less well-documented — it appears in SDK references, MCP server implementations, and community tooling rather than in the primary Canvas API docs.

The canvases.sections.lookup API reveals section types like chart, citation, and sfdc_record_mention that are not listed in the document_content supported elements. These represent internal or feature-flagged constructs that exist in the Canvas data model but are not available for creation via the public API.

Behaviors observed that are not explicitly documented:

  • h4+ elements do not error on write — content is silently inserted as a paragraph, and canvases.getContent returns it as a paragraph. The heading markup is gone.
  • The callout fenced-div syntax works reliably in practice, and roundtrips correctly through getContent. Color options and icon customization are not fully specified.
  • Table cell formatting is richer than the docs suggest — checkboxes, lists, and unfurls inside cells work. The boundary of what fails in cells is not fully specified; test before relying on it in production.
  • Nested list in blockquote errors are whole-payload failures — not line-level. One offending structure blocks the entire canvas.

This document does not speculate about future Slack roadmap items. The supported set documented today is the set to build against.


The bottom line for migration teams

Slack Canvas is a constrained document target, not a generic markdown renderer. It handles the fundamentals well — headings up to h3, lists, tables up to 300 cells, inline formatting, code blocks — but has hard limits on nesting depth, heading levels, interactivity, image handling, and document size that will break assumptions built on richer source formats.

The four things that catch teams off guard every time:

  1. Heading levels stop at h3. h4+ is silently converted to a plain paragraph on write, and the heading is gone on readback. Every source document with deeper structure needs pre-processing.
  2. Nested structures inside blockquotes cause hard API failures. One list inside a blockquote blocks creation of the entire canvas — not just that section.
  3. Images require a separate upload cycle. There is no way to inline binary image data in the markdown payload. Image uploads must be pre-staged before canvas creation begins.
  4. Canvas access is private by default. Canvases created without channel_id linkage are invisible to other users until explicitly shared. Decide the sharing model before the migration run.

Knowing the supported set precisely — and building your converter to be explicit about what falls outside it — is the difference between a migration that works and one that looks like it works until someone needs the content that got silently dropped.

Frequently Asked Questions

What markdown elements does Slack Canvas support?
Slack Canvas supports headings h1–h3, bold, italic, strikethrough, inline code, code blocks, bulleted/ordered/checklists, blockquotes, callouts, dividers, column layouts (max 3), markdown tables (max 300 cells), inline links, @mentions, emojis, and five unfurl types (canvas, file, message, user, website). The document_content payload is capped at 1 MiB per call.
Can you use Block Kit inside a Slack Canvas?
No. Slack's documentation explicitly states that Block Kit is not supported in canvases. Interactive elements like buttons, select menus, date pickers, and input blocks have no representation in Canvas markdown. Attempting to inject Block Kit JSON into a Canvas payload will cause a hard API failure.
How do you embed images in a Slack Canvas via the API?
Images require either a publicly reachable URL or a Slack-hosted permalink. For private images, upload the file using files.getUploadURLExternal and files.completeUploadExternal, then retrieve the permalink via files.info and reference it in your markdown as ![alt](permalink). Base64 or binary image data cannot be embedded in the markdown payload.
What heading levels does Slack Canvas support?
Slack Canvas supports h1, h2, and h3 only. Heading levels h4, h5, and h6 are silently rendered as plain paragraph text with no error from the API. Source documents with deeper heading hierarchies must be flattened before conversion.
Why does the Canvas API fail with 'Unsupported block type (List) within block quote'?
Slack Canvas does not support nested lists inside blockquotes. The API rejects the entire payload — not just the offending line. You need to flatten the structure by hoisting lists out of blockquotes or converting list items to plain quoted text before submitting the markdown.

More from our Blog