Recommended Free Tools
Google Sheets cannot render a website screenshot from a URL on its own. To capture a list of pages, use Apps Script to send each URL to a browser-based screenshot renderer or screenshot API, then place the returned image in the sheet or save it to Drive and link to it. The practical workflow is: keep one URL per row, run a script from a menu or button, and record each capture or error alongside its source URL.
What the workflow does—and what Sheets does not do
A URL is an address, not an image. Google Apps Script can make HTTP and HTTPS requests with UrlFetchApp, but that service does not open a browser and render a page. The screenshot renderer must do that work and return an image or a URL for one. Google’s Apps Script documentation describes the network-request service in its UrlFetchApp reference.
The basic pipeline is:
- Read website URLs from a column in a Google Sheet.
- Send each URL to a screenshot renderer, with whatever authentication and capture options that service requires.
- Receive image bytes or a fetchable image URL.
- Insert the image in the sheet, or save it to Drive and place a durable link in the row.
Google’s documentation supports multiple requests through UrlFetchApp.fetchAll(). The right batch size, concurrency, quotas, and output limits depend on the renderer and Apps Script execution context, so test those against the service you choose rather than assuming a universal number.
Prepare the spreadsheet
Use a clear input and output layout
Put one fully qualified website URL per row in column A. Add headers in row 1, such as Website URL, Screenshot, and Status. Reserve column B for the image and column C for a status message. A sample layout:
#1 Best Overall
- hole punched
- high quality card stock
- 4 pages
- made in USA
- keyboard shortcuts
| Website URL | Screenshot | Status |
|---|---|---|
| https://example.com | Image or link | Pending |
Use https:// URLs, remove blank rows from the processing range, and keep any headers out of the URL list. If the sheet is shared, decide who should be allowed to see the captures and the stored image files before making images public.
Authorize Apps Script for external requests
Open the spreadsheet, choose Extensions > Apps Script, and authorize the script when prompted. Apps Script’s external-request service needs the script.external_request OAuth scope when you configure scopes explicitly. If you use a renderer requiring an API key, store it as a script property rather than placing it in a visible cell; anyone who can edit the script project may still be able to access project configuration, so limit editor access accordingly.
Choose where the screenshot will appear
Insert an image from a blob over the grid
Sheet.insertImage(blob, column, row) adds an image over the cells. It is useful when the API response contains image bytes, but Google’s current reference documents a maximum supported image size of 2 MB for this insertion method. A renderer may return a larger image, so request a smaller viewport, compressed format, or reduced dimensions if necessary. The image floats over the grid rather than becoming the cell’s value.
Insert an image from a URL
Sheet.insertImage(url, column, row) requires an image URL that is publicly accessible. Google’s Class Sheet reference states that “The provided URL must be publicly accessible.” This option is not suitable for private URLs that require an authorization header or a logged-in session unless you first copy the image to storage with an appropriately accessible link.
The Tool Desk
Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →Use a cell image or store the file in Drive
If the image should behave as cell content rather than an over-grid object, use a cell-image approach supported by Sheets, or save the image in Drive and place a link to the file in the row. Consider access permissions: a Drive file may not be visible to every sheet reader unless it is shared appropriately.
Do not assume a Google-hosted image content URL is a permanent storage link. The Class CellImage reference says that getContentUrl() returns a requester-tagged URL that “expires after a short period of time.” Its availability can also be affected by spreadsheet sharing settings. For a long-lived record, save the underlying image in a storage location with suitable sharing and retention, rather than treating that temporary URL as an archive.
Rank #2
- Mastering Google Sheets: A Step by Step Handbook for Beginners to Simplify Data Analysis, Boost Productivity, and Unlock Your Full Spreadsheet Potential
- ABIS BOOK
Build the Apps Script around your screenshot service
There is no provider-neutral screenshot endpoint or request format: each service defines its own authentication, URL parameter, response format, and capture options. The Apps Script below is therefore a safe integration skeleton, not a complete request for an unspecified service. Replace callRenderer_() with the endpoint and authentication documented by the renderer you select. It expects that function to return a successful PNG, JPEG, or WebP response as a blob.
In the spreadsheet, select Extensions > Apps Script, replace the starter code with the following, and save. On reload, a Screenshots menu appears. The menu starts the work from a normal script invocation instead of a custom cell formula.
Quick wins for a faster PC:
Repair Windows errors before they cause bigger problemsFix Now →Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Clear out junk files and repair common Windows errorsFree Scan →function onOpen() {
SpreadsheetApp.getUi()
.createMenu('Screenshots')
.addItem('Capture URLs', 'captureUrls')
.addToUi();
}
function captureUrls() {
const sheet = SpreadsheetApp.getActiveSheet();
const firstDataRow = 2;
const urlColumn = 1;
const imageColumn = 2;
const statusColumn = 3;
const lastRow = sheet.getLastRow();
if (lastRow < firstDataRow) return;
const rows = sheet.getRange(firstDataRow, urlColumn,
lastRow - firstDataRow + 1, 1).getValues();
rows.forEach((row, i) => {
const targetRow = firstDataRow + i;
const url = String(row[0] || '').trim();
if (!url) return;
try {
if (!/^https?:///i.test(url)) {
throw new Error('Enter a complete http:// or https:// URL.');
}
const blob = callRenderer_(url);
if (!blob || !blob.getBytes().length) {
throw new Error('Renderer returned an empty response.');
}
// Remove an earlier over-grid image anchored at this output cell.
sheet.getImages().forEach(image => {
if (image.getAnchorCell().getRow() === targetRow &&
image.getAnchorCell().getColumn() === imageColumn) {
image.remove();
}
});
sheet.insertImage(blob, imageColumn, targetRow);
sheet.getRange(targetRow, statusColumn).setValue('Captured');
} catch (err) {
sheet.getRange(targetRow, statusColumn)
.setValue('Error: ' + (err.message || err));
}
});
}
function callRenderer_(url) {
// Replace this function with your provider's documented request.
// It must return image bytes as an Apps Script Blob.
throw new Error('Configure callRenderer_() for your screenshot service.');
}
After you implement callRenderer_(), return the actual image blob, not a JSON response containing metadata. Keep provider-specific secrets out of the sheet. If the chosen API returns JSON with a hosted image URL instead of bytes, fetch that URL only if it is accessible to Apps Script, or save the image to suitable storage and use a method appropriate to the resulting access model.
Why a custom cell function is the wrong trigger
A formula such as =CAPTURE(A2) looks convenient, but external requests have authorization and recalculation behavior that make custom spreadsheet functions an unsuitable place for this work. A screenshot API vendor’s integration guide recommends starting external capture from a menu, button, or trigger instead. The menu approach above makes the action explicit and allows the script to update status cells. For a scheduled workflow, use an installable time-driven trigger and account for Apps Script and provider execution limits.
Scale to a longer URL list safely
For a small list, sequential processing is easier to understand and gives a clear row-level status. For a longer list, a single run may exceed a script execution limit or a provider’s request allowance. Do not assume that one invocation can process every row.
- Process in chunks: store a next-row checkpoint in script properties and process a bounded number of rows per invocation. Resume from the checkpoint until the list is complete.
- Skip completed work: check the status or an existing output before making another paid capture. Offer a deliberate recapture action when the source page changes.
- Handle failures per row: record the URL and error, continue with later rows, and retry transient timeouts or rate limits separately rather than restarting every capture.
- Use concurrency carefully:
UrlFetchApp.fetchAll()can issue multiple requests, but the appropriate batch size depends on Apps Script quotas and the screenshot service’s concurrency and rate limits. - Track output size: large full-page images can hit the documented 2 MB limit for blob-based
insertImage. Consider a smaller viewport, compression, or external storage with a link in the sheet. - Plan for duplicate runs: remove or replace the prior image at the output anchor, or deliberately preserve versions elsewhere. Avoid leaving overlapping images after recaptures.
For each renderer, check its authentication requirements, capture options, response size, quotas, caching, pricing, and retention behavior. The sources do not establish one universal batch limit or a best provider; those are service-specific choices.
Rank #3
Troubleshooting common failures
Authorization or external request errors
If Apps Script reports that the request is unauthorized or the scope is missing, approve the script’s requested permissions and confirm script.external_request is included if you explicitly maintain OAuth scopes. Check that the API key is valid and sent in the exact location required by the service. Do not paste secrets into a publicly shared sheet.
The output is not an image
A nonempty response is not necessarily an image. A service may return a JSON error, HTML challenge page, or text response. Check the HTTP status and response content type before inserting the result. Update the script to detect non-success status codes and report a short provider error instead of treating those bytes as a screenshot.
The screenshot exceeds 2 MB
insertImage(blob, ...) may fail when its image exceeds the documented 2 MB supported size. Reduce the capture dimensions, avoid unnecessarily long full-page images, use a more compact image format if supported, or save the file elsewhere and insert a link or suitable public image URL.
The image URL does not load in the sheet
URL-based insertion requires a publicly accessible image URL. A link that works only in your browser because you are signed in will not meet that requirement. Adjust access intentionally or use a blob-based or cell-image/storage workflow that fits the file’s sharing requirements.
The displayed image disappears later
If the sheet uses a URL from CellImage.getContentUrl(), that URL may expire after a short period. Store the original capture in Drive or other durable storage and use an appropriate stable reference with permissions that match the sheet audience.
The script stops partway through the list
Long runs can run into Apps Script execution limits, provider timeouts, or rate limits. Process fewer rows per run, write status as each row finishes, and resume from the first unfinished row. Add a bounded retry for temporary errors; do not blindly repeat every completed request.
Rank #4
- The Google Workspace Bible: [14 in 1] The Ultimate All in One Guide from Beginner to Advanced Including Gmail, Drive, Docs, Sheets, and Every Other App from the Suite
- ABIS BOOK
Or skip the browser setup
ScreenshotNeo is a screenshot API and MCP server; Apps Script can call its GET endpoint and insert the returned bytes. Use the ScreenshotNeo documentation for request options and response details. This complete Apps Script example uses one request per populated URL and places each response as an image over the grid:
function onOpen() {
SpreadsheetApp.getUi()
.createMenu('Screenshots')
.addItem('Capture URLs', 'captureUrls')
.addToUi();
}
function captureUrls() {
const sheet = SpreadsheetApp.getActiveSheet();
const firstDataRow = 2;
const lastRow = sheet.getLastRow();
if (lastRow < firstDataRow) return;
const urls = sheet.getRange(firstDataRow, 1,
lastRow - firstDataRow + 1, 1).getValues();
const key = PropertiesService.getScriptProperties()
.getProperty('SCREENSHOTNEO_ACCESS_KEY');
if (!key) throw new Error('Set SCREENSHOTNEO_ACCESS_KEY in script properties.');
urls.forEach((row, i) => {
const r = firstDataRow + i;
const url = String(row[0] || '').trim();
if (!url) return;
try {
const response = UrlFetchApp.fetch(
'https://api.screenshotneo.com/v1/shot?' +
'access_key=' + encodeURIComponent(key) +
'&url=' + encodeURIComponent(url),
{ muteHttpExceptions: true, followRedirects: true }
);
if (response.getResponseCode() < 200 || response.getResponseCode() >= 300) {
throw new Error('HTTP ' + response.getResponseCode());
}
const blob = response.getBlob();
sheet.getImages().forEach(image => {
if (image.getAnchorCell().getRow() === r &&
image.getAnchorCell().getColumn() === 2) image.remove();
});
sheet.insertImage(blob, 2, r);
sheet.getRange(r, 3).setValue('Captured');
} catch (err) {
sheet.getRange(r, 3).setValue('Error: ' + (err.message || err));
}
});
}
In Apps Script, open Project Settings > Script Properties and add SCREENSHOTNEO_ACCESS_KEY with your API key. The script makes an individual call for each nonblank row; keep list size and execution limits in mind, and chunk a long list rather than assuming one run will finish it. ScreenshotNeo accepts cookie or consent banners and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; those steps can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and responses identify page verdict and billing status in headers. Its MCP server provides take_screenshot, get_page_info, and capture_pdf tools for AI agents. The Free plan includes 1,000 screenshots monthly with no card; paid plans start at $5 for 3,000. Sign up for ScreenshotNeo’s free plan.
FAQ
Can I capture screenshots with a Google Sheets formula alone?
No. A formula can display or reference an image, but rendering a website requires a browser-based renderer or screenshot service. Apps Script can send the request and manage the output.
Can Sheets put the screenshot inside the same cell as the URL?
Yes, use a cell-image method when you want an in-cell presentation. The script example uses insertImage(blob, column, row), which places an image over the grid instead.
Can I capture a private or logged-in page?
That depends on the screenshot service’s support for authentication, cookies, and request headers, and on whether the service can access the page. Do not expose session credentials in sheet cells or publicly shared script code.
Can I make screenshots run automatically?
Yes. A trigger can invoke the capture function, but scheduled runs still need to respect Apps Script execution constraints and the renderer’s quotas. Test with a small list before scheduling repeated captures.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Quick Recap
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.




