---
title: "Archbee to Slack Canvas Migration: The Technical Guide"
slug: archbee-to-slack-canvas-migration-the-technical-guide
date: 2026-09-28
author: Abdul Aleem
categories: [Archbee, Slack Canvas, Migration Guide]
excerpt: "No import path exists from Archbee to Slack Canvas. Technical guide covering extraction, block-by-block mapping, structure redesign, and API rate limits."
tldr: "Archbee to Slack Canvas requires a custom converter — no import path exists. Expect lossy block mapping, undocumented API limits, and a flat hierarchy with no folders."
canonical: https://clonepartner.com/blog/archbee-to-slack-canvas-migration-the-technical-guide
---

# Archbee to Slack Canvas Migration: The Technical Guide


# Archbee to Slack Canvas Migration: The Technical Guide

There is [no import button](https://clonepartner.com/blog/blog/how-to-import-data-into-slack-canvas-api-limits-guide), no plugin, and no middleware that moves Archbee content into Slack Canvas. Archbee stores document bodies in a proprietary JSON block format — not Markdown, not HTML — so every migration requires a custom converter that extracts Archbee's block structure and emits the specific subset of Markdown that Canvas accepts. This is converter engineering, and the sooner a team acknowledges that, the fewer surprises they'll face mid-project. ([archbee.com](https://www.archbee.com/docs/exporting-documents))

Archbee is hierarchical by design: workspaces contain spaces, spaces contain document trees with nested categories acting as folders. Archbee's editor exposes 30+ custom block types — vertical splits, OpenAPI/Swagger, Mermaid diagrams, tabs, changelogs, and more. Slack Canvas is much narrower. The API accepts a `document_content` object whose only supported type is `markdown`, and Slack explicitly states that Block Kit is not supported in canvases. That structural and format mismatch is why a straight export-and-paste results in broken formatting, lost metadata, and missing content. ([docs.slack.dev](https://docs.slack.dev/surfaces/canvases/))

This guide covers the extraction path from Archbee, the block-by-block mapping decisions your converter must make, the Archbee features that have no Canvas equivalent, the structural redesign from spaces to channels, the rate limits (documented and otherwise) you'll hit on the Slack side, and an end-to-end migration runbook.

## How to get content out of Archbee

**Archbee's bulk Markdown export is the pragmatic starting point.** You can export an entire space as a Markdown ZIP archive — categories become folders, each page becomes a separate `.md` file — or export the full organization from Organization Settings, which emails admins a ZIP of everything. Individual documents can also be exported to Markdown or PDF from the document options menu. ([archbee.com](https://www.archbee.com/docs/exporting-documents))

The alternative is reading documents through the Archbee API. Authentication uses a bearer token obtained from your Archbee organization settings (Settings → API Keys). The required scope is `docs:read` for document retrieval. `GET https://api.archbee.com/api/public-api/doc` with a `docId` parameter returns the document's content in Archbee's proprietary block format. The API supports a `format` query parameter accepting `markdown`, `html`, `json`, or `source`, giving you more control but requiring you to parse every block type yourself, one document at a time. Space listing is available via `GET /api/public-api/spaces`, and document enumeration within a space via `GET /api/public-api/docs?spaceId={id}`. Pagination uses cursor-based navigation: each response includes a `nextCursor` field; pass it as `cursor` in subsequent requests until no cursor is returned.

For most migrations, the bulk Markdown export is the better entry point because Archbee's own renderer has already handled the block-to-text conversion. Your converter script can download the resulting `.zip`, read the Markdown files, and focus entirely on translating that Markdown into Slack Canvas's specific flavor.

> [!WARNING]
> **The Markdown export is lossy.** Archbee's export strips or flattens features that don't have a Markdown representation: variables render as their current value (or as raw `{{tokens}}` if unresolved), OpenAPI reference pages export as a basic description rather than the interactive Swagger UI, expandable headings lose their collapse behavior, and custom widgets like the documentation widget and changelog are omitted entirely. You'll need a separate pass to decide what to do with those.

### Block types by export quality

The following table specifies which Archbee block types export cleanly via Markdown, which export degraded, and which require API access or alternative handling:

| Archbee block type | Markdown export quality | Recommended extraction method |
|---|---|---|
| Paragraph, bold, italic, strikethrough | Clean | Markdown export |
| Headings h1–h3 | Clean | Markdown export |
| Headings h4–h6 | Degraded (clamped to h3 or body text) | Markdown export + converter post-processing |
| Bulleted/ordered lists | Clean | Markdown export |
| Checklists | Clean | Markdown export |
| Expandable headings | Degraded (flattened, behavior lost) | Markdown export; document loss |
| Code blocks (single language) | Clean | Markdown export |
| Code blocks (multi-tab) | Degraded (tabs collapsed to first or all) | API (`json` format) for tab structure |
| Code drawer (two-column) | Degraded (layout lost, code preserved) | Markdown export; note layout loss |
| Inline code | Clean | Markdown export |
| Tables (≤300 cells) | Clean | Markdown export |
| Tables (>300 cells) | Truncated or rejected | API + manual split logic |
| Blockquotes | Clean (flat only) | Markdown export |
| Callouts (info/warning/tip) | Partially clean | Markdown export + syntax rewrite |
| Vertical split / column layout | Degraded (linearized) | Markdown export; note layout loss |
| Internal document links | Broken (Archbee docId paths) | API; requires link rewriting map |
| User mentions | Degraded (username text, not Slack ID) | API; requires user-mapping table |
| Mermaid diagrams | Omitted | Export as image via API or screenshot |
| draw.io embeds | Omitted | Export as image |
| iframe embeds (YouTube, etc.) | Omitted | Replace with hyperlink |
| OpenAPI/Swagger pages | Omitted (description stub only) | Link to external spec host |
| Variables (`{{token}}`) | Partially resolved or raw token | Pre-resolve before conversion |
| Documentation widget | Omitted | Keep in-product; out of scope for Canvas |
| Changelog block | Omitted | Convert to reverse-chronological h2 list |

### When to use the API instead

For small workspaces or when you need block-level metadata, reading the API directly is viable. Archbee returns the document body as a JSON array of typed blocks — headings, paragraphs, code editors, API endpoints, callouts, vertical splits, and more. Each block has a `type` field and type-specific properties. The problem is scale: you need to enumerate every document in every space, handle cursor pagination, respect Archbee's rate constraints, and write a parser for every block type you encounter.

The pragmatic approach: use the bulk export as your primary source and fall back to the API only for block types the export doesn't handle well (multi-tab code blocks, large tables, internal link resolution). Keep PDF export as an archive lane for pages that are mostly generated content (OpenAPI pages, widget-driven pages) where Markdown conversion would produce unreadable output. ([archbee.com](https://www.archbee.com/docs/exporting-documents))

## What Slack Canvas actually accepts

**Slack Canvas content is Markdown — not mrkdwn, not Block Kit, not HTML.** When you create or edit a canvas via the API, you provide a `document_content` object with `"type": "markdown"` and a `markdown` string. That string is limited to **1 MiB (1,048,576 characters)** per call. Any payload containing [unsupported syntax](https://clonepartner.com/blog/blog/slack-canvas-markdown-what-renders-what-fails-what-drops) is rejected with an HTTP 400 error. ([docs.slack.dev](https://docs.slack.dev/surfaces/canvases/))

The supported Markdown elements, per the official Slack developer docs:

| Element | Canvas support | Syntax |
|---|---|---|
| Headings | h1, h2, h3 only | `#`, `##`, `###` |
| Bold | Yes | `**text**` |
| Italic | Yes | `_text_` |
| Strikethrough | Yes | `~text~` |
| Bulleted lists | Yes | `- item` |
| Ordered lists | Yes | `1. item` |
| Checklists | Yes | `- [ ] task` / `- [x] done` |
| Code blocks | Yes (fenced) | ` ``` code ``` ` |
| Code spans | Yes | `` `inline` `` |
| Blockquotes | Yes | `> text` |
| Callouts | Yes | Slack-specific syntax |
| Dividers | Yes | `---` |
| Column layout | Yes (flexbox) | Slack-specific |
| Links | Yes | `[text](url)` |
| Tables | Yes, **300-cell max** | Pipe syntax |
| Mentions | Users and channels | `! [](@U123)` / `! [](#C123)` |
| Unfurls | Canvas, message, website, file | Auto from URLs |

Nothing else is supported. If your script attempts to push a Block Kit JSON payload into a Canvas update, the API will fail.

**On the 300-cell table limit:** Slack counts all body cells plus header cells. A table with 1 header row and 29 data rows across 10 columns = 300 cells total — at the exact limit. A 31-row × 10-column table (310 cells) will be rejected. Enforcement is per-table, not per-canvas, so splitting one large table into two smaller ones resolves the issue without affecting other tables in the same canvas.

> [!NOTE]
> **Undocumented constraint:** the `canvases.edit` method's `changes` array accepts only one change per request. Passing multiple operations in a single call fails with `"no more than 1 items allowed"`. This is not mentioned in Slack's official documentation but is consistently enforced. Plan your pipeline to make one edit call per section. ([docs.slack.dev](https://docs.slack.dev/reference/methods/canvases.edit/))

## Block-by-block mapping: what the converter decides

Whether you're processing the Markdown export or parsing the raw JSON blocks, your converter must make a mapping decision for every content type. Here's what each Archbee block type maps to — and where the mapping breaks.

### Text and headings

Archbee supports heading levels 1 through 6. Canvas supports **h1 through h3 only**. Your converter needs to clamp h4–h6 down to h3, or demote them to bold text. Paragraphs and inline formatting (bold, italic, strikethrough) map directly.

Archbee's **expandable headings** — collapsible sections triggered by a `>>` prefix — have no Canvas equivalent. Flatten them into standard headings; the expand/collapse behavior is lost.

### Lists and checklists

Bulleted and ordered lists map directly. Archbee checklists (`- [ ]` syntax) map to Canvas checklists. Nested lists work in most cases, but deeply nested structures (more than two or three levels) can cause Canvas to silently flatten or reject content.

> [!CAUTION]
> **Nested structures inside blockquotes are rejected.** A bulleted list inside a blockquote — valid in standard Markdown and in Archbee — will trigger a `canvas_editing_failed` error in Slack. Your converter must detect this pattern and either extract the list outside the quote or flatten it to plain text within the quote. ([docs.slack.dev](https://docs.slack.dev/surfaces/canvases/))

For example, if Archbee exports this:

```markdown
> Here is a list:
> * Item 1
> * Item 2
```

Your converter must flatten it into sequential blocks:

```markdown
> Here is a list:

* Item 1
* Item 2
```

Every regex or syntax tree parser in your converter must actively detect and flatten unsupported nesting — not as an afterthought but as a core step.

### Code blocks and code spans

Archbee's multi-language code editor block (supporting 30–40 languages with syntax highlighting) maps to a fenced code block in Canvas. Canvas supports language tags on fenced blocks, but syntax highlighting is minimal. If an Archbee code editor contains multiple tabbed language examples, you must decide: emit them all sequentially, pick one, or use a heading to label each variant. When you need to preserve tab structure, fetch the block via the API using `format=json` — the tabbed structure is explicit in the block's JSON, whereas the Markdown export collapses or discards it.

Archbee's **code drawer** — the two-column layout with scrolling code on the right — has no direct equivalent. The code itself converts fine; the side-by-side presentation is lost.

Inline code spans map directly with backtick syntax.

### Blockquotes and callouts

Archbee callout blocks (info, warning, tip variants) map to Canvas callouts, which use their own Slack-specific syntax. Standard blockquotes (`>`) map directly. The key constraint: keep callout and quote content flat. No nested lists, no nested blockquotes.

### Tables

Archbee tables map to Canvas Markdown tables using pipe-and-dash syntax. Canvas enforces a **300-cell ceiling per table** — rows × columns including the header row must not exceed 300. A 10-column table can have at most 29 data rows (30 rows × 10 columns = 300). If your Archbee docs contain large data tables, your converter must either truncate them or split them into multiple tables, each under the 300-cell limit.

Cells support bold, italic, strikethrough, code, links, lists, checkboxes, and mentions. Merged cells (column or row spans) are not supported — flatten them to the most specific value. ([docs.slack.dev](https://docs.slack.dev/surfaces/canvases/))

### Column layouts

Archbee's **vertical split** block creates multi-column layouts. Canvas supports column layout via flexbox, but the Markdown syntax for it is Slack-specific and not well-documented. Simple two-column layouts can work if you follow the internal syntax; anything more complex (three columns, nested splits) may not render correctly. The safe fallback is to linearize the content: left column first, then right column, separated by a divider.

### Links, cross-references, and Canvas URL resolution

Inline links (`[text](url)`) map directly. Archbee's internal document links — using dynamic linking that resolves to a `docId` — will break. Canvas has no concept of Archbee document IDs.

When you create a canvas via `canvases.create`, the API response includes both a `canvas_id` (format: `F` followed by an alphanumeric string, e.g., `F012AB3CDEF`) and a `url` field containing the fully-qualified Slack URL for that canvas (e.g., `https://yourworkspace.slack.com/canvas/F012AB3CDEF`). Store both in your local mapping table keyed by original Archbee `docId`. During the link-rewriting pass, replace every `archbee.com/...` internal path with the corresponding Canvas URL from your map. Links to Archbee documents not included in the migration scope should be replaced with plain text or a note indicating the content was not migrated.

Do not leave dead Archbee paths in migrated docs.

### Mentions

Archbee user mentions won't carry over directly. Canvas expects Slack user IDs in the format `! [](@U123ABCDEFG)`. To preserve mentions, build a user-mapping table using the following approach:

1. Extract all Archbee mention references from the export (username or email format, depending on your Archbee configuration).
2. Call `users.list` against the Slack API (Tier 2, paginated via `cursor`). Each user object includes `id` (the `UXXXXXXXX` Slack member ID), `name`, `real_name`, and `profile.email`.
3. Match Archbee mentions to Slack users by email where available, falling back to display name. Store the resulting `archbee_username → slack_member_id` map.
4. In the converter, replace each Archbee mention with `! [](@{slack_member_id})`.

Without this mapping, convert mentions to plain text with the person's name. Do not leave unresolved Archbee mention syntax in Canvas content.

### Diagrams and embeds

Archbee's Mermaid diagram block, draw.io embeds, and iframe embeds (YouTube, Vimeo, maps) have no Canvas equivalent. Canvas does not render diagrams or embedded media. Your options for diagrams:

1. **Export as image and upload via `files.upload`** (or the newer `files.getUploadURLExternal` + `files.completeUploadExternal` flow for files >1 MB). The returned `file.permalink` or `file.permalink_public` URL can be embedded in Canvas Markdown as a standard link — Canvas will unfurl image files shared within the workspace. Use `- ! [Alt text](file_url)` syntax if the file is in the same workspace.
2. **Replace with a hyperlink** to the original Mermaid source, draw.io file, or video URL. Canvas will auto-unfurl recognized domains.

For iframes (YouTube, Vimeo), replace with a plain URL on its own line — Slack Canvas auto-unfurls YouTube and Vimeo URLs into preview cards without requiring any special syntax.

### Dividers

Horizontal rules (`---`) map directly. No conversion needed.

## Which Archbee features have no Canvas equivalent?

Several Archbee features don't just lose formatting in Canvas — they have no target at all. Your migration plan needs an explicit decision for each.

### Variables

**Archbee variables** are reusable tokens (defined with `{{variable_name}}` syntax) that substitute their stored value at render time. Variable names can be up to 40 characters and values up to 100 characters. Variables can be global or space-specific. The placeholder semantics matter, not just the rendered text — if a variable name changes in Archbee, old references stop rendering. ([archbee.com](https://www.archbee.com/docs/reusable-variables?docId=GZKfIYLzU2JXuPcXqo7uq&hostName=docs.archbee.com))

In the Markdown export, variables either render as their current resolved value or appear as raw `{{token_name}}` strings. Canvas has no variable substitution system. If your docs rely on variables to keep product names, URLs, or version numbers consistent:

1. **Pre-resolve** all variables before conversion, locking in current values.
2. **Document** which strings were formerly variables, so future updates require manual find-and-replace across canvases.
3. **Audit for raw tokens.** After conversion, grep the output for `{{` and `}}` — any remaining unresolved tokens will appear as literal text in Canvas and should be resolved or removed before publishing.

### OpenAPI reference pages

Archbee auto-generates interactive API reference pages from OpenAPI/Swagger spec files, complete with a Swagger UI component that lets users make live requests. Imported OpenAPI files cannot be manually modified in Archbee — this is an entire rendered application, not static content. ([archbee.com](https://www.archbee.com/docs/importing-openapi-swagger?docId=BKf0SkSgsYaIMm6EK1CPb&hostName=docs.archbee.com&title=OpenAPI%2FSwagger%2Bblock&utm_source=openai))

Canvas cannot host interactive API references. Your choices:

- **Link out** to the spec hosted elsewhere (Swagger UI, Redocly, Stoplight).
- **Extract** endpoint summaries and render them as static Markdown tables (method, path, description, parameters) — useful for quick reference but a significant downgrade.
- **Skip** these pages entirely if the team maintains a separate API docs portal.

### Documentation widget

Archbee's contextual documentation widget embeds docs directly inside a product's UI via JavaScript. This is a feature of Archbee's hosting platform, not a document type. Canvas is a Slack document surface — it does not replace in-app contextual help. Keep the widget where it lives, or replace it with another in-product docs mechanism. ([archbee.com](https://www.archbee.com/docs/app-documentation-widget?utm_source=openai))

### Changelog entries

Archbee includes a changelog block for logging version updates (added, fixed, improved, broken). These are structured entries, not free-form text. Canvas has no changelog concept. Convert them to a reverse-chronological Markdown document with `h2` headers for each date, or maintain changelogs in a separate tool. ([archbee.com](https://www.archbee.com/docs/editor-blocks?utm_source=openai))

If a page is mostly one of these non-equivalent features, exporting it as PDF and linking the archive from an index canvas is often cheaper than forcing a fake one-to-one conversion.

## How does Archbee's structure map to Slack channels?

**Canvas has no folder hierarchy.** This is the biggest structural mismatch. Archbee organizes content into workspaces → spaces → document trees (with categories acting as folders and documents nested in a tree). Slack Canvas has two types: **standalone canvases** (owned by a user, shareable) and **channel canvases** (one per channel, pinned to the channel). A channel holds exactly one channel canvas. There is no nesting, no sub-pages, no table-of-contents tree.

| Archbee concept | Slack target | Notes |
|---|---|---|
| Workspace | Slack workspace | 1:1 if single workspace |
| Space | Slack channel | One channel per Archbee space |
| Top-level category | Channel canvas (index) | Becomes the navigation layer |
| Document (leaf page) | Standalone canvas | Linked from the index |
| Nested sub-documents | Standalone canvases | Flat — no hierarchy |
| Folder-only category | H2 section in index | Do not create empty canvases |
| OpenAPI or widget page | Link, unfurl, or PDF archive | No true Canvas equivalent |

The practical pattern:

1. **Create one Slack channel per Archbee space.** Name it to match the space.
2. **Create a channel canvas as the index page.** This canvas lists and links to all standalone canvases representing the space's documents.
3. **Create a standalone canvas for each document.** Use `canvases.create` with the converted Markdown content. The API response includes both `canvas_id` and `url` — store both in a local mapping table keyed by Archbee `docId`.
4. **Build the index.** Once all standalone canvases exist and you have their URLs, generate index Markdown with `[Document Title](canvas_url)` links and push it to each channel canvas via `canvases.edit`.

This is imperfect. Users lose the sidebar navigation tree they had in Archbee. The index canvas is a flat list of links. For large spaces with 50+ documents, this gets unwieldy — consider splitting into multiple channels or using Slack's bookmark bar for high-traffic pages.

> [!WARNING]
> **Standalone canvases require a paid Slack plan.** Channel and DM canvases are available on all plans, but standalone canvases are only available on paid plans. If you're on a free workspace, design around a channel canvas and linked resources instead of a many-canvas knowledge base. ([slack.com](https://slack.com/help/articles/33536064287891-Manage-canvas-settings-in-Slack?utm_source=openai))

## What rate limits will you hit?

Slack's rate limits are tiered per method, per workspace, per app. The documentation publishes minimum rates but not exact burst ceilings.

| Method | Tier | Published minimum |
|---|---|---|
| `canvases.create` | Tier 2 | 20+ requests/min |
| `conversations.canvases.create` | Tier 2 | 20+ requests/min |
| `canvases.edit` | Tier 3 | 50+ requests/min |
| `canvases.sections.lookup` | Tier 3 | 50+ requests/min |
| `canvases.getContent` | Tier 3 | 50+ requests/min |
| `canvases.access.set` | Tier 3 | 50+ requests/min |
| `users.list` | Tier 2 | 20+ requests/min |
| `conversations.create` | Tier 2 | 20+ requests/min |

At Tier 2 rates, creating 200 standalone canvases takes a minimum of 10 minutes — assuming no bursts and no retries. Slack tolerates short bursts above the published minimum before returning HTTP 429 with a `Retry-After` header; the actual ceiling is not published. Staying at approximately 1 request per 3 seconds (20/min) keeps migrations below the threshold that triggers sustained 429 responses. The `Retry-After` value is in seconds and should be used directly — do not use a fixed sleep when Slack has told you the exact wait. ([docs.slack.dev](https://docs.slack.dev/reference/methods/conversations.canvases.create/))

### The one-change-per-request problem

The `canvases.edit` `changes` array is documented as accepting a list of operations, but Slack enforces a limit of exactly one change per request. If a canvas needs three edits (insert content, add a section, rename), that's three API calls, each rate-limited independently. For a migration touching hundreds of canvases, this multiplies your API call count significantly. Budget 3–5 `canvases.edit` calls per canvas for a typical document (initial content insert, access permissions, plus any section operations).

### Concurrent edit locks

Sending concurrent `canvases.edit` requests to the same Canvas ID can trigger a `canvas_editing_locked` error. Use a single-writer queue per canvas — never send parallel writes to the same canvas. Parallel writes to *different* canvases are safe within rate limits. ([docs.slack.dev](https://docs.slack.dev/reference/methods/canvases.edit/))

### Throttling strategy

```python
import time
import requests

def create_canvas_with_backoff(token, title, markdown, max_retries=5):
    url = "https://slack.com/api/canvases.create"
    headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"}
    payload = {
        "title": title,
        "document_content": {"type": "markdown", "markdown": markdown}
    }
    for attempt in range(max_retries):
        resp = requests.post(url, json=payload, headers=headers)
        data = resp.json()
        if resp.status_code == 429:
            wait = int(resp.headers.get("Retry-After", 10))
            time.sleep(wait)
            continue
        if data.get("ok"):
            return data["canvas_id"], data["url"]  # store both for link rewriting
        raise Exception(f"Canvas create failed: {data.get('error')}")
    raise Exception("Max retries exceeded")


def build_user_mapping(token):
    """Returns dict of {archbee_email: slack_member_id}"""
    users = {}
    cursor = None
    while True:
        params = {"limit": 200}
        if cursor:
            params["cursor"] = cursor
        resp = requests.get(
            "https://slack.com/api/users.list",
            headers={"Authorization": f"Bearer {token}"},
            params=params
        )
        data = resp.json()
        if not data.get("ok"):
            raise Exception(f"users.list failed: {data.get('error')}")
        for member in data.get("members", []):
            email = member.get("profile", {}).get("email")
            if email:
                users[email] = member["id"]
        cursor = data.get("response_metadata", {}).get("next_cursor")
        if not cursor:
            break
        time.sleep(1)  # users.list is Tier 2; stay under limit
    return users
```

Key principles:

- **Respect `Retry-After` headers.** Don't guess — the header tells you exactly how long to wait.
- **Use exponential backoff** for non-429 transient errors.
- **Separate create and edit queues.** They're on different tiers with different rate budgets.
- **Run the migration during off-peak hours** for the target Slack workspace to maximize available burst capacity.
- **Log every failure with the full response body.** A `canvas_editing_failed` error on one document almost always means the Markdown in that specific document has an unsupported nesting pattern. The error message will not identify which element failed — you must bisect the Markdown to find it.
- **Read back what you wrote.** `canvases.getContent` returns the full canvas as Markdown or HTML — use it for automated diffing and spot QA. ([docs.slack.dev](https://docs.slack.dev/reference/methods/canvases.getContent/?utm_source=openai))

## The migration pipeline

1. **Inventory the corpus.** Use Archbee's space listing (`GET /api/public-api/spaces`) and document enumeration (`GET /api/public-api/docs?spaceId={id}`, cursor-paginated) to enumerate all documents. Tag each page as direct-convert, flatten, or archive. Flag variables, OpenAPI pages, widget-driven docs, changelogs, large tables (>300 cells), multi-tab code blocks, and folder-only categories before writing conversion code.
2. **Export in lanes.** Pull Markdown for standard docs and PDF for generated or non-equivalent pages. Retain source `docId` values and original paths so link rewriting stays deterministic. ([archbee.com](https://www.archbee.com/docs/exporting-documents))
3. **Pre-process variables and mentions.** Resolve all `{{variable}}` tokens to their current values; grep for remaining `{{` patterns after substitution. Build a user-mapping table from Archbee emails to Slack member IDs using the `users.list` API (see code sample above).
4. **Build and run the converter.** Walk each Markdown file, validate against Canvas's supported element list, and transform incompatible patterns: clamp h4–h6 headings, extract lists from blockquotes, truncate or split tables over 300 cells, linearize column layouts, and resolve internal links.
5. **Map structure.** Define which Archbee spaces become which Slack channels. Create channels via `conversations.create` if they don't exist.
6. **Create leaf canvases first.** Use `canvases.create` for standalone canvases, throttled at approximately 1 request per 3 seconds. Store both `canvas_id` and `url` from each response in a mapping table keyed by Archbee `docId`. Handle HTTP 429 responses using the `Retry-After` header directly.
7. **Build index canvases.** Once all standalone canvases exist and you have their URLs, generate index Markdown with `[Document Title](canvas_url)` links and push it to each channel canvas via `conversations.canvases.create` and `canvases.edit`. Remember: one change per `canvases.edit` call.
8. **Validate.** Read back canvases with `canvases.getContent` and spot-check at least 10% of converted content. Look for: flattened headings (h4+ that should have been demoted intentionally), broken links (remaining `archbee.com` paths), raw variable tokens (`{{`), empty sections where widgets were stripped, and tables that were silently truncated at 300 cells.
9. **Archive exceptions.** Link out to PDFs or external API references where Canvas has no true equivalent. Don't leave dead ends.
10. **Communicate the cutover.** Canvases don't support redirects. Old Archbee URLs will 404 after decommissioning. Share the new index channels with every team and update any external links pointing to Archbee docs.

## What this migration does not give you

Be honest with stakeholders about what's lost:

- **No search parity.** Archbee's full-text search across spaces is replaced by Slack's general search, which indexes canvas content but doesn't offer the same faceted, documentation-specific experience.
- **No versioning.** Archbee tracks document revision history. Canvas has no version history API — once content is overwritten, the previous version is gone. There is no way to recover a previous canvas state through the API.
- **No publishing workflow.** Archbee's preview → production publish cycle, with staging environments, doesn't exist in Canvas. A canvas is live the moment it's created.
- **No analytics.** Archbee provides page view and search analytics. Canvas provides none.
- **No SEO.** Archbee spaces can be published to custom domains with SEO controls. Canvases are internal to Slack and not indexable by search engines.
- **No granular permissions per document.** Archbee supports per-document access controls. Canvas permissions are set at the canvas level via `canvases.access.set`, but the permission model is simpler: workspace members, specific users, or everyone with the link.

If the team needs any of these capabilities, Canvas may not be the right migration target — or it may be the right target for internal docs only, with a separate tool handling customer-facing content.

## Effort estimation

A team with strong scripting skills can expect the following rough timelines:

| Corpus size | Variable/widget usage | Estimated converter build + migration time |
|---|---|---|
| <50 documents, simple content | Low | 1–3 days |
| 50–200 documents, moderate complexity | Medium | 1–2 weeks |
| 200–500 documents, heavy variables/OpenAPI | High | 3–6 weeks |
| 500+ documents, complex layouts | High | 6–12 weeks |

The largest time sinks are: (1) building and testing the block-type mapping table for edge cases, (2) manual triage of non-equivalent content (OpenAPI pages, changelogs, widget-driven pages), and (3) link rewriting when Archbee documents cross-reference each other heavily. API rate-limit throttling adds calendar time but not engineering time if the backoff logic is built correctly from the start.

If you'd rather spend that time on product work, ClonePartner has built converters for block-based editors, proprietary JSON formats, and constrained target APIs. We handle the block-by-block mapping, the rate-limit throttling, the structure flattening, and the edge-case triage.

> Need to move Archbee content into Slack Canvas without building a custom converter from scratch? Our engineering team handles the extraction, block mapping, and API throttling — so your docs land in Canvas correctly the first time.
>
> [Talk to us](https://clonepartner.com/talk-to-us?duration=30&utm_source=blog&utm_medium=button&utm_campaign=demo_bookings&utm_content=cta_click&utm_term=demo_button_click)

## Frequently asked questions

### Can I import Archbee content directly into Slack Canvas?

No. There is no native import, plugin, or middleware. Archbee stores content in a proprietary JSON block format, and Slack Canvas accepts only Markdown via its API. You must build a custom converter or use a bulk Markdown export as an intermediate step.

### What Archbee features are lost when migrating to Slack Canvas?

Variables (render-time substitution), OpenAPI/Swagger interactive reference pages, the documentation widget, changelog entries, expandable headings, Mermaid diagrams, iframe embeds, version history, the publish workflow, page analytics, and SEO controls are all lost. Canvas has no equivalent for any of these.

### What are the Slack Canvas API rate limits for bulk migration?

canvases.create is Tier 2 (20+ requests per minute) and canvases.edit is Tier 3 (50+ per minute). An undocumented constraint limits canvases.edit to one change per request. At Tier 2 rates, creating 200 canvases takes at least 10 minutes. Always respect the Retry-After header on 429 responses.

### How do Archbee spaces map to Slack channels and canvases?

Slack Canvas has no folder hierarchy. The practical approach is one Slack channel per Archbee space, a channel canvas as the index/table of contents, and one standalone canvas per document. Nested document trees are flattened. Standalone canvases require a paid Slack plan.

### Does Slack Canvas support Block Kit or HTML content?

No. Canvas content must be standard Markdown — not mrkdwn, not Block Kit, not HTML. Supported elements include h1–h3 headings, bold, italic, strikethrough, lists, checklists, code blocks, quotes, callouts, dividers, links, tables (300-cell max), and mentions. Content is capped at 1 MiB per API call.
