ScreenshotNeo

BlogHow-to

How to Use a Screenshot API from Google Sheets to Capture URLs

Capture website URLs from Google Sheets with Apps Script, insert screenshots into rows, and handle image limits, quotas, errors, and API keys.

By the ScreenshotNeo team4 October 202613 min read

Google Sheets does not render a webpage URL into a screenshot by itself. Use Google Apps Script to send each URL to a browser-based screenshot API, fetch the returned image bytes, and insert them into the corresponding rows. For small and medium batches, floating images are straightforward; for images that must sort and filter with their rows, use a durable image URL in a cell instead.

This guide uses ScreenshotNeo for the API example. The same Apps Script pattern applies to other providers, but their authentication, parameters, response format, and limits may differ. Google documents UrlFetchApp for HTTP requests and sheet methods for inserting images; it does not document a webpage-rendering function in Sheets. UrlFetchApp reference · Sheet reference

1. Choose how screenshots should appear

Output How it works Use it when Tradeoff
Floating image Apps Script inserts an image blob over the grid with insertImage(blob, column, row). You want the image visible in the sheet without setting up image hosting. It is an over-grid object, not a cell value. Sorting or filtering rows may not keep it associated as you expect. The documented blob limit is 2 MB.
Image URL in a cell Put a URL serving the image into a cell and use the sheet’s URL-based image mechanism, such as =IMAGE(B2). The image should behave more like row data when sorting and filtering. The image must be available from a URL that Sheets can fetch. A private API response or Apps Script blob is not automatically a durable public image URL.
Link to a stored image Store the capture in an image store you control, then write its link into the row. Images are too large to insert, or you need to retain and reuse them outside Sheets. You must choose and operate the storage, access rules, and retention policy.

The example below inserts floating images. It writes a status in column B and places the image over column C, keeping the URL and result status as ordinary cell values. Change the column numbers if your sheet layout differs.

2. Set up the sheet and Apps Script

  1. Put one webpage URL per row in column A, starting at A2. Use a header in A1, such as Page URL.
  2. Open Extensions → Apps Script from the spreadsheet.
  3. In Apps Script, open Project Settings → Script Properties and add SCREENSHOTNEO_API_KEY with your ScreenshotNeo API key as its value. Do not put the key in a cell or formula.
  4. Replace the editor contents with the script below and save it.
  5. Run captureNextBatch from the Apps Script editor once. Review the requested authorization, then allow it. The script needs permission to make external requests and edit the spreadsheet.

The script captures at most ten URLs per run, starting at row 2. It saves its next-row checkpoint in script properties. When it reaches the end, it resets to row 2 so you can run it again for a fresh pass. Change BATCH_SIZE to fit the expected image sizes and execution time.

const INPUT_COLUMN = 1;       // A: page URL
const STATUS_COLUMN = 2;       // B: status or error
const IMAGE_COLUMN = 3;        // C: floating image anchor
const FIRST_DATA_ROW = 2;
const BATCH_SIZE = 10;
const API_ENDPOINT = 'https://api.screenshotneo.com/v1/shot';

