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.
| Parameter | Value | Why it matters in Sheets |
|---|---|---|
full_page | true | Captures 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. |
width | 1280 | Viewport width. 1280 covers most desktop layouts. Use 390 for mobile captures. |
device | mobile | Full mobile emulation including touch events and mobile user agent. Better than just setting a narrow width. |
block_cookies | true | Strips GDPR consent banners that would otherwise cover 40% of European site screenshots. |
block_ads | true | Removes ad banners. Cleaner screenshots for presentations and reports. |
dark_mode | true | Captures the dark color scheme on sites that support prefers-color-scheme: dark. |
delay | 2000 | Waits 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_ttl | 86400 | Serves the cached version for 24 hours. Cuts API costs when sheets recalculate frequently. |
resize_width | 640 | Shrinks the output image. A 640px thumbnail loads faster in cells and uses less Drive storage than a full 1280px capture. |
format | png/jpeg/webp | JPEG 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 KeyFrequently asked questions
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.
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.