How to Use Screenshot.rocks with Google Sheets to Capture Website URLs in Bulk
Screenshot.rocks styles screenshots; Google Sheets needs a separate renderer to capture website URLs in bulk. Connect Sheets to a screenshot API with Apps Script.
Short answer: Screenshot.rocks is for styling and exporting screenshots you already have. Its homepage does not document bulk URL capture or a Google Sheets integration. To capture URLs in bulk, keep one URL per row in Google Sheets, use Apps Script to send each URL to a screenshot-rendering service, then write the result or its status back to the sheet. You can use Screenshot.rocks afterward to present selected captures as mockups. Screenshot.rocks says image processing for its editor happens in your browser and images are not stored on its servers; that statement does not describe the separate renderer, Google Drive, or any other provider.
How the workflow fits together
A website URL is not an image. Google Sheets’ IMAGE function displays an image from an image URL; it does not visit a webpage and take a screenshot. The capture step must happen first, through a rendering service that can load the page and return image bytes or an accessible image URL.
- Put one complete URL in each row of a Google Sheet.
- Run Apps Script, which reads the URLs and sends requests to a screenshot provider using
UrlFetchApp. - Save each returned image to an appropriate destination or use the provider’s returned image URL.
- Write a result link and status back to that URL’s row. Optionally display accessible image URLs with
IMAGE. - Open selected captures in Screenshot.rocks if you want to add a background, browser frame, device frame, or annotations.
The spreadsheet and Apps Script orchestrate the job. The screenshot provider renders the sites. Check the provider’s current documentation for endpoint, authentication, response format, full-page support, limits, storage, retention, and pricing before adapting the example below.
Prepare the spreadsheet and Apps Script
Create a sheet named Captures with these headers in row 1:
URL | Image URL | Status | Captured at
Enter one complete URL per row in column A, starting at row 2. Use a custom menu to run the capture from the spreadsheet. The example is runnable after you fill in the provider-specific endpoint, authentication, and response handling. Those details vary; there is no universal screenshot API response schema.
Store the API key
In Apps Script, open Project Settings and add a script property named SCREENSHOT_API_KEY. Keep the key out of cells, formulas, and source control. Script properties are accessible to project editors, so limit editor access and rotate the key if it is exposed.
Apps Script batch runner
const SHEET_NAME = 'Captures';
const FIRST_DATA_ROW = 2;
const URL_COLUMN = 1;
const IMAGE_URL_COLUMN = 2;
const STATUS_COLUMN = 3;
const CAPTURED_AT_COLUMN = 4;
const BATCH_SIZE = 10;
function onOpen() {
SpreadsheetApp.getUi()
.createMenu('Screenshots')
.addItem('Capture next batch', 'captureNextBatch')
.addToUi();
}
function captureNextBatch() {
const sheet = SpreadsheetApp.getActive().getSheetByName(SHEET_NAME);
if (!sheet) throw new Error(`Sheet not found: ${SHEET_NAME}`);
const lastRow = sheet.getLastRow();
if (lastRow < FIRST_DATA_ROW) return;
const rowCount = lastRow - FIRST_DATA_ROW + 1;
const values = sheet.getRange(FIRST_DATA_ROW, 1, rowCount, 4).getValues();
const apiKey = PropertiesService.getScriptProperties()
.getProperty('SCREENSHOT_API_KEY');
if (!apiKey) throw new Error('Set SCREENSHOT_API_KEY in Script Properties.');
let processed = 0;
for (let index = 0; index < values.length && processed < BATCH_SIZE; index++) {
const row = FIRST_DATA_ROW + index;
const url = String(values[index][URL_COLUMN - 1] || '').trim();
const existingImage = values[index][IMAGE_URL_COLUMN - 1];
const status = String(values[index][STATUS_COLUMN - 1] || '').trim();
// One-off runs skip completed rows. Clear the result/status to recapture.
if (!url || (existingImage && status === 'OK')) continue;
if (!/^https?:\/\//i.test(url)) {
sheet.getRange(row, STATUS_COLUMN).setValue('ERROR: enter a complete http(s) URL');
processed++;
continue;
}
try {
const result = requestScreenshot_(url, apiKey);
sheet.getRange(row, IMAGE_URL_COLUMN).setValue(result.imageUrl);
sheet.getRange(row, STATUS_COLUMN).setValue('OK');
sheet.getRange(row, CAPTURED_AT_COLUMN).setValue(new Date());
} catch (error) {
sheet.getRange(row, STATUS_COLUMN).setValue(`ERROR: ${String(error.message || error).slice(0, 400)}`);
}
processed++;
}
}
function requestScreenshot_(targetUrl, apiKey) {
// Replace this illustrative request with the provider's documented contract.
const endpoint = 'https://YOUR_PROVIDER_ENDPOINT';
const response = UrlFetchApp.fetch(endpoint, {
method: 'post',
contentType: 'application/json',
headers: { Authorization: `Bearer ${apiKey}` },
payload: JSON.stringify({ url: targetUrl, format: 'png' }),
muteHttpExceptions: true
});
const code = response.getResponseCode();
const body = response.getContentText();
if (code < 200 || code >= 300) {
throw new Error(`Provider returned HTTP ${code}: ${body.slice(0, 250)}`);
}
// Adapt the property name to the provider's documented response schema.
const data = JSON.parse(body);
if (!data.image_url) throw new Error('Response did not include image_url');
return { imageUrl: data.image_url };
}
Replace the placeholder endpoint, method, headers, request body, and image_url parsing with the selected provider’s documented API contract. Some services return image bytes rather than a hosted URL; in that case, the script must save the blob to storage and write a suitable link. Confirm whether provider links expire, require authentication, or are private by default before relying on them.
Display results and schedule refreshes
If column B contains a directly accessible image URL, put this formula in a separate preview column (for example E2) and fill down:
=IF(B2="", "", IMAGE(B2))
IMAGE displays an image URL; it cannot render the URL in column A. A Drive-backed image may require sharing settings that allow Sheets to fetch it. Making a Drive file available to anyone with the link can expose the capture to anyone who obtains that link. Avoid public sharing for sensitive pages; use an access-controlled destination or a provider-supported display method instead.
For recurring refreshes, create an installable time-driven trigger for the capture function. Apps Script triggers run under the account that created them. Make the function’s skip behavior intentional: this sample skips successful rows, so a scheduled run will not refresh them automatically. To refresh, clear the result/status for chosen rows or adapt the script to overwrite results and retain history elsewhere. Google documents Apps Script-backed macros, triggers, and URL fetching in its Sheets Apps Script guide, installable triggers guide, and UrlFetchApp reference.
Scaling beyond a handful of URLs
Keep batches bounded. A script execution can hit runtime or service quotas, while a slow page can make a sequential loop take longer than expected. Start with a small batch, record progress in the sheet, and rerun until all rows have a terminal status. For a larger recurring job, checkpoint the next row or select pending rows by status instead of scanning and retrying the entire sheet.
- Bound provider calls: respect documented rate limits and batch endpoint limits. A provider’s batch capture feature is separate from batching spreadsheet writes.
- Reduce sheet round trips: read ranges together and write results in groups where practical. The Sheets API supports batch requests for spreadsheet operations; that does not batch screenshot rendering.
- Handle retries carefully: retry transient network failures and rate limits with backoff, but do not retry permanent invalid URL or authentication errors indefinitely.
- Track each row: keep status and capture time, and consider an attempt count and error detail column for operations.
- Make duplicate runs safe: use a stable row identifier or URL plus refresh timestamp if rows may be sorted while jobs run.
- Check quotas and retention: Google Apps Script quotas and provider limits can change. Check current official documentation and your plan before scheduling a large run.
Google’s Sheets API batch requests guide covers spreadsheet request batching, not image generation. Screenshot providers differ in synchronous versus asynchronous jobs, batch size, storage, authorization, output format, and cost; use the chosen provider’s own current documentation.
Errors and troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| HTTP 401 or 403 | Missing, invalid, expired, or insufficiently scoped API key | Check the script property name and provider authentication instructions; rotate a key that was exposed. |
| HTTP 400 | Malformed URL, unsupported option, or request body that does not match the endpoint | Validate complete http(s) URLs and compare parameter names and types with the provider’s docs. |
| HTTP 429 | Rate limit or quota reached | Reduce batch size, add backoff, and check the provider plan and current rate limits. |
| HTTP 5xx or fetch exception | Provider or network failure, or request exceeded a service limit | Record the failure, retry transient errors with bounded exponential backoff, and keep the batch small enough to finish. |
| JSON parse error | Provider returned an error page, plain text, or image bytes instead of JSON | Check status and content type before parsing; follow the documented response format. |
| Success response but no image | Response field mismatch, expired link, or inaccessible hosted result | Inspect the provider schema and link access/retention; do not assume a returned URL is permanent or public. |
IMAGE shows an error |
The URL is not a directly accessible image, requires authentication, or is blocked from Sheets | Use a supported image URL or a properly permissioned storage link; remember that public links can disclose captures. |
| Some rows remain pending | Batch cap reached, invalid rows skipped, or execution ended before finishing | Run the next batch and inspect each row’s status; tune batch size for render time and Apps Script limits. |
| New menu is missing | Sheet has not reloaded or onOpen has not run |
Reload the spreadsheet, authorize the script when prompted, or run the function from the Apps Script editor. |
Performance, reliability, and cost
Capture time is largely determined by page loading and rendering at the provider, then by Apps Script request and write overhead. Full-page captures, large pages, slow third-party resources, and wait conditions can increase latency. Use a bounded batch and the least amount of waiting that still produces the required screenshot. If a provider offers asynchronous jobs, use them for workloads that do not fit comfortably in a synchronous Apps Script run.
For reliability, distinguish a successful HTTP request from a useful screenshot. A provider may return a valid response containing an error, blank page, bot challenge, or incomplete render. Check any status or page-verdict fields the provider documents, and store enough detail to identify failed rows. Do not treat a transient capture link as archival storage; verify retention and access rules.
Cost depends on the rendering provider’s billing model, which may count requests, successful captures, or other units. Also account for any storage costs. Google Apps Script has quotas and service limits; check current Google documentation. Estimate total volume before scheduling frequent refreshes: rows per run multiplied by runs per day and days per month. There is no universal price or quota across providers.
Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server. One GET request takes a URL and returns an image or PDF. Its API accepts screenshot-provider parameter names used by other APIs, which can make switching easier. See the ScreenshotNeo API documentation for the endpoint and options.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`ScreenshotNeo returned HTTP ${res.status}`);
await import('node:fs/promises').then(fs => fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer())));
To use it from Sheets, adapt the Apps Script request to ScreenshotNeo’s documented GET endpoint and handle its image response as documented; the sample renderer above uses a deliberately generic JSON contract. ScreenshotNeo accepts cookie and consent banners and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each step can be turned off. Bot checks/CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, and response headers identify page verdict and billing. Its MCP server gives AI agents tools to take screenshots, get page information, and capture PDFs. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000. These features are available on every plan.
Sign up for ScreenshotNeo to get 1,000 screenshots a month free with no card.
FAQ
Can Screenshot.rocks capture URLs directly from a spreadsheet?
The official homepage describes screenshot mockup composition and export, not a documented bulk URL renderer or Google Sheets integration. Use a separate rendering service for capture, then use Screenshot.rocks to style selected screenshots.
Can I use Sheets’ IMAGE function on a website URL?
No. It displays an image URL. Capture the webpage first, then give Sheets a directly accessible image URL.
Can I automate this every day?
Yes, with an installable time-driven Apps Script trigger, subject to Google and provider limits. Decide whether completed rows should be skipped or recaptured.
Should the screenshots be public?
Only if the page content is safe for anyone with the link to see. Use access controls for private or sensitive captures.


