Launched:self-serve migrations intoSuperhuman Docs (Coda)
Try it now
01Agent-first
Runs where you already work
Plug it into Claude, ChatGPT or Cursor. Describe the move in plain English; the agent runs it.
02Engineer-led
Our production engine, unlocked
The pipeline our engineers use on managed enterprise migrations — the same code, now something you can drive yourself.
03Pricing
Try 10 pages free, then $1 a page
Credit-based, pay-as-you-go. No scoping call, no quote — sample it on your own docs before you spend anything.
04Sources
NotionSlabConfluenceSoonGoogle DocsSoon
Skip to content

Slack Canvas to Google Docs Migration: API Limits & Data Mapping

Technical guide to migrating Slack Canvas to Google Docs via the API: index drift in batchUpdate, heading mapping, table rebuilding, image hosting, rate limits, and edge cases.

Roopendra Talekar Roopendra Talekar · · 18 min read
Slack Canvas to Google Docs Migration: API Limits & Data Mapping
TALK TO AN ENGINEER

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

Slack Canvas to Google Docs Migration: API Limits & Data Mapping

There is no native export tool to convert a Slack Canvas into a Google Doc — no Google importer, no Slack exporter that produces .gdoc files. You extract canvas content as markdown via Slack's canvases.getContent method, parse it, and rebuild each document through the Google Docs API's documents.batchUpdate endpoint using index-based insert requests. The central engineering challenge is index drift: every insertion shifts subsequent character positions, corrupting formatting unless you order your requests deliberately.

This guide covers the exact API mechanics, data mapping, size constraints, rate limits, required OAuth scopes, and error codes you will hit when building this pipeline. If you are moving an entire Slack organization, see Slack Enterprise Grid Migration: The Complete 2026 Technical Guide. If you need background on exporting content from Google Docs afterward, see How to Export Data from Google Docs. If you are migrating in the opposite direction, see Google Docs to Slack Canvas Migration: The Technical Guide.

Required OAuth scopes for both APIs

Before writing a single line of migration code, confirm your tokens have every scope the pipeline needs. Missing a scope produces a 403 that looks identical to a permission error, wasting debugging time.

Slack scopes

Scope Token Type Required For
files:read User token files.list (full workspace visibility)
canvases:read Bot or User canvases.getContent
channels:read Bot or User Resolving linked_channel_id metadata
users:read Bot or User Resolving @mention display names
files:read Bot or User Downloading image binaries via files.info

Use a user token from a Workspace Admin for inventory. Bot tokens only see canvases in channels the bot has joined, and standalone canvases require explicit sharing. A bot-token inventory will silently under-count.

Google scopes

Scope Required For
https://www.googleapis.com/auth/documents documents.create, documents.batchUpdate, documents.get
https://www.googleapis.com/auth/drive Drive file creation, permission modification
https://www.googleapis.com/auth/drive.file Narrower alternative if you create all files via the API

Service account vs. OAuth user token for Google. Rate limits on the Google side are attributed per user per project. A service account running as a single identity is subject to the 60-writes-per-minute-per-user cap regardless of how many threads call it. If you need higher throughput, use domain-wide delegation to impersonate multiple users and distribute write load across accounts — each impersonated identity gets its own 60-writes-per-minute bucket.

How to inventory all Slack Canvases before migration

There is no canvases.list endpoint. To enumerate all canvases in a workspace, use the files.list method filtered to the canvas type (Slack canvas surface docs):

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

The endpoint returns a paginated list of file objects, each including a canvas_id (prefixed with F), title, timestamps, and ownership metadata. The file object also exposes canvas-specific fields like is_channel_space and linked_channel_id, which help you separate standalone canvases from channel-linked ones during mapping.

files.list is Tier 3 on Slack's rate limit scale — 50+ requests per minute per app per workspace. With the default page size of 100, that is roughly 5,000 canvas records per minute before you start hitting 429s. For large Enterprise Grid workspaces, paginate with the page parameter and implement retry logic using Retry-After headers.

One documentation edge case worth noting: the generic files.list reference only says its type list is not exhaustive. The canvas surface guide is the page that actually documents types=canvas. Trust the canvas guide for this pair.

Warning

Bot tokens only see canvases they have access to. A bot must be invited to a channel to discover that channel's canvas, and standalone canvases require explicit sharing. Run the inventory with a user token (files:read scope) from a Workspace Admin to get a complete count. If you skip the access audit, your inventory will be incomplete.

