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 to Document360 Migration: A Technical Guide

How to move Slack Canvas content into Document360 without broken links, bad taxonomy, or publishing internal-only context to the public.

Rishabh Makhar Rishabh Makhar · · 17 min read
Slack Canvas to Document360 Migration: A Technical Guide
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

Migrating Slack Canvas content into Document360 is not a data conversion — it is an editorial project. Canvases are internal documents written for people who already share context: they assume the reader knows the team, reference @mentions that resolve to Slack user IDs, embed channel links that go nowhere outside the workspace, and contain Slack-hosted images that require authentication to view. Moving them into a structured knowledge base means every canvas needs a rewrite pass before it becomes an article.

There is no native importer. Document360 has no Slack integration for pulling canvas content, and Slack has no Document360 export option. (docs.document360.com) The pipeline is: inventory canvases via the Slack API, extract markdown, rewrite for a public audience, design a category tree that Canvas never had, re-upload images, and push articles through Document360's API. This guide covers each step with the real constraints you will hit.

For a broader framework on knowledge base migrations, see The Ultimate Knowledge Base Migration Checklist.

Feature Slack Canvas Document360 Migration Action Required
Hierarchy Flat (tied to channels/DMs) Strict (Category → Article) Manual taxonomy design
Headings H1 to H3 only H1 to H6 supported Restructure during rewrite
Tables Max 300 cells, no formulas Standard Markdown/HTML tables Verify size; convert if targeting WYSIWYG
Images Private Slack permalinks Hosted in Document360 Drive Download via token, re-upload
Mentions Slack User IDs Standard text Regex replacement
Checklists Interactive UI elements No checklist widget Convert to bullet/ordered lists
Comments Threaded, tied to canvas UI Not importable Review for missing context; do not publish
Tabs (post-Apr 2025) Multiple canvas tabs per channel Single article per canvas Each tab requires a separate article

Required Slack API Scopes and Document360 Permissions

Before writing a line of migration code, collect every credential you need. Missing one scope mid-run forces a token regeneration and a partial re-run.

Requirement Value Why It Is Needed
Slack: files:read Bot token scope files.list inventory, file metadata
Slack: canvases:read Bot token scope canvases.getContent body retrieval
Slack: users:read Bot token scope users.list for mention ID-to-name mapping
Slack: channels:read Bot token scope Channel name lookup for category mapping
Document360: API key Project-scoped All article, category, and Drive endpoints
Document360: project_version_id Retrieved via GET /v2/ProjectVersions Required on every article creation call

Add all scopes before installing the Slack app to a workspace. Slack does not allow adding scopes to an existing token without reinstalling.

Why This Is a Rewrite Project, Not an Export

Slack canvases are written for an audience already inside the workspace. A canvas pinned to #eng-onboarding can say "ask @sarah" and everyone knows who Sarah is, or link to a thread in #incidents and assume the reader has access. The moment you publish that content to a Document360 knowledge base, every one of those assumptions breaks.

When you migrate between traditional knowledge bases — like moving from Confluence to Document360 — the content is already structured as standalone articles. Slack Canvases are fundamentally different. They are written for a specific channel, assuming the reader has the full context of the surrounding conversations.

