PostPrep
← Blog
2025-08-14

Why =IMAGE() Doesn't Work for Canva Bulk Create (And What Actually Does)

The =IMAGE() formula in Google Sheets looks like it embeds images — it doesn't. Here's why Canva Bulk Create ignores it, plus a working Apps Script solution that actually embeds the images.

Pete B.
Pete B.
Founder, Postprep

If you've tried to use Google Sheets with Canva Bulk Create and added a column of =IMAGE() formulas, you've run into one of the most frustrating surprises in this workflow: Canva completely ignores them.

The images look fine in your sheet. The formula renders a visible preview. Export to XLSX, upload to Canva — blank slots.

Here's exactly what's going wrong and two ways to fix it: a free Apps Script approach for people who want full control, and a faster option for larger batches.


What =IMAGE() Actually Does (and Doesn't Do)

The =IMAGE("https://...") formula in Google Sheets is a display formula. It tells Google Sheets to fetch and render a preview of the image inside the cell while you're viewing the spreadsheet. That's all it does.

The image data never lives in your sheet. There's no binary image data stored in the file — just a formula string that Google's rendering engine resolves in real time. Think of it like a <img src="..."> tag in HTML: the image appears on screen, but the file itself is just a reference.

When you export your Google Sheet to XLSX, that formula becomes either an empty cell or plain text. The XLSX file has no image in it, because there was never an image in the Google Sheet to begin with — just an instruction to go fetch one.

Canva Bulk Create reads the XLSX file directly. It looks for images embedded in cells as DrawingML objects — actual binary image data stored inside the file. It finds the formula output (nothing), so the image slot in your design stays blank.

The =IMAGE() formula is a viewer shortcut, not a storage mechanism. For Canva Bulk Create, you need the real thing.


What Actually Works: Google Apps Script

Google Sheets has a separate API for in-cell images, accessible via Apps Script: SpreadsheetApp.newCellImage(). Unlike =IMAGE(), this actually stores image data in the cell. When you export to XLSX, the image travels with the file — and Canva can read it.

Here's a working script that reads image URLs from one column and embeds the actual images into another column:

function embedImages() {
  const sheet = SpreadsheetApp.getActiveSheet();
  const startRow = 2;  // adjust to your first data row
  const urlCol = 5;    // column E — change to match your URL column
  const imgCol = 19;   // column S — change to your target image column
  const lastRow = sheet.getLastRow();

  const urls = sheet.getRange(startRow, urlCol, lastRow - startRow + 1, 1).getValues();

  urls.forEach((row, i) => {
    const url = row[0];
    if (!url || !url.toString().startsWith('http')) return;

    try {
      const image = SpreadsheetApp.newCellImage()
        .setSourceUrl(url)
        .build();
      sheet.getRange(startRow + i, imgCol).setValue(image);
      Utilities.sleep(150); // avoid hitting Google's rate limits
    } catch (e) {
      sheet.getRange(startRow + i, imgCol).setValue('ERR: ' + e.message);
    }
  });
}

Before you run it, update two numbers

urlCol is the column number where your image URLs live. Column A = 1, B = 2, C = 3 … E = 5, G = 7, J = 10, S = 19. In the example screenshots below, URLs are in column E (5).

imgCol is where you want the embedded images to appear. In the example, that's column S (19) — a new column added to the right of existing data.

startRow should be 2 if your first row is a header. Change it to 1 if data starts on row 1 with no header.


Step-by-Step Walkthrough

Step 1: Open your Google Sheet

Your sheet needs a column of image URLs — any publicly accessible URLs work. Here's what a typical sheet looks like before running the script, with image URLs visible in column E:

Google Sheet with image URL column visible

Step 2: Open the Apps Script editor

In the menu bar, go to Extensions → Apps Script.

Extensions menu open with Apps Script highlighted

This opens the Apps Script editor in a new browser tab with a default empty function.

Step 3: Paste the script

Delete the default content and paste the full embedImages function. The editor will look like this after pasting:

Apps Script editor with the embedImages script pasted in

Update urlCol and imgCol to match your actual sheet. The arrows below highlight exactly which two lines to change:

Apps Script editor with arrows pointing to urlCol and imgCol lines

Step 4: Authorize the script

Click the Run button (▶). The first time, Apps Script asks for authorization. Click Review permissions:

Authorization required dialog with Review permissions button

Google will show an "unverified app" warning — this is expected for any personal script that hasn't been submitted to Google for review. Click Advanced, then Go to [project name] (unsafe):

Google unverified app warning showing Advanced and Go to unsafe link

On the next screen, click Continue to grant access to your spreadsheet:

Google permissions consent screen with Continue button

Step 5: Watch the execution log

The script runs and the execution log shows progress:

Execution log showing Execution started and Execution completed

For larger sheets the gap between start and completion will be longer — the Utilities.sleep(150) between rows prevents rate limit errors but adds time.

Step 6: Check your sheet

Switch back to your Google Sheet. The target column now has real embedded images — not formulas, not URLs. Column S is populated with the actual images:

Google Sheet with embedded images visible in column S

This is fundamentally different from =IMAGE(). Click one of the image cells — the formula bar shows nothing, because there's no formula. The image data is stored directly in the cell.

Step 7: Export as XLSX and upload to Canva

Go to File → Download → Microsoft Excel (.xlsx). Upload the downloaded file to Canva Bulk Create — your images will appear correctly in every generated design.


Limitations of the Apps Script Approach

This works well and it's completely free. But it has real friction worth knowing:

It's slow for large sheets. The sleep delay is necessary to avoid rate limits. 100 rows takes 2-3 minutes. 500 rows takes 15+ minutes.

It needs re-run on new rows. Add new data, run the script again. There's no auto-update.

Column numbers need updating if your sheet structure changes. Easy to forget when you add or reorder columns.

The unverified app warning trips up non-technical teammates. Anyone sharing the workflow will hit friction at the authorization step.

For occasional use on sheets under 100 rows, this is the right free solution. For regular use or larger batches, the friction compounds quickly.


The Faster Alternative

Postprep does the same conversion as a web app — no script setup, no column tracking, no rate limit waits. Upload your CSV or XLSX, select your image column, download a Canva-ready XLSX.

Free for up to 100 rows. One-time unlock for larger files.

The Apps Script approach is the right call if you want to stay inside Google Sheets and can maintain a script. For everyone else, Postprep handles it in one step.


Frequently Asked Questions

Why does Google warn me the app is "unverified"?

Any personal Apps Script triggers this until it's submitted to Google for verification — not worth doing for a script you're only running yourself. Click Advanced → Go to [project name] (unsafe) to proceed. The script only touches your own spreadsheet.

Can I modify the script to skip rows that already have an image?

Yes — before the setValue call, read the existing value in imgCol for that row and skip it if it's already non-empty. That way re-running the script doesn't redo work already done.

Some cells show ERR: ... instead of an image. What happened?

The URL in that row wasn't reachable — expired, behind authentication, or blocked. Fix the URL and re-run for those rows.

What's the column number for my column?

A=1, B=2, C=3, D=4, E=5, F=6, G=7, H=8, I=9, J=10 … S=19, T=20. Click the column header letter in your sheet — the column letter shows in the Name Box (top left).


Summary

=IMAGE() is a display formula — it renders a preview but stores nothing. Canva Bulk Create reads the XLSX, finds no image data, and leaves the slot blank.

The Apps Script approach using newCellImage() actually embeds image data in each cell. The resulting XLSX export contains real images that Canva reads correctly. It's free, it works, and it's the right manual solution for this problem.

For larger batches or a simpler setup, Postprep handles the conversion without a script.

Try it free

Canva Bulk Create with real embedded images.

Upload your CSV or XLSX. Select your image column. Download a Canva-ready file. Free for up to 100 rows — no account needed.

Try Postprep →
Pete B.

Written by

Pete B.

Pete is a small business owner who got tired of social media eating his evenings. Posting product graphics across channels used to mean opening Canva, dropping in each image by hand, and repeating that for every listing and every platform. He kept hitting the same wall: Canva Bulk Create can save hours, but it refuses to read image URLs from a spreadsheet — turning a 5-minute workflow into a 2-hour copy-paste job. So he built Postprep to fix that one specific limitation, and writes about bulk content workflows, design tool limitations, and shipping social content without burning out.

Discussion