Slab to Notion Migration Guide: API Limits & Data Mapping
Complete Slab to Notion migration guide covering Slab export gaps, Notion API limits, block-level data mapping, and internal link preservation.
There is no native migration path from Slab to Notion. Notion's import menu does not list Slab as a source — no connector, no one-click importer. Your options are: export Markdown files from Slab and import them manually into Notion (hitting a hard cap of ~120 files every 12 hours), or build a custom pipeline using both APIs. For a workspace with hundreds of posts organized across topics, the manual path breaks down fast. (notion.com)
Admin does not mean full access. Slab admins can bulk-download published posts and topics, but they cannot export secret topics they are not part of or drafts. Admins also cannot use privilege to join secret topics, and Slab's API only returns content the Slab Bot can access. If you skip this access audit, your migration will be incomplete before it starts. (help.slab.com)
This guide covers the exact export limitations on the Slab side, the import constraints on the Notion side, what maps cleanly between the two platforms, what breaks, and how to architect a reliable migration pipeline.
Why Teams Migrate from Slab to Notion
Slab is a focused wiki with a clean editor. It does one thing well: distraction-free document creation. But as teams grow, they outgrow Slab's feature set. The most common triggers:
- No relational databases. Slab is document-only. Notion's database-first architecture lets teams build project trackers, CRMs, and sprint boards alongside their docs.
- Limited integrations. Slab connects to Slack, GitHub, and a handful of others. Notion's ecosystem is broader, with a public API, webhooks, and a growing marketplace.
- Tool consolidation. Teams running Slab for docs, Trello for tasks, and Airtable for tracking can often collapse all three into Notion.
- Lack of advanced views and permissions. Notion's filtered database views, granular permissions, and workspace-level controls give IT leads more flexibility.
The technical risk in any wiki migration is losing document structure, internal links, and formatting. Slab's content model is fundamentally different from Notion's, and that gap is where most migrations fail.
Slab Export Limitations: What Gets Left Behind
Slab's built-in export lives under Team Settings → Import & Export. Only admins can trigger a bulk export. (help.slab.com)
What the bulk export includes
- All published posts across all topics the admin has access to
- Export formats: Markdown or DOCX
- After clicking Download, Slab sends an email with a download link (typically within a few minutes)
What the bulk export excludes
Critical gap: Slab's Help Center confirms that admins "cannot export secret topics they are not a part of or drafts." If your workspace uses secret topics for sensitive team documentation (HR policies, security runbooks, board materials), those posts will silently be missing from the export ZIP. There is no warning in the UI. (help.slab.com)
A full migration requires either:
- Having an admin manually join every secret topic before exporting, or
- Using the Slab GraphQL API to extract content programmatically (which respects the Slab Bot user's access — you need to grant it access to secret topics as well)
New posts in Slab are draft-only by default until published, so the draft gap is typically bigger than most admins expect. (help.slab.com)
Export format details
Slab's Markdown export produces one .md file per post. The file structure mirrors your topic hierarchy, with folders named after topics. Embedded images are included as external URLs hosted on Slab's CDN. Internal links between posts are exported as relative Markdown links.
What breaks on export:
- Internal links become relative paths that point nowhere once you leave Slab
- Embedded third-party content (Figma, Loom, Google Docs, custom iframes) exports as raw URLs, not embeds
- Tables export reasonably well in Markdown, but complex formatting (merged cells, colored rows) is lost
- Code blocks survive but language annotations may vary
- Image references point to Slab's CDN — those URLs may expire or become inaccessible after your Slab subscription ends
- Metadata — document IDs, precise creation timestamps, user attributions, and verification status are stripped
Audit before you export. Use Slab's advanced search filters to inventory what you actually have. The documented filters include in:, is:, by:, owner:, contributor:, and mention:. The is: filter is especially useful for identifying draft, published, public, verified, expired, and archived content before the move — the fastest way to produce a gap list rather than discovering missing content after cutover. (help.slab.com)
Markdown is the better default for most Slab-to-Notion migrations. Slab's editor already supports Markdown shortcuts, Notion can import Markdown files natively, and Notion's enhanced Markdown API is a cleaner fit for scripted page creation than building every page as raw block JSON. Keep DOCX as a fallback for the small subset of pages where complex tables or layout render better through Word import. (help.slab.com)
Notion Native Import Constraints: Rate Limits and File Sizes
Notion accepts Markdown files through its import menu (Settings → Import → Markdown). You can upload individual files or a ZIP archive. This is the path of least resistance — and the one that breaks first at scale. (notion.com)
Hard limits on Notion's importer
| Constraint | Limit | Source |
|---|---|---|
| File imports per window | ~120 files every 12 hours | Notion Help Center |
| File size (Free plan) | 5 MB per file | Notion Help Center |
| File size (Paid plan) | 50 MB per file | Notion Help Center |
| Progress tracking | None — no progress bar or status indicator | Notion Help Center |
| Anchor links / nonstandard Markdown | May not import cleanly | Notion Help Center |
For a detailed breakdown of every import path and its gotchas, see our guide to importing data into Notion.
What this means in practice
A Slab workspace with 500 published posts will take at least 5 import batches spread across 60+ hours just to get the raw Markdown into Notion. During that time:
- Every internal link is broken (Slab relative paths don't resolve in Notion)
- Images referencing Slab's CDN may render, but are not re-hosted to Notion's storage
- Topic hierarchy (the folder structure from your export) creates Notion pages, but the nesting may not match your intended sidebar structure
- There is no deduplication — if you accidentally import a batch twice, you get duplicate pages
- Large ZIP imports with deeply nested folder structures can cause the importer to crash or partially import without an error log
Created/updated dates: Notion's importer does not preserve original timestamps. All imported pages show the import date as their creation date. If audit trails or version history matter to your team, plan to store original dates in a database property after import.
Do not use PDF as your migration source. Notion can import PDFs, but headings, equations, quotes, toggles, to-dos, code, callouts, and dividers may lose structure. Paid-plan PDF imports cap at 20 MB per file. PDF is an archive format, not a migration format. (notion.com)
For PDF rendering errors or stuck exports during testing, see our troubleshooting guide on Notion export and PDF issues.
Native Importer vs. Custom Script vs. Migration Service
Before committing to a path, here's how the options compare:
| Path | Good fit | Where it breaks |
|---|---|---|
| Notion native importer | Small, mostly text-only workspaces (< 100 posts) | File throttles, broken anchors, no metadata, no access-aware extraction |
| DIY API script | Teams with in-house migration engineering time | Retry logic, payload splitting, asset staging, link rewriting, QA debt |
| Engineer-led service | Large, regulated, or link-sensitive wikis | Higher cost, but much less operational risk |
If a vendor only asks for a ZIP file, ask two questions: how are internal Slab links rewritten after new Notion page IDs exist, and how are secret topics or draft gaps handled? If the answer is "we import files," you're buying a file converter, not a migration.
Building a Custom Script: Slab and Notion API Limits
For workspaces beyond a few dozen posts, the API route is the only path that preserves structure at scale. Both APIs have constraints that will throttle your script if you don't plan for them.
Slab's GraphQL API
Slab exposes a GraphQL API for programmatic access. Key constraints:
- Plan requirement: API access is only available on the Business or Enterprise plan. Free and Startup plans cannot use the API. (help.slab.com)
- Authentication: Uses an API token generated in Team Settings. All requests run as the Slab Bot user — content visibility is limited to what Slab Bot can access.
- Rate limiting: Slab's GraphQL API uses a complexity-based rate limit with a documented ceiling of 1,000 complexity points per minute, enforced via a token bucket algorithm. Fields cost 0, objects cost 1, and many-to-many relations cost 2, with costs of nested relations multiplying. A simple query fetching post titles might cost 5–10 points. A deep query fetching full post content with topic metadata and user details can cost 50–100+ points — and the multiplier effect on nested relations can exhaust the budget in a single overly broad query.
- Pagination: Results use cursor-based pagination. You need to iterate through all posts using
pageInfocursors.
# Example: Fetch posts with content
query {
posts(first: 50, after: "cursor_value") {
edges {
node {
id
title
content
insertedAt
updatedAt
topics {
id
name
}
}
}
pageInfo {
hasNextPage
endCursor
}
}
}Minimize query complexity. Only request the fields you need. Fetching user details, reactions, and comments in the same query as post content will burn through your complexity budget fast. Split extraction into phases: content first, metadata second.
Notion's REST API
On the write side, Notion's API has its own set of constraints: (developers.notion.com)
| Limit | Value | Notes |
|---|---|---|
| Request rate | 3 requests/second per integration | Token bucket; bursts up to ~10 allowed |
| Blocks per request | 1,000 max | Total across all nested levels |
| Payload size | 500 KB max | Per request |
| Children array | 100 elements max | Per individual children array |
| Nesting depth | 2 levels per request | Deeper nesting requires follow-up append calls |
| Rich text per object | 2,000 characters | Text exceeding this must be split across objects |
These limits interact in ways that are easy to underestimate. A long Slab post with deeply nested bullet lists, multiple code blocks, and inline images can require 5–10 API calls just to create a single Notion page.
Here's why: the first Create Page call can include up to 1,000 blocks, but only 100 elements per children array and only 2 levels of nesting. A bullet list nested 4 levels deep requires:
- First call: Create the page with top-level blocks + 2 levels of nesting
- Second call: Append children at level 3
- Third call: Append children at level 4
At 3 requests per second, a workspace of 1,000 posts averaging 5 calls each means ~5,000 requests — roughly 28 minutes of wall-clock time at sustained throughput, assuming zero retries.
Enhanced Markdown API
As of Notion API version 2026-03-11, you can create and update page content using enhanced Markdown instead of only block JSON. This format supports most Notion block types including callouts, toggles, tables, media blocks, synced blocks, columns, page references, and color annotations. Unsupported Markdown output still includes bookmarks, embeds, link previews, breadcrumbs, and template buttons. (developers.notion.com)
For Slab exports already in Markdown, this path is usually cleaner than building nested block payloads by hand. It does not remove the need for retries, asset staging, or fallback handling for unsupported block types.
Data Mapping: Translating Slab Content to Notion Blocks
This is where the architectural difference between the two platforms creates the most work.
Slab's content model
Slab is built on Quill, the open-source rich text editor that Slab itself develops and maintains. Internally, Slab stores content using Quill's Delta format — a JSON-based specification that describes rich text as an array of insert, delete, and retain operations. When you export via Markdown, Slab serializes these Deltas into standard Markdown syntax. When you use the API, you can retrieve content as Markdown or the underlying structured format. (help.slab.com)
Slab organizes content as:
- Posts — individual documents (equivalent to Notion pages)
- Topics — containers that group posts (closest Notion equivalent: a parent page or database)
- Subtopics — nested topics for deeper organization
Notion's content model
Notion is block-based. Every element — paragraph, heading, bullet, image, callout, code block — is an individual block object with a unique ID. Pages are blocks. Databases are blocks. Everything nests inside everything else. The API represents all content as a tree of typed block objects.
Object-level mapping
| Slab Object | Notion Equivalent | Notes |
|---|---|---|
| Post | Page | 1:1. Each Slab post becomes a Notion page. Store the original Slab post ID as a text property for QA and link rewriting. |
| Topic | Page (as parent) or database property | Create a Notion page per topic; nest post pages inside. Use properties for reporting. |
| Subtopic | Nested page | Maintain hierarchy by nesting under topic pages. |
| Bold / Italic / Strikethrough | Rich text annotations | Maps directly to Notion's annotations object. |
| Heading (H1, H2, H3) | heading_1, heading_2, heading_3 blocks |
Direct mapping. Both platforms support H1–H3. |
| Bullet list | bulleted_list_item block |
Direct mapping, but nesting depth requires recursive API calls beyond 2 levels. |
| Numbered list | numbered_list_item block |
Same nesting constraints as bullets. |
| Checkbox / Task list | to_do block |
Maps cleanly including checked state. |
| Code block | code block |
Language annotation may need normalization (e.g., js → javascript). Must match Notion's supported enum values or the request fails/defaults to plain text. |
| Blockquote | quote block |
Direct mapping. |
| Horizontal rule | divider block |
Direct mapping. |
| Callout / Hint | callout block |
Requires extracting text and explicitly assigning a default emoji or icon URL in the API payload. |
| Image | image block |
Requires re-hosting: download from Slab CDN and re-upload via Notion's File Upload API. |
| Embedded content (Figma, Loom, YouTube) | embed block |
URL-based embeds map to Notion's embed block, but Notion's allowlist may reject some sources. |
| Table | table + table_row blocks |
Notion tables have a 100-column limit. Slab tables are simpler, so this usually maps fine. |
| Internal link (post-to-post) | Page mention or inline link | Requires two-pass link rewriting. Build a lookup table mapping Slab post IDs/URLs to new Notion page IDs. |
| Post owner | People property or text | Use people property if the user exists in the Notion workspace, otherwise store as text. Do not block the migration on user matching. |
| Published / updated dates | Date properties | Notion's native timestamps reflect import date. Store originals as custom properties for audit and delta logic. |
| Post verification status | Select or checkbox property | No native Notion equivalent. Store if your team still needs review state after migration. |
| Post series | Index page or relation property | No native series object in Notion. |
| Comments / Annotations | Notion comments API (partial) | Slab inline comments don't map natively. Notion comments are page-level or block-level, not inline text annotations. Inline positioning is lost. |
| Reactions | ❌ No equivalent | Cannot be migrated. |
| Version history | ❌ No equivalent | Does not transfer. Notion creates its own history from import forward. |
| Analytics / read receipts | ❌ No equivalent | Slab analytics data is not exportable. |
Edge Cases That Break Migrations
1. Inline images from Slab's CDN. Slab hosts uploaded images on its own CDN. Those URLs work as long as your Slab account is active. After cancellation, they will likely become inaccessible. A proper migration must download every image and re-upload it to Notion using the File Upload API. On Notion's Free plan, each file is capped at 5 MB; paid plans allow up to 5 GB per file via the API.
2. Internal link rewriting. Slab internal links use the format https://<team>.slab.com/posts/<slug>. After migration, these links point to nothing. You need a two-pass approach:
- Pass 1: Migrate all posts, recording a mapping of
slab_post_id → notion_page_id - Pass 2: Walk every migrated page, find blocks containing Slab URLs, and replace them with Notion page links using your mapping table. Handle orphaned links for posts that referenced content that wasn't migrated.
3. Deeply nested lists. Notion's API limits nesting to 2 levels per request. A Slab post with a 5-level nested bullet list requires 3+ sequential API calls per section. If your script doesn't handle this, you'll get a validation_error and the nested items will be silently dropped.
4. Rich text length. Notion caps each rich text object at 2,000 characters. A single Slab paragraph longer than 2,000 characters must be split into multiple rich text objects within the same paragraph block. This is uncommon but does happen in documentation with long inline code strings or URLs.
5. Callouts and info boxes. Slab's hint blocks and custom callout styling (via Quill extensions) have no standard Markdown representation. They export as plain text and need to be programmatically converted to Notion callout blocks.
6. Heading deep-links. Slab lets users link to specific headings within a post. Notion's Markdown importer does not preserve anchor links, so heading-level deep links need rewrite logic or a Table of Contents fallback. (help.slab.com)
7. Permissions. Slab topic visibility can be Open, Private, Secret, or Public, and permissions cascade into post visibility. Notion sharing is page- and database-based. You have to redesign access rather than trying to mirror it mechanically. (help.slab.com)
Step-by-Step Migration Runbook
This is the sequence we recommend for any Slab-to-Notion migration beyond ~50 posts.
Step 1: Audit your Slab workspace
Count total published posts, drafts, and secret topics. Use Slab's advanced search filters (is:draft, is:published, is:verified, is:archived) to produce a complete inventory instead of discovering gaps after cutover. List every secret-topic owner and draft owner whose access you'll need. Flag posts with heavy embedded content (Figma, Loom, Google Sheets). Map your topic hierarchy to your intended Notion structure. (help.slab.com)
Step 2: Design your Notion target model
Decide whether topics become nested pages, a database-backed knowledge base, or a hybrid. For most teams, the best model is hybrid: pages for browsing and reading, plus a lightweight database for metadata like owner, source ID, status, and last-reviewed date. This gives you a clean landing zone without forcing every article into a database row. This is also where you redesign Slab's topic permissions into Notion's page- and database-level sharing.
Step 3: Export from Slab
For small workspaces (< 100 posts): Use the admin bulk export in Markdown format. Download the ZIP, inspect the folder structure, and verify all expected posts are present.
For larger workspaces or those with secret topics: Use the Slab GraphQL API. Write a script that:
- Fetches all posts (including those in secret topics the Bot can access)
- Retrieves content as Markdown
- Downloads all referenced images locally
- Records metadata:
post_id,title,topic,created_at,updated_at
API access requires Slab's Business or Enterprise plan. (help.slab.com)
Step 4: Run a pilot import
Do not test on five clean docs. Import 20–50 pages that include headings, code blocks, tables, callouts, attachments, internal links, and edge cases from secret or public topics. The goal is to discover what breaks before you commit to a batch model. Check especially for anchor-link breakage, flattened embeds, and missing images. (notion.com)
Step 5: Transform content
- Parse each Markdown file into Notion block objects (or enhanced Markdown if using API version
2026-03-11) - Split paragraphs exceeding 2,000 characters into multiple rich text objects
- Convert Slab CDN image URLs to local file references for re-upload
- Normalize code block language tags to match Notion's supported enum values
- Identify internal Slab links and flag them for later rewriting
Step 6: Create Notion structure and import
Build your topic/page hierarchy in Notion first — parent pages, databases if applicable. Use the Notion API to create each page with block content, respecting the 1,000-block / 100-children / 2-level nesting limits. Upload images via the File Upload API. Record the mapping: slab_post_id → notion_page_id.
Step 7: Rewrite internal links
Walk every migrated Notion page. Find blocks containing Slab URLs and replace them with Notion page mentions or links using your mapping table. Handle orphaned links for posts that referenced content that wasn't migrated.
Step 8: Run a delta pass before cutover
Don't freeze authors for days if you can avoid it. Stage the initial migration, then re-pull any Slab posts whose updatedAt changed after the first extraction. Reapply the transform, and rewrite links again if new pages were created. This keeps the knowledge base live until final cutover. For more on this approach, see our zero-downtime migration explainer.
Step 9: Validate
Before cutover, test at minimum:
- Page counts: Slab total vs. Notion total (published, drafts, secret topics)
- Formatting spot-check: 10–15% of posts, prioritizing pages with the worst tables and richest formatting
- Images: Verify all render correctly and are hosted on Notion's storage, not Slab's CDN
- Internal links: Test end-to-end, including heading-level deep links
- Missing content: Check for gaps from secret topics or drafts
- Recent edits: Verify changes that landed after the initial export
What Cannot Be Migrated
Be explicit with stakeholders about what doesn't transfer:
| Data Type | Migrateable? | Notes |
|---|---|---|
| Published posts (text + formatting) | ✅ Yes | Full fidelity with API approach |
| Topic hierarchy | ✅ Yes | Maps to Notion page nesting |
| Inline images | ✅ Yes (with re-hosting) | Must download and re-upload |
| Internal links | ✅ Yes (with rewriting) | Requires two-pass link mapping |
| Post metadata (created/updated dates) | ⚠️ Partial | Stored as custom properties; Notion's native timestamps reflect import date |
| Comments & annotations | ⚠️ Partial | Can migrate as page-level comments; inline annotation positioning is lost |
| User attribution | ⚠️ Partial | Author names stored as text; Notion won't link them to workspace members automatically |
| Version history | ❌ No | Does not transfer |
| Reactions (emoji) | ❌ No | No Notion equivalent |
| Verification status | ❌ No (native) | Can store as a custom database property |
| Analytics / read receipts | ❌ No | Slab analytics data is not exportable |
How ClonePartner Handles Slab to Notion Migrations
We've migrated knowledge bases with thousands of posts across platforms with similar architectural gaps. Here's what we do differently:
- API-first extraction that bypasses the 120-file native import cap entirely. Our scripts use optimized batching with queue management against both APIs, handling rate limit backoff automatically.
- Full content model translation — we map Slab's Quill Delta format directly into Notion's block types, preserving callouts, code blocks, nested lists, tables, and image embeds without manual cleanup.
- Image re-hosting — every image is downloaded from Slab's CDN and re-uploaded to Notion's storage before the original URLs expire.
- Internal link preservation — we build and apply a complete link mapping in a dedicated second pass, so your knowledge base stays fully navigable after migration.
- Delta sync and zero downtime — your team keeps reading and editing in Slab while we run the migration. We do a final delta sync before cutover, so no one has to stop working. For more on this approach, see our zero-downtime migration process.
- Secret topic and draft extraction — we handle the content that Slab's admin export misses, extracting it via the API with proper access choreography.
The honest limitation is worth stating: no migration partner can bypass Slab's access model. Secret topics and drafts still require explicit access from the right owners or collaborators, because Slab does not let admins auto-join secret topics and its API only returns what the bot can see. We handle that access choreography and the API mechanics — we don't pretend the restriction doesn't exist. (help.slab.com)
Making the Right Call
For teams with fewer than 100 posts and no secret topics, the manual export-and-import path works. It's slow and you'll lose internal links, but it's free and requires no code.
For anything larger — or if you need to preserve links, images, and complete topic structures — you need an API-based approach. Building it yourself is feasible if you have an engineer who can dedicate a week or two to understanding both APIs' constraints and edge cases.
The big mistake is not choosing the wrong tool. It's pretending a knowledge-base migration is only a file export. The hard part is always the same: access gaps, link rewriting, batch control, and honest QA.
Frequently Asked Questions
- Can I import Slab directly into Notion?
- No. Notion does not have a native Slab importer. You must export from Slab as Markdown or DOCX files, then import those into Notion manually — or use both platforms' APIs to build a custom migration pipeline.
- What data is lost when exporting from Slab?
- Slab's bulk export excludes secret topics the admin hasn't joined and all draft posts. Version history, reactions, analytics, and inline comment positions are also lost. Internal links between posts become broken relative paths, and image URLs point to Slab's CDN which may become inaccessible after cancellation.
- What is Notion's native import file limit?
- Notion limits native file imports to approximately 120 files every 12 hours. File size is capped at 5 MB per file on the Free plan and 50 MB on paid plans. There is no progress bar or status tracking during import.
- How do I preserve internal links when migrating from Slab to Notion?
- You need a two-pass approach: first migrate all posts and record a mapping of Slab post IDs to new Notion page IDs, then walk every migrated page and replace Slab URLs with Notion page links using that mapping table. Handle orphaned links for any content that wasn't migrated.
- What are the Notion API rate limits for migration scripts?
- Notion's API allows an average of 3 requests per second per integration with bursts up to 10. Each request can contain a maximum of 1,000 block elements and 500 KB. Individual children arrays are capped at 100 elements with only 2 levels of nesting per request. Rich text objects are limited to 2,000 characters each.