Features Full Page Screenshot Wait for Selector & Delay Block Cookie Banners Custom Viewport & Device Website to PDF HTML to Image Dark Mode Image Format & Quality MCP Server Webhook Pricing Docs Blog Log In Sign Up

Google Sheets Screenshot API — Capture Website Screenshots in Cells

You have a spreadsheet with 200 prospect URLs. Your sales team wants to see what each website looks like without opening 200 tabs. Google Sheets can display images inside cells with the =IMAGE() function, but it can't render a web page. It only accepts direct image URLs like PNGs or JPGs. That gap between "I have a URL" and "I can see the website" is exactly what a screenshot API fills.

ScreenshotRun captures any URL as an image and returns a direct link you can plug into Sheets. Two approaches below: a simple one-off function you run from a menu, and a batch processor for entire URL columns.

Why =IMAGE() alone won't work (and the workaround)

Your first instinct might be =IMAGE("https://api.screenshotrun.com/v1/screenshots/capture?url=...") right in a cell. It won't work. ScreenshotRun authenticates via the Authorization: Bearer header, and Google Sheets' IMAGE function can only send a bare GET request. No custom headers, no Bearer token. Every screenshot API that uses header-based auth has this same limitation.

The workaround: use Apps Script to call the API, save the resulting image to Google Drive, and feed that Drive URL into IMAGE. The Drive URL is a plain image link that IMAGE handles perfectly. That's what both methods below do.

One cell-sizing note before we get into code. Google Sheets cells default to about 100x21 pixels. You'll need to resize rows and columns to see screenshots properly. A row height of 150-200 pixels and column width of 300 pixels works well. Use =IMAGE(url, 1) (mode 1) to stretch the image to fit the cell.

Capture a single URL with a menu button

A quick note on why this isn't a =SCREENSHOT() cell formula: Google Apps Script blocks UrlFetchApp inside custom functions called from cells. It's a security restriction Google enforces on all custom formulas. Any function that makes external HTTP requests has to run from a menu, button, or trigger instead.

Open your spreadsheet, go to Extensions > Apps Script, and paste this:

function onOpen() {
  SpreadsheetApp.getUi()
    .createMenu('Screenshots')
    .addItem('Capture selected URL', 'captureSelectedUrl')
    .addToUi();
}