The specific failure modes:

  • @mentions render as Slack user IDs (<@U071CCRCVFH>) in the exported markdown. Outside Slack, they mean nothing. Every mention must be replaced with a name, a role, or removed entirely.
  • Channel links (<#C073UAJRW4R>) resolve to nothing for readers who are not in the workspace. References like "see the discussion in #platform-bugs" are dead ends.
  • Canvas and message unfurls — canvases can embed previews of other canvases and Slack messages. These are Slack-internal rich previews that will not render anywhere else. When exported, they revert to bare URLs or Slack-internal embed references.
  • Slack-hosted images use authenticated URLs (url_private_download) that return a sign-in page for anyone without a valid Slack session. (docs.slack.dev)
  • Date formatting — Slack uses a special timestamp format (<!date^1392734382^{date_num}|2014-02-18>) that must be parsed and replaced with human-readable dates.
  • Assumed context — internal shorthand, project codenames, relative time references like "this week," and references to "the doc in the channel" need to be made explicit or cut.

This is not a quality-assurance pass you can skip. Publishing internal content without rewriting it creates a knowledge base full of broken references and insider jargon that confuses the people it is supposed to help.

Not every canvas should become an article. Slack positions canvases for project plans, onboarding, incident coordination, and meeting notes as much as formal knowledge capture. (docs.slack.dev) Some of that material is working state, not reader-facing documentation. It should stay in Slack or be archived instead of polished into public docs.

A useful triage heuristic: a canvas is worth migrating if it answers a question a new reader — someone without workspace context — would actually ask. A canvas is not worth migrating if it is primarily a record of decisions made rather than guidance for decisions to be made.

What Breaks in the Canvas Format Itself?

Slack Canvas supports headings only from H1 through H3, so the article structure you extract will be shallow. Document360 reserves H1 for the article title and supports H2 through H6 in the body. If your Document360 articles need deeper heading hierarchies, those must be added during the rewrite — they do not exist in the source material. (docs.slack.dev)

Canvas tables are capped at 300 cells in any combination of rows and columns, and they do not support formulas or computed values. They export as standard Markdown tables, which Document360 renders natively in Markdown article mode. If your canvases contain tables with calculations, those come through as static values — confirm the numbers are correct before publishing.

Warning

Checklists will not survive. Slack Canvas checklists are interactive elements tied to Slack's UI. Document360 has no checklist widget. Convert them to bullet lists or ordered steps during the rewrite.

Tip

Use Markdown articles when the source is mostly text and tables. Document360's Advanced WYSIWYG editor does not render Markdown table syntax as a table, so if you target WYSIWYG or HTML articles, convert Slack tables to HTML first. (docs.document360.com)

The Canvas API represents content as markdown through the document_content object. Supported formatting includes bold, italic, strikethrough, bulleted and ordered lists, checklists, code blocks, quotes, dividers, links, tables, and @mentions — but no images as inline data. Images are referenced by Slack-internal URLs.

Canvas comments are not returned by canvases.getContent. The API returns only the canvas body — the threaded comment layer visible in the Slack UI is a separate data structure and is not included in the content response. If comments contain clarifications or corrections that are not reflected in the body, you must retrieve those separately via the conversations API or capture them manually before migration. Do not assume the exported body is the complete record.

Info

April 2025 tab change: Slack began converting channel and DM canvases into multi-tab canvases on April 9, 2025. Each tab is a discrete canvas with its own canvas ID. canvases.getContent returns the content of a single tab — not all tabs in a channel canvas. If a channel canvas has three tabs, you need three separate canvases.getContent calls with three different canvas IDs. Audit current canvas tab structure before assuming a one-to-one relationship between channel canvases and articles. (slack.com)

How Do You Inventory All Canvases in a Slack Workspace?

Slack does not have a canvases.list method. To get an inventory of all canvases—a constraint we also hit when migrating Slack Canvas to Notion—use files.list filtered to type canvas. (docs.slack.dev)

import time
from slack_sdk import WebClient
 
client = WebClient(token="xoxb-your-bot-token")
canvases = []
page = 1
 
while True:
    resp = client.files_list(types="canvas", count=100, page=page)
    files = resp["files"]
    if not files:
        break
    canvases.extend(files)
    page += 1
    time.sleep(1.5)  # Tier 3: ~50 req/min, be conservative
 
print(f"Found {len(canvases)} canvases")

The files.list endpoint is rate-limited at Tier 3 (~50 requests per minute). Each response includes a paging object with total and pages fields. You can filter by channel to scope the inventory to a specific channel, which is useful for building the taxonomy later. Extraction requires the files:read scope for inventory and canvases:read for content retrieval. (api.slack.com)

To extract the actual content of each canvas, use canvases.getContent, which returns the full canvas as markdown (default) or HTML. The entire canvas comes back in a single content string with no pagination.

for canvas in canvases:
    content = client.canvases_getContent(
        canvas_id=canvas["id"],
        content_type="markdown"
    )
    markdown_body = content["content"]
    # Store alongside metadata: title, channel, created, updated
Info

The markdown returned by canvases.getContent is capped at 1 MiB (1,048,576 characters) per canvas. Most internal docs fall well under this limit, but sprawling canvases used as wikis may hit it. If a canvas is truncated at the 1 MiB boundary, the response does not include a truncation warning — the content simply ends. Validate completeness by checking for structural markers like closing headings or list items that appear to terminate mid-document.

Building the Category Tree from Channels

Document360 organizes content as Projects → Workspaces → Categories → Articles. A canvas carries none of this structure. There is no folder concept in Slack Canvas — a canvas either belongs to a channel or exists as a standalone document. The taxonomy for your Document360 knowledge base has to be designed, not derived.

Three signals to work from:

  1. Channel names and purposes. If your canvases are concentrated in channels like #product-docs, #api-reference, and #support-playbooks, those channel names often map naturally to top-level Document360 categories. A channel like #refunds might become a Folder category called Billing and Refunds.
  2. Index canvases. Many teams create a "master" canvas in a channel that links to other canvases — a table of contents. These index canvases reveal the team's intended grouping, even though Slack never enforced it as a hierarchy.
  3. Judgment. Some canvases will not fit any channel-based grouping. Meeting notes, one-off guides, and ad-hoc docs need a human decision about where they belong — or whether they belong at all.

Document360 supports up to seven levels of category nesting, but their own best practices recommend staying within two levels of subcategories. Category types include Folder (navigation-only), Index (has a slug and SEO settings), and Page (full article functionality at the category level). (docs.document360.com)

Do not fake languages with categories named "FR" or "DE" — Document360 already has first-class language support inside each workspace. A versioned or audience-specific channel may be a better fit for a separate workspace than a deeply nested category. (docs.document360.com)

Plan the tree before writing a single API call. Create the categories in Document360 first, capture their IDs, then use those IDs when creating articles. Document360 requires a category_id for every article — if you do not map this in advance, you cannot automate the upload.

Generating Article Slugs

Document360 can auto-generate a slug from the article title on creation, but title changes do not later update the slug. Changing a published slug requires redirect rules. If your migration team uses temporary titles on import and fixes them later, you create avoidable redirect work. (docs.document360.com)

Slack allows multiple canvases with the exact same title, so your migration script must generate unique slugs. Appending a short fragment of the Slack file ID prevents collisions:

import re
 
def generate_doc360_slug(title, file_id):
    slug = title.lower()
    slug = re.sub(r'\s+', '-', slug)
    slug = re.sub(r'[^a-z0-9\-]', '', slug)
    return f"{slug}-{file_id[:4].lower()}"

Rewriting Slack Markdown for Document360

Slack's flavor of Markdown contains proprietary syntax that Document360 will not understand. Before pushing content to the API, run a translation pass.

User Mentions: Find <@U [A-Z0-9]+> and replace with the user's actual name. Build a local map of Slack User IDs to display names using the users.list API before starting the rewrite loop — this avoids one API call per mention per canvas.

Channel Links: Find <#C [A-Z0-9]+\|channel-name> and replace with standard text. If the channel corresponds to a new Document360 category, rewrite it as a Markdown link pointing to the new Document360 URL.

Date Formatting: Slack uses a special timestamp format: <!date^1392734382^{date_num}|2014-02-18>. Parse this and replace it with a human-readable date string. The fallback text after the pipe character is a reliable source if you do not want to convert Unix timestamps.

Unfurls: Rich preview cards revert to bare URLs or Slack-internal embed references. Replace with standard links or screenshots where appropriate.

Multi-column layout: Slack canvases support column layouts that have no equivalent in Document360's article format. Collapse columns into a single reading order instead of trying to preserve Slack layout.

What Document360 returns for invalid markdown: If the content field contains syntax that Document360 cannot parse, the API accepts the article but renders the raw syntax as visible text rather than rejecting the request. There is no parse error in the API response. This means a broken markdown table or an unresolved Slack syntax fragment will silently appear as garbled text in the published article. Validate rendered output post-import, not just API response codes.

How Do You Handle Images and File Attachments?

Slack Canvas images are stored behind authenticated URLs. The url_private_download field points to the image, but fetching it requires a valid Slack token. Anyone without a Slack session — meaning every reader of your public knowledge base — sees a sign-in page instead of the image. (docs.slack.dev)

The migration path:

  1. Parse the markdown for all image URLs matching https://files.slack.com/files-pri/...
  2. Download each image using your Slack bot token in the Authorization header.
  3. Upload to Document360 Drive, which gives every file a permanent CDN URL in the format https://cdn.document360.io/[project-id]/Images/Documentation/[filename].
  4. Rewrite the image references in the markdown body to point to the new Drive URLs.

Document360's Drive keeps CDN URLs stable even when files are moved between folders, so early folder choices do not have to be perfect. (docs.document360.com)

Slack Canvases often contain embedded PDFs or CSVs, represented as links to other Slack files. These must be downloaded via the same Bearer token method, uploaded to Document360 Drive, and the markdown links updated. If you skip this, users clicking a PDF link in Document360 get redirected to a Slack login page.

Document360's v3 API includes Drive management endpoints that support programmatic file upload. If you are on the v2 API or on a plan that does not expose Drive endpoints, images must be uploaded manually through the portal — a serious bottleneck for canvas libraries with dozens of images per document. Confirm Drive API access against your specific plan before building the automation. Check POST /v3/drive/files in the v3 API reference for current endpoint availability.

For a deeper look at image migration patterns, see How to Migrate Images, Attachments & Embeds Without Broken Links.

Pushing Articles to the Document360 API

Document360's article creation endpoint is POST /v2/Articles. It accepts a content_type parameter: 0 for Markdown, 1 for WYSIWYG (HTML), 2 for Advanced WYSIWYG. Since canvas content is already markdown, content_type: 0 is the natural fit.

{
  "title": "Getting Started with the Platform API",
  "category_id": "8dfb5c7e-fcbe-4797-b144-1a7ca2508f50",
  "project_version_id": "rffb5c7e-fcbe-4797-b144-1a7ca2508fvew",
  "user_id": "team-account-id",
  "content": "# Getting Started\n\nThis guide walks you through...",
  "content_type": 0,
  "order": 0
}

Key mapping decisions:

Canvas Property Document360 Field Notes
file.title title Review for public readability — internal titles like "Q3 runbook v2 FINAL" need editing
Canvas body (markdown) content After the rewrite pass, not raw
Channel association category_id Manually mapped from your planned category tree
N/A slug Auto-generated from title; override to control URLs
N/A project_version_id Required — get via GET /v2/ProjectVersions
N/A Language Default language is set per workspace; multilingual requires separate article versions

Common API error responses to handle explicitly:

HTTP Status Condition Resolution
400 Bad Request Invalid category_id or missing required field Verify category IDs were captured after creation, not before
409 Conflict Duplicate slug within a category Use the file-ID suffix slug generation; do not rely on auto-generated slugs
429 Too Many Requests Rate limit exceeded Implement exponential backoff; Document360 does not return a Retry-After header
200 OK with garbled body Invalid markdown accepted silently Validate rendered content post-import, not just status codes

Articles are created as drafts by default. This is an advantage — it gives you a review gate before anything goes live. Publish in batches after editorial review.

Tip

Document360 has launched API v3 with OAuth 2.0, scoped API keys, and expanded endpoints including Drive management. The v3 authentication header changes from api_token to a Bearer token structure. Existing v2 integrations continue to work, but v3 offers better permission controls and Drive API access for migration scripts. If starting a new integration, use v3. If extending an existing v2 script, test authentication headers before mixing versions.

API Rate Limits on Both Sides

Platform Endpoint Rate Limit
Slack files.list, canvases.getContent Tier 3: ~50 requests/minute
Document360 All endpoints (Professional / Business) 60 writes/min, 120 reads/min per api_token
Document360 All endpoints (Enterprise / Enterprise Plus) 100 writes/min, 200 reads/min per api_token

Document360 rate limits are enforced per api_token, and tokens are project-scoped. Read requests (GET/HEAD) and write requests (POST/PUT/PATCH/DELETE) have independent limits, so a write-heavy migration script does not compete with read calls for quota. (apidocs.document360.com)

At 60 writes per minute on a Professional plan, creating 200 articles (one API call each, ignoring image uploads and category creation) takes a minimum of ~3.3 minutes of pure API time. Factor in image uploads (one write per image), publish calls (one write per article), category creation, and retry cycles on 429s, and a 200-article migration with an average of 5 images per canvas realistically consumes 45–90 minutes of API time at the Professional tier. At the Enterprise tier (100 writes/min), that compresses to roughly 25–50 minutes.

Implement exponential backoff unconditionally on every write call. Document360 returns HTTP 429 when the limit is exceeded but does not include a Retry-After header — your script must determine its own retry interval. A starting backoff of 2 seconds doubling to a maximum of 32 seconds handles most burst scenarios without stalling the pipeline.

Validating the Migration Post-Import

Every migration guide that stops at "push articles" is incomplete. Silent failures are the norm, not the exception.

What to check after import:

  1. Broken image detection. Fetch each article's rendered HTML and scan for <img> tags still pointing to files.slack.com domains. These are images that were not re-uploaded. A simple regex check against files.slack.com in article content catches all failures.
  2. Markdown render errors. Search article bodies for raw Slack syntax patterns: <@U, <#C, <!date^. Any match means the rewrite pass missed an instance. Document360 renders these as visible text, not errors.
  3. Heading structure validation. Check that no article body contains an <h1> tag — Document360 uses H1 for the article title, and a second H1 in the body creates duplicate heading hierarchy issues. Canvases that start with an H1 heading will produce this by default.
  4. Empty articles. Canvas content at or near the 1 MiB limit may have been truncated. Check article word counts against the source canvas sizes.
  5. Slug collisions that resolved incorrectly. Confirm that every article has a unique URL. A 409 Conflict that was not caught correctly may have resulted in a silent overwrite depending on how your error handling was structured.
  6. Draft vs. published state. Confirm no articles were accidentally published before editorial review. Query GET /v2/Articles filtered by status and verify draft counts match expectations.

A post-import audit run that checks all six conditions takes roughly an hour to script and prevents the most common category of migration failures from reaching readers.

Designing Reader Groups — You Cannot Derive Them from Channels

A Document360 reader group is an access control construct used in Private or Mixed projects to grant workspace, language, category, or article-level access to many readers at once. (docs.document360.com)

It is tempting to map Slack channel membership to Document360 reader groups. This does not work for three reasons:

  1. Channel membership is not an access policy. People join Slack channels for notifications, not because they have a defined need to read every document in that channel.
  2. Your audience changes. The whole point of moving to Document360 is usually to reach people who are not in the Slack workspace — customers, partners, or a wider internal audience.
  3. Document360 groups are category-scoped, not document-scoped. A reader group grants access to categories and their contents. Slack channels are flat; they do not map onto a hierarchical permission model.

Document360 uses project access mode first — Public, Mixed, or Private — and then reader groups for fine-grained control. If the whole project is Public, reader groups do not apply at all. Design groups based on the intended audience of the published content: by customer tier, by product line, by role. Set permissions at the Category level and let articles inherit those rules during upload.

The Practical Migration Sequence

  1. Credential setup — Collect all Slack bot token scopes (files:read, canvases:read, users:read, channels:read) and Document360 API key plus project_version_id before starting. Missing any one scope requires reinstalling the Slack app.
  2. Inventory — Run files.list?types=canvas across all channels. Tag each canvas with its channel, author, last-updated date, and tab count (post-April 2025). For multi-tab canvases, enumerate each tab's canvas ID separately.
  3. Triage — Flag canvases that are outdated, duplicated, or purely internal (incident timelines, 1:1 notes, working drafts). Cut aggressively. A canvas worth migrating answers a question a reader without workspace context would actually ask.
  4. Build the mention map — Call users.list once and build a complete Slack User ID → display name dictionary. Use this throughout the rewrite phase rather than calling the API per mention.
  5. Design the category tree — Use channel groupings and index canvases as starting points. Create the categories in Document360 first. Capture their IDs.
  6. Extract and rewrite — Pull markdown via canvases.getContent. Rewrite each canvas: strip @mentions, replace channel links, convert date tokens, remove unfurls, collapse multi-column layouts, add context that was previously assumed, and expand headings beyond H3 where the structure is too shallow. Note that canvas comments are not included in the API response — retrieve separately if needed.
  7. Handle images and files — Download from Slack using the bot token as a Bearer header, upload to Document360 Drive via v3 Drive API or manually if the endpoint is unavailable on your plan, and update all asset references in the markdown.
  8. Push articles — POST /v2/Articles with content_type: 0 (Markdown). Articles land as drafts. Generate unique slugs using the file-ID suffix method.
  9. Validate post-import — Check for residual Slack image URLs, raw Slack syntax in article bodies, duplicate H1 headings, empty articles near the 1 MiB boundary, and unexpected publish states.
  10. Review and publish — Use Document360's review workflow for a final editorial check. Publish in batches.
  11. Configure access — Set up reader groups if any content is private or mixed. Assign groups to categories based on intended audience, not source channel membership.

What This Migration Actually Costs in Time

The API work — extracting, transforming, and pushing content — is the smaller part. A competent engineer can build the extraction and loading scripts in a day or two. The rewrite pass is where the real time goes.

Editorial time per canvas breaks down roughly as:

  • Mention and syntax cleanup (automated): 0 minutes per canvas if scripted correctly
  • Context gap assessment — reading the canvas and identifying what a non-workspace reader would not understand: 5–10 minutes per canvas
  • Active rewriting — expanding shorthand, removing dead references, restructuring headings: 10–20 minutes per canvas for a typical 500–800 word internal doc
  • Image verification — confirming re-uploaded images render correctly: 2–5 minutes per canvas

Total: roughly 20–35 minutes per canvas for editorial work, assuming automation handles the syntax transformation. A workspace with 100 canvases means 35–60 hours of editorial work. That estimate scales linearly — the API pipeline does not change that number.

If the editorial burden is more than your team can absorb alongside regular work, that is the point where bringing in help makes sense.

Frequently Asked Questions

Can I export all Slack Canvases at once into Document360?
No. Document360 has no native Slack Canvas importer, and Slack has no Document360 export. Use the Slack API files.list endpoint filtered by types=canvas to build your inventory, then fetch each canvas body with canvases.getContent. Content still needs rewriting, taxonomy design, and media re-upload before it belongs in Document360.
What happens to @mentions when migrating Slack Canvas to Document360?
@mentions export as Slack user IDs (e.g., <@U071CCRCVFH>) in the markdown. Outside Slack, they are meaningless strings. Every mention must be replaced with a real name, a role description, or removed entirely during the editorial rewrite.
Does Document360 accept Slack Canvas Markdown directly?
Document360 accepts standard Markdown, but Slack's proprietary tags for user mentions, channel links, and date formatting must be converted first. The v2 API takes Markdown in the content field with content_type 0; the newer v3 API accepts content plus a content_type parameter.
Are Slack Canvas images accessible outside of Slack?
No. Slack-hosted images require authentication via a bearer token. Anyone without a valid Slack session sees a sign-in page. You must download each image with a Slack token and re-upload it to Document360 Drive, then update the image URLs in the article markdown.
How do I organize Slack Canvas content into Document360 categories?
Slack Canvas has no folder concept. Design the category tree using channel names as starting points, index canvases (master canvases that link to others) as grouping signals, and editorial judgment for docs that do not fit a channel-based structure. Create all categories in Document360 before pushing articles.

More from our Blog