function captureNextBatch() {
  const sheet = SpreadsheetApp.getActiveSheet();
  const props = PropertiesService.getScriptProperties();
  const apiKey = props.getProperty('SCREENSHOTNEO_API_KEY');
  if (!apiKey) throw new Error('Set SCREENSHOTNEO_API_KEY in Script Properties first.');

  const lastRow = sheet.getLastRow();
  let startRow = Number(props.getProperty('SCREENSHOT_NEXT_ROW') || FIRST_DATA_ROW);
  if (startRow > lastRow) {
    props.setProperty('SCREENSHOT_NEXT_ROW', String(FIRST_DATA_ROW));
    startRow = FIRST_DATA_ROW;
  }
  if (lastRow < FIRST_DATA_ROW) return;

  const endRow = Math.min(lastRow, startRow + BATCH_SIZE - 1);
  const values = sheet.getRange(startRow, INPUT_COLUMN, endRow - startRow + 1, 1).getValues();

  for (let i = 0; i < values.length; i++) {
    const row = startRow + i;
    const pageUrl = String(values[i][0] || '').trim();
    if (!pageUrl) {
      sheet.getRange(row, STATUS_COLUMN).setValue('Skipped: blank URL');
      continue;
    }
    if (!/^https?:\/\//i.test(pageUrl)) {
      sheet.getRange(row, STATUS_COLUMN).setValue('Error: URL must start with http:// or https://');
      continue;
    }

    const requestUrl = API_ENDPOINT + '?' + [
      'access_key=' + encodeURIComponent(apiKey),
      'url=' + encodeURIComponent(pageUrl),
      'format=jpeg',
      'width=1280',
      'height=800'
    ].join('&');

    try {
      const response = UrlFetchApp.fetch(requestUrl, { muteHttpExceptions: true });
      const code = response.getResponseCode();
      const blob = response.getBlob();
      const contentType = String(blob.getContentType() || '').toLowerCase();
      if (code < 200 || code >= 300) {
        sheet.getRange(row, STATUS_COLUMN).setValue('HTTP ' + code + ': ' + response.getContentText().slice(0, 300));
        continue;
      }
      if (!contentType.startsWith('image/')) {
        sheet.getRange(row, STATUS_COLUMN).setValue('Error: response was not an image (' + contentType + ')');
        continue;
      }
      if (blob.getBytes().length > 2 * 1024 * 1024) {
        sheet.getRange(row, STATUS_COLUMN).setValue('Error: image exceeds the 2 MB Sheets blob limit; use a smaller capture or store it externally.');
        continue;
      }
      blob.setName('screenshot-row-' + row + '.jpg');
      sheet.insertImage(blob, IMAGE_COLUMN, row);
      sheet.getRange(row, STATUS_COLUMN).setValue('Captured');
    } catch (error) {
      sheet.getRange(row, STATUS_COLUMN).setValue('Error: ' + String(error).slice(0, 300));
    }
  }

  props.setProperty('SCREENSHOT_NEXT_ROW', String(endRow + 1));
}

function resetScreenshotCheckpoint() {
  PropertiesService.getScriptProperties().setProperty('SCREENSHOT_NEXT_ROW', String(FIRST_DATA_ROW));
}

The request uses a fixed viewport and JPEG to keep each image modest in size. ScreenshotNeo supports PNG, JPEG, and WebP; adjust the format and capture parameters to suit your use case. Before relying on any provider-specific response or parameter, check its current API documentation. For ScreenshotNeo’s supported options and API details, see the ScreenshotNeo documentation.

Why the script checks every response

  • muteHttpExceptions: true lets the loop inspect non-success HTTP responses and record an error for that row instead of stopping at the first failed request.
  • The content type check prevents an error page or JSON response from being treated as a screenshot.
  • The size check handles the documented 2 MB maximum for an image blob inserted into a sheet.
  • The checkpoint bounds work per execution, so a long URL list can continue in later runs.

3. Run the job and keep rows aligned

  1. Run captureNextBatch. On its first run, Apps Script asks you to authorize the script.
  2. Review column B for Captured, skipped rows, and row-specific errors. Images are anchored over column C.
  3. Run the function again to process the next batch. The checkpoint is stored in Script Properties.
  4. To start from the first data row again, run resetScreenshotCheckpoint.

For an unattended workflow, add a time-driven trigger in Apps Script’s Triggers panel to call captureNextBatch at an interval that fits your queue and quota. Start with a small batch and inspect execution times before increasing it. A trigger runs with the authorization of the account that created it.

Floating images are convenient for visual review, but they are not values in the URL or status columns. If users sort or filter the sheet, verify how the images move in your sheet before treating them as durable row data. For a workflow where the image must follow the row as data, store the capture somewhere that provides an accessible image URL and put that URL in a dedicated cell. Then use a URL-based image function such as =IMAGE(B2), after verifying that the host’s access and URL format work with Sheets.

4. Adapt the screenshot request

ScreenshotNeo’s basic request needs an API key and target URL. It can return a screenshot or PDF and supports options for full-page capture, element selection, viewport and device settings, output format, waits, custom CSS or JavaScript, headers and cookies, blocking, caching, and more. Consult the API documentation for exact parameter names and supported values before adding options.

