GitBook to Slack Canvas Migration: Source of Truth Guide
GitBook to Slack Canvas migration guide: two source-of-truth models, extraction paths (Git Sync vs API), conversion losses, and when docs-as-code teams actually benefit.
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
GitBook to Slack Canvas Migration: Source of Truth Guide
Moving documentation from GitBook to Slack Canvas is technically possible but architecturally destructive for most docs-as-code teams. There is no native migration path between these platforms. GitBook is a docs-as-code platform: spaces sync bidirectionally with GitHub or GitLab, content moves through change requests before merge, and the API returns page bodies as proprietary document JSON rather than raw Markdown. A Slack canvas is edited in place — no branches, no review step, no revision export for normal API consumers, and no git sync.
Before you write a single line of migration code, you need to answer one question: where does the source of truth live after the move?
The answer determines whether this migration makes your team faster or cripples the workflow that keeps your docs accurate. This guide covers both source-of-truth models, the extraction paths, the API constraints on both sides, the specific conversion losses you will encounter, and an honest assessment of which teams actually benefit.
If you are evaluating broader knowledge-base moves, see our migration checklist. For teams considering GitBook as a target rather than a source, our Confluence to GitBook guide covers that direction.
What changes when you move from GitBook to Slack Canvas?
GitBook change requests are a branching mechanism modeled on pull requests. A change request is a copy of your main content based on the branching concept: you edit, update, and delete content, request reviews, then merge back into the main version. Merge rules allow you to define requirements that must be met before a change request merges — such as requiring a review from a specific user or a subject and description for the request. With Git Sync enabled, every change request merge results in a commit to the selected GitHub branch.
Slack Canvas has none of this. Canvases are surfaces built into Slack where members create and share fully-formatted content. Editing is live and immediate — anyone with edit access changes the document in place. There is no branching, no diff view, no approval gate, and no merge.
Slack Canvas does have version history: editors can view and restore previous versions when admins allow it. When you export your workspace data, the current version of a canvas is included in HTML format. Prior versions are included for customers on plans that allow full-conversation data export, and Enterprise Grid customers can retrieve canvas version history via the Discovery API. But version history on a live document is not a Git branch-and-merge workflow. There is no per-canvas revision export endpoint for standard API consumers, and no way to sync a canvas back to a Git repository.
This is the core trade-off: GitBook gives you controlled change flow; Canvas gives you instant, low-friction collaboration inside Slack at the cost of editorial governance.
Two source-of-truth models: which fits your team?
There are two viable approaches. The one you pick shapes every subsequent decision — extraction format, pipeline architecture, and ongoing maintenance.
Option A: Canvas becomes the source of truth
You migrate content from GitBook into canvases and abandon the Git repository as the upstream. Canvases are the living documents from that point on. When an engineer updates a runbook or an architecture decision record (ADR), the change is live immediately. There are no change requests, no staging environments, and no required approvals.
When this works:
- Internal ops docs, runbooks, or onboarding guides consumed primarily inside Slack
- Small teams (under ~15 people) where informal editing is faster than review cycles
- Content that changes frequently and does not need audit trails or rollback
- Teams that were using GitBook only because someone set it up years ago, not because they need docs-as-code
- Ephemeral project documentation relevant for a six-week sprint, where GitBook governance is unnecessary overhead
When this breaks:
- Any content that requires peer review before publication
- Documentation tied to release cycles or compliance processes
- More than ~50 pages — canvases have no hierarchy, so discovery degrades fast
- Teams that rely on GitBook's Git Sync to keep docs versioned alongside code
- Compliance-heavy teams that require an audit trail of who approved a documentation change
Option B: Git repository stays upstream; canvases are generated output
The Git repository (or GitBook itself) remains the source of truth. A CI pipeline (e.g., GitHub Actions) or scheduled job exports content, converts the Markdown, and pushes updates to existing canvases via the canvases.edit API. Canvases are treated as read-only mirrors that Slack users can reference without leaving Slack.
When this works:
- Engineering teams that want docs reviewable in PRs but readable in Slack
- Reference material that changes on a release cadence, not ad hoc
- Environments where Slack is the primary communication surface and people don't want to context-switch to a docs site
When this breaks:
- If people start editing the canvases directly, you have two divergent copies with no merge path back to Git — a split-brain scenario where the canonical version is ambiguous and neither copy is fully trustworthy
- If the content changes hourly — the sync lag and API rate limits make real-time mirroring impractical
- If you need more than ~200 canvases — managing access, naming, and discoverability at that scale inside Slack is painful
Recommendation: If your team currently uses GitBook change requests and Git Sync as part of your development workflow, keep the repository as upstream (Option B) or reconsider whether you need canvases at all. A link to your published GitBook site pinned in a Slack channel often solves the "context-switch" problem without any migration.
How to extract content from GitBook
GitBook offers two extraction paths. The right choice depends on whether Git Sync is already configured and whether your space is a single Space or part of a Collection.
GitBook Collections vs. single Spaces
GitBook Collections group multiple Spaces under a shared navigation structure. The extraction path for a Collection differs from a single Space in an important way: the /v1/spaces/{spaceId}/content endpoint operates on one Space at a time. To extract a Collection, you must first enumerate its member Spaces — either through the GitBook UI or by querying /v1/collections/{collectionId}/spaces — then iterate the extraction process across each Space independently. Navigation relationships between Spaces in a Collection are not preserved in the API response or Git Sync export; you must reconstruct cross-Space links manually.
Path 1: Git Sync export (the easier bulk path)
You can export GitBook content as Markdown by syncing with a Git repository. There is no direct Markdown export in the GitBook app — sync the section you want to export with an empty GitHub or GitLab repository using Git Sync, and the repository becomes your Markdown export.
If your space is already git-synced, clone the repo and you have every page as a .md file with a SUMMARY.md (or equivalent) defining the table of contents. This bypasses the need to parse GitBook's proprietary JSON format entirely.
Caveats:
- Some blocks lack a Markdown representation and appear as raw HTML in the export.
- GitBook's Git Sync output uses GitBook-flavored Markdown: hint blocks, embedded content, and tabs use proprietary template tags (e.g.,
{% hint style="warning" %}...{% endhint %}) that will not render in other Markdown consumers without a conversion step. - A community CLI tool called gitbook2md (GitHub:
dinhanhthi/gitbook2md) converts GitBook-flavored Markdown into standard Markdown syntax. It handles hint block conversion and cleans proprietary tags, but the tool's maintenance cadence is irregular — verify compatibility against your GitBook export format before building it into an automated pipeline. - Git Sync does not solve the asset problem. Images and attachments referenced in the Markdown still point to GitBook-hosted URLs, which will expire or break once the GitBook account is closed.
Path 2: GitBook API (/v1/spaces/{spaceId}/content)
GitBook's REST API is versioned at /v1/. The endpoint returns the page tree for a space, with each page's title, ID, and slug. Individual page content comes back as GitBook document JSON — a structured Abstract Syntax Tree (AST), not Markdown:
{
"object": "block",
"type": "paragraph",
"nodes": [
{
"object": "text",
"text": "This is a paragraph in GitBook."
}
]
}To migrate via the API, your script must traverse this JSON tree and reconstruct Markdown manually, or convert directly to Slack's Markdown subset. GitBook also supports format=markdown on individual page lookups, letting you fetch Markdown page-by-page, but this is not a space-level export operation. You must query the root, then recursively query child pages to reconstruct nesting. GitBook distinguishes a page's slug from its full path — use the path to rebuild hierarchy in your migration script.
Rate limits are a real constraint. Different API methods are subject to different rate limits, returned in X-RateLimit-Limit response headers. A 429 response indicates rate limiting; the documented cooldown is 60 seconds. For a space with 200+ pages, expect the extraction step alone to take 10–20 minutes with proper exponential backoff.
OpenAPI reference pages cannot be extracted usefully. GitBook generates API reference pages dynamically from your OpenAPI specification — these pages are computed at view time, not stored as static content. When you query an API reference page via the GitBook API, you receive a single block indicating that an OpenAPI file is attached, not the rendered documentation. Snapshotting that into a canvas produces a stale point-in-time copy that will never update. Keep your API reference wherever the spec lives and link to it from Slack.
Handling expiring assets and attachments
Asset migration is where most custom scripts fail. Files returned by the GitBook API include a downloadURL, but these are time-limited signed URLs — temporary addresses generated by a cloud storage provider (typically Google Cloud Storage or AWS S3) that grant read access to a specific file for a limited window, usually expiring within minutes to a few hours depending on the storage configuration.
If your script copies the Markdown into Slack Canvas with the original URLs intact, the images will render on day one and display as broken links within hours.
Your migration pipeline must execute this sequence for every asset:
- Detect the asset reference in the GitBook Markdown or JSON.
- Fetch the file immediately via
GET /v1/spaces/{spaceId}/assets/{assetId}. Do not batch downloads for later — the URLs expire. - Upload to Slack using
files.getUploadURLExternalfollowed byfiles.completeUploadExternal. The olderfiles.uploadendpoint is deprecated and being retired — do not use it for new pipelines. - Replace the original GitBook URL in the document with the new Slack-hosted file permalink before pushing the content to the canvas.
For asset-heavy workspaces, pair this guide with How to Migrate Images, Attachments & Embeds Without Broken Links.
Required Slack OAuth scopes
Before writing any code, configure your Slack app with the following OAuth scopes. Missing any of these is a common failure point that produces missing_scope errors with no further detail:
| API Method | Required Scope |
|---|---|
canvases.create |
canvases:write |
canvases.edit |
canvases:write |
canvases.getContent |
canvases:read |
canvases.sections.lookup |
canvases:read |
files.getUploadURLExternal |
files:write |
files.completeUploadExternal |
files:write |
channels.info (for channel attachment) |
channels:read |
Your bot token must be installed to the workspace with these scopes granted. If you are operating under Slack Enterprise Grid, canvas API behavior — including access control inheritance and Discovery API access — differs from standard workspaces; test your pipeline in a sandbox EG org before production deployment.
How to load content into Slack Canvas
When creating or editing a canvas with the API, you will encounter a document_content object with two properties: type and markdown. Currently, the only supported type is markdown.
The canvases.create endpoint accepts a document_content block with a markdown string. The markdown content is limited to 1 MiB (1,048,576 bytes) per document_content object. That is generous for most documentation pages but worth checking if you are concatenating multiple GitBook pages into one canvas.
The canvases.edit endpoint supports granular operations: insert_after, insert_before, insert_at_start, insert_at_end, replace, or delete. These operations are section-based and require looking up section IDs via canvases.sections.lookup. Slack currently supports only one edit operation per canvases.edit call. Serialize writes per canvas.
Slack Canvas Markdown: supported and unsupported elements
Slack Canvas accepts a constrained Markdown dialect. The following matrix documents what survives, what degrades, and what is rejected:
| Element | GitBook Support | Slack Canvas Behavior |
|---|---|---|
| Headings H1–H3 | H1–H6 | Supported as-is |
| Heading H4–H6 | Supported | Rejected or silently downgraded to bold text; behavior varies — test your API version |
| Bold, italic, strikethrough | Supported | Supported |
| Inline code | Supported | Supported |
| Fenced code blocks | Supported | Supported |
| Tables | Supported | Supported, 300-cell limit (rows × columns ≤ 300) |
| Blockquotes | Supported | Supported |
| Ordered and unordered lists | Supported | Supported |
| Nested lists (>2 levels) | Supported | Partially supported; deep nesting may flatten |
| Images (inline) | Supported | Supported via Slack-hosted URLs only |
| Hyperlinks | Supported | Supported |
| HTML tags | Supported (passthrough) | Not supported; stripped silently |
Hint/callout blocks ({% hint %}) |
Supported | Not supported; convert to blockquote + emoji |
| Tabs blocks | Supported | Not supported; inline content sequentially |
| Embedded content (Figma, Loom, YouTube) | Supported (interactive) | Bare URL only; no interactive embed |
| Reusable content blocks | Supported | Not supported; each instance becomes a standalone copy |
| OpenAPI blocks | Supported (computed) | Not supported |
| Footnotes | Supported | Not supported |
| Definition lists | Not standard | Not supported |
Task lists (- [ ]) |
Supported | Supported |
Callout/hint conversion pattern:
GitBook's {% hint style="warning" %} blocks must be converted before upload. The recommended Slack-compatible representation:
> ⚠️ **Warning**
> This is a translated GitBook hint block.Map GitBook hint styles to emoji: info → ℹ️, warning → ⚠️, danger → 🚨, success → ✅.
Handling canvas_editing_locked
The canvas_editing_locked error occurs when another edit operation is in progress on the same canvas. This is not a rate limit error — it is a concurrency lock. The lock typically releases within 2–5 seconds. The correct retry strategy is:
- Catch
canvas_editing_lockedin your response handler. - Wait a fixed 3-second interval (not exponential — the lock duration is short and predictable).
- Retry the same operation up to 5 times before logging a failure and moving to the next canvas.
- Do not parallelize writes to the same canvas. Serialize all
canvases.editcalls for a given canvas ID.
If the lock persists beyond 5 retries (>15 seconds total), the canvas may be open in a user's browser for live editing. Log the canvas ID, skip it, and retry after the migration batch completes.
Rate limits and the migration script pattern
The rate limit for canvases.create is Tier 2: 20+ per minute. canvases.edit, canvases.getContent, and canvases.sections.lookup are Tier 3. At the Tier 2 rate, creating 100 canvases takes at least 5 minutes even without processing overhead. A naive script that attempts to create 500 canvases concurrently will hit HTTP 429 errors immediately.
import requests
import time
import logging
SLACK_TOKEN = "xoxb-your-bot-token"
logger = logging.getLogger(__name__)
def create_canvas(title: str, markdown_content: str, channel_id: str = None, max_retries: int = 5):
"""
Create a Slack canvas. Handles rate limiting (429) and canvas lock (canvas_editing_locked).
Requires OAuth scopes: canvases:write, (channels:read if using channel_id)
"""
payload = {
"title": title,
"document_content": {
"type": "markdown",
"markdown": markdown_content
}
}
if channel_id:
payload["channel_id"] = channel_id
for attempt in range(max_retries):
resp = requests.post(
"https://slack.com/api/canvases.create",
headers={"Authorization": f"Bearer {SLACK_TOKEN}"},
json=payload
)
if resp.status_code == 429:
retry_after = int(resp.headers.get("Retry-After", 10))
logger.warning(f"Rate limited. Retrying after {retry_after}s.")
time.sleep(retry_after)
continue
data = resp.json()
if not data.get("ok"):
error = data.get("error", "unknown_error")
if error == "canvas_editing_locked":
logger.warning(f"Canvas locked on attempt {attempt + 1}. Retrying in 3s.")
time.sleep(3)
continue
logger.error(f"Canvas creation failed: {error}")
return None
return data
logger.error(f"Failed to create canvas '{title}' after {max_retries} attempts.")
return NoneFor each GitBook page: clean the Markdown using the conversion rules above, split if it exceeds 1 MiB, and call canvases.create. If using Option B (canvases as generated output), attach canvases to designated channels using the channel_id parameter so readers find them in channel tabs rather than hunting through a flat list.
After writing, use canvases.getContent to read back the created canvas and compare it with expected output. Keep a report of every page that was split, downgraded, or linked out.
What gets lost in the conversion
The gap between GitBook's document model and Slack Canvas's capabilities is substantial. Some of that loss is acceptable for internal operational docs. It is a bad bargain for source-controlled product docs unless Canvas is only the delivery layer.
Page nesting becomes flat
GitBook spaces support deep page hierarchies — parent pages with children nested multiple levels, organized via a table of contents sidebar. Slack canvases are flat. There is no parent-child relationship between canvases.
To work around this, you must either concatenate an entire GitBook Space into a single massive Canvas (which creates an unreadable scrolling document) or create a "Directory Canvas" containing hyperlinks to dozens of individual canvases. You can also group canvases under channel tabs, but Slack caps those at 15 tabs per channel. For a documentation site with 5+ levels of nesting, none of these approaches replicate GitBook's sidebar navigation. Plan on naming conventions (01 - Getting Started, 02 - Authentication > 01 - OAuth), manual tables of contents, and cross-links.
Headings beyond H3 are dropped or silently downgraded
Slack Canvas supports headings H1–H3 only. GitBook supports H1–H6. If your technical documentation relies on H4 or deeper sections, your migration script must convert these to bold text or bullet points before upload. Behavior when passing H4 to the API is inconsistent — some versions silently render it as bold, others reject the block. Test your specific API behavior and implement explicit downgrade logic rather than relying on API fallback. You also lose the structural hierarchy that screen readers and document scanners rely on.
Tables hit the 300-cell ceiling
Canvas tables are limited to 300 cells per table, calculated as rows × columns. A 10-column, 30-row table hits exactly 300 cells. GitBook documentation — especially API reference pages, comparison tables, or data dictionaries — regularly exceeds this. A configuration matrix that is 10 columns wide and 40 rows deep (400 cells) will be rejected by the Slack API. Your migration script must detect table dimensions and either split oversized tables into multiple smaller tables, convert them to formatted code blocks, or link out to an external source (Google Sheets, Notion, etc.).
OpenAPI reference pages are not migratable
GitBook transforms imported OpenAPI documents into interactive, testable API blocks rendered at view time from the underlying spec — these are not stored as static page content. Querying an API reference page via the GitBook API returns a block indicating an OpenAPI file is attached, not the rendered documentation. Slack Canvas has no capability to render OpenAPI specs. Host your API docs elsewhere (ReadMe, Redoc, or raw Swagger UI) and link to them from Slack. Do not attempt to migrate these pages.
GitBook-specific blocks lose fidelity
- Hint/callout blocks: Convert
{% hint style="X" %}to blockquote + emoji as described above. - Tabs blocks: No Canvas equivalent. Content inside tabs must be inlined sequentially; label each section with a bold header to indicate the tab name.
- Embedded content (Figma, Loom, YouTube): Renders as bare URLs in Canvas. Interactive embeds do not survive.
- Reusable content blocks: A GitBook feature with no Canvas equivalent. Each instance becomes its own standalone copy, creating maintenance divergence immediately after migration.
- Footnotes: Not supported in Slack Canvas Markdown. Convert to inline parenthetical text or numbered endnotes at the bottom of the canvas.
Comments and change history do not transfer
GitBook inline comments, resolved threads, and the full change-request history stay behind. Slack Canvas has its own commenting system, but there is no migration path for threaded review discussions from GitBook. If audit trail or comment history matters for compliance, export GitBook comments separately before decommissioning the space.
How long does a GitBook to Slack Canvas migration take?
For a small space (under 50 pages, no OpenAPI content), a competent engineer can build and run a one-off migration script in 1–2 days:
| Phase | Estimated time | Notes |
|---|---|---|
| Git Sync export + Markdown cleanup | 2–4 hours | Faster if already git-synced |
| Markdown normalization (proprietary blocks) | 4–8 hours | Depends on block variety and hint block volume |
| Asset download + re-upload to Slack | 2–4 hours | Must happen promptly; signed URLs expire |
| Canvas creation via API | 1–2 hours | Rate-limited at Tier 2 (20+/min) |
| Naming, channel attachment, QA | 4–8 hours | Manual review required |
For larger spaces (200+ pages, Collections, OpenAPI content, and deep nesting), expect a full week of engineering time — plus hard conversations about what to exclude entirely.
When does a docs-as-code team actually benefit from this migration?
Rarely. A team that has invested in GitBook's docs-as-code workflow — Git Sync, change requests, merge rules, CI-triggered deploys — loses more than it gains by moving content into canvases. The review workflow disappears. The version history disappears. The structural hierarchy disappears. The integration with your deployment pipeline disappears.
The cases where it does make sense:
- Incident response runbooks. During a Sev-1 incident, context switching kills resolution time. Having your operational runbooks live natively as canvases attached to your
#incident-managementchannel ensures engineers don't have to authenticate into a third-party wiki while the database is on fire. - Sunsetting GitBook entirely. If the team is leaving GitBook and Slack Canvas is the agreed-upon destination for internal-only docs (not public-facing), the migration is a one-time cost.
- Non-technical stakeholders who refuse to use GitBook. Product managers, support leads, or ops teams who live in Slack and need access to specific docs without learning another tool.
- Option B as a read-only distribution layer. Keep GitBook (or the Git repo) as the source of truth, push rendered content to canvases on a schedule. The team that writes docs never changes workflow; the team that reads docs gets content where they already are.
For public-facing documentation, API references, or anything tied to a software release process, Canvas is not a viable replacement for GitBook.
GitBook vs Slack Canvas: architecture comparison
| Capability | GitBook | Slack Canvas |
|---|---|---|
| Version control | Change requests (branching + merge) | In-place editing, version history view-only |
| Git integration | Bidirectional sync with GitHub/GitLab | None |
| Review workflow | Change requests with merge rules | None |
| Page hierarchy | Deep nesting with TOC | Flat (standalone or channel-attached) |
| Heading levels | H1–H6 | H1–H3 only |
| Table limits | No hard cell limit | 300 cells per table (rows × columns) |
| API content format | Document JSON (proprietary AST) | Markdown string (1 MiB limit) |
| OpenAPI rendering | Interactive computed blocks | Not supported |
| Collection / multi-space | Supported | Not supported (per-canvas only) |
| Access control | Space-level + per-page | Per-canvas or channel-inherited |
| Export | Git Sync to Markdown, PDF | Print to PDF, workspace export (HTML) |
| Revision export API | Via change request history | Enterprise Grid Discovery API only |
| OAuth scope required | API key (no OAuth) | canvases:write, canvases:read, files:write |
| Embedded interactives | Figma, Loom, YouTube (interactive) | URL only (no embed) |
Making the call
If your team is seriously evaluating this migration, start with a pilot. Pick 5–10 pages with a mix of content types — one with tables over 200 cells, one with H4+ headings, one deeply nested, one with images, one with OpenAPI content, one with hint blocks. Run the full extraction, conversion, and load cycle on those pages. Measure what breaks, what looks acceptable, and what is unrecoverable. That pilot will tell you more than any planning document.
For most docs-as-code teams, the honest answer is: keep GitBook as the source of truth, and use canvases only as a lightweight distribution channel for the content that your Slack-first colleagues actually need. If even that feels like too much overhead, a pinned link to your published docs site costs zero engineering time.
If you have already decided to migrate and need help with the extraction pipeline, Markdown normalization, or asset handling, our engineers build custom extraction and loading pipelines for this exact problem — including multi-Space Collection handling, asset re-hosting, and validation reporting.
Frequently Asked Questions
- Can you bulk import GitBook pages into Slack Canvas?
- No. There is no native import path on either side. You export from GitBook via Git Sync (Markdown) or the API (document JSON), normalize the Markdown to strip GitBook-proprietary block syntax, then create canvases one by one via Slack's canvases.create API.
- Does Slack Canvas support headings and tables from GitBook?
- Partially. Slack canvases support headings H1–H3 and markdown tables up to 300 cells. GitBook content using H4+ headings or tables with more than 300 cells will lose structure during migration.
- How do I prevent broken images when moving from GitBook?
- GitBook asset URLs are signed and expire within minutes or hours. Your migration script must download files immediately via the GitBook API and re-upload them to Slack using the files.getUploadURLExternal endpoint before creating the canvas.
- Can you keep GitBook as the source of truth and sync to Slack Canvas?
- Yes. Keep the Git repository upstream and run a CI job or scheduled script that converts Markdown and pushes it to canvases via the Slack API. Treat canvases as read-only generated output. This preserves your review workflow but requires ongoing maintenance of the sync pipeline.
- What GitBook content cannot be migrated to Slack Canvas?
- OpenAPI reference pages (computed at render time from the spec), reusable content blocks, inline comments, change request history, tabs blocks, and interactive embeds. These either have no Canvas equivalent or produce stale, incomplete copies.