Naming & Indexing 10,000+ Slack Canvases After Migration
Slack Canvas has no folders or canvases.list API. After migrating thousands of documents, discoverability must be built into the migration — here's how.
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
Naming & Indexing 10,000+ Slack Canvases After Migration
Slack Canvas has no built-in content management layer. There is no folder tree, no canvases.list API, no tag taxonomy, and no way to browse all canvases in a workspace without scrolling the Files view. When you migrate 10,000+ documents from Quip into Slack canvases, the content arrives — and within a week, nobody can find anything.
Discoverability is something the migration has to build. Canvas does not provide it.
This guide covers the platform constraints you are working with, naming schemes that survive at volume, what Slack search actually reaches inside canvas content, how to use index canvases as a deliberate migration artifact, and the rate limits that govern all of it.
The Platform Reality: How Slack Exposes Canvases
Before building any naming or indexing strategy, you need to understand the architectural constraints that define what is possible. Every design decision in this guide is shaped by these API limits.
Hard limits reference table
| Constraint | Value | Source |
|---|---|---|
| Canvas listing endpoint | files.list?types=canvas only — no canvases.list |
Slack API docs |
Entities per canvases.access.set call |
20 max (channel IDs or user IDs, not both) | Slack API docs |
| Canvas body size | 1 MiB (1,048,576 characters) per document_content |
Slack API docs |
| Canvas title length | 255 characters | Slack API docs |
| Channel canvas per channel | 1 (error: channel_canvas_already_exists) |
Slack API docs |
| Channel header tabs | 15 tabs max | Slack help |
| Channel header items/folders | 100 items max | Slack help |
canvases.create rate limit |
Tier 2: 20+ per minute | Slack API docs |
canvases.access.set rate limit |
Tier 3: 50+ per minute | Slack API docs |
files.list rate limit |
Tier 3: 50+ per minute | Slack API docs |
conversations.canvases.create rate limit |
Tier 2: 20+ per minute | Slack API docs |
canvases.edit operations per call |
1 operation per API call | Slack API docs |
Required OAuth token scopes
Any integration built from this guide will hit missing_scope errors before it hits rate limits. The required scopes for a migration bot:
| Scope | Required for |
|---|---|
canvases:write |
canvases.create, canvases.edit, canvases.delete, canvases.access.set |
canvases:read |
canvases.getContent, canvases.sections.lookup |
channels:read |
conversations.info, conversations.list |
files:read |
files.list (canvas enumeration) |
chat:write |
Posting migration announcement messages |
files.list is the only canvas listing path
files.list filtered to types=canvas is the only API method for programmatically enumerating canvases in a workspace. Slack's official docs state: "To programatically look up a list of canvases, use the files.list method while filtering for the canvas type" with a query like https://slack.com/api/files.list?types=canvas.
There is no canvases.list endpoint. Every inventory script, audit job, reporting pipeline, and reconciliation tool you build post-migration runs on top of this single endpoint.
# Enumerate all canvases in a workspace
curl -s "https://slack.com/api/files.list?types=canvas&count=100&page=1" \
-H "Authorization: Bearer $SLACK_BOT_TOKEN" | jq '.files[] | {id: .id, title: .title, created: .created}'files.list is rate-limited at Tier 3 (50+ requests per minute). It returns a paginated list of file objects, defaulting to 100 per page, and can be filtered by channel ID, user ID, and timestamp range (ts_from, ts_to). For a workspace with 15,000 canvases, that is 150 pages minimum — roughly 3 minutes of wall-clock time at the rate limit floor.
There is no filter for title substring, no folder filter (folders do not exist), and no way to search canvas body content through this endpoint. It is a listing method, not a search method. You can use files.info to retrieve individual canvas metadata — including title, creation timestamp, and sharing state — after enumeration.
If you are migrating 50,000 documents, you cannot query this API to check whether a canvas exists by name in real-time. You must maintain a local state database of migrated document IDs to prevent API exhaustion.
Standalone canvases are invisible until access is granted
A standalone canvas created by a bot or app is visible to nobody except its creator until access is explicitly set. A standalone canvas is a free-floating document — it belongs to the app that made it and is visible to nobody else until you call canvases.access.set. When access is set to "Invite only" (the default), the canvas won't appear in search results or the Canvases browser for anyone it hasn't been shared with.
This is the single most common post-migration failure mode. A migration script creates 12,000 canvases using a bot token, and every single one is invisible to every human in the workspace. The canvases exist. They have correct titles and content. But they are unfindable — not in search, not in the canvas browser, not anywhere — because canvases.access.set was never called.
canvases.access.set sets the access level to a canvas for specified entities, runs at Tier 3 (50+ per minute), and supports at most 20 IDs per request. Both channel_ids and user_ids cannot be passed in the same call. Granting access to 50 channels requires at least 3 calls per canvas.
Your migration script must call canvases.access.set for every standalone canvas it creates, granting at minimum read access to the appropriate channels or users. An unshared canvas is effectively deleted from the user's perspective.
Each channel holds exactly one channel canvas
A channel canvas is the canvas tab attached to a channel — exactly one per channel. A second call to create one returns channel_canvas_already_exists. Unlike standalone canvases, there are no access implications nor is it necessary to share a channel canvas to grant access — access is tied to channel membership.
This one-per-channel constraint makes the channel canvas the natural index surface for migrated content. You cannot create five canvases inside a channel tab, but you can create one that serves as a table of contents linking to all the standalone canvases associated with that channel.
Since April 2025, Slack has been converting older channel canvases into canvases in tabs. The API still documents channel.properties.canvas, conversations.canvases.create, and the channel_canvas_already_exists error. In practice, treat each channel as having one canonical landing canvas and design your index strategy around it. Slack also limits channels to 15 tabs and 100 items or folders — scatter migrated content across tabs without a plan and you make the header unusable.
When to use canvases.create vs. conversations.canvases.create
| Scenario | Method | Notes |
|---|---|---|
| Migrating document content that should be independently accessible | canvases.create |
Creates a standalone canvas; requires explicit canvases.access.set |
| Creating a per-channel table of contents / landing page | conversations.canvases.create |
Creates the channel's tab canvas; one per channel; access inherited from channel membership |
| Migrating a document that belongs in exactly one channel and needs no separate link | conversations.canvases.create |
Simpler access model but severely limits future sharing |
| Any document that may need to be shared across multiple channels | canvases.create + canvases.access.set |
More API calls but correct model for cross-channel content |
In practice: use canvases.create for all migrated content documents, and conversations.canvases.create only for the index canvas in each channel.
Failure modes reference
| Failure Mode | Symptom | Fix |
|---|---|---|
| Unshared canvas | Canvas exists but invisible to all users; absent from search and canvas browser | Call canvases.access.set with correct entity IDs for every standalone canvas |
| Title collision | Multiple canvases with identical titles; users open wrong document | Run deduplication logic before creation; use path prefix + parent folder disambiguation |
| Rate limit exhaustion | 429 responses; migration stalls or produces partial results |
Enforce per-tier delays; maintain local state DB; do not re-query API to check existence |
| Stale index canvas | Index links to renamed, deleted, or moved canvases | Schedule weekly regeneration job using canvases.sections.lookup + canvases.edit |
| Missing migration footer | Cannot reconcile canvas back to source after external mapping is lost | Embed source ID footer in every canvas body at creation time |
| Wrong token scope | missing_scope error before any rate limit is reached |
Verify all five required scopes are granted before migration run |
| Delta sync collision | Source document deleted after migration; canvas orphaned in Slack | Track deletion events during delta window; call canvases.delete for orphaned canvases |
Naming Schemes That Survive at 10,000+ Canvases
Canvas titles are free-text strings with no enforced convention, no namespace hierarchy, and no uniqueness constraint. Slack will happily let you create 500 canvases all titled "Meeting Notes." At migration scale, that is a discoverability disaster.
Carry source folder paths into canvas titles
Quip organizes documents in a folder tree. When those documents land in Slack as flat canvases, the structural context vanishes. The fix is to encode the source path into the canvas title.
Use > (space-angle-bracket-space) as the separator. It is visually scannable, does not conflict with Slack markdown, and sorts predictably in alphabetical listings. The same approach works for other source systems: Confluence space + page hierarchy, Notion database + page title, Google Drive folder tree.
| Source System | Source Location | Canvas Title |
|---|---|---|
| Quip | Engineering / Backend / API Design Guide |
Engineering > Backend > API Design Guide |
| Quip | Sales / Q3 Playbook / ACME Account Plan |
Sales > Q3 Playbook > ACME Account Plan |
| Confluence | Engineering / Backend / API Design Guide |
Engineering > Backend > API Design Guide |
| Notion | Product / PRDs / Feature X Spec |
Product > PRDs > Feature X Spec |
| Google Drive | HR / Onboarding / Day 1 Checklist |
HR > Onboarding > Day 1 Checklist |
For deeply nested documents, the full path can exceed practical title length. Slack canvas titles accommodate up to 255 characters, but titles above ~200 characters become unwieldy in search results and link previews. The best practice: extract the top-level department folder and the immediate parent folder, discarding middle nodes. A Quip path of Engineering / 2025 / Q3 / Backend / API Redesign becomes Engineering > Backend > API Redesign.
For root-level documents, use just the document name — no prefix needed. For private Quip folders, prefix with the owner's name or team:
{Owner Name} > {Folder} > {Document Title}
The Quip Automation API exposes folder structure through get_folder, which returns child document IDs and subfolder IDs — use this to build the path prefix programmatically during extraction. For Confluence, the REST API returns ancestors for each page. For Notion, the retrieve a block endpoint returns parent objects that can be traversed to reconstruct the hierarchy.
Standard naming decisions for source-to-Canvas migrations:
| Scenario | Recommendation |
|---|---|
| Document title is short and unique | Use title as-is |
| Document in nested folder structure | Prepend last 2–3 meaningful folder levels with > |
| Multiple docs with identical titles | Append parent folder name in brackets |
| Title is empty (Quip allows untitled docs) | Use Untitled [{source_id_short}] |
| Title contains only emoji or special chars | Preserve original, append source ID |
| Title exceeds ~200 chars with path prefix | Truncate path to deepest two levels |
Handle title collisions programmatically
In Quip, you can have Engineering/RFC Template and Product/RFC Template as separate documents. In the flat canvas namespace, these collide. The path-in-title approach handles most cases — Engineering > RFC Template vs Product > RFC Template — but your migration script should detect and resolve remaining collisions before creating canvases:
from collections import Counter
def deduplicate_titles(documents):
title_counts = Counter(doc['target_title'] for doc in documents)
seen = Counter()
for doc in documents:
title = doc['target_title']
if title_counts[title] > 1:
seen[title] += 1
parent = doc.get('source_folder', 'root')
doc['target_title'] = f"{title} [{parent}]"
# If parent is also duplicated, fall back to source ID
if title_counts[f"{title} [{parent}]"] > 1:
short_id = doc['source_id'][:8]
doc['target_title'] = f"{title} [{parent}-{short_id}]"
return documentsDo not use sequential numbering (API Redesign (1), API Redesign (2)). Sequential numbers provide zero context to end users and make delta migrations — syncing changes made after the initial migration — brittle. Disambiguate with stable data: the parent folder name, the original author, or a short hash of the source document ID like [quip:a3f2].
# Good: deterministic, context-carrying disambiguation
Sales > EMEA > Pricing FAQ
Sales > US > Pricing FAQ
Sales > EMEA > Pricing FAQ [quip:5f2c91]
# Bad: meaningless, unstable
Pricing FAQ
Pricing FAQ (2)
Pricing FAQ copy 3If you do nothing else: make the title searchable without needing the migration spreadsheet. The title is what users see in search results, canvas pickers, links, and index pages.
Where should source document IDs live?
Every migrated canvas must retain its original source document ID. This is non-negotiable for post-migration reconciliation, delta syncs, and investigating reports of missing content.
Store the source ID outside Slack first, and inside Slack second. Your external mapping table is the primary source of truth. At minimum, track:
source_systemandsource_doc_idsource_path_at_migrationslack_canvas_idandlinked_channel_idcanonical_titleandindex_canvas_idbatch_id,content_hash, andlast_validated_at
Inside Slack, embed a machine-readable footer in the canvas body. This footer survives if the external mapping is lost and lets you reconcile later using canvases.getContent, which returns the full canvas content as a structured response. (docs.slack.dev)
---
**Migration Metadata**
Source ID: `quip:a1b2c3d4e5f6`
Source Path: `Engineering / 2025 / Q3 / Backend`
Migration Batch: 2026-09-14-01
Migrated By: clonepartner-migratorDo not put the source ID in the canvas title. It consumes character budget and pollutes search results. The footer keeps metadata accessible to scripts and humans without degrading the user experience.
The embedded source ID is the only approach that survives if the external mapping is lost — which makes it non-negotiable. The external mapping table and an optional Migration Manifest canvas (a dedicated canvas per workspace containing a table of all source-to-target mappings, searchable within Slack) are strongly recommended on top.
What Does Slack Search Actually Reach Inside Canvas Content?
Slack's official position is that canvases are searchable: "like channel content in Slack, canvases are searchable, enabling knowledge management across your organization." The reality is more nuanced.
What can be confirmed
- Canvases are a first-class search result type. Slack search shows a dedicated "Canvases" tab in results alongside Messages, Files, Channels, and People. (slack.com)
- Body text is indexed. When searching for files, keywords can appear in the file title or anywhere inside the document. Canvases are classified as files in Slack's data model — they have file IDs starting with
F— so the same indexing behavior applies. - Search respects the access model. A canvas set to "Invite only" won't appear in search results for anyone it hasn't been shared with. If your migration did not share the canvases, they are invisible to search — this is a direct consequence of the unshared canvas failure mode described above.
- Slack supports query modifiers useful for migrated canvases. Documented modifiers include exact phrases in quotes,
in:,from:,creator:, date filters likebefore:andduring:, and prefix matching with*. (slack.com) - Salesforce confirms that converted Quip canvases surface by original title and Quip folder name. This is one of the strongest official signals that carrying source context into titles helps users find migrated content. (help.salesforce.com)
- Service account creator queries are useful for ops work. If all migrated canvases are created by one bot token,
creator:@MigrationBotbecomes a reliable filter for audit and support work.
What should not be assumed
- Indexing latency. No documented SLA exists for how quickly new or updated canvas content enters the search index. Community reports from large Slack Enterprise deployments suggest new canvases may take anywhere from minutes to over 24 hours to appear in search results after bulk creation — Slack has not published a guaranteed window. After a bulk migration creating thousands of canvases, treat search as unavailable for at least 24 hours.
- Full-text depth in large canvases. Whether Slack indexes the complete body of a very large canvas, or truncates at some threshold, is not documented. Title-based search is more reliable than body-text search for long-form documents.
- Markdown-specific elements. Whether search reaches text inside code blocks, table cells, or collapsed sections within a canvas has not been publicly confirmed. A 500-row table may behave differently from 500 paragraphs of equivalent byte count — this has not been documented by Slack.
- Embedded objects. Slack's AI search help explicitly excludes images and embedded objects from AI answers. Do not assume ordinary keyword search indexes these either. (slack.com)
Do not rely on search alone for discoverability. Even if Slack indexes every byte of canvas content, search only works when users know what to search for. After a migration, most users don't know the new titles, don't know the content exists in Slack, and won't think to search. You need navigational structures — not just search.
How Channel Canvas Index Pages Work as a Migration Artifact
An index canvas is a channel canvas that serves as a table of contents for all migrated content associated with that channel. It is not a feature of Slack — it is a deliberate artifact that the migration process generates. Without it, users open a channel and see messages but have no way to browse the 40 or 400 documents that were migrated into standalone canvases linked to that channel.
Why the channel canvas is the right surface
The conversations.canvases.create method creates a new channel canvas for the channel. Channel canvases can act as a resource hub, providing users with information highlights and channel-specific details. Once a channel canvas has been created with content, the canvas icon in the upper right of the channel switches to indicate a channel canvas exists.
The channel canvas is visible to every channel member automatically — no sharing step required. It appears as a tab in the channel header, one click from the message stream. Since Slack's post-2025 UI lets you set a canvas to Show canvas by default, this becomes the natural per-channel landing surface. (slack.com)
Calling conversations.canvases.create when a channel canvas already exists returns channel_canvas_already_exists. The index canvas is a singleton by design — no risk of creating duplicates.
Generating index canvases during migration
The migration script generates the index canvas as a final step, after all standalone canvases for a channel have been created and shared:
def generate_index_canvas(channel_id, migrated_docs, bot_token):
"""Create a channel canvas with a table of contents for migrated documents."""
lines = ["## Migrated Documents\n"]
lines.append("| Document | Source Location | Migrated |")
lines.append("|---|---|---|")
for doc in sorted(migrated_docs, key=lambda d: d['target_title']):
canvas_url = f"https://workspace.slack.com/docs/{doc['canvas_id']}"
lines.append(
f"| [{doc['target_title']}]({canvas_url}) "
f"| {doc['source_path']} "
f"| {doc['migrated_at']} |"
)
lines.append(f"\n---\n*{len(migrated_docs)} documents migrated. "
f"Last updated: {datetime.now().isoformat()}*")
markdown_content = "\n".join(lines)
response = slack_client.conversations_canvases_create(
channel_id=channel_id,
document_content={
"type": "markdown",
"markdown": markdown_content
}
)
return responseA good index canvas includes:
- Every migrated document linked by title to its standalone canvas
- Source location so users recognize documents by their old path
- Migration timestamp so users know how current the index is
- A total count so the channel owner can verify completeness
- Escalation hints — who owns the migration map or where to request a missing document
Canvas markdown content is limited to 1 MiB (1,048,576 characters) per document_content object. For a channel with 500 migrated documents, a table row averaging 150 characters means ~75KB — well within the limit. Channels with more than ~6,000 documents at 150 characters per row approach the 1 MiB ceiling and should use paginated index canvases.
Batch your channel canvas updates. Do not update the channel canvas after every single document migration. If a folder contains 500 documents, updating the channel canvas 500 times will trigger aggressive rate limiting. Migrate all documents for a channel, collect their Slack URLs, and update the channel canvas once.
Scaling beyond a single channel canvas
For massive source directories spanning thousands of files, a single channel canvas becomes unreadable. A channel canvas works for high-level context, not a directory of 5,000 links.
In these scenarios, generate standalone Index Canvases that replicate the legacy folder structure. If a Quip folder had sub-folders, the migration script creates an Index Canvas for the parent folder containing links to Index Canvases for each sub-folder, which in turn link to the actual content canvases.
Generate these bottom-up:
- Migrate the documents in the lowest-level sub-folder.
- Generate an Index Canvas for that sub-folder, linking to the documents.
- Move up one level. Generate an Index Canvas for the parent folder, linking to the sub-folder Index Canvases.
- Link the top-level Index Canvas from the channel canvas.
Bottom-up generation ensures every link is valid at the time of creation, preventing broken references. For channels with more than ~5,000 documents, consider alphabetical splits ("Index A–M" and "Index N–Z"). The same hierarchical approach applies to Confluence space migrations (space → page tree → leaf pages) and Notion database migrations (database → grouped views → individual pages).
Regenerating indexes when content moves
Be honest with stakeholders: these indexes are static. If a user renames a canvas, deletes it, or moves it to a different channel, the index goes stale.
To keep indexes accurate, use canvases.sections.lookup to find the table-of-contents section by heading text. In Slack's API, a section is a discrete, addressable block within a canvas — identified by heading type and text — that can be targeted for replacement without rewriting the entire canvas. Use canvases.edit to replace just that section. Slack documents that canvases.edit supports one operation per API call, with each change limited to 1 MiB of markdown, and can fail with canvas_editing_locked or canvas_too_large. Regeneration should be a queued job with retries, not a best-effort script. (docs.slack.dev)
def regenerate_index(channel_id, bot_token):
# Step 1: Get existing channel canvas ID
info = slack_client.conversations_info(channel=channel_id)
canvas_id = info['channel']['properties']['canvas']['file_id']
# Step 2: Find the TOC section by heading text
section_id = slack_client.canvases_sections_lookup(
canvas_id=canvas_id,
criteria={"section_type": "h2", "contains": "Migrated Documents"}
)
# Step 3: Enumerate current canvases shared to this channel
canvases = []
page = 1
while True:
resp = slack_client.files_list(
channel=channel_id, types='canvas', page=page
)
canvases.extend(resp['files'])
if resp['paging']['page'] >= resp['paging']['pages']:
break
page += 1
# Step 4: Rebuild the markdown and replace the section
markdown = build_index_markdown(canvases)
slack_client.canvases_edit(
canvas_id=canvas_id,
changes=[{
"operation": "replace",
"section_id": section_id,
"document_content": {
"type": "markdown",
"markdown": markdown
}
}]
)You can retrieve the existing channel canvas ID via the channel.properties.canvas section of a conversations.info response — this is also the recommended path when conversations.canvases.create returns channel_canvas_already_exists. The files.list call scoped to a specific channel returns only canvases shared to that channel, keeping the index accurate. Use files.info to pull full metadata for any individual canvas surfaced during enumeration.
Run regeneration on a schedule — weekly at minimum. Also trigger regeneration on deletion events: when a source document is deleted after migration, the corresponding canvas should be removed via canvases.delete and the index regenerated to remove the stale link.
Build regeneration into your migration tooling from day one. An index canvas that is accurate on migration day and wrong by week two is worse than no index at all — it actively misleads users. Ship the regeneration script alongside the migration script.
Rate Limits and Throughput at Migration Scale
A migration creating 10,000 canvases and sharing each one calls three core API methods. Here is the throughput reality:
| Method | Rate Limit | Calls for 10K Docs | Minimum Time |
|---|---|---|---|
canvases.create |
Tier 2: 20+ per minute | 10,000 | ~500 min (~8.3 hrs) |
canvases.access.set |
Tier 3: 50+ per minute | 10,000+ | ~200 min (~3.3 hrs) |
canvases.edit (metadata footer) |
Tier 3: 50+ per minute | 10,000 | ~200 min (~3.3 hrs) |
conversations.canvases.create |
Tier 2: 20+ per minute | 1 per channel | Varies |
files.list (audit pass) |
Tier 3: 50+ per minute | 150 pages (15K canvases) | ~3 min |
The canvases.create call is the bottleneck at Tier 2. For 10,000 documents, the creation step alone takes over 8 hours at the published minimum rate. In practice, Slack allows bursts above the floor, but plan for 6–10 hours of creation time for a five-figure migration.
Parallelization across workspaces helps. Rate limits are applied per app per workspace. If you are migrating into an Enterprise Grid org with multiple workspaces, you can run creation calls against different workspaces in parallel. Note that Enterprise Grid introduces additional access inheritance considerations: canvases shared at the org level may have different visibility behavior than those shared to individual workspace channels — verify sharing behavior in a test workspace before running at scale.
Post-Migration Discoverability Audit
Discoverability does not end when the migration script finishes. The first week after migration is when adoption either happens or fails.
A discoverability audit answers five questions: is each canvas present, shared, named predictably, indexed, and reconcilable back to the source?
- Present: Does
files.list(types=canvas)return every canvas in your migration manifest? - Shared: Does each standalone canvas have the expected channel or user access? Did you respect the 20-entity batching limit in
canvases.access.set? - Named: Does each title match your canonical naming function? If a title has changed, is the rename intentional?
- Indexed: Is the canvas linked from the correct channel index canvas?
- Reconcilable: Can you recover the source ID from the canvas body with
canvases.getContent? Does your mapping table have theslack_canvas_id?
Use files.info for spot-checking individual canvases during the audit — it returns current title, sharing state, and timestamps without paginating through the full files.list response. In Enterprise Grid, run this as a scheduled job, not a spreadsheet someone promises to maintain.
Beyond the automated audit:
- Post an announcement in each channel. A bot message saying "40 documents from Quip have been migrated. Click the Canvas tab to browse them." drives more discovery than any naming scheme.
- Pin the announcement. Pinned messages survive the scroll. An announcement from two weeks ago does not.
- Monitor search queries. If your workspace has Slack Enterprise with analytics, watch for queries that return zero results — these indicate naming mismatches or unshared canvases.
What the Migration Has to Build That Canvas Does Not Provide
Slack Canvas is a document surface, not a document management system. It provides a rich-text editor, per-document sharing controls, search with access-scoped results, and a canvas tab in channels.
It does not provide:
- Folders or hierarchies
- Tags or categories
- A browsable index of all canvases
- A bulk listing UI beyond the Files sidebar
- Automatic table-of-contents generation
- Title uniqueness enforcement
- Cross-canvas linking metadata
Every one of those gaps is something the migration has to fill — through naming conventions, index canvases, access grants, and post-migration tooling. If you treat the migration as "copy content, done," the content is technically present but practically lost.
The organizations that get this right treat naming and index generation as first-class migration deliverables, not afterthoughts. That means building these tools during migration planning — while you still have API access to the source system's folder structure and can extract the path hierarchy that drives the naming scheme.
Frequently Asked Questions
- Is there a canvases.list API in Slack?
- No. Slack has no canvases.list endpoint. The only way to enumerate canvases programmatically is files.list with types=canvas. It returns paginated results at 100 per page with a Tier 3 rate limit of 50+ requests per minute.
- Why are my migrated Slack canvases not showing up in search?
- Standalone canvases created by a bot are invisible to all users until access is granted via canvases.access.set. The default access level is 'Invite only,' which excludes the canvas from search results and the canvas browser for anyone not explicitly shared.
- Can a Slack channel have more than one channel canvas?
- No. Each channel supports exactly one channel canvas. Calling conversations.canvases.create when one already exists returns a channel_canvas_already_exists error. Use canvases.edit to update the existing canvas.
- Does Slack search index the full body text of canvases?
- Slack states canvases are searchable, and file search covers titles and document content. However, there is no documented guarantee about indexing depth for very large canvases, indexing latency after bulk creation, or coverage of content inside code blocks or table cells.
- How do you handle duplicate document names when migrating to Slack Canvas?
- Encode the source folder path into the canvas title using a separator like ' > ' (e.g., 'Engineering > RFC Template'). For documents that still collide after path prefixing, append a short hash of the source document ID to guarantee uniqueness.