Once you have the full list, pull each canvas's content with canvases.getContent:

POST https://slack.com/api/canvases.getContent
{"canvas_id": "F08ABC1234X", "content_type": "markdown"}

canvases.getContent is also Tier 3 (50+ per minute) and returns the entire canvas in a single content string without pagination — which makes preflight size checks and checksum validation straightforward. For a workspace with 500 canvases, the read phase alone takes at least 10 minutes if you respect rate limits conservatively.

canvases.getContent returns the current published version only. Slack canvas revision history is not accessible through standard canvas API methods. If you need to preserve edit history, that data cannot be migrated through this pipeline — it is a hard limitation of Slack's API surface, not a solvable engineering problem.

The index drift problem in Google Docs batchUpdate

Index drift is the phenomenon where inserting content at one position in a Google Doc shifts every subsequent index forward, causing later requests in the same batch to target the wrong positions. Google's own best practices state it plainly: within a single call to documents.batchUpdate, order your requests in descending order of index location to eliminate the need to compute index changes.

Docs indexes are measured in UTF-16 code units, not characters. Emoji and other non-BMP characters consume two index positions. Emoji-heavy canvases will break naive character counts (Google Docs API: Move text).

Here is a concrete example of the problem:

# WRONG: top-to-bottom order causes drift
requests = [
    {"insertText": {"text": "First heading\n", "location": {"index": 1}}},
    {"insertText": {"text": "Second heading\n", "location": {"index": 16}}},
    {"insertText": {"text": "Third heading\n", "location": {"index": 32}}},
]

The first insert lands correctly at index 1. But it pushes every subsequent index forward by the length of the inserted text. The second insert now targets index 16, which may land in the middle of the first paragraph. The third is even further off. If your pipeline builds a document top-to-bottom without recalculating offsets, formatting requests will apply to the wrong words, splitting sentences and breaking tables.

Insert content from the bottom of the document to the top. Each insert lands at a position that has not been shifted because nothing has been inserted below it:

# Build in document order, then reverse before sending
requests = [
    {"insertText": {"text": "First heading\n", "location": {"index": 1}}},
    {"insertText": {"text": "Second heading\n", "location": {"index": 1}}},
    {"insertText": {"text": "Third heading\n", "location": {"index": 1}}},
]
requests.reverse()

The complexity is not reversing an array — it is computing correct index offsets for every formatting request (bold, italic, heading styles) that must pair with the text it references. Tables and images compound this because they occupy variable amounts of index space.

Append with endOfSegmentLocation

For simple append-only document builds, insert each element at the end of the document body. This sidesteps index math entirely:

requests = [
    {"insertText": {"text": "First\n", "endOfSegmentLocation": {"segmentId": ""}}},
    {"insertText": {"text": "Second\n", "endOfSegmentLocation": {"segmentId": ""}}},
    {"insertText": {"text": "Third\n", "endOfSegmentLocation": {"segmentId": ""}}},
]

The catch: endOfSegmentLocation only appends. The moment you need to insert a table or image between existing paragraphs, or apply formatting to a range with updateParagraphStyle, you are back to explicit indexes. And formatting always requires explicit index ranges — you cannot style a heading without knowing its startIndex and endIndex.

For new-document creation, the append-only approach with a tracked UTF-16 cursor is often easier. Reverse ordering becomes essential when styling existing ranges or backfilling content into structures you have already created.

Retry and backoff for Google Docs API errors

Do not rely on linear polling. The correct retry pattern for batchUpdate calls:

import time, random
 
def batchUpdate_with_retry(service, doc_id, requests, max_retries=5):
    base_delay = 1.0  # seconds
    for attempt in range(max_retries):
        try:
            return service.documents().batchUpdate(
                documentId=doc_id, body={"requests": requests}
            ).execute()
        except HttpError as e:
            if e.resp.status == 429:
                delay = base_delay * (2 ** attempt) + random.uniform(0, 1)
                time.sleep(delay)
            elif e.resp.status in (500, 503):
                time.sleep(base_delay * (2 ** attempt))
            else:
                raise  # 400, 403, 404 are not retryable
    raise Exception("Max retries exceeded")

