---
title: Slack Canvas API Limits That Break Bulk Migrations
slug: slack-canvas-api-limits-that-break-bulk-migrations
date: 2026-10-01
author: Roopendra Talekar
categories: [Slack Canvas, Migration Guide, Quip]
excerpt: "Hard limits on Slack Canvas content size, table cells, sharing batches, and enumeration break at bulk scale. Here is each ceiling and what to build around it."
tldr: "Slack Canvas API limits — 1 MiB content, 300-cell tables, 20-ID sharing batches, no canvases.list — shape every architecture decision in a bulk migration."
canonical: https://clonepartner.com/blog/slack-canvas-api-limits-that-break-bulk-migrations
---

# Slack Canvas API Limits That Break Bulk Migrations


# Slack Canvas API Limits That Break Bulk Migrations

The Slack Canvas API works fine for one-off document creation. It breaks at bulk scale. Six hard limits — on content size, table cells, sharing batches, plan tiers, channel fanout, and object enumeration — are invisible when you create a handful of canvases but become architecture constraints when you migrate thousands of documents.

If you are moving Quip documents to Slack Canvases ahead of the [Quip end-of-life](https://clonepartner.com/blog/blog/quip-to-slack-canvases-migration-the-official-salesforce-path), or consolidating knowledge bases into an [Enterprise Grid org](https://clonepartner.com/blog/blog/slack-enterprise-grid-migration-the-complete-2026-technical-guide), every ceiling in this guide shapes your pipeline design.

## Every Canvas API Limit That Matters at Volume

| Limit | Value | Documented? | API Method / Surface |
|-------|-------|-------------|----------------------|
| Document content size | 1 MiB (1,048,576 characters) per `document_content` | ✅ Yes | `canvases.edit`, `canvases.create`, `conversations.canvases.create` |
| Table cells | 300 cells per table (any row × column combination) | ✅ Yes | Canvas markdown tables |
| Sharing batch size | 20 `channel_ids` OR 20 `user_ids` per call (mutually exclusive) | ✅ Yes | `canvases.access.set` |
| Canvas-to-channel fan-out | 1,000 channels per canvas | Partial — admin docs only, not API reference | Enterprise Grid |
| Standalone canvas creation | Paid plans only | ✅ Yes | `canvases.create` |
| Canvas enumeration | No `canvases.list` exists | ✅ Yes | `files.list?types=canvas` |
| Operations per edit call | 1 operation per `canvases.edit` call | ✅ Yes | `canvases.edit` |
| Create rate limit | Tier 2: 20+ requests/min | ✅ Yes | `canvases.create`, `conversations.canvases.create` |
| Edit rate limit | Tier 3: 50+ requests/min | ✅ Yes | `canvases.edit` |
| Sharing rate limit | Tier 3: 50+ requests/min | ✅ Yes | `canvases.access.set` |
| getContent rate limit | Tier 3: 50+ requests/min | ✅ Yes | `canvases.getContent` |
| files.list rate limit | Tier 3: 50+ requests/min | ✅ Yes | `files.list` |

## Required OAuth Scopes by Method

Missing scopes produce `missing_scope` errors that are easy to confuse with permission errors. Add all required scopes before starting a migration run.

| Method | Required Scope(s) |
|--------|-------------------|
| `canvases.create` | `canvases:write` |
| `conversations.canvases.create` | `canvases:write` |
| `canvases.edit` | `canvases:write` |
| `canvases.delete` | `canvases:write` |
| `canvases.getContent` | `canvases:read` |
| `canvases.access.set` | `canvases:write` |
| `canvases.access.delete` | `canvases:write` |
| `canvases.sections.lookup` | `canvases:read` |
| `files.list` | `files:read` |

On Enterprise Grid with org-level tokens, canvas methods operate across all workspaces in the org. Workspace-scoped tokens issued to individual workspace apps see only canvases within their workspace — the same canvas created with a workspace-scoped token is invisible to a different workspace-scoped token even within the same Grid org. If your migration script uses a workspace-scoped bot token and your canvases span multiple Grid workspaces, you need one token per workspace or an org-level token with `canvases:read` and `canvases:write` granted at the org level.

Each limit and scope is described below with what it forces you to build.

## What Does the 1 MiB Document Content Limit Mean?

**The `document_content` object in any canvas write call accepts at most 1 MiB — 1,048,576 characters — of markdown.** This limit applies per `document_content` object: when calling `canvases.edit`, it applies to each change in the `changes` array independently. Slack documents this in the [Canvas surface docs](https://docs.slack.dev/surfaces/canvases/) and the [`canvases.edit` reference](https://docs.slack.dev/reference/methods/canvases.edit/).

For a single hand-written canvas, 1 MiB is generous. For a migration pulling documents out of [Quip](https://clonepartner.com/blog/blog/quip-to-slack-canvas-migration-at-scale-api-limits-scripting-guide), [Confluence](https://clonepartner.com/blog/blog/slack-canvas-vs-confluence-architecture-limits-and-migration), or Google Docs, it is the first wall you hit. A 40-page product spec with embedded tables, code blocks, and image references regularly exceeds 1 MiB after conversion to Slack's markdown dialect. Legacy documents with years of historical logs, deep nested lists, or base64-encoded inline images are almost guaranteed to exceed it.

**Base64-encoded inline images are particularly dangerous.** A single 100 KB PNG encoded as base64 expands to approximately 133 KB of text — and Slack Canvas does not render base64-encoded image data inline. The correct strategy is to upload each image separately using [`files.upload_v2`](https://docs.slack.dev/reference/methods/files.getUploadURLExternal/), retrieve the resulting permalink, and inject the permalink as a standard markdown image reference `! [alt](permalink)` into the canvas body. This reduces payload size and produces images that are actually visible in the rendered canvas.

### Pre-flight measurement

Never discover a payload is too large by reading an API error. Before any API call, measure the byte length of the serialized `document_content` value. The limit is documented in characters, but if your source documents contain multi-byte Unicode characters (complex emojis, CJK text), a character-count check alone can yield false negatives on byte-level constraints. Measure both.

```python
import json

MAX_CONTENT_BYTES = 1_048_576

def check_content_size(markdown_text: str) -> bool:
    payload = json.dumps({"type": "markdown", "markdown": markdown_text})
    return len(payload.encode("utf-8")) <= MAX_CONTENT_BYTES
```

Flag anything over roughly 900 KiB, leaving headroom for JSON wrapper overhead. This preserves your rate-limit budget for successful writes instead of burning Tier 3 calls on payloads that will be rejected.

### How to split oversized documents

When a document exceeds the limit, split it across multiple canvases on semantic boundaries — not by raw character count.

1. **Parse the document AST.** Do not split arbitrarily. Parse the source document into an abstract syntax tree so you can identify structural boundaries without breaking markdown formatting or table structures.
2. **Cut at heading boundaries.** Traverse the AST and find the nearest top-level heading (H1, H2, or H3) before the size threshold. Keep tables, checklists, callouts, and code blocks intact within each chunk.
3. **Create sequential canvases.** Title each part with a stable name — `Architecture Spec — Part 1 of 3` — and store that part number in your manifest so re-runs do not create new fragments.
4. **Backfill cross-references in a second pass.** In pass one, create every destination canvas and capture the returned IDs. In pass two, write content and inject previous/next links now that you know all destination IDs.

Blind 1 MiB slicing is how you end up with broken tables, orphaned list items, and references that point nowhere.

> [!WARNING]
> **One operation per `canvases.edit` call.** Slack currently supports only one operation per API call in the `changes` array. The supported operation types are `insert_at_start`, `insert_at_end`, `insert_after`, `insert_before`, and `replace`. You cannot batch multiple operations in a single request, so writing a split document means one API call per section. For first-write bulk imports, a single `replace` operation targeting the whole canvas is usually simpler than multiple section-relative `insert_after` calls.

## How Many Cells Can a Slack Canvas Table Hold?

**A canvas table supports a maximum of 300 cells per table, in any combination of rows and columns.** A 10-column table gets 30 rows. A 3-column table gets 100 rows. A table with 15 columns and 25 rows (375 cells) will cause the API to reject the entire `document_content` payload. Slack documents this in the [Canvas surface reference](https://docs.slack.dev/surfaces/canvases/).

This is separate from the Block Kit `data_table` block used in messages, which has its own limits (20 columns, 100 data rows, 10,000 aggregate characters). Canvas tables and message tables are different surfaces with different constraints.

For migrations from Quip or Confluence, where users frequently treat document tables as lightweight spreadsheets, 300 cells is the constraint that bites most often.

### Handling oversized tables

Pre-flight, compute `rows × columns` for every table in every source document. Flag any table over 300 cells before your migration run starts. Then apply one of these strategies:

- **Chunk by row range.** Split at row boundaries, repeating headers in each chunk. A 500-row × 4-column table (2,000 cells) becomes seven canvas tables of roughly 71 rows each (71 × 4 = 284 cells per chunk). Add a label above each chunk: "Rows 1–71", "Rows 72–142", etc. This preserves searchability within the canvas but degrades readability.
- **Convert to CSV and attach.** For data-heavy tables, convert the source table to a CSV file, upload it with `files.upload_v2`, retrieve the permalink, and inject a markdown link in the canvas. This guarantees zero data loss and keeps the canvas itself readable.
- **Route to a different destination.** A 200-column spreadsheet from Quip is not a canvas table. Attach it as a file, link to a Google Sheet, or acknowledge that this data type does not belong in a canvas.

Radical honesty matters here: a destination that works for narrative documents can still be a poor fit for large tabular data. "Different destination for tables over this shape" is often cleaner than forcing every source artifact into one surface.

## How Does `canvases.access.set` Work at Scale?

**`canvases.access.set` accepts a maximum of 20 `channel_ids` or 20 `user_ids` per call, and the two arrays are mutually exclusive — you cannot pass both in the same request.** This is documented in the [method reference](https://docs.slack.dev/reference/methods/canvases.access.set/), which lists `Minimum: 1`, `Maximum: 20` for both parameters.

If you pass both `channel_ids` and `user_ids`, the API returns an `invalid_parameters` error. If you pass more than 20 IDs in either array, the request fails.

### Batched sharing with partial-failure handling

If a canvas needs to be shared with 85 channels and 12 users, that is 5 calls for channels (20 + 20 + 20 + 20 + 5) and 1 call for users (12) — 6 total API calls, each of which can independently succeed or fail.

```python
def share_canvas(client, canvas_id, channel_ids, user_ids, access_level="read"):
    failed = []
    for i in range(0, len(channel_ids), 20):
        batch = channel_ids[i:i+20]
        try:
            client.canvases_access_set(
                canvas_id=canvas_id,
                access_level=access_level,
                channel_ids=batch
            )
        except SlackApiError as e:
            failed.append({"type": "channels", "ids": batch, "error": str(e)})
    for i in range(0, len(user_ids), 20):
        batch = user_ids[i:i+20]
        try:
            client.canvases_access_set(
                canvas_id=canvas_id,
                access_level=access_level,
                user_ids=batch
            )
        except SlackApiError as e:
            failed.append({"type": "users", "ids": batch, "error": str(e)})
    return failed
```

Partial failure is the real problem. If batch 3 of 5 channel batches fails (rate limit, transient error, one invalid channel ID), the canvas is shared with 40 channels but not the remaining 45. Your script must track which batches succeeded and retry only the failures — without re-sharing already-shared batches, which is safe but wastes rate-limit budget.

Failures in `canvases.access.set` often occur because a `user_id` belongs to a deactivated account or a guest lacking workspace permissions. Your retry logic should catch the specific error code (`user_not_found`, `cant_invite_self`, `no_permission`), identify the offending ID by parsing the API error response, remove it from the batch, and re-send with the remaining IDs.

> [!NOTE]
> **DMs and MPDMs are excluded.** Access levels can only be set for regular channels. Channel IDs for direct messages or multi-party DMs return an error. To share with individual users in DMs, use `user_ids` instead.

### Choosing the right canvas type for your ACL model

Access for a [**channel canvas**](https://clonepartner.com/blog/blog/slack-canvas-api-channel-vs-standalone-for-document-migrations) is tied to channel membership — `canvases.access.set` returns `canvas_not_found` when you try to change access on one. If a document belongs to a single channel, `conversations.canvases.create` is the simpler path.

If you need one document shared across many channels or with explicit user-level permissions, create a **standalone canvas** on a paid plan and fan out sharing from there.

## Can a Canvas Be Shared in More Than 1,000 Channels?

**On Enterprise Grid, each canvas can be shared in up to 1,000 channels.** This limit appears in Enterprise Grid admin documentation but does not appear in the `canvases.access.set` API reference. It applies to both manual sharing and automated sharing through workflows.

**Important caveat:** This limit has not been confirmed in Slack's primary API reference documentation. Treat it as operational guidance to validate against your own tenant before building hard guardrails around it. The limit is consistent with Slack's general channel-fan-out architecture on Grid, but the exact value may differ across org configurations or change without appearing in the API changelog.

For most migrations this is not the binding constraint — few documents need distribution to 1,000+ channels. But if you are migrating company-wide templates or policy documents, you may hit it. The workaround is to set org-wide default access via canvas sharing settings rather than per-channel sharing.

## Why Does `canvases.create` Fail on Free Plans?

**Standalone canvases — canvases not attached to a channel — are only available on paid Slack plans (Pro, Business+, Enterprise Grid).** Slack's [help center](https://slack.com/help/articles/33536064287891-Manage-canvas-settings-in-Slack) states this directly: channel and DM canvases are available on all plans, while standalone canvases require a paid plan. The API returns `free_teams_cannot_create_standalone_canvases` on free workspaces.

For a migration into a paid workspace, this is a non-issue. But if you are building a migration tool that targets multiple customer workspaces, your code must detect the plan tier before choosing between `canvases.create` (standalone) and `conversations.canvases.create` (channel-attached). Call `team.info` and check the `plan` field before your first create attempt.

> [!WARNING]
> **Channel canvases are being converted to tab canvases.** As of April 2025, Slack began converting existing channel and DM canvases into canvases surfaced as channel tabs, as part of a broader canvas UI consolidation. The `conversations.canvases.create` method continues to work but returns `channel_canvas_already_exists` if the channel already has one. If your migration targets channels that already have canvases, call `conversations.info` first and check `channel.properties.canvas` to retrieve the existing canvas ID before attempting creation.

## How Do You List All Canvases Without `canvases.list`?

**There is no `canvases.list` method.** The Canvas API surface has `canvases.create`, `canvases.edit`, `canvases.delete`, `canvases.getContent`, `canvases.access.set`, `canvases.access.delete`, and `canvases.sections.lookup` — but no list or search endpoint. Slack's documentation points to the workaround: call [`files.list`](https://docs.slack.dev/reference/methods/files.list/) with `types=canvas`.

```
GET https://slack.com/api/files.list?types=canvas&count=100&page=1
```

`files.list` is a Tier 3 method (50+ requests/min) and paginates with `count` and `page` parameters, defaulting to 100 files per page. A workspace with 5,000 canvases requires 50 paginated calls to enumerate all of them — at Tier 3, that is approximately 60 seconds of serial pagination.

This matters for **reconciliation** — [verifying that every source document was successfully migrated](https://clonepartner.com/blog/blog/how-to-validate-a-slack-canvas-migration-reconciliation-guide). After a migration run, page through `files.list?types=canvas`, build a set of canvas IDs, and diff against your source manifest. Any source document missing from the set was either skipped, failed, or created under a different title.

The workaround has a cost: `files.list` returns all canvases across the workspace — including user-created drafts and channel canvases unrelated to your migration — so relying on it for exact counts is unreliable. Maintain a strict mapping of `source_document_id` to `slack_file_id` in your migration database, and inject a unique identifier into the canvas title if you need to filter results.

For Enterprise Grid orgs with files spread across workspaces, pass the `team_id` parameter to scope the listing to each workspace individually. With an org-level token, omitting `team_id` returns canvases from all workspaces, which is rarely what you want during a targeted migration run.

### Using `canvases.getContent` for verification

[`canvases.getContent`](https://docs.slack.dev/reference/methods/canvases.getContent/) retrieves the full canvas body as a single markdown string — no pagination, no cursor. It is a Tier 3 method (50+ requests/min). The response returns the canvas content in the same markdown format accepted by `canvases.edit`, which makes it suitable for content hash verification: serialize the content, hash it, and compare against a hash computed from your source document after applying the same markdown transformation.

`canvases.getContent` does not impose a separate response size limit — it returns whatever the canvas contains, up to the 1 MiB write limit. However, very large responses (canvases near the 1 MiB ceiling) can cause HTTP timeout issues in some SDK versions. Set an explicit request timeout of at least 30 seconds for getContent calls on large canvases.

### Using `canvases.sections.lookup` for targeted operations

`canvases.sections.lookup` (scope: `canvases:read`) allows you to find specific sections within a canvas by searching for contained text or by section type. In the context of bulk migrations, it is most useful for **post-migration patching**: if you need to update a specific section across many already-created canvases — for example, replacing a placeholder link with the final destination URL after a second-pass backfill — `canvases.sections.lookup` returns the section ID needed to target an `insert_after` or `replace` operation in a subsequent `canvases.edit` call. Without it, updating a specific section requires replacing the entire canvas body.

## Rate Limits and Throughput Math

Slack rate limits are evaluated per method, per workspace, per app. The canvas-relevant tiers:

| Method | Tier | Guaranteed minimum |
|--------|------|--------------------|
| `canvases.create` | Tier 2 | 20 req/min |
| `conversations.canvases.create` | Tier 2 | 20 req/min |
| `canvases.edit` | Tier 3 | 50 req/min |
| `canvases.access.set` | Tier 3 | 50 req/min |
| `canvases.getContent` | Tier 3 | 50 req/min |
| `canvases.sections.lookup` | Tier 3 | 50 req/min |
| `files.list` | Tier 3 | 50 req/min |

The create methods are the bottleneck. At Tier 2, you can create roughly 20 canvases per minute per workspace. A 2,000-document migration takes approximately 100 minutes of create calls alone — before any content edits or sharing. If each canvas also needs content written via `canvases.edit` and permissions set via `canvases.access.set`, total wall-clock time extends to several hours.

The "20+ per minute" language means Slack guarantees at least 20 and tolerates occasional bursts above that. Do not plan around burst capacity. Design for the documented minimum.

**Handling `ratelimited` responses:** When any Canvas API method returns HTTP 429, the response includes a `Retry-After` header specifying the number of seconds to wait before retrying. Typical values observed in practice are 1–60 seconds, with Tier 2 methods (creates) producing longer back-off windows than Tier 3 methods under sustained load. Always read and honor the `Retry-After` value rather than using a fixed sleep interval — Slack adjusts the window dynamically based on recent traffic from your app.

**Recommended backoff strategy:**
1. On first `ratelimited`, wait exactly `Retry-After` seconds.
2. On second consecutive `ratelimited` for the same method, wait `Retry-After × 1.5`.
3. After three consecutive failures on the same batch item, log and defer to a retry queue — do not block the entire pipeline.

Before starting a run, estimate total API calls: creates + edits + sharing batches + reconciliation pages. Divide by rate limits to get expected wall-clock time. A 2,000-canvas migration with one edit per canvas and average 3 sharing batches per canvas (60 channel targets ÷ 20) produces: 2,000 creates ÷ 20 rpm = 100 min, 2,000 edits ÷ 50 rpm = 40 min, 6,000 sharing calls ÷ 50 rpm = 120 min, plus reconciliation pages. Minimum wall-clock time: ~4.5 hours under ideal conditions.

## Idempotency and Checkpointing

### Preventing duplicate canvases

`canvases.create` has no built-in idempotency key. If your script creates a canvas, the network drops the response, and you retry, you get a second canvas with the same title and content. Slack does not deduplicate by title.

The fix is a **local idempotency map**:

1. Before creating, check your local store (database, JSON file, Redis) for a mapping of `source_document_id → canvas_id`.
2. If the mapping exists, skip creation and proceed to edit or share.
3. If not, create the canvas, capture the returned `canvas_id`, and write the mapping immediately.
4. If the create call fails with an ambiguous error (timeout, 500), query `files.list?types=canvas` filtered by your bot user (`user` parameter) and a narrow `ts_from`/`ts_to` timestamp window to check whether the canvas was actually created before retrying.

For `conversations.canvases.create`, Slack provides a natural idempotency guard: calling it on a channel that already has a canvas returns `channel_canvas_already_exists`, and you can find the existing canvas ID in `channel.properties.canvas` from `conversations.info`. This makes channel canvases inherently safer to retry than standalone canvases.

> [!CAUTION]
> **Do not blindly retry `canvases.create` after `internal_error` or `fatal_error`.** Slack documents that some aspect of the operation may have succeeded before the error was raised. Reconcile first using `files.list`, then resume from your manifest. Always honor `Retry-After` on `ratelimited` responses before queuing a retry.

### Checkpointing long runs

A multi-thousand-canvas migration takes hours. Network errors, rate-limit pauses, and transient Slack outages are expected. Without checkpointing, a failure at canvas #1,847 means restarting from scratch — or duplicating 1,847 canvases.

Each phase — create, edit, share channels, share users — must be checkpointed separately. On restart, the script reads the checkpoint, skips completed items, and resumes pending ones.

```json
{
  "source_id": "quip_abc123",
  "status": "shared",
  "canvas_id": "F07XXXXXX",
  "sharing_completed": ["C001", "C002"],
  "sharing_pending": ["C003"],
  "error": null
}
```

Because `canvases.edit` supports only one operation per call, even writing a single canvas can require multiple API calls — for example: one `replace` for the body, one `insert_at_end` for an index block, one additional `replace` in a second pass to inject backfilled links. Each of those operations needs its own checkpoint entry — they are separate calls with separate failure modes, not a single atomic patch.

This is the same resilience pattern described in our [migration checklist](https://clonepartner.com/blog/blog/the-ultimate-knowledge-base-migration-checklist-a-zero-downtime-plan).

## Pre-Flight Checks That Save Your Migration

Pre-flight measurement catches problems before you burn rate-limit budget on doomed API calls. Run these checks against your full source document set before making any API calls:

1. **Verify OAuth scopes.** Call `auth.test` and confirm `canvases:read`, `canvases:write`, and `files:read` are present. On Enterprise Grid, confirm whether you are using a workspace-scoped or org-level token and whether your canvas targets fall within that token's scope.
2. **Size every document.** Serialize to Slack markdown, measure UTF-8 byte length. Flag anything over 900 KiB. Identify and replace all base64-encoded inline images with `files.upload_v2` permalinks before measuring final payload size.
3. **Count every table's cells.** Rows × columns. Flag anything over 300. Apply chunking or CSV-attachment strategy before the migration run.
4. **Count sharing targets per canvas.** If any canvas needs more than 1,000 channels, flag it for org-level sharing instead of per-channel batching.
5. **Verify plan tier.** Call `team.info` and check the `plan` field. Confirm the target workspace is on a paid plan before attempting standalone canvas creation.
6. **Choose the create method.** Channel canvas (`conversations.canvases.create`) vs. standalone (`canvases.create`), based on the document's ACL requirements and whether the channel already has a canvas.
7. **Estimate total API calls and wall-clock time.** Sum creates + edits + sharing batches + reconciliation pages. Divide by rate tier minimums to get a lower-bound time estimate. Add a 30% buffer for rate-limit pauses.
8. **Validate Enterprise Grid token scope.** If migrating across multiple Grid workspaces, confirm you have either an org-level token or one workspace-scoped token per target workspace.

Turn every Slack limit into a deterministic pre-flight rule. Discover limits through pure functions against your source data, not through failed API writes.

## Migrations Above a Certain Scale Require Specific Engineering Components

Migrations under 50 documents with simple text content are manageable with a direct scripting approach. Once you cross into hundreds or thousands of documents — with tables, images, complex sharing permissions, and Enterprise Grid scoping — the engineering required includes:

- **AST-aware document splitter** that respects heading boundaries and table integrity
- **Image extraction and re-upload pipeline** using `files.upload_v2` before canvas creation
- **Pre-flight validation suite** covering all eight checks listed above
- **Batched sharing coordinator** with per-batch success tracking and error-code-aware retry
- **Idempotency store** mapping source document IDs to canvas IDs with per-phase status
- **Checkpoint-resumable runner** that can restart mid-migration without duplication
- **Reconciliation pass** using `files.list` diff and `canvases.getContent` hash verification

Each of these components addresses a specific API limit or failure mode documented above. Omitting any one of them introduces a failure class that only manifests at scale.

> Dealing with Slack Canvas API limits on a bulk migration? We've built the pre-flight checks, splitters, and checkpoint recovery so you don't have to. Book a 30-minute call and we'll scope it together.
>
> [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

### What is the maximum size of a Slack canvas document_content?

Each document_content object in canvases.create or canvases.edit accepts at most 1 MiB (1,048,576 characters) of markdown. Documents exceeding this must be split across multiple canvases at heading boundaries.

### How many cells can a Slack canvas table have?

A canvas table supports a maximum of 300 cells in any combination of rows and columns. A 10-column table gets 30 rows; a 3-column table gets 100 rows. Tables exceeding 300 cells must be chunked, converted to CSV, or routed to a different destination.

### Is there a canvases.list API method in Slack?

No. Slack does not provide a canvases.list method. To enumerate canvases programmatically, use files.list with types=canvas, as Slack's own documentation recommends.

### Can you create standalone canvases on Slack's free plan?

No. Standalone canvases via canvases.create require a paid Slack plan (Pro, Business+, or Enterprise Grid). Free plans can only create channel-attached canvases using conversations.canvases.create.

### How many channels can a single Slack canvas be shared with?

On Enterprise Grid, a canvas can be shared in up to 1,000 channels according to admin documentation. The canvases.access.set API also limits each call to 20 channel_ids or 20 user_ids (not both), requiring batched sharing for wider distribution.
