Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.

Use Apps Script to read each page URL, send it to a browser-based screenshot API with UrlFetchApp.fetch(), then insert the returned image blob with Sheet.insertImage(blob, column, row). This puts a movable, floating image over the sheet grid—not an image contained inside a cell. The approach works well for a menu-driven batch, provided each returned image stays within Google’s documented 2 MB blob limit.

What Apps Script does—and what it does not do

UrlFetchApp sends an HTTP request and returns an HTTP response. It does not render a web page as a browser would. To capture a JavaScript-heavy site, your script needs to call a screenshot service that renders the page in a browser, then use the service’s image response as the blob to insert into the sheet. Site-Shot documents a REST GET endpoint and Chromium rendering with JavaScript execution (Site-Shot API).

The workflow below expects website URLs in column A, beginning at A2, and inserts screenshot objects anchored at column B in the corresponding rows. Change those columns and the screenshot options to match your sheet.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Set up the sheet and API key

  1. Put page URLs in column A. Start in A2; leave a cell blank to skip that row.
  2. Get an API key from your chosen screenshot service. The example below uses Site-Shot’s documented endpoint and its userkey parameter. Check the provider’s current documentation and terms before using it.
  3. Store the key in Apps Script properties. In the spreadsheet, open Extensions → Apps Script. In the Apps Script editor, open Project Settings, find Script Properties, and add SITE_SHOT_KEY with your key as its value.
  4. Add and save the script. Paste the code in the next section, save the project, then run onOpen once from the editor and approve the requested access. Reopen or reload the spreadsheet to display the custom menu.

The key is not exposed in a cell formula, but it is not hidden from people who can edit the container-bound Apps Script project. If sheet editors should not be able to inspect or use the key, put the API call behind a server-side proxy instead of sharing the key-bearing script with them.

Use a menu-driven Apps Script batch

This example adds a Website screenshots → Capture URLs in column A menu. It checks the response code before inserting the response blob, reports failures in column C, skips blank or malformed URLs, and continues after row-level errors. The request options shown are Site-Shot-specific; follow your selected provider’s documented parameter names if you use a different service.

function onOpen() {
  SpreadsheetApp.getUi()
    .createMenu('Website screenshots')
    .addItem('Capture URLs in column A', 'captureColumn')
    .addToUi();
}

function captureColumn() {
  const sheet = SpreadsheetApp.getActiveSheet();
  const key = PropertiesService.getScriptProperties()
    .getProperty('SITE_SHOT_KEY');

  if (!key) {
    SpreadsheetApp.getUi().alert(
      'Add SITE_SHOT_KEY in Apps Script Project Settings first.'
    );
    return;
  }

  const lastRow = sheet.getLastRow();
  if (lastRow < 2) return;

  const values = sheet.getRange(2, 1, lastRow - 1, 1).getValues();

  for (let i = 0; i < values.length; i++) {
    const row = i + 2;
    const pageUrl = String(values[i][0]).trim();
    if (!pageUrl) continue;

    if (!/^https?:///i.test(pageUrl)) {
      sheet.getRange(row, 3).setValue('Skipped: enter a full http(s) URL');
      continue;
    }

    const query = [
      'url=' + encodeURIComponent(pageUrl),
      'userkey=' + encodeURIComponent(key),
      'format=jpeg',
      'width=1280',
      'height=800'
    ].join('&');
    const endpoint = 'https://api.site-shot.com/?' + query;

    try {
      const response = UrlFetchApp.fetch(endpoint, {
        muteHttpExceptions: true
      });
      const code = response.getResponseCode();

      if (code !== 200) {
        sheet.getRange(row, 3).setValue('HTTP ' + code);
        continue;
      }

      const blob = response.getBlob();
      if (blob.getBytes().length > 2 * 1024 * 1024) {
        sheet.getRange(row, 3).setValue('Skipped: image exceeds 2 MB');
        continue;
      }

      sheet.insertImage(blob, 2, row);
      sheet.getRange(row, 3).clearContent();
    } catch (error) {
      sheet.getRange(row, 3).setValue('Capture failed: ' + error.message);
    }
  }
}