Key points: 429 (rate limit) and 5xx (transient server errors) are retryable with exponential backoff plus jitter. 400, 403, and 404 are not retryable — they indicate a structural problem with the request or permissions that will not resolve on retry. Looping on a 400 wastes quota and delays detection of real bugs.

Info

Engineering cost of reverse ordering. For a simple markdown-to-Docs converter, reverse ordering adds roughly two days of work. The real time is spent computing correct index offsets for every formatting request that must pair with the text it references. Tables and images compound this because they occupy variable amounts of index space. Budget for this up front.

Expected HTTP error codes and their migration-specific causes

Understanding which error codes are structural versus transient prevents retry loops on unfixable problems and speeds up debugging.

HTTP Code API Specific Cause in This Pipeline Retryable?
400 Bad Request Google Docs Document exceeds ~1,020,000 character limit; malformed index in batchUpdate; invalid namedStyleType value No
403 Forbidden Google Docs Image URI not publicly accessible during InsertInlineImageRequest; missing OAuth scope; Drive file permission not yet propagated No (fix permission first)
403 Forbidden Slack Bot token accessing canvas without channel membership No (fix access first)
404 Not Found Google Docs documentId created but deleted between steps; stale canvas_id from inventory No
429 Too Many Requests Slack Exceeded Tier 3 (50+/min) on files.list or canvases.getContent; check Retry-After header Yes
429 Too Many Requests Google Docs Exceeded 60 writes/min (user) or 600/min (project); check Retry-After or use 60s default Yes
500 Internal Server Error Google Docs Transient Google infrastructure error; safe to retry with backoff Yes
503 Service Unavailable Either Temporary service outage Yes

The most common silent failure in this pipeline: a 403 on InsertInlineImageRequest because the Drive file permission was set but not yet propagated. Google's permission propagation is not instantaneous — adding a 2–3 second sleep between the Drive permissions.create call and the Docs batchUpdate call eliminates most of these.

How to map Slack Canvas headings to Google Docs named styles

Slack Canvas markdown supports three heading levels: # h1, ## h2, and ### h3. These are the only heading levels available in canvases; h4 through h6 are not supported due to Slack Canvas's simpler architecture.

Google Docs supports six named paragraph styles: HEADING_1 through HEADING_6. Map them directly and leave the extra levels unused:

Canvas Markdown Google Docs Named Style Notes
# Heading HEADING_1 Direct 1:1 map
## Heading HEADING_2 Direct 1:1 map
### Heading HEADING_3 Direct 1:1 map
(not available) HEADING_4–HEADING_6 Unused — no canvas source

Do not invent sub-heading levels that did not exist in the source. If a canvas author used only h1 and h3 (skipping h2), preserve that structure exactly. The goal is fidelity, not aesthetics.

Applying a heading requires two operations in your batchUpdate payload: one to insert the plain text, and a subsequent UpdateParagraphStyleRequest to apply the named style to the exact index range:

{
    "updateParagraphStyle": {
        "range": {"startIndex": start, "endIndex": end},
        "paragraphStyle": {"namedStyleType": "HEADING_1"},
        "fields": "namedStyleType"
    }
}

How to migrate Slack Canvas tables into Google Docs

Slack Canvas tables are capped at 300 cells per table — any combination of rows and columns that totals 300. They support plain text, links, checkboxes, and basic formatting, but no formulas, no merged cells, and no nested tables. Treat them as static layout, not spreadsheet data.

Google Docs tables are structural elements with their own start and end indexes. You cannot insert a table and fill its cells in the same batchUpdate; Google requires you to insert the table first, then send a separate update to write cell content (Google Docs API: Tables). The sequence:

  1. Insert the table structure with InsertTableRequest at a specific index, specifying rows and columns.
  2. Read back the document with documents.get to find the index of each cell's paragraph element.
  3. Insert text into cells in a second batchUpdate, working in reverse document order to avoid index drift.
# Step 1: Insert a 3x2 table at index 1
{"insertTable": {"rows": 3, "columns": 2, "location": {"index": 1}}}
 
# Step 2: Refetch document to get cell indexes
# Step 3: Fill cells in reverse order in a second batchUpdate
cell_requests = [
    {"insertText": {"text": "C3", "location": {"index": cell_6_idx}}},
    {"insertText": {"text": "C2", "location": {"index": cell_5_idx}}},
    # ... continuing in reverse
]

