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

How to Export Data from Slack Canvas: Methods, API Limits and Gaps

There is no canvases.list endpoint and no bulk export. Learn how to inventory, retrieve, and verify Slack Canvas data using files.list, canvases.getContent, and the admin export.

Abdul Wahab Abdul Wahab · · 20 min read
How to Export Data from Slack Canvas: Methods, API Limits and Gaps
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

How to Export Data from Slack Canvas: Methods, API Limits and Gaps

Getting content out of Slack Canvas is harder than putting it in. Slack provides write-oriented canvas methods — create, edit, delete — but the read and inventory side is thin. There is no bulk export button in the Slack UI, no dedicated canvases.list endpoint, and canvas bodies come back as markdown or HTML with structural losses around comments, images, and access grants.

This guide covers every available export path, the API constraints you will hit, and the gaps you need to plan around before your data lands in its next home.

If you are migrating into Slack Canvas from Quip, see our Quip to Slack Canvases migration guide (or our scripting guide for large-scale Quip migrations). For broader Slack workspace consolidation, our Enterprise Grid migration guide covers the full 2026 timeline.

Warning

No canvases.list endpoint exists. Enumerating canvases requires calling files.list with types=canvas. Your inventory is bounded by what that call can see — a bot token only returns canvases in channels the bot has joined, and a user token only returns canvases the user can access. If you skip the visibility audit, your export will be silently incomplete.

How Slack Canvas Data Is Structured

A Slack canvas is a rich document surface stored as a file object within Slack. Canvas IDs follow the standard file ID format (e.g., F1234ABCD) and carry the mimetype application/vnd.slack-docs with filetype quip in file metadata. There are two types:

  • Channel canvases — attached to a specific channel or DM. Available on all Slack plans, including Free.
  • Standalone canvases — not attached to any channel. Available only on paid plans (Pro, Business+, Enterprise Grid).

The body of a canvas is represented through a document_content object containing type and markdown. The only supported type is markdown. When you read a canvas through the API, you get the markdown representation of headings (h1–h3), bold, italic, strikethrough, bulleted and ordered lists, checklists, code blocks, quotes, dividers, links, tables, and @mentions.

The file object also includes canvas-specific metadata: is_channel_space, linked_channel_id, canvas_creator_id, title_blocks, edit_timestamp, editors, comments_count, and standard file metadata like permalink. Capture these fields during your inventory pass — they are essential for reconciliation and permission mapping later. (docs.slack.dev)

What Are the Export Methods for Slack Canvas?

There are three paths, each serving a different job.

Workspace data export is Slack's built-in admin path (Settings → Import/Export Data). The export includes the current canvas version in HTML, references to anchored comments as pointers to file thread messages, and embedded file references with download URLs. Prior versions are only included when your workspace or org has "data exports for all conversations" enabled — a paid feature on Business+ and Enterprise Grid. Standard Pro exports do not include version history. (slack.com)

Web API export is the migration path. You enumerate canvases with files.list, then fetch each body with canvases.getContent in markdown or HTML. Slack returns the body, but not a bundled object containing comments, binaries, permissions, or version history. Each of those must be captured separately. (docs.slack.dev)

Discovery API export is the compliance path. Available on Enterprise plans, Org Owners can use approved third-party eDiscovery or DLP apps. The Discovery API includes canvases in JSON alongside the full history of communications, including edits and deletions. (slack.com)

If you want a human-readable archive, the admin export may be enough. If you want to migrate content into another system, the Web API's markdown path is easier to transform and map into target platforms.

Required OAuth Scopes for the Complete Pipeline

Before writing a single line of export code, confirm your token has all required scopes. Missing scopes produce silent failures or incomplete data — files.list returns an empty array rather than an error when files:read is absent.

Method Required Scope Notes
files.list?types=canvas files:read Bot or user token
canvases.getContent canvases:read Bot or user token
files.info files:read For image url_private_download
conversations.replies channels:history (public), groups:history (private), im:history (DMs), mpim:history (group DMs) All four needed for full coverage
admin.teams.list admin Enterprise Grid only; requires org-level token
url_private_download fetch files:read (token in Authorization header) Standard HTTP GET, not an API method

