Recommended Free Tools
Use Puppeteer’s Page.screenshot() method: launch a browser, navigate to the URL, wait for the page state you need, and save or return the image. The smallest useful script is:
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('https://news.ycombinator.com', { waitUntil: 'networkidle2' });
await page.screenshot({ path: 'hn.png' });
await browser.close();
})();
This guide shows how to adapt that pattern for viewport, full-page, clipped, and element captures, choose an output format, wait for dynamically rendered content, and diagnose failures.
Set up Puppeteer
Puppeteer runs a Chromium-based browser that it controls through JavaScript. Create a project, install Puppeteer, and use a recent Node.js runtime supported by the Puppeteer version you install.
mkdir site-capture
cd site-capture
npm init -y
npm install puppeteer
The current official documentation surfaced version 25.12.0 on September 29, 2026. Puppeteer’s behavior and bundled browser can change, so check the API documentation matching your installed version when a version-specific detail matters.
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 →#1 Best Overall
Capture the visible viewport
page.screenshot() captures the current viewport. Supplying path writes the result to disk; the filename extension selects the image type when type is omitted. PNG is the documented default.
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch();
try {
const page = await browser.newPage();
await page.goto('https://example.com', { waitUntil: 'networkidle2' });
await page.screenshot({ path: 'example.png' });
} finally {
await browser.close();
}
})();
Use an absolute path when a job runs from an unfamiliar working directory. If you omit path, the method returns screenshot bytes instead of creating a file:
const imageBytes = await page.screenshot();
// imageBytes is a binary Buffer by default
To receive a Base64 string, request it explicitly:
const base64Image = await page.screenshot({ encoding: 'base64' });
Choose the capture area
Full page
Set fullPage: true to capture the document’s full scrollable page rather than only what is visible.
await page.screenshot({
path: 'full-page.png',
fullPage: true
});
This is useful for documentation, landing-page reviews, and regression images. It can produce a very tall file, so consider the image dimensions and downstream storage before using it in bulk.
A rectangular region
Pass a clip rectangle when you need coordinates within the page viewport. The rectangle uses x, y, width, and height.
Rank #2
await page.screenshot({
path: 'hero-region.png',
clip: { x: 0, y: 120, width: 1280, height: 640 }
});
captureBeyondViewport controls whether a clipped capture may include pixels outside the viewport. Its documented default is false without a clip and true with a clip. Set it deliberately when the rectangle extends beyond what is currently visible.
One element
For a component such as a card, chart, or article, obtain an element handle and call its screenshot() method. Puppeteer scrolls the element into view if necessary.
const element = await page.waitForSelector('main');
if (!element) {
throw new Error('main was not found');
}
await element.screenshot({ path: 'main.png' });
An element handle becomes invalid if the page replaces that node. In that case, acquire a fresh handle after the replacement and capture the new element.
Do these 3 things before closing this tab:
1Scan for outdated or missing drivers - takes under a minute2Repair Windows errors before they cause bigger problems3Fix the driver behind crashes, sound loss and screen glitchesControl when the page is ready
Navigation completion and visual readiness are different things. Puppeteer’s guide demonstrates waitUntil: 'networkidle2', which is a useful starting point, not a universal guarantee that fonts, animations, client-side data, or a particular widget have finished rendering.
await page.goto('https://example.com/dashboard', {
waitUntil: 'networkidle2'
});
await page.waitForSelector('[data-report-ready]');
await page.screenshot({ path: 'dashboard.png' });
Prefer a condition that represents the page state you actually need:
- Wait for a stable, page-specific selector after navigation.
- Wait for the element you plan to capture before taking an element screenshot.
- For content populated by JavaScript, wait for the populated state rather than assuming the first network idle event is sufficient.
- Inspect a sample image; a successful HTTP navigation can still yield an empty, blocked, or partially rendered result.
If a site continuously opens connections, a network-idle condition may take longer than expected or never represent “finished.” Use a known DOM condition and set your own job timeout around the whole capture operation.
Configure the image output
| Option | Use | Important behavior |
|---|---|---|
path |
Save the image to a file | The extension determines the type when type is omitted. |
type |
Select PNG, JPEG, or WebP | PNG is the documented default. |
quality |
Control lossy image quality | Accepts 0–100 and does not apply to PNG. |
omitBackground |
Hide the default white background | Allows transparent output where the page supports it. |
encoding |
Choose the returned representation | Defaults to binary; use base64 for a Base64 string. |
fullPage |
Capture the complete document | Use instead of a viewport-only image when the page scrolls. |
clip |
Capture a rectangle | Provide x, y, width, and height. |
captureBeyondViewport |
Permit clipped pixels outside the viewport | Documented default is false without a clip and true with a clip. |
JPEG, WebP, and transparency examples
await page.screenshot({
path: 'preview.webp',
type: 'webp',
quality: 82
});
await page.screenshot({
path: 'transparent.png',
omitBackground: true
});
Do not expect quality to reduce a PNG file; choose JPEG or WebP when lossy compression is appropriate.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
A reusable capture script
This version accepts a URL and output path, waits for the navigation condition, and always closes the browser even if capture fails.
const puppeteer = require('puppeteer');
async function capture(url, outputPath) {
const browser = await puppeteer.launch();
try {
const page = await browser.newPage();
await page.goto(url, { waitUntil: 'networkidle2' });
await page.screenshot({
path: outputPath,
fullPage: true
});
} finally {
await browser.close();
}
}
const [, , url, outputPath = 'screenshot.png'] = process.argv;
if (!url) {
console.error('Usage: node capture.js <url> [output-path]');
process.exit(1);
}
capture(url, outputPath).catch((error) => {
console.error(error);
process.exit(1);
});
node capture.js https://example.com page.png
For production jobs, validate the URL, constrain concurrency, give each navigation a finite timeout, and retain the error and output path in your job logs. Reuse a browser process for a controlled batch, but create an isolated page per target so one page’s DOM and cookies do not leak into another.
Troubleshoot common failures
The screenshot is blank or incomplete
- Cause: the capture ran before client-side content appeared. Fix: wait for a selector or another page-specific ready state, then inspect the image.
- Cause: the site returned a bot check, error page, or blocked response. Fix: log the final URL and page state and verify the target manually; a successful navigation call alone does not prove useful content was rendered.
- Cause: a full-page capture is extremely tall. Fix: capture a defined element or clip, or process the resulting file according to your storage limits.
waitForSelector never resolves
The selector may be wrong, hidden behind a different rendering path, or absent for some users. Confirm it in the page’s DOM, make the selector specific to the target state, and apply an outer timeout so a failed page cannot occupy a worker indefinitely.
Rank #4
Element capture says the node was detached
The page replaced the element after you obtained its handle. Wait for the replacement to finish, call waitForSelector again, and capture the newly returned handle.
The output format is unexpected
Check both type and the file extension. If type is omitted, Puppeteer derives the format from the extension; with neither a usable extension nor an explicit type, PNG is the documented default.
The browser does not close after an error
Put capture code in a try/finally block and close the browser in finally. This prevents failed navigations from leaving Chromium processes behind.
Performance, reliability, and operating cost
- Wait for the smallest sufficient state: a page-specific selector can finish sooner and be more predictable than waiting for every network connection to become idle.
- Choose scope carefully: viewport and element shots use less memory than very tall full-page images.
- Control parallelism: too many simultaneous Chromium pages can exhaust CPU or memory; use a bounded worker pool.
- Make captures reproducible: use the same viewport, URL state, readiness selector, output type, and quality settings for each run.
- Verify outcomes: retain the screenshot and navigation error details so a technically successful request cannot silently become a bad visual artifact.
Puppeteer itself supplies the screenshot API inside your browser-automation process; your costs are the compute, storage, and operational work required to run that process. There is no separate screenshot charge in the documented API.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Or skip the browser setup
ScreenshotNeo provides a hosted website screenshot API and MCP server. One GET request returns a PNG, JPEG, WebP, or PDF, so you do not need to install Chromium or maintain a browser worker.
Free tools Windows power users keep installed
One-click scans. No signup required.
See the complete parameter reference in the ScreenshotNeo documentation.
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)
open("shot.webp", "wb").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}`);
Before capture, ScreenshotNeo accepts cookie and consent banners and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each cleanup step can be disabled. It supports full-page shots with lazy images loaded, CSS-selector element capture, dark mode, 12 device presets plus custom viewports, retina scale, PDF paper and page-range controls, HTML/CSS-to-image, custom JavaScript and CSS, pre-capture clicks, hidden selectors, waits for selectors, delays or network idle, request and resource blocking, custom headers, cookies, user agents and Authorization, timezone and geolocation, transparent backgrounds, resizing, user-selected cache TTLs, signed public-image links, asynchronous jobs with signed webhooks, bulk capture of up to 100 URLs per call, a usage API, and an OpenAPI specification. Parameter names used by other screenshot APIs also work, which can simplify migration.
Only clean shots are billed. Bot checks and CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, and each response identifies the result with X-Page-Verdict and X-Billed headers. Its MCP server exposes take_screenshot, get_page_info, and capture_pdf to Claude, Cursor, and other MCP clients.
| Plan | Price | Included shots |
|---|---|---|
| Free | $0 | 1,000 per month; no card |
| Starter | $5 | 3,000 |
| Growth | $15 | 15,000 |
| Pro | $39 | 60,000 |
| Scale | $99 | 250,000 |
| Business | $249 | 1,000,000 |
Every feature is available on every plan, and yearly billing provides two months free. Create a free ScreenshotNeo account to get 1,000 screenshots each month without a card.
Frequently Asked Questions
Which Puppeteer version does this guide refer to?
The official documentation surfaced version 25.12.0 on September 29, 2026. Confirm the version installed in your project and use the matching API reference when behavior differs.
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.