Need Practical choice Consideration
Smaller images Use a bounded viewport and a compressed format such as JPEG or WebP. Choose quality and dimensions that remain legible at the display size.
Capture the whole document Enable full-page capture. Long pages can produce much larger images and take longer to render. Large blobs may exceed Sheets’ 2 MB insertion limit.
Capture one component Target an element with a CSS selector. Selectors can change when a site redesigns; record failures and revisit them.
Wait for dynamic content Wait for a selector, a delay, or network idle. Long waits consume execution time. Prefer a meaningful selector when the page has a reliable ready state.
Make the image match a device Choose a device preset or set the viewport and retina scale. Higher pixel dimensions can increase output size.
Control page state Set supported cookies, headers, user agent, timezone, or geolocation. Keep credentials out of sheet cells and avoid sharing sensitive captures broadly.
Keep an output transparent Use transparent background when supported by the chosen format. JPEG does not preserve transparency.

For one request per row, URL length is usually manageable, but encode both the API key and target URL as the example does. Google documents a URL length limit of about 2 KB for UrlFetch requests. If adding long headers or other parameters pushes a request over that limit, use a provider-supported POST method or a controlled proxy if available. Check that provider’s documentation for the actual request format.

5. cURL, Python, and Node.js equivalents

These examples make the same basic ScreenshotNeo capture outside Sheets. They are useful for checking that an API key and target URL work before wiring the request into Apps Script. Replace the sample target URL as needed, and keep the key private.

cURL

curl -G "https://api.screenshotneo.com/v1/shot" \
  -d access_key=YOUR_API_KEY \
  --data-urlencode url=https://stripe.com \
  -o shot.webp

Python

import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
    timeout=90,
)
r.raise_for_status()
with open("shot.webp", "wb") as f:
    f.write(r.content)

Node.js

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(`Screenshot request failed: ${res.status}`);
const bytes = new Uint8Array(await res.arrayBuffer());
await import('node:fs/promises').then(fs => fs.writeFile('shot.webp', bytes));

6. Or skip the browser setup

Apps Script still needs an image response that it can fetch and insert. ScreenshotNeo provides a website screenshot API, so the sheet script can request the capture directly without you running a browser yourself. Use the same endpoint in the Apps Script example, or make a one-call request like this:

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

Cookie and consent banners are accepted like a visitor and removed before the capture, along with 60+ known consent platforms, newsletter popups, and chat widgets; each step can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits cost nothing, and response headers identify the page verdict and whether it was billed. An MCP server provides take_screenshot, get_page_info, and capture_pdf tools for AI agents using Claude, Cursor, or other MCP clients. The Free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 screenshots. Every feature is on every plan. Read the ScreenshotNeo documentation for request options, then sign up free for 1,000 screenshots a month with no card.

7. Quotas, security, performance, and cost

Apps Script quotas and execution time

Google’s published quotas list 20,000 URL Fetch calls per day for consumer accounts and 100,000 per day for Workspace accounts. The maximum runtime is six minutes per script execution and 30 seconds for a custom function. These limits can change, so check the current Apps Script quota documentation when planning a high-volume job. Each screenshot request ordinarily uses at least one UrlFetch call, and the screenshot provider may impose separate limits.

Do not make a long screenshot batch a spreadsheet custom function. Custom functions have a shorter runtime limit and are a poor fit for work that needs authorization, progress tracking, or visible per-row errors. Use a manually run function or a time-driven trigger, process a bounded number of rows, and save a checkpoint.

Image size and capture time

The Apps Script Sheet reference documents a 2 MB maximum for an image blob passed to insertImage. A full-page capture, large viewport, or high retina scale can exceed that limit. Use a smaller viewport or a compressed format, or store the image elsewhere and put its URL in the sheet. A request that waits for a slow page also consumes more of the six-minute execution window; keep batches small enough to leave time for failures and spreadsheet writes.

API key handling

Script Properties keep the key out of ordinary cell contents, but they do not make it secret from spreadsheet editors. Editors of a container-bound spreadsheet can edit its attached Apps Script and may access values used by that script. Treat editors as trusted with the key. If the sheet has a broad or changing editor list, consider putting the provider credential behind a proxy controlled outside the spreadsheet project. This recommendation follows from the editor access relationship; it is not a claim that Script Properties protect a key from editors.

Do not put an API key in a cell formula or a publicly shared script. Limit who can edit the spreadsheet and its Apps Script project, and remove or rotate credentials if access changes.

Network access and reliability

UrlFetchApp requires the https://www.googleapis.com/auth/script.external_request scope. Apps Script requests originate from Google IP ranges; a provider that restricts inbound traffic may require an allowlist. Check the provider’s current network requirements. Use row-level status values and rerun only failed rows where practical, rather than assuming that an entire batch succeeded because the function completed.

Cost planning

Estimate one screenshot request for each nonblank URL you intend to capture, then account for retries and recaptures. Apps Script’s URL Fetch quotas are separate from the screenshot provider’s price and limits. ScreenshotNeo bills only clean shots: bot checks, blank pages, timeouts, failed loads, and cache hits are not billed. Its monthly plans are Free for 1,000 shots with no card, Starter $5 for 3,000, Growth $15 for 15,000, Pro $39 for 60,000, Scale $99 for 250,000, and Business $249 for 1,000,000. Yearly billing gives two months free, and every feature is on every plan. Confirm current pricing and API behavior in the product documentation before sizing a production workflow.

8. Troubleshooting

Symptom Likely cause Fix
Authorization error on first run The script has not been authorized for external requests or spreadsheet edits. Run the function from Apps Script and complete the requested authorization. Confirm that the external request scope is present if the project uses explicit scopes.
Set SCREENSHOTNEO_API_KEY... The Script Property is missing, misspelled, or set in a different Apps Script project. Open the spreadsheet’s bound project settings and set the property name exactly as shown.
HTTP error written to the status column The API rejected the credentials or parameters, or returned an error for the request. Check the API key, endpoint, parameter names, target URL, and response text. Confirm current provider documentation. Avoid printing secrets into logs or cells.
Response was not an image The endpoint returned an error body, JSON, or another non-image response. Check the HTTP status and response body, then correct the request. Do not insert the response until its content type is an image.
Image exceeds 2 MB The capture dimensions or full-page content produced a blob larger than Sheets permits. Use a smaller viewport or compressed format, or store the image outside the sheet and record its URL.
Request URL too long Encoded target URL and options exceed UrlFetch’s documented URL length limit, about 2 KB. Remove unnecessary parameters or use a provider-supported POST request or controlled proxy.
Script times out The batch is too large, pages are slow, or waits are too long for the six-minute execution cap. Reduce BATCH_SIZE, shorten unnecessary waits, and resume from the saved checkpoint.
Daily quota exceeded The project reached its URL Fetch or other Apps Script daily quota. Reduce request volume, spread work over time, and check Google’s current quota page. The provider may also have its own limits.
Images appear detached after sorting Floating images are over-grid objects rather than values in the row’s cells. Use a hosted image URL in a cell with a URL-based image formula, then verify sorting and filtering in the actual sheet.
Some pages are blank or incomplete The page may require more rendering time, interaction, authentication, or a specific viewport. Use a suitable selector wait or other provider-supported page options. Check whether the page blocks automated access. Keep failures visible per row.

9. Frequently asked questions

Can I use a formula to take the screenshot?

A formula does not itself render a webpage. A URL-based image formula can display an image from a supported image URL, while Apps Script can call a screenshot API and handle the returned bytes.

Can the images stay inside the cells?

Use a URL-backed in-cell image method if the image host returns a URL Sheets can fetch. The example inserts floating images from blobs, which are not cell values.

Can I capture a page that requires login?

That depends on the provider’s supported authentication options and the site’s access rules. If you use cookies or headers, protect them like credentials and avoid sharing captures that expose private content.

Can one run capture a whole spreadsheet?

Only if the work fits within Apps Script’s execution and daily quotas and the image sizes remain acceptable. For larger lists, process bounded batches and resume later.

Does Google Sheets charge for UrlFetchApp?

The research here establishes Apps Script quotas, not a separate UrlFetch price. Screenshot rendering may have its own provider pricing and limits, so check the selected provider’s current terms.