Best Chrome Flags for Reliable Website Screenshots in Headless Mode
Use a small set of documented Chrome flags, fixed capture dimensions, and a page-specific readiness check for repeatable headless screenshots.
For a straightforward screenshot, use Chrome’s --headless, --screenshot, and --window-size flags. For repeatable captures of pages whose content loads asynchronously, the most important reliability step is to wait for a page-specific ready condition before capturing. No single Chrome flag proves that every site’s content has finished rendering.
chrome --headless --screenshot --window-size=1280,800 https://example.com/
Chrome writes screenshot.png in the current working directory. Choose dimensions that match the viewport your capture needs; 1280 by 800 is only an example. Add a timeout as a ceiling when useful, and use Puppeteer when you need an explicit, application-specific readiness check.
1. What each Chrome flag does
| Flag | Use | Limit |
|---|---|---|
--headless |
Run Chrome without displaying a browser window. | It does not mean the page is ready to capture. |
--screenshot |
Capture the page to screenshot.png in the current working directory. |
It does not define an application-specific readiness condition. |
--window-size=WIDTH,HEIGHT |
Set the capture dimensions for a controlled viewport. | Choose values appropriate to the page and test; dimensions alone do not make captures pixel-identical across environments. |
--timeout=MS |
Set the maximum time Chrome waits before capture. | Chrome may capture when the page is still loading. This is a time bound, not proof of readiness. |
--virtual-time-budget=MS |
Allow timer-driven page code, such as setTimeout and setInterval, to advance as if the given time elapsed, while Chrome runs it as quickly as possible. |
It is not a real-time timeout or universal readiness signal. |
--screen-info |
Configure virtual Headless display details such as size and scale factor when screen-level behavior matters. | Available in stable Chrome from version 142; usually unnecessary for ordinary viewport screenshots. |
Modern Chrome Headless retains browser functionality while creating platform windows that are not displayed. Avoid relying on old advice that treats current Headless as a separate, inherently limited browser implementation.
2. Take a basic screenshot from the command line
Run the command from the directory where you want the output file. Replace the URL and dimensions with your target and intended viewport.
chrome --headless --screenshot --window-size=1280,800 https://example.com/
To bound the wait before capture, add --timeout in milliseconds:
chrome --headless --screenshot --window-size=1280,800 --timeout=5000 https://example.com/
This asks Chrome to wait at most five seconds before capturing. If a page takes longer, the screenshot can still be taken while it is loading. Raising the timeout can help when a page needs more time, but it cannot tell Chrome that a particular product panel, chart, or image has appeared.
Use virtual time only for timer-driven pages
Some pages reveal content through timers. A virtual time budget advances timer-dependent code without waiting the same amount of wall-clock time:
chrome --headless --screenshot --window-size=1280,800 --virtual-time-budget=42000 https://example.com/
This is useful when the relevant state is driven by timers. It does not establish that external data requests, image decoding, web fonts, or other application work have completed. That limitation follows from the option’s documented timer-oriented scope. If those states affect the screenshot, use automation and check them explicitly.
3. Use Puppeteer for an application-specific readiness check
A navigation lifecycle event is a useful starting point, not a guarantee that all visible content is stable. Puppeteer lets you wait for navigation and then wait for a condition that represents readiness on the page you are capturing.
Install Puppeteer in a Node.js project:
npm install puppeteer
Save this as capture.mjs, then run node capture.mjs https://example.com/. The example waits for navigation and then for a target element. Replace #report-ready with a selector that appears only when the content you need is ready on your own page.
import puppeteer from 'puppeteer';
const url = process.argv[2] ?? 'https://example.com/';
const browser = await puppeteer.launch({ headless: true });
try {
const page = await browser.newPage();
await page.setViewport({ width: 1280, height: 800 });
await page.goto(url, {
waitUntil: 'networkidle2',
timeout: 30_000,
});
await page.waitForSelector('#report-ready', { timeout: 15_000 });
await page.screenshot({ path: 'capture.png' });
} finally {
await browser.close();
}
networkidle2 is one available navigation wait strategy. Pages with polling, persistent connections, or delayed application rendering may need a different navigation condition or a separate page-specific wait. The selector above is an example, not a built-in Puppeteer property.
Wait for an application state instead of a selector
If your application exposes a deterministic state, page.waitForFunction() can wait for it. The following is illustrative: your page must define and update window.appReady.
await page.goto(url, { waitUntil: 'networkidle2', timeout: 30_000 });
await page.waitForFunction(() => window.appReady === true, { timeout: 15_000 });
await page.screenshot({ path: 'capture.png' });
Prefer a condition tied to the content you need over a fixed sleep. If fonts, animations, lazy-loaded images, or continuously polling requests matter, include explicit checks for those states in your capture workflow. A delay can make a capture slower without making it deterministic.
4. Choose the capture area and viewport deliberately
Use the same viewport dimensions and capture mode when comparing screenshots. Chrome’s --window-size controls dimensions for a CLI capture. Puppeteer offers options for capturing the viewport, the full page, or a clipped region.
| Intent | Puppeteer option | What to check |
|---|---|---|
| Capture the current viewport | page.screenshot() |
Set the viewport before navigation or capture and keep it consistent. |
| Capture the full page | page.screenshot({ fullPage: true }) |
Confirm the page has loaded content that appears only while scrolling; a full-page option is not itself a readiness check. |
| Capture a specific region | page.screenshot({ clip: { x, y, width, height } }) |
Make sure the clip coordinates and dimensions correspond to the intended page region. |
// Full-page screenshot
await page.screenshot({ path: 'full-page.png', fullPage: true });
// Clip a region in page coordinates
await page.screenshot({
path: 'region.png',
clip: { x: 0, y: 0, width: 640, height: 400 },
});
Puppeteer also documents captureBeyondViewport for controlling capture beyond the viewport. Select the option that matches the desired output instead of combining capture modes without checking their intended geometry.
Configure a virtual screen only when needed
For tests that depend on screen scale, orientation, work area, or multiple displays, Chrome’s --screen-info configures a virtual Headless display. This is a specialized control and is version-sensitive: the documented stable Chrome availability starts at version 142. For routine screenshots at a fixed viewport, begin with --window-size and add screen configuration only when the test depends on display-level behavior.
5. Which method should you choose?
| Method | Best fit | Readiness control | Geometry control |
|---|---|---|---|
| Chrome command line | A simple one-off viewport capture. | A bounded --timeout and timer-oriented virtual time. |
--window-size. |
| Puppeteer | Repeatable captures that need application-specific waits or scripted capture behavior. | Navigation wait conditions plus selectors or page predicates. | Viewport, full-page, clip, and capture-beyond-viewport options. |
This is a practical comparison of the documented controls, not a benchmark. Choose the simplest method that can express your readiness requirement.
6. Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Screenshot shows a spinner, skeleton, or incomplete content. | The timeout elapsed before the target content was ready, or navigation completed before client-side rendering. | In Puppeteer, wait for a page-specific selector or state. If using the CLI, increase the maximum wait only when a bounded delay is an acceptable compromise. |
| Capture hangs or times out in Puppeteer. | The chosen navigation wait condition may not complete for a page with persistent network activity, or the page exceeds the configured timeout. | Choose a navigation condition suitable for the page and wait separately for the required content. Keep an explicit timeout so failures are bounded. |
| Content animated by a timer is missing. | The capture ran before the timer-driven state appeared. | For CLI captures, consider --virtual-time-budget; for scripted captures, wait for the resulting state. Verify any external data or assets separately. |
| Full-page image omits content that loads on scroll. | The page’s lazy-loaded content may not have been triggered or ready before capture. | Use a workflow that triggers and verifies the content your page loads on scroll before taking the full-page screenshot. |
| Screenshot dimensions or scale differ from expectation. | Viewport dimensions, device scale, or display-level settings differ between runs. | Keep viewport and environment consistent. For screen-level behavior, review --screen-info support and Chrome version; it requires stable Chrome 142 or later according to the documentation. |
Advice to add --disable-gpu does not fix capture reliability. |
There is no basis in the current documented guidance here for treating it as a universal reliability fix. | Use minimal documented arguments. Add a flag only when a reproduced issue and version-appropriate documentation support it. |
7. Performance, reliability, and cost
Performance
A CLI command has little setup for a single capture. Puppeteer adds browser automation code, but it can avoid arbitrary long sleeps by waiting for a specific condition. A larger viewport or a full-page image can mean more content to capture; choose the output size the task actually needs. These are practical considerations, not measured performance claims.
Reliability
- Record the Chrome version and operating environment.
- Use the same viewport and scale settings for comparisons.
- Define what “ready” means for the target page and encode that condition.
- Use timeouts to bound work, not as a substitute for readiness.
- Choose viewport, full-page, or clipped capture intentionally.
The documented controls do not promise pixel-identical results across different browser builds or machines. Keep the relevant environment and capture settings with each result when reproducibility matters.
Cost
Chrome’s command-line capture and Puppeteer are browser-software workflows; the sources for this guide provide no usage price, performance benchmark, or paid product requirement. Account for the compute and maintenance needed to run your own browser environment. For a hosted screenshot API alternative, ScreenshotNeo offers 1,000 shots per month on its free plan with no card, and paid plans start at $5 for 3,000 shots.
8. Or skip the browser setup
With ScreenshotNeo, one GET request returns a screenshot or PDF. The API accepts the parameter names used by other screenshot APIs, which can make switching easier. See the ScreenshotNeo API documentation for request 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(`Screenshot request failed: ${res.status}`);
await Bun.write('shot.webp', new Uint8Array(await res.arrayBuffer()));
In Node.js versions with global fetch, the request runs directly; the example uses Bun’s file-writing helper to save the response. Replace that final line with your preferred file-writing method when using Node.js.
- Cookie and consent banners, newsletter popups, and chat widgets are removed before the shot; each cleanup step can be turned off.
- Bot checks, blank pages, failed loads, timeouts, and cache hits are never billed. Response headers identify the page verdict and billing status.
- An MCP server provides
take_screenshot,get_page_info, andcapture_pdffor Claude, Cursor, and other MCP clients. - The free plan includes 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 screenshots.
Sign up for ScreenshotNeo and get 1,000 free screenshots a month with no card.
9. FAQ
Does --timeout wait until a site is fully rendered?
No. It sets a maximum wait before capture; Chrome can capture while the page is still loading.
Is --virtual-time-budget the same as waiting in real time?
No. It advances timer-dependent page code as though time passed while Chrome executes it as quickly as possible.
Which flag makes headless screenshots reliable for every website?
There is no universal flag. Fixed capture settings and a readiness condition based on the page’s actual content are the dependable foundation.
Do I need --screen-info for a fixed viewport?
Usually not. It is for virtual screen configuration such as display scale or orientation; ordinary viewport sizing uses --window-size.