The refetch between step 1 and step 3 costs one read request per table. For a canvas with 10 tables, that is 10 extra documents.get calls against your 300-reads-per-minute-per-user quota. You can compute cell indexes arithmetically from the table's startIndex since each empty cell occupies a known number of index positions — but this is fragile and breaks if any prior request modified the document structure.

Tip

Batch the table fill with reversal. The most reliable approach: insert the table in one batchUpdate, immediately documents.get to learn the structure, then fill all cells in a second batchUpdate with requests sorted in descending index order. Two API calls per table, zero index drift.

Canvas table checkboxes become plain text ☐ / ☑ in Google Docs since Docs has no native checkbox in table cells. If a team was using a canvas table as a lightweight tracker, the safe target in Google Docs is still just a table, not a Sheets-like model.

How to handle images from Slack Canvas in Google Docs

The Google Docs API does not accept binary image uploads. The InsertInlineImageRequest requires a uri field — a publicly accessible HTTPS URL. The URI must be under 2 KB in length, and the image must be PNG, JPEG, or GIF, under 50 MB, and no more than 25 megapixels. You cannot POST image bytes directly through batchUpdate.

Slack Canvas images are referenced in the markdown as standard image syntax pointing to Slack-hosted URLs:

![graph](https://your_workspace.slack.com/files/U071SRU8BA7/F073FVDABQS/image.png)

These URLs require authentication (Authorization: Bearer xoxb-...). Google's servers cannot fetch them. Two options:

Option A: Upload to Google Drive, then insert by Drive URL. This is the most reliable approach. The pipeline:

  1. Download the binary image from Slack using the authenticated files.info download URL.
  2. Upload it to Google Drive via the Drive API (drive.files.create).
  3. Set the Drive file permissions to anyone with the link can view temporarily using drive.permissions.create with role: reader and type: anyone.
  4. Wait 2–3 seconds for permission propagation before proceeding.
  5. Extract the webContentLink from the Drive API response.
  6. Pass that link into the Google Docs API InsertInlineImageRequest.
  7. Revoke the public permission on the Drive file using drive.permissions.delete.

If you skip the permission modification or proceed immediately without the propagation delay, the Google Docs API will return a 403 Forbidden error when attempting to fetch the image URI.

Option B: Host on a temporary public URL. Upload images to a cloud storage bucket (GCS, S3) with a short-lived signed URL, insert them into the document, then revoke access. The Docs API fetches and embeds the image at insertion time, so the URL only needs to be live during the batchUpdate call.

Either way, parse every ! [alt](url) reference in the canvas markdown, resolve the image, host it, and replace the markdown reference with an InsertInlineImageRequest at the correct index position. For a canvas with 15 images, this adds 15 download + upload round trips before you start building the document. For a deeper treatment of image migration strategy, see How to Migrate Images, Attachments & Embeds Without Broken Links.

Size limits: Canvas document_content vs. Google Docs characters

A Slack Canvas document_content object is capped at 1,048,576 characters (1 MiB). A Google Doc is limited to approximately 1,020,000 characters (1.02 million). The gap is roughly 28,576 characters.

Platform Character Limit Notes
Slack Canvas 1,048,576 (1 MiB) Per document_content object
Google Docs ~1,020,000 (1.02M) Includes structural elements like paragraph markers

These two ceilings are close enough that most canvases fit, but the near-identical limits create a specific architectural risk. A canvas near the 1 MiB limit could exceed the Google Docs ceiling after conversion — especially if the transformation adds characters. Expanding Slack emoji shortcodes (:large_green_circle:) into Unicode, or the hidden newline characters (\n) required to terminate every paragraph in Google Docs, can push you over. The batchUpdate request will return a 400 Bad Request error citing quota limits.

Info

Character counting differs between platforms. Slack counts raw markdown characters. Google Docs counts rendered text plus structural elements like paragraph markers and table cell boundaries. Count characters in UTF-16 code units before creating the target Doc — emoji shortcodes expanded to Unicode may occupy two UTF-16 code units each. Split oversized canvases before the first write attempt, not after the first 400 error.

In practice, canvases at 95%+ capacity are rare. But your pipeline should preflight the output character count before the final batchUpdate and flag documents over the limit. The only workaround for oversized documents is splitting them, which destroys cross-references and internal links.

What happens to Slack Canvas comments during migration

Slack Canvas comments live as messages in the canvas's associated Slack channel conversation — they are not standalone structured objects with text-range anchors. You can retrieve comment text from the channel conversation using conversations.replies, but the anchoring information that ties a comment to a specific section of the canvas content is not preserved in the export.

The Google Docs API supports programmatically creating anchored comments via the Drive API's comments.create endpoint, which accepts an anchor field referencing a text range. However, the problem is on the Slack side: Slack's standard canvas methods do not expose comment text paired with stable text-range anchors. Even with Drive API comment creation capability, you cannot reconstruct which paragraph or sentence a comment was attached to.

Your realistic options:

  • Migrate comments as unanchored Drive comments. Use the Drive API's comments.create endpoint to create a comment on the Google Doc for each canvas comment, including the original author and timestamp in the comment body. The reader loses spatial context.
  • Append comments as a footnote section. Add a "Comments" section at the end of the Google Doc with each comment's text, author, and timestamp. Low-tech but preserves all content.
  • Drop comments entirely. If the canvases are primarily reference documents where comments were transient discussion, this may be acceptable. Confirm with stakeholders first.

None of these preserve the original anchoring. This is a hard limitation of Slack's data model, not a solvable engineering problem.

Rate limits on both sides of the pipeline

The migration pipeline hits two separate rate limit regimes simultaneously.

Slack API rate limits (source)

Slack uses a tiered system, applied per API method, per app, per workspace:

Method Tier Minimum Rate
files.list Tier 3 50+ per minute
canvases.getContent Tier 3 50+ per minute
files.info Tier 4 100+ per minute
conversations.replies Tier 3 50+ per minute

Slack returns HTTP 429 with a Retry-After header when you exceed a tier. Always read the Retry-After value from the response header rather than assuming a fixed delay — Slack's actual retry window varies. The + in "50+ per minute" means Slack tolerates short bursts above the floor, but do not design around burst capacity for a sustained migration.

Google Docs API rate limits (target)

Google Docs enforces two layers of rate limits per Google Cloud project:

Operation Per Project Per User Per Project
Read requests 3,000/min 300/min
Write requests 600/min 60/min

The per-user limit is the binding constraint for migration scripts running as a single service account. At 60 writes per minute per user, and assuming 2 batchUpdate calls per document (structure + cell fill), you can migrate roughly 30 documents per minute. A workspace with 500 canvases takes approximately 17 minutes of pure write time, ignoring reads and retries. A workspace with 10,000 canvases reaches nearly six hours of continuous API execution.

To increase Google throughput beyond 60 writes/min: use domain-wide delegation to impersonate multiple user accounts. Each delegated user identity receives its own 60-writes-per-minute bucket. Two delegated users give you 120 writes/min effective throughput; ten give you 600/min (matching the project ceiling). You will hit the 600-writes-per-minute project cap before you hit per-user limits at approximately 10 parallel identities.

Info

batchUpdate counts as one write request. A single batchUpdate call containing 200 insert operations still counts as one request against the 60-writes-per-minute quota. Pack as many operations as possible into each batch. This is the single biggest throughput lever on the Google side.

Combining both sides: the Slack read phase (50 canvases/min at Tier 3) is slower than the Google write phase (30 documents/min with a single identity), so Slack is your bottleneck for most workspaces unless you use multiple Google identities. Your migration script must implement exponential backoff and jitter on both APIs — see the retry pattern in the index drift section above.

The full migration pipeline

  1. Inventory — files.list?types=canvas with user token to enumerate all canvases. Page through results. Record canvas_id, title, linked_channel_id, owner, and timestamps.
  2. Extract — canvases.getContent for each canvas, requesting markdown format. Preflight character count in UTF-16 code units. Flag documents over ~900,000 UTF-16 units for manual review.
  3. Parse — Walk the markdown AST. Identify headings (h1–h3), tables, images, code blocks, lists, bold/italic ranges, links, emoji shortcodes, and mentions. Maintain a UTF-16 cursor throughout.
  4. Resolve images — Download each image from Slack (authenticated via files.info), upload to Google Drive or a public staging bucket. Complete permission setup before building the Docs payload.
  5. Create target doc — documents.create via the Google Docs API. Record the returned documentId.
  6. Build content — Assemble all batchUpdate requests: text insertions, heading styles, inline images. Order requests in descending index order.
  7. Handle tables — Insert table structures first, refetch the document via documents.get to get cell indexes, then fill cells in a second batchUpdate with reverse-ordered requests.
  8. Migrate metadata — Set permissions via Drive API. Migrate comments as unanchored Drive comments or a footnote section.
  9. Validate — Compare source canvas heading count, table count, image count, link count, and UTF-16 character count against the output document. Log any mismatches as migration warnings rather than silent failures.

Steps 3 through 7 are where the engineering time lives. Parsing canvas markdown is straightforward — any markdown parser handles it. Generating correct, reverse-ordered batchUpdate requests with paired formatting ranges, handling the table refetch cycle, and managing image authentication handoffs is the part that takes days, not hours.

Edge cases that break migrations silently

  • Slack emoji shortcodes. Canvas markdown contains :emoji_name: syntax. Google Docs does not render these. Convert to Unicode equivalents where possible; each expanded emoji may consume 2 UTF-16 code units. Log unconvertible shortcodes rather than silently dropping them.
  • Channel and user mentions. Canvas markdown uses ! [](#C073UAJRW4R) for channel mentions and ! [](@U071CCRCVFH) for user mentions. These have no Google Docs equivalent — convert to plain text like #channel-name or @username by resolving IDs against conversations.list and users.list respectively.
  • Code blocks. Canvas supports fenced code blocks. Google Docs has no native code block element. Apply a monospace font and optionally a background color via updateTextStyle and updateParagraphStyle.
  • Checklists. Canvas supports - [ ] and - [x] checkbox syntax. Google Docs has checklist support applied via updateParagraphStyle with a bullet preset CHECKBOX — not the same as inserting markdown checkboxes. The implementation path differs from a simple text insertion.
  • Column layouts. Canvas supports a flexbox-based column layout. Google Docs has no column layout in the document body. Content from multi-column layouts must be linearized, which changes the reading order.
  • Drive permission propagation delay. As noted in the image section, setting a Drive file to public and immediately using its URL in InsertInlineImageRequest produces a 403. Add a 2–3 second delay between the permissions.create call and the batchUpdate that uses the URL.

When to build this yourself

For a workspace with fewer than 50 simple canvases — mostly text, few tables, no images — a senior engineer can build a working pipeline in 3–5 days. The reverse-ordering logic, table refetch cycle, and image staging are the time sinks.

For workspaces with hundreds of canvases, complex tables, embedded images, and comments that need preserving, the engineering investment grows non-linearly. Each edge case — emoji conversion, mention resolution, checklist mapping, column linearization, permission propagation handling — adds a day or more. At 200+ canvases with mixed content, expect 2–3 weeks of focused engineering work including validation.

Writing a script to pull markdown from Slack is a weekend project. Writing a deterministic layout engine that parses markdown, calculates exact UTF-16 string lengths, models Google Docs table cell boundaries, manages image authentication handoffs, handles Drive permission propagation timing, and executes reverse-ordered batchUpdate requests is a multi-week engineering commitment.

Frequently Asked Questions

Is there a native way to migrate Slack Canvas to Google Docs?
No. There is no built-in migration path. You must extract canvas content as markdown via canvases.getContent, transform it, and rebuild each document through the Google Docs API's documents.batchUpdate endpoint.
What is index drift in the Google Docs API?
Index drift occurs when inserting content at one position shifts all subsequent character indexes forward. Google's documentation recommends ordering batchUpdate requests in descending index order to avoid this. Failing to handle it corrupts formatting and misplaces content.
How do you list all Slack Canvases via the API?
There is no canvases.list endpoint. Use files.list filtered to type canvas (GET https://slack.com/api/files.list?types=canvas). This is a Tier 3 method allowing 50+ requests per minute per workspace.
Can you embed images directly into Google Docs via the API?
No. The InsertInlineImageRequest requires a uri field with a publicly accessible HTTPS URL. You must first upload Slack-hosted images to Google Drive or a public storage bucket, then reference that URL in the insert request.
What are the character limits for Slack Canvas vs Google Docs?
Slack Canvas document_content is capped at 1,048,576 characters (1 MiB). Google Docs is limited to approximately 1.02 million characters. The gap is roughly 28,000 characters, so most canvases fit, but near-limit documents should be checked before conversion.

More from our Blog