Before running a large batch, try one or two rows. A successful insert creates a floating image anchored to the specified grid position; it is not written into the cell value. The code checks the response status and blob length, but it does not verify that a 200 response actually contains a decodable image. If you need stronger validation, inspect the response content type and confirm the provider’s response format before insertion.

Choose how the image should appear

Floating image from a fetched blob

sheet.insertImage(blob, column, row) is the direct choice when Apps Script fetched the image bytes. Google documents that this inserts a floating image and states a maximum supported blob size of 2 MB (Google Apps Script Sheet reference). The image overlays the grid and can be moved or resized; it does not behave like text stored in a cell. If a screenshot is too large, request JPEG, reduce the capture width and height, or use a bounded viewport instead of a full-page image.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Image from a public URL

sheet.insertImage(url, column, row) uses a URL rather than bytes already fetched by the script. Google requires the image URL to be publicly accessible (Google Apps Script Sheet reference). This can be useful when your screenshot provider gives you a stable public image URL, but it is not a substitute for the blob flow when the source is private or requires credentials the image loader cannot provide.

In-cell image

If the image must be associated with a cell rather than placed as a floating object, use Sheets’ URL-backed in-cell image option where suitable. In-cell image formulas or objects need an image URL; they cannot consume the fetched Apps Script blob directly. If you do not have a safely shareable public URL, the blob insertion method is the practical option, with floating placement as the trade-off.

Authorize HTTP requests and image insertion

Apps Script’s UrlFetchApp.fetch() makes the external HTTP request and returns an HTTPResponse, which provides both getResponseCode() and getBlob() (UrlFetchApp reference; HTTPResponse reference). When a project explicitly declares OAuth scopes, the external request needs https://www.googleapis.com/auth/script.external_request. Spreadsheet image insertion requires a spreadsheet scope such as https://www.googleapis.com/auth/spreadsheets.currentonly or https://www.googleapis.com/auth/spreadsheets, according to Google’s Sheet reference and Spreadsheet reference. If you do not declare scopes manually, Apps Script generally prompts for the permissions required by the services your script uses.

Run captures safely in batches

A menu command is a useful starting point because it makes the side effect explicit: a person chooses when the script adds images. A normal Apps Script function can also be run from an installable trigger or another supported entry point, but batch size and execution limits matter. Avoid starting an unbounded capture loop on every sheet edit.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
  • Keep the input range bounded. The example reads only through the sheet’s last populated row, not the entire million-row column.
  • Use small batches for long lists. If a batch approaches Apps Script’s execution limits or the screenshot provider’s request limits, process a fixed number of rows per run and save a next-row marker in Script Properties.
  • Make retries deliberate. The example records an error in column C. You can rerun after correcting the cause; consider clearing or replacing existing images if you do not want duplicate floating screenshots.
  • Use a trigger only when the workflow needs automation. Installable triggers run with the authorizing account’s permissions. Review who can edit the script and which account’s credentials are being used.

A custom spreadsheet function is the wrong control surface for this job: custom functions are designed to return values to their calling cells, not to make arbitrary side-effecting changes elsewhere on the sheet. Google’s Apps Script guidance recommends using a custom menu for this kind of action (Custom functions in Google Sheets).

Control screenshot size, completeness, and cost

The sample requests a JPEG at 1280 × 800 pixels. Those are example capture dimensions, not a guarantee about the final compressed file size: image complexity and the provider’s rendering affect the number of bytes. The relevant insertion constraint is the returned blob’s size, not just its pixel dimensions. A full-page screenshot can be much taller and is more likely to exceed the 2 MB limit, so use it only when the extra content is needed and check each response.

Screenshot generation is a separate service call for each URL. Network latency, browser rendering time, remote-site behavior, provider quotas and billing can all affect how long a batch takes and what it costs. A script-side loop does not make those service calls free or instantaneous. Site-Shot’s quickstart, as available in 2026, lists API access as paid and plans starting at $5/month for 2,000 screenshots; check its current quickstart for pricing and availability before relying on that figure.

Or skip the browser setup