function captureSelectedUrl() {
  var sheet = SpreadsheetApp.getActiveSheet();
  var cell = sheet.getActiveCell();
  var url = cell.getValue();

  if (!url || !url.toString().match(/^https?:\/\//)) {
    SpreadsheetApp.getUi().alert('Select a cell with a valid URL first.');
    return;
  }

  var apiKey = PropertiesService.getScriptProperties().getProperty('SR_API_KEY');
  if (!apiKey) {
    SpreadsheetApp.getUi().alert('Set SR_API_KEY in Project Settings > Script Properties.');
    return;
  }

  var endpoint = 'https://api.screenshotrun.com/v1/screenshots/capture'
    + '?url=' + encodeURIComponent(url)
    + '&format=png&width=1280'
    + '&block_cookies=true&cache_ttl=86400';

  var response = UrlFetchApp.fetch(endpoint, {
    headers: { 'Authorization': 'Bearer ' + apiKey },
    muteHttpExceptions: true
  });

  if (response.getResponseCode() !== 200) {
    cell.offset(0, 1).setValue('Error: ' + response.getResponseCode());
    return;
  }

  var blob = response.getBlob().setName(url.replace(/https?:\/\//, '') + '.png');
  var file = DriveApp.createFile(blob);
  file.setSharing(DriveApp.Access.ANYONE_WITH_LINK, DriveApp.Permission.VIEW);
  cell.offset(0, 1).setValue('https://drive.google.com/uc?id=' + file.getId());
}

Before running anything, store your API key securely. In the Apps Script editor, click Project Settings > Script Properties > Add, set the key name to SR_API_KEY and paste your API key as the value. Grab a free key from the dashboard if you don't have one yet (no credit card). The first time you run the function, Google will ask you to authorize Drive and external URL access. That's normal for any script that calls an outside API.

Reload the spreadsheet and a Screenshots menu appears in the toolbar. Put a URL in any cell, select it, click Screenshots > Capture selected URL. The function calls the API with Bearer auth (the key never appears in the sheet), saves the screenshot to your Google Drive, and drops a permanent Drive link in the next column.

To see the image inline, wrap the Drive link with IMAGE in a third column: =IMAGE(B2, 1).

Drive URLs don't expire and don't count against your API quota on sheet recalcs. The tradeoff: each screenshot creates a file in your Drive. For large batches, the next section organizes them into a dedicated folder. One security note on setSharing: the code makes each Drive file viewable by anyone with the link. That's necessary for =IMAGE() to render it. If your screenshots contain sensitive data, skip the sharing step and view them directly in Drive instead.

Process an entire URL column in one click

Clicking a menu button per URL works for one-offs. For a column of 50 or 200 URLs, you need a batch function that walks down the list automatically. This one reads every URL in column A, captures it, saves the image to a dedicated Drive folder, and writes the link back to column B.

Add this function to your Apps Script project:

function captureAllUrls() {
  var sheet = SpreadsheetApp.getActiveSheet();
  var data = sheet.getDataRange().getValues();
  var apiKey = PropertiesService.getScriptProperties().getProperty('SR_API_KEY');

  var folder = DriveApp.getFoldersByName('Screenshots').hasNext()
    ? DriveApp.getFoldersByName('Screenshots').next()
    : DriveApp.createFolder('Screenshots');

  for (var i = 1; i < data.length; i++) {
    var url = data[i][0];      // Column A: URLs
    var existing = data[i][1]; // Column B: skip if already captured

    if (!url || !url.toString().match(/^https?:\/\//) || existing) continue;

    try {
      var endpoint = 'https://api.screenshotrun.com/v1/screenshots/capture'
        + '?url=' + encodeURIComponent(url)
        + '&format=png&width=1280&full_page=true'
        + '&block_cookies=true&block_ads=true'
        + '&cache_ttl=86400';

      var response = UrlFetchApp.fetch(endpoint, {
        headers: { 'Authorization': 'Bearer ' + apiKey },
        muteHttpExceptions: true
      });

      if (response.getResponseCode() === 200) {
        var blob = response.getBlob().setName(
          url.replace(/https?:\/\//, '').replace(/[\/\?#]/g, '_') + '.png'
        );
        var file = folder.createFile(blob);
        file.setSharing(DriveApp.Access.ANYONE_WITH_LINK, DriveApp.Permission.VIEW);
        sheet.getRange(i + 1, 2).setValue('https://drive.google.com/uc?id=' + file.getId());
        sheet.getRange(i + 1, 3).setValue(new Date());
      } else {
        sheet.getRange(i + 1, 2).setValue('Error: ' + response.getResponseCode());
      }
    } catch (e) {
      sheet.getRange(i + 1, 2).setValue('Error: ' + e.message);
    }

    Utilities.sleep(1500); // respect rate limits
  }
}

Put your URLs in column A starting from row 2 (row 1 is headers). Run captureAllUrls from the Apps Script editor or add a custom menu to trigger it from the sheet. Column B fills in with Drive links, column C gets timestamps. Already-captured rows are skipped on re-runs, so you can add new URLs to the bottom and run it again without re-processing the entire list.

The Utilities.sleep(1500) adds a 1.5-second pause between requests. That keeps you well within rate limits on any plan. The cache_ttl=86400 parameter means re-running the same list within 24 hours serves cached images instead of burning new captures. For the free tier (200 captures/month), a batch of 200 URLs still uses your entire monthly allowance on the first run. Worth knowing before you hit execute on a 500-row sheet.

About that 6-minute limit: Apps Script kills any function that runs longer than 6 minutes on consumer Google accounts (30 minutes on Workspace). At 1.5 seconds per URL plus capture time, you'll process roughly 80-100 URLs before timeout. For bigger lists, the bulk screenshot guide covers workarounds including splitting into batches with continuation tokens.

Keep screenshots fresh with scheduled triggers

Screenshots go stale. Your competitor redesigns their homepage, a client's SSL certificate expires and the site shows a warning, a product page changes pricing. Manually re-running the batch function every week gets old after the second week.

Apps Script time-driven triggers fix this. In the Apps Script editor, click the clock icon (Triggers), then Add Trigger:

  • Function: captureAllUrls
  • Event source: Time-driven
  • Type: Week timer or Day timer
  • Day/time: pick what fits your workflow

There's a subtlety here. The batch function skips rows that already have a value in column B. For recurring captures, you'll want a modified version that overwrites existing screenshots. Change the skip condition to if (!url || !url.toString().match(/^https?:\/\//)) continue; (removing the existing check) and it will re-capture every URL on each run.

For competitor monitoring specifically, consider keeping a history. Instead of overwriting column B, append each capture to a new sheet tab named with the date. That gives you a visual timeline of how a page evolves week over week. One of our early users ran this on a 30-page product catalog and spotted a supplier who quietly swapped product images three days after a contract renewal.

API parameters that work well in spreadsheets

Every ScreenshotRun parameter works from Google Sheets. These are the ones that matter most for spreadsheet workflows.

ParameterValueWhy it matters in Sheets
full_pagetrueCaptures the entire page, not just the viewport. Useful for archiving but produces tall images that don't preview well in cells. Consider using resize_width to create a thumbnail.
width1280Viewport width. 1280 covers most desktop layouts. Use 390 for mobile captures.
devicemobileFull mobile emulation including touch events and mobile user agent. Better than just setting a narrow width.
block_cookiestrueStrips GDPR consent banners that would otherwise cover 40% of European site screenshots.
block_adstrueRemoves ad banners. Cleaner screenshots for presentations and reports.
dark_modetrueCaptures the dark color scheme on sites that support prefers-color-scheme: dark.
delay2000Waits 2 seconds before capturing. Helps with JS-heavy sites where content loads after the initial render. Most sites don't need this, but SPAs built on React or Next.js often do.
cache_ttl86400Serves the cached version for 24 hours. Cuts API costs when sheets recalculate frequently.
resize_width640Shrinks the output image. A 640px thumbnail loads faster in cells and uses less Drive storage than a full 1280px capture.
formatpng/jpeg/webpJPEG at quality 80 produces files roughly 4x smaller than PNG. For 500-row sheets, that difference in load time is noticeable.

The full API reference covers 30+ parameters including geolocation, stealth mode, CSS injection, and PDF export.

When Google Sheets isn't enough

Sheets works well for lists under 200 URLs with weekly or daily refresh cycles. Past that, you'll feel the friction: Apps Script execution limits, slow recalculations on large sheets, Drive storage filling up with PNG files.

For high-volume or real-time workflows, consider stepping outside the spreadsheet. Zapier and Make.com handle trigger-based captures (new form submission, new CRM record) without code, and both connect to Sheets as a data source. If you're self-hosting, n8n processes large batches with proper rate limiting and no per-execution billing — its Split In Batches node handles 1,000+ URLs without hitting the 6-minute timeout. For full control, direct API calls from Node.js, Python, or PHP with async webhook callbacks let you fire 500 requests and collect results as they finish.

The API endpoint is identical regardless of where the HTTP request comes from. A workflow that starts in Google Sheets can graduate to n8n or a Python script with zero API changes. Only the caller changes.

For a side-by-side look at how different screenshot APIs handle pricing and Google Sheets compatibility, see the screenshot API comparison.

Turn your spreadsheet URLs into screenshots

The free tier includes 200 monthly captures. Paste the Apps Script code above, add your API key in Script Properties, and the first screenshot lands in your Drive within a couple of minutes.

Get Your Free API Key

Frequently asked questions

Not by itself. Google Sheets' IMAGE function only renders direct image URLs (PNG, JPG, GIF). It can't send HTTP headers, and ScreenshotRun authenticates via the Authorization: Bearer header. The workaround: use Apps Script to call the API, save the screenshot to Google Drive, and feed the Drive URL into IMAGE(). The page above walks through exactly how to set this up.
Yes, when you use the Apps Script approach. Store the key in Script Properties (Project Settings > Script Properties) and it never appears in any cell or formula. The key is only accessible to users who can open the Apps Script editor, not to anyone who merely views or edits the spreadsheet.
That depends on your ScreenshotRun plan. The free tier includes 200 captures per month. The Starter plan at $9/month covers 5,000 captures. Apps Script also has its own limits: standalone functions run for up to 6 minutes on consumer accounts (30 minutes on Google Workspace). At 1.5 seconds delay plus 3-5 seconds per capture, expect to process about 80-100 URLs per batch run before timeout.
Yes. Add full_page=true to your API call and the screenshot includes everything below the fold. Keep in mind that full-page captures produce tall images that don't display well in standard-height spreadsheet cells. Use resize_width=640 to create a smaller thumbnail for the cell, and link to the full-resolution version saved in Google Drive.
Set up a time-driven trigger in Apps Script. Go to Triggers > Add Trigger, select your batch function, choose "Time-driven" as the event source, and pick a daily or weekly schedule. The function runs automatically and updates your sheet with fresh captures. Remove the skip-if-exists check from the code if you want to re-capture all URLs on each run.
No. The free tier (200 captures/month) works with both methods on this page. You only need a paid plan if your spreadsheet workflow exceeds 200 monthly captures or you need faster rate limits for large batch processing.