How to Capture a Website Screenshot at a Specific Device Pixel Ratio in Chrome Headless
Set Chrome Headless’s device pixel ratio with a CLI flag or CDP, capture the page, and verify the resulting image dimensions.
To capture a Chrome Headless screenshot at a specific device pixel ratio (DPR), set the viewport in CSS pixels and the scale factor separately. For a one-off capture, pass --force-device-scale-factor=N alongside --window-size. For repeatable automation, use Chrome DevTools Protocol (CDP): call Emulation.setDeviceMetricsOverride with deviceScaleFactor, then capture with Page.captureScreenshot.
chrome --headless \
--window-size=1280,800 \
--force-device-scale-factor=2 \
--screenshot=shot.png \
https://example.com
This requests a 1280 × 800 CSS-pixel viewport at DPR 2. A viewport-sized PNG will commonly be 2560 × 1600 physical pixels, but confirm the actual output: capture mode, page bounds, and Chrome version can affect dimensions. Chrome documents --screenshot and --window-size; the scale-factor switch is a Chromium command-line option. For explicit control through the protocol, use CDP.
1. Understand viewport size and DPR
The viewport dimensions and DPR control different things. Viewport width and height determine the page’s CSS layout area. DPR maps CSS pixels to device pixels and affects raster density and values such as window.devicePixelRatio. At DPR 2, a 1 CSS-pixel line may be rasterized across 2 device pixels.
Choose the CSS viewport first, then set the DPR. If you want to reproduce a responsive layout, use the target CSS viewport dimensions; increasing DPR should increase image pixel density without making the page behave like a wider CSS viewport.
| Setting | Controls | Example |
|---|---|---|
| Viewport width and height | CSS layout area and responsive breakpoints | 1280 × 800 CSS pixels |
| DPR / device scale factor | Mapping from CSS pixels to output device pixels | 2 |
| Screenshot bounds | Viewport, selected clip, or full page | Viewport capture or full-page capture |
Do not rely on --window-size to set DPR. Use --force-device-scale-factor for the CLI workflow, or CDP’s named deviceScaleFactor parameter for direct emulation.
2. Capture a one-off screenshot from the command line
Run the following with a Chrome or Chromium binary available on your PATH. Replace the URL and dimensions as needed:
chrome --headless \
--window-size=1280,800 \
--force-device-scale-factor=2 \
--screenshot=shot.png \
https://example.com
On systems where the executable is named chromium or google-chrome, use that executable name instead. The output is written to the current working directory. Chrome’s documented screenshot flag defaults to screenshot.png when no output name is supplied.
Wait for a page that loads slowly
The CLI supports --timeout, which allows capture after a maximum wait even if loading has not completed. For example:
chrome --headless \
--window-size=1280,800 \
--force-device-scale-factor=2 \
--timeout=5000 \
--screenshot=shot.png \
https://example.com
A timeout is not proof that images, fonts, or application data are ready. For dynamic pages, prefer automation that waits for a selector or another application-specific readiness condition before capturing.
3. Control DPR with Chrome DevTools Protocol
CDP is the clearest option when a script must set the viewport and DPR explicitly. The protocol’s Emulation.setDeviceMetricsOverride accepts viewport dimensions, a deviceScaleFactor, and a mobile setting. Then Page.captureScreenshot returns base64-encoded image data.
Runnable Node.js example with Puppeteer
Install Puppeteer in a project with Node.js, then save this as capture.cjs. Puppeteer provides a CDP session through createCDPSession.
npm install puppeteer
const fs = require('node:fs/promises');
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch({ headless: true });
try {
const page = await browser.newPage();
const client = await page.createCDPSession();
await client.send('Emulation.setDeviceMetricsOverride', {
width: 1280,
height: 800,
deviceScaleFactor: 2,
mobile: false,
});
await client.send('Page.enable');
await page.goto('https://example.com', { waitUntil: 'networkidle0' });
const { data } = await client.send('Page.captureScreenshot', {
format: 'png',
fromSurface: true,
});
await fs.writeFile('shot.png', Buffer.from(data, 'base64'));
console.log('Saved shot.png');
} finally {
await browser.close();
}
})().catch((error) => {
console.error(error);
process.exitCode = 1;
});
Run it with node capture.cjs. The script applies metrics before navigation so page code sees the emulated dimensions from the start. networkidle0 is a useful starting condition, but pages with persistent network activity may never reach it; in that case wait for a known selector or use a bounded timeout and an application-specific readiness check.
Protocol sequence without a specific automation library
After connecting to a Chrome DevTools Protocol page target and enabling the Page domain, send these commands. The exact connection and session setup depends on the CDP client you use.
await client.send('Emulation.setDeviceMetricsOverride', {
width: 1280,
height: 800,
deviceScaleFactor: 2,
mobile: false,
});
const { data } = await client.send('Page.captureScreenshot', {
format: 'png',
});
// Decode data from base64 and save it as a PNG.
For a screenshot of content beyond the viewport, full-page capture is a separate concern. Choose a full-page capture method supported by your automation library, or use CDP’s screenshot bounds where appropriate, and verify the resulting dimensions. Do not assume a normal viewport screenshot includes the entire document.
4. Verify DPR and output dimensions
Check both the emulated browser value and the output file. This catches a missing or overridden DPR setting and distinguishes CSS viewport size from raster dimensions.
const dpr = await page.evaluate(() => window.devicePixelRatio);
const viewport = await page.evaluate(() => ({
width: window.innerWidth,
height: window.innerHeight,
}));
console.log({ dpr, viewport });
Inspect the PNG dimensions with an image utility available in your environment, for example identify shot.png if ImageMagick is installed. For a viewport capture at 1280 × 800 CSS pixels and DPR 2, expect dimensions near 2560 × 1600 pixels; treat the file’s measured dimensions as authoritative, especially for full-page or clipped captures.
- If
window.devicePixelRatiois 1, check that the scale-factor switch or CDP override reached the Chrome instance and was applied to the correct page target. - If the image dimensions are unexpected, check whether you captured the viewport, a clip, or the full page, then inspect the actual CSS viewport and DPR.
- If a site’s layout changes, verify that the CSS viewport did not change along with the DPR.
5. Choose between CLI and CDP
| Approach | Best for | Control | Trade-off |
|---|---|---|---|
| Chrome CLI flags | One-off local captures and simple scripts | Viewport, scale-factor switch, output path | Limited readiness handling and orchestration |
| CDP | Repeatable browser automation | Explicit device metrics and screenshot parameters | Requires a CDP connection and lifecycle handling |
Use the CLI when you need a quick image and the page is straightforward. Use CDP when you need deterministic setup, readiness waits, output-format choices, or integration into a larger automation flow.
6. Common problems and fixes
| Symptom | Likely cause | Fix |
|---|---|---|
| DPR remains 1 | The scale switch was omitted, misspelled, or applied to another Chrome process; CDP metrics were sent to the wrong target. | Use --force-device-scale-factor=2 or send Emulation.setDeviceMetricsOverride on the active page session. Read window.devicePixelRatio to verify. |
| Screenshot is blurry or smaller than expected | The output was captured at DPR 1, or dimensions were assumed from CSS pixels alone. | Set the DPR explicitly and inspect the resulting file dimensions. |
| Responsive layout is wrong | Viewport CSS dimensions differ from the target, or mobile emulation was enabled unintentionally. | Set width and height to the intended CSS viewport and choose mobile deliberately. |
| Screenshot is blank or incomplete | Capture happened before client-rendered content, fonts, or images were ready. | Wait for a page-specific selector or readiness condition. A fixed CLI timeout is only a maximum wait, not a readiness guarantee. |
| CLI cannot find Chrome | The binary is not on PATH or has a platform-specific name. | Install Chrome/Chromium or invoke its full executable path; use the installed binary name. |
| CDP command fails or has no effect | Protocol session is not attached to the page, the page domain is not enabled, or the command ran before the intended target was selected. | Attach to the page target, enable the needed domain, apply metrics to that session, then navigate and capture. |
| Capture hangs waiting for network idle | The page polls or keeps a connection open. | Wait for a specific element instead, or use a bounded wait with an explicit readiness check. |
7. Performance, reliability, and cost
Higher DPR produces more output pixels. For the same CSS dimensions, doubling DPR in both directions can produce roughly four times as many pixels to encode and store, so image memory, output size, and processing time may increase. Choose the lowest DPR that meets the sharpness requirement.
For repeatability, keep Chrome’s version, viewport, DPR, capture bounds, and readiness condition consistent. Dynamic page content, animations, network-dependent data, and late-loading assets can still change between captures. Where visual consistency matters, wait for the relevant content and consider disabling or waiting out animations in your own automation.
Running Chrome yourself has no per-screenshot API charge, but you are responsible for browser installation, compute, execution time, and maintenance. A hosted screenshot service trades browser setup for a per-plan allowance; compare those costs against your capture volume and operational needs.
8. Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server. One GET request returns a PNG, JPEG, WebP, or PDF; its API supports viewport and device presets, retina scale, full-page capture, and other capture options. See the API documentation.
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}`);
Cookie banners, popups, and chat widgets are removed before the shot. Bot checks, blank pages, and failed loads are never billed. An MCP server lets AI agents take screenshots. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000. Sign up for 1,000 free screenshots a month, with no card.
9. FAQ
Does DPR change the page’s CSS layout width?
No. DPR changes the device-pixel mapping; the CSS viewport width is a separate value. Set both explicitly when reproducing a target environment.
Is --window-size the same as a device screen override?
No. It requests window dimensions. CDP’s metrics override explicitly sets emulated device dimensions and scale factor; use that for scripted control.
Can I use fractional DPR values?
CDP’s deviceScaleFactor is numeric, so it can represent fractional values. Check the actual output dimensions and browser behavior for your chosen value.
Does a viewport screenshot include the full page?
Not by default. Select a full-page capture method or screenshot bounds that cover the document, then verify the output.