If you want the same Apps Script pattern with ScreenshotNeo, keep the image insertion logic and replace the provider-specific request with a call to its screenshot endpoint. ScreenshotNeo is a website screenshot API and MCP server from Yorker Media; its API returns a PNG, JPEG or WebP image, or a PDF. For this sheet workflow, request an image format and insert the response blob.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
function captureWithScreenshotNeo(pageUrl, sheet, row) {
  const key = PropertiesService.getScriptProperties()
    .getProperty('SCREENSHOTNEO_API_KEY');
  if (!key) throw new Error('Add SCREENSHOTNEO_API_KEY in Script Properties.');

  const response = UrlFetchApp.fetch(
    'https://api.screenshotneo.com/v1/shot?' + [
      'access_key=' + encodeURIComponent(key),
      'url=' + encodeURIComponent(pageUrl),
      'format=jpeg',
      'width=1280',
      'height=800'
    ].join('&'),
    { muteHttpExceptions: true }
  );
  const code = response.getResponseCode();
  if (code !== 200) throw new Error('ScreenshotNeo HTTP ' + code);

  const blob = response.getBlob();
  if (blob.getBytes().length > 2 * 1024 * 1024) {
    throw new Error('Screenshot exceeds Google Sheets’ 2 MB blob limit.');
  }
  sheet.insertImage(blob, 2, row);
}

See the ScreenshotNeo API documentation for request parameters and response details. ScreenshotNeo removes cookie and consent banners, newsletter popups and chat widgets before capture; failed loads, blank pages and bot checks are not billed; an MCP server lets AI agents take screenshots; and its Free plan includes 1,000 shots per month without a card. Paid plans start at $5 for 3,000 shots. Those benefits do not change Google Sheets’ image-size ceiling or the need to handle HTTP responses in your script.

Sign up for ScreenshotNeo free to get 1,000 screenshots a month with no card.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Troubleshoot common failures

The script cannot find the API key

Confirm that the Script Property name exactly matches SITE_SHOT_KEY or SCREENSHOTNEO_API_KEY, depending on which function you use. Script Properties are project-specific; adding a property to a different Apps Script project will not make it available here.

The request returns an HTTP error

Check the provider’s required endpoint, authentication parameter, URL encoding, account status and quotas. With muteHttpExceptions: true, the script can inspect non-success HTTP responses instead of stopping at the first such response. It writes the status code into column C so the rest of the loop can continue. Do not treat HTTP 200 alone as proof that the body is a valid image.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Google reports that the image is too large

The returned blob may exceed Google’s 2 MB supported maximum. Request JPEG rather than a lossless format, lower the viewport dimensions, or capture only the needed viewport instead of a full page. The example measures the returned bytes before insertion and writes a row-level message if the blob is over the limit.

The screenshot shows a blank or incomplete page

UrlFetchApp itself is not rendering the target page; the screenshot provider is. A target may load content slowly, require interaction, block automated traffic, or depend on JavaScript. Check the provider’s rendering options and the target page’s behavior. If the provider returns an error page or other non-image body with a successful status, validate the response before insertion.

The image floats instead of staying in its cell

That is the expected result of insertImage(blob, column, row). It creates a floating image anchored at a row and column. For an in-cell image, use a URL-backed option and ensure the image URL is publicly accessible; the blob returned by UrlFetchApp cannot be passed directly into an in-cell image formula.

The script stops before finishing the list

Large batches can take too long because every URL requires a separate remote render and fetch. Restrict each run to a smaller range, record progress, and resume in another run. Also inspect provider quotas and Apps Script execution limits for the account and runtime you are using.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

FAQ

Can Apps Script take a screenshot without a third-party renderer?

Not with UrlFetchApp alone. It fetches an HTTP response; a browser-rendering screenshot endpoint must produce the screenshot bytes.

Can a custom function insert an image beside its formula cell?

Use a menu, trigger or other normal Apps Script entry point for side-effecting insertion. Custom functions are intended to return a value to their calling cell, not to modify arbitrary cells elsewhere.

Can I store the API key in a spreadsheet cell?

You can technically place values in cells, but putting a credential in formulas or shared sheet content exposes it to people with access. Script Properties avoid putting the key in each row; they do not protect it from editors of the attached script.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.