If your bot will export canvases from private channels, it must be invited to those channels before files.list will return their canvases. Scope alone is not sufficient — channel membership is required.

How to List All Canvases in a Slack Workspace

There is no canvases.list endpoint. The documented canvas method family includes access, create, delete, edit, getContent, and sections.lookup — but not a list method. The only programmatic way to enumerate canvases is files.list with types=canvas. (docs.slack.dev)

curl -s "https://slack.com/api/files.list?types=canvas&count=100&page=1" \
  -H "Authorization: Bearer xoxb-your-bot-token"

files.list uses page-based pagination (page numbers and count), not cursor-based. The default count is 100 results per page. The response includes a paging object with count, total, page, and pages. (api.slack.com)

{
  "ok": true,
  "files": [ ... ],
  "paging": {
    "count": 100,
    "total": 347,
    "page": 1,
    "pages": 4
  }
}

Record the paging.total value before you begin exporting content — this is your pre-export baseline for verification.

Visibility Constraints by Token Type

Your inventory is bounded by token visibility:

Token type Visibility Limitation
Bot token (xoxb-) Channels the bot has been added to Standalone canvases shared directly with users but not posted in a channel will not appear
User token (xoxp-) All canvases accessible to that user Still limited to what the user can see; broader than a bot token
Org-level token (Enterprise Grid) Workspace-scoped per query Requires team_id parameter; must iterate across all workspaces in the org

Slack does not promise that files.list?types=canvas is a canonical whole-org canvas census. Treat completeness as access-scoped unless you are using the admin export or Discovery API.

Tip

Pre-export checklist: Before starting, add your bot to every channel that may contain canvases. For standalone canvases, you may need a user token from an admin with broad access. Compare your paging.total against the count in Slack's admin UI (Files → Canvases sidebar) to catch visibility gaps early.

Rate Limits for files.list

files.list is Tier 2: approximately 20+ requests per minute. At 100 canvases per page, a workspace with 2,000 canvases needs 20 pages — roughly 1 minute of wall-clock time for pagination alone. Workspaces with tens of thousands of canvases should add a delay between pages and respect Retry-After headers on any HTTP 429 response. Implement exponential backoff in your HTTP client.

How to Retrieve Canvas Content as Markdown

canvases.getContent is the method for reading canvas bodies. It returns the full content of a canvas in a single content string with no pagination. It accepts markdown (default) or html as the content_type argument. This method requires the canvases:read scope and works with both bot and user tokens. (docs.slack.dev)

curl -X POST "https://slack.com/api/canvases.getContent" \
  -H "Authorization: Bearer xoxb-your-bot-token" \
  -H "Content-Type: application/json" \
  -d '{"canvas_id": "F1234ABCD", "content_type": "markdown"}'

A successful response:

{
  "ok": true,
  "content": "# Project plan\n\n- [ ] Draft spec\n- [x] Kickoff meeting\n"
}

Common Error Codes from canvases.getContent

Error string Cause Remediation
canvas_not_found Canvas ID is invalid or the token cannot see it Verify the canvas ID from files.list; check bot channel membership
not_authed No valid token provided Add Authorization: Bearer header
missing_scope Token lacks canvases:read Reinstall app with correct scopes
access_denied Token can see the canvas file but lacks read permission Use a user token with broader access or request canvas access grant
ratelimited Tier 3 limit exceeded Back off per Retry-After header value

If the canvas does not exist or the token cannot see it, Slack returns canvas_not_found — the same error code for both cases, which makes debugging token visibility issues harder than it should be. Log the canvas ID and token type alongside every error for post-run diagnosis.

Markdown is the better format for migrations because Slack accepts the same format on canvases.create and canvases.edit — it round-trips cleanly. HTML is available if you need a display-oriented snapshot, but markdown is easier to diff, map, and replay into another document system.

Slack uses proprietary formatting for user mentions (<@U123456>), channel links (<#C123456>), and custom emojis within the markdown. Your export pipeline must include a translation layer that maps these Slack-specific IDs back to human-readable names or the corresponding identifiers in your destination platform.

The 1 MiB Limit on document_content

Slack documents a 1 MiB limit (1,048,576 characters) per document_content object on canvases.create and per individual change on canvases.edit. This limit matters for re-import: if your exported canvas exceeds 1 MiB, you will need to chunk it when writing back to Slack or another system that enforces the same constraint. (docs.slack.dev)

The Slack UI does not enforce this same limit, so canvases can grow beyond 1 MiB through normal editing. Slack's documentation does not specify the failure mode when canvases.getContent reads an oversized canvas. In practice: if a returned content string hits exactly 1,048,576 characters, treat it as a truncation signal and flag the canvas for manual review — do not assume the content is complete.

For canvases.edit, the 1 MiB limit applies to each individual change in the changes array, not to the total canvas size. Only one operation per API call is currently supported.

Rate Limits for canvases.getContent

canvases.getContent is Tier 3: approximately 50+ requests per minute. If you are exporting 2,000 canvases sequentially with a 1.2-second delay between requests, that is roughly 40 minutes of wall-clock time with no parallelism. Plan for this in your export window.

Consolidated Rate Limit Reference

Method Rate limit tier Approximate limit Notes
files.list Tier 2 20+ req/min Page-based; 100 results per page max
canvases.getContent Tier 3 50+ req/min No pagination; full body per call
files.info Tier 4 100+ req/min Use for image metadata and sharing info
conversations.replies Tier 3 50+ req/min Per thread fetch for comments
admin.teams.list Tier 2 20+ req/min Enterprise Grid only

All tiers are subject to workspace-level and org-level burst limits. Respect Retry-After response headers; they take precedence over tier estimates.

The Alternative Read Path: files.info + url_private_download

Before canvases.getContent existed, the standard approach was to treat a canvas as a file:

  1. Call files.info?file=CANVAS_ID to get the file metadata, including url_private_download.
  2. Fetch the content from url_private_download with a valid token in the Authorization header.

This path returns the canvas as HTML (Quip-based HTML with custom elements), not markdown. The url_private_download path is also how the Slack admin export works — the canvases.json file in an export ZIP contains file objects with url_private_download URLs. (slack.com)

The canvases.getContent method is cleaner for new integrations, but the files.info path remains useful for:

  • Accessing file metadata (created/updated timestamps, creator user ID, sharing info)
  • Downloading the raw HTML when your target system prefers it
  • Compatibility with scripts written before canvases.getContent was available

What Comes Back in a Slack Admin Export

Slack's built-in workspace export (Settings → Import/Export Data) includes canvases, but with significant caveats.

Included Not included
Current text content (HTML) Inline comment positions within the document
Version history (if all-conversations export enabled) Access permission grants (who can read/write)
References to anchored comments (as pointers to file thread messages) Binary image data (referenced by URL, not embedded)
Embedded file references (with download URLs) Canvas templates
File metadata (creator, timestamps, sharing channels) Slack Connect canvas content (see below)
Info

Version history is only included when your workspace or org has "data exports for all conversations" enabled — a paid feature on Business+ and Enterprise Grid. Standard exports on Pro plans do not include it. Admins can also disable canvas version history entirely, in which case only the current version is available. (slack.com)

In public-channel-only exports, canvases.json only contains canvases shared in public channels. The export JSON uses a canvas pretty_type and Slack Docs MIME type, but the filetype and mode combination looks file-like rather than canvas-specific. Use canvases.json or types=canvas filtering as your source of truth for identifying canvas records.

What Survives Export and What Breaks

Extracting the markdown body is only part of the work. The contextual data surrounding a canvas — comments, permissions, images, and version history — does not travel with the document body. Each element requires separate handling.

Comments: Beside the Canvas, Not Inside It

Canvas comments behave like threaded messages beside the canvas, not content inside it. They appear in Threads and the Activity tab. There is no API that anchors a comment to a specific position within the canvas body. (slack.com)

The Slack admin export includes "a reference to the appropriate file conversation message that contains the text of the comment" — a pointer to a thread, not inline content. In a workspace export, you map them through file_conversations.json to an FC:<canvas-id> folder.

To export comments via the API:

  1. Use files.info to get the shares field, which tells you which channels the canvas appears in.
  2. In each channel, find the message that shares the canvas file.
  3. Call conversations.replies on that message's ts to get the comment thread.

This gives you comments as messages with timestamps and authors — but not their position within the canvas. When migrating to Confluence, Notion, or similar, the practical approach is to append comments as a separate section at the bottom of the migrated document.

Images: References, Not Embedded Binaries

Images inside a canvas are not embedded as Base64 or binary data in the markdown. They are references to Slack-hosted files:

![Architecture Diagram](https://your_workspace_URL.slack.com/files/U071SRU8BA7/F073FVDABQS/image.png)

These URLs are authenticated — they require a valid token and will return a login page or 403 without one. Do not assume these URLs are publicly accessible, even if the is_public flag is set on the file. (docs.slack.dev)

To preserve images in your migration:

  1. Parse the exported markdown for image references (! [...](url) patterns).
  2. For each URL, call files.info to get the url_private_download.
  3. Download the binary with the token in the Authorization header.
  4. Upload to your target system and rewrite the URL in the migrated content.

Character encoding note: Image filenames and canvas content may contain non-ASCII characters in multilingual workspaces. Ensure your HTTP client decodes responses as UTF-8 and that your local file system handles Unicode filenames. Writing image files with byte-string filenames will corrupt them on case-insensitive or ASCII-only file systems.

Permissions: No Read-Back API

Permissions set through canvases.access.set are not part of the document_content object. You can set access (read, write, owner for specific users or channels) but there is no documented canvases.access.get or canvases.access.list method. You cannot programmatically read back who has what. (docs.slack.dev)

If permission fidelity matters, build an ACL sidecar during export. Record the canvas owner, which channels or users have access (from files.info sharing metadata), and any org-level sharing restrictions. Note that canvases.access.set handles channels and users differently, and DM or MPDM sharing uses user IDs rather than channel IDs.

Mapping Slack's channel-based and user-based permission model to a hierarchical system like Confluence or Slab is complex. Most migrations export all canvases into a restricted holding area in the destination platform and let users re-share manually.

Version History: Outside the Body Export Path

Version history is not available through canvases.getContent. Prior canvas versions appear in workspace exports when data exports from all conversations are enabled, and Enterprise customers can retrieve version history through the Discovery API. Admins can disable canvas version history entirely, in which case only the current version remains. (slack.com)

Canvas Tables: 300-Cell Limit

Canvas tables are limited to 300 cells per table. The markdown representation preserves table structure, but rendering fidelity in the target system depends on its GFM table support.

Canvases can link to other canvases. When a canvas body contains a link of the form https://your_workspace.slack.com/docs/T.../F..., that is a reference to another canvas by file ID. A naive linear export will export the text of the link but leave the linked canvas unprocessed if it was not already in your inventory.

To handle this correctly:

  1. After exporting each canvas body, scan the markdown for Slack docs URLs matching the pattern slack.com/docs/<team_id>/<file_id>.
  2. Extract the file_id component (begins with F).
  3. Check it against your exported canvas inventory. If absent, add it to an export queue.
  4. Re-run canvases.getContent for any newly discovered canvas IDs.
  5. Repeat until the queue is empty (graph exhausted).

In practice, most workspaces have shallow canvas link graphs (one or two levels), but large knowledge bases with hub-and-spoke structures can have dozens of linked canvases that will be silently omitted by a flat export. Track visited IDs to prevent infinite loops on circular references.

Slack Connect Canvases: Cross-Organizational Boundary Behavior

Slack Connect allows canvases to be shared across organizational boundaries in shared channels. The export behavior for Slack Connect canvases has several important differences from standard workspace canvases:

  • Ownership is workspace-local. A canvas created in your workspace and shared via a Connect channel belongs to your workspace. The external organization's export will not include its content — only a reference.
  • files.list?types=canvas in a Connect channel returns the canvas only if the querying token belongs to the workspace that owns the canvas. External participants see the canvas in the UI but the API returns it under the owning workspace's token.
  • Admin export coverage: Standard workspace exports do not include canvases from external organizations shared into your channels via Slack Connect. Your export covers only canvases owned by your workspace.
  • Discovery API behavior: Enterprise Grid Discovery API covers canvases within the org boundary. Canvases owned by external orgs in Connect channels are outside this boundary.

Operationally: when auditing a workspace for export completeness, query files.list?types=canvas with your workspace token and separately identify Connect channels using conversations.list with exclude_archived=false. Any canvas visible in a Connect channel that does not appear in your files.list results is likely owned by the external org and must be exported by that org independently.

Standalone Canvases and Plan Constraints

Standalone canvases — canvases not attached to any channel or DM — are only available on paid Slack plans (Pro, Business+, Enterprise Grid). Channel and DM canvases are available on all plans, including Free. A free workspace can still have canvas content to export, but not the standalone-canvas surface area you see on paid plans. (slack.com)

If your workspace downgrades from a paid plan, standalone canvases become inaccessible through the UI. Whether they remain accessible via the API is not documented. As a precaution, export all standalone canvases before any planned downgrade — do not assume you can retrieve them after the plan change.

Enterprise Grid: Org-Wide Considerations

On Enterprise Grid, canvases exist within specific workspaces, and canvas sharing is administered at the organization level. Org Owners and Admins manage sharing settings for everyone — workspace admins cannot independently change canvas sharing policies. (slack.com)

A complete export requires:

  1. Enumerate all workspaces using admin.teams.list.
  2. Query files.list?types=canvas&team_id=T... for each workspace.
  3. Deduplicate — a canvas shared across workspaces may appear in multiple workspace inventories. Use the canvas file ID as the deduplication key, not the title.
  4. Respect org-level sharing policies — if sharing is restricted to owners only, your export bot may need explicit access grants from canvas owners.

Workspace-specific Enterprise exports also exclude multi-workspace channels, so verify coverage across the entire Grid topology. Failing to iterate across workspaces will leave cross-workspace canvases silently behind.

The Discovery API on Enterprise plans provides endpoints for recently created or edited canvases, comments, and version history. For legal hold or compliance-grade export, the Discovery API is the appropriate path.

Building a Complete Export Pipeline

A reliable Slack Canvas export is not one file per canvas. It is one inventory record, one body export, zero or more comment-thread artifacts, zero or more binary assets, one permission sidecar, and optional version history. Run the job in distinct passes.

Step 1 — Inventory All Canvases

Call files.list?types=canvas&count=100&page=1 and paginate through all pages. Record every canvas ID and its metadata: title, creator, created/updated timestamps, channels it appears in, comments_count, and edit_timestamp. Store the paging.total as your expected count.

Step 2 — Export Bodies with Checkpointing

For each canvas, call canvases.getContent with content_type=markdown. Write each canvas to disk immediately after retrieval. Maintain a checkpoint file mapping canvas IDs to export status so you can resume after interruptions without re-fetching completed canvases.

import json, time, requests, hashlib
 
TOKEN = "xoxb-your-token"
HEADERS = {"Authorization": f"Bearer {TOKEN}"}
 
def export_canvases():
    page = 1
    total_exported = 0
    checkpoint = load_checkpoint()  # {canvas_id: "done"}
 
    while True:
        resp = requests.get(
            "https://slack.com/api/files.list",
            params={"types": "canvas", "count": 100, "page": page},
            headers=HEADERS
        ).json()
 
        if not resp["ok"]:
            raise Exception(f"files.list failed: {resp['error']}")
 
        expected_total = resp["paging"]["total"]
 
        for f in resp["files"]:
            if f["id"] in checkpoint:
                total_exported += 1
                continue
 
            content_resp = requests.post(
                "https://slack.com/api/canvases.getContent",
                headers=HEADERS,
                json={"canvas_id": f["id"], "content_type": "markdown"}
            ).json()
 
            if content_resp["ok"]:
                body = content_resp["content"]
                content_length = len(body)
                content_hash = hashlib.sha256(body.encode("utf-8")).hexdigest()
 
                # Flag potential truncation
                if content_length == 1048576:
                    log_warning(f["id"], "Content length equals 1 MiB limit — possible truncation")
 
                save_canvas(f["id"], f, body, content_length, content_hash)
                checkpoint[f["id"]] = "done"
                total_exported += 1
            else:
                log_error(f["id"], content_resp["error"])
 
            time.sleep(1.2)  # ~50 req/min Tier 3 limit
 
        if page >= resp["paging"]["pages"]:
            break
        page += 1
 
    print(f"Exported {total_exported} of {expected_total} canvases")

Step 3 — Detect and Resolve Linked Canvases

After the first body export pass, scan all exported markdown for Slack docs URLs:

import re
 
SLACK_DOCS_PATTERN = re.compile(
    r'https://[a-z0-9\-]+\.slack\.com/docs/[A-Z0-9]+/([A-Z0-9]+)'
)
 
def find_linked_canvas_ids(markdown_body):
    return set(SLACK_DOCS_PATTERN.findall(markdown_body))

Add any discovered IDs not already in your checkpoint to an export queue and run canvases.getContent for each. Repeat until the queue is empty. Track all visited IDs to avoid infinite loops on circular canvas references.

Step 4 — Pull Images Separately

After exporting all canvas markdown, scan for image URLs, fetch binaries with authenticated requests, and store them alongside each canvas. Map the original Slack URL to the local file path for URL rewriting during import. Decode all HTTP responses as UTF-8; write binary image files in binary mode to avoid encoding corruption.

Step 5 — Capture Comments

For canvases where comments_count is non-zero, use files.info to find sharing channels, locate the file-sharing message, and call conversations.replies to retrieve the comment threads. Store them as separate thread artifacts — positional anchoring within the canvas is not available via the API.

Step 6 — Handle edit_timestamp Drift During Live Exports

If the workspace remains active during export, canvases may be edited between your inventory pass (Step 1) and your body fetch (Step 2). Drift strategy:

  1. Record edit_timestamp for each canvas during inventory.
  2. After fetching the body, compare edit_timestamp from a fresh files.info call against the inventory value.
  3. If the timestamps differ, re-fetch the body immediately and log the delta.
  4. For high-activity workspaces, consider a two-pass reconciliation: complete the full export, then re-inventory, identify canvases where edit_timestamp changed during the export window, and re-fetch those bodies only.

Requesting read-only access from workspace admins to pause canvas editing during export windows is the cleanest solution, but rarely practical.

Step 7 — Verify Against Pre-Export Count

Compare the number of successfully exported canvases against the paging.total from your initial inventory. Any discrepancy means canvases were missed — typically due to visibility constraints, Slack Connect ownership boundaries, or canvases created between inventory and export. Reconcile every miss by ID. Request counts are not verification — unique canvas IDs are.

What the API Does Not Cover

These gaps, along with other Canvas API limits, affect migration planning directly:

  • No canvases.list — Discovery depends entirely on files.list, which returns only canvases the token can see. Canvases shared via direct link but never posted in a channel may be invisible to your bot.
  • No canvases.access.get — You can set access but cannot programmatically read back current access grants.
  • No positional comment anchoring — Comments are retrievable as thread messages, but no API maps them to positions within the canvas body.
  • No bulk export endpoint — Every canvas must be fetched individually. There is no batch API for canvas content.
  • canvases.sections.lookup returns IDs only — It finds section IDs matching criteria (heading level, text match) but does not return section content. It is a targeting tool for edits, not a read tool.
  • No Slack Connect cross-org canvas access — Canvases owned by external organizations in shared channels are not accessible to your workspace token or admin export.
  • No recursive canvas graph traversal — The API has no method to enumerate all canvases linked from a given canvas. You must parse bodies and chase IDs manually.

When to Use the API vs. the Admin Export

Scenario Best path
One-time migration of all canvases API pipeline (files.list + canvases.getContent)
Legal/compliance export with version history Admin export with all-conversations enabled, or Discovery API
Selective export (specific channels or users) API pipeline with channel or user filters on files.list
Ongoing sync to another system API pipeline with ts_from / ts_to filtering for incremental pulls
Quick manual grab of a few canvases Copy-paste from the Slack UI
Cross-org Slack Connect canvases Each org must export its own owned canvases independently
Canvases on Free plan API pipeline — admin export covers public channels only

Known Failure Modes in Canvas Exports

These are the operational failure patterns that appear repeatedly in canvas migration work:

Failure Symptom Cause Fix
Silent inventory gap paging.total lower than admin UI count Bot not in all channels; standalone canvases invisible to bot Add bot to channels; switch to user token for standalone canvas inventory
Truncated body Content length exactly 1,048,576 chars Canvas exceeds 1 MiB API limit Flag for manual review; compare against UI render
Broken image URLs 403 on image fetch post-migration Slack-hosted images require authenticated requests Download binaries during export; rehost in target system
Missing comments Comment count > 0 but no comments in export conversations.replies not called; wrong channel looked up Build comment-fetch pass keyed on comments_count field
Duplicate canvases in Enterprise Grid Same canvas exported multiple times Canvas shared across workspaces appears in multiple workspace inventories Deduplicate on file ID, not title
Linked canvas omitted Migrated canvas has broken internal links Linked canvas not in inventory (linked via URL, not channel post) Implement linked canvas graph traversal (Step 3 above)
Slack Connect canvas missing Canvas visible in UI but absent from export Canvas owned by external org Coordinate with external org for their independent export
Unicode corruption Garbled filenames or content HTTP response not decoded as UTF-8 Enforce response.encoding = 'utf-8' in HTTP client

Summary of Data Elements and Export Coverage

Canvas element Admin export Web API Discovery API
Body text (current version) ✓ HTML ✓ Markdown or HTML ✓ JSON
Version history Business+ / EG only ✗ ✓ Enterprise only
Comments (thread messages) Pointer only Manual via conversations.replies ✓
Comment positions within canvas ✗ ✗ ✗
Embedded images (binary) URL reference only Manual download required URL reference only
Permission grants ✗ ✗ (no read API) ✗
Canvas metadata (creator, timestamps) ✓ ✓ via files.info ✓
Slack Connect (external org) canvases ✗ ✗ ✗
Canvas templates ✗ ✗ ✗
Linked canvas graph ✗ Manual traversal required Partial

Frequently Asked Questions

Is there a canvases.list endpoint in the Slack API?
No. The documented canvas method family does not include a list method. To enumerate canvases, call files.list with types=canvas. This returns only canvases visible to the token — bot tokens see only channels the bot has joined.
How do I export a Slack Canvas as Markdown?
Call canvases.getContent with content_type=markdown. Slack returns the full canvas in a single content string with no pagination. The method requires the canvases:read scope and is rate-limited at Tier 3 (roughly 50+ requests per minute).
What is the size limit for Slack Canvas document_content?
Slack documents a 1 MiB (1,048,576 characters) limit per document_content object on canvases.create and per change on canvases.edit. The UI does not enforce this limit, so canvases can grow larger, but oversized canvases may behave unpredictably when read through the API.
Can you export comments from a Slack Canvas?
Canvas comments surface as threaded messages beside the canvas, not inside it. You can retrieve them via conversations.replies, but no API anchors a comment to its position within the canvas body. Most migrations append comments as a separate section.
Are standalone Slack canvases available on the free plan?
No. Standalone canvases (not attached to a channel or DM) are only available on paid Slack plans — Pro, Business+, and Enterprise Grid. Channel and DM canvases are available on all plans.

More from our Blog