Why Is My Puppeteer Screenshot Blurry? DPI and Resolution Settings
Diagnose blurry Puppeteer screenshots by separating viewport size, device scale, image compression, capture area, and browser version issues.
Short answer: Puppeteer has no universal DPI switch that fixes every blurry screenshot. Set the CSS viewport with page.setViewport(), use deviceScaleFactor when you need more output pixels for that viewport, choose the right capture area and image format, and verify the saved file’s actual dimensions. If the result is still unexpected, check the Puppeteer and Chromium versions together.
“DPI” is often used loosely in screenshot discussions. For a browser screenshot, the practical settings are the CSS viewport, device scale factor, captured area, and image encoding. A larger scale factor creates more pixels for the same CSS layout; it cannot restore detail missing from a source image or undo a later resize or lossy re-encode.
1. Check the file before changing settings
Inspect the screenshot’s pixel dimensions and format first. Compare its dimensions to the CSS viewport and scale factor you requested. Do not judge resolution only by how large an image viewer displays the file.
For example, a 3840 × 2160 CSS viewport with deviceScaleFactor: 2 requests 7680 × 4320 output pixels. Puppeteer issue discussion about an external Chromium setup described that dimension relationship as consistent with the request; it is an example, not a benchmark or universal guarantee. See the compatibility issue discussion.
2. Set the CSS viewport and device scale separately
The viewport controls the page’s CSS layout dimensions. deviceScaleFactor controls how many device pixels represent each CSS pixel. Set both explicitly when the output dimensions matter.
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch();
try {
const page = await browser.newPage();
await page.setViewport({
width: 1280,
height: 800,
deviceScaleFactor: 2,
});
await page.goto('https://example.com', { waitUntil: 'networkidle2' });
await page.screenshot({ path: 'capture.png', type: 'png' });
} finally {
await browser.close();
}
This requests a 1280 × 800 CSS viewport at scale factor 2 and saves PNG. Verify the resulting image dimensions in your environment. The example is explanatory and has not been run as a test. Puppeteer’s viewport reference and screenshot guide describe these APIs.
| Setting | What it controls | When to change it |
|---|---|---|
width, height |
CSS viewport and responsive layout | The page layout or viewport crop is the wrong size |
deviceScaleFactor |
Device pixels per CSS pixel | You need more output pixels for the same CSS layout |
fullPage |
Whether to capture the full document | You need the page beyond the initial viewport |
clip |
A specific capture rectangle | You need a defined region of the page |
type, quality |
File encoding and lossy compression | You need to change output format or compression |
Higher scale factors increase output dimensions and can increase memory use, file size, and capture work. Choose the smallest scale that meets the destination’s pixel requirements. A high-density capture is not a substitute for a larger CSS viewport: changing the viewport may also change responsive breakpoints and layout.
3. Distinguish blurry output from a wrong capture area
A normal screenshot captures the viewport. Use fullPage: true for the full document, or clip for a defined rectangle. These options change what area is captured; they are not image-sharpness controls.
// Full document
await page.screenshot({ path: 'full.png', type: 'png', fullPage: true });
// A defined region in page coordinates
await page.screenshot({
path: 'region.png',
type: 'png',
clip: { x: 0, y: 0, width: 640, height: 400 },
});
Current ScreenshotOptions documentation says captureBeyondViewport defaults to false when there is no clip and true when a clip is supplied. Check the current option documentation and your capture mode before copying an old workaround: ScreenshotOptions reference.
4. Separate resolution from JPEG and WebP quality
Puppeteer’s quality option controls lossy JPEG or WebP encoding; it does not increase resolution. The documented range is 0–100 for applicable formats. PNG is the default screenshot type and does not use the quality setting.
// Use PNG to isolate whether lossy encoding is making details look soft.
await page.screenshot({ path: 'capture.png', type: 'png' });
// If using JPEG, test a higher quality value for less compression.
await page.screenshot({ path: 'capture.jpg', type: 'jpeg', quality: 90 });
// WebP also accepts quality where supported.
await page.screenshot({ path: 'capture.webp', type: 'webp', quality: 90 });
If PNG looks sharp but JPEG or WebP looks soft, inspect encoding quality and any later processing step. A service, upload pipeline, editor, or image tag may resize or re-encode the file after Puppeteer saves it.
5. Check the content that looks blurred
If only specific text or images appear soft, increasing the whole screenshot’s scale may not solve the cause. Check the rendered page itself:
- Check whether the source image has enough pixels for the size at which the page displays it.
- Inspect CSS dimensions, transforms, scaling, and responsive image selection.
- Wait for fonts and images to finish loading before capture; a navigation event alone may not mean every visual asset is ready.
- Compare a PNG capture with the page rendered at the same viewport in a browser.
- Confirm no later step resizes the screenshot or converts it with lossy compression.
These are diagnostic checks, not claims that Puppeteer has one specific rendering defect for these symptoms.
6. Keep Puppeteer and Chromium versions compatible
Record both the Puppeteer version and the browser executable version when investigating unexpected dimensions or visual differences. Prefer the Chromium version paired with your Puppeteer release, or check the project’s current compatibility guidance before supplying an external executable.
A Puppeteer issue opened in 2023 concerned Puppeteer 18.1.0 and 21.6.0 alongside external HeadlessChrome 119.0.6045.199; the issue discussion identified an unsupported version pairing in that case. Those are historical case details, not current version recommendations. Read the issue history.
7. Troubleshooting blurry or truncated screenshots
| Symptom | Likely cause | What to do |
|---|---|---|
| Image dimensions are smaller than expected | Viewport or scale factor differs from the intended values, or a later step resized the file | Log the viewport and scale factor, inspect the saved file’s pixel dimensions, and check the processing pipeline |
| Layout is too small or uses the wrong breakpoint | The CSS viewport is wrong; device scale does not set layout width | Set the intended viewport width and height, then choose scale separately |
| JPEG/WebP looks softer than PNG | Lossy compression or later re-encoding | Compare PNG, then adjust the applicable format’s quality setting and inspect downstream conversions |
| Only page images look blurry | Source image resolution, CSS scaling, or responsive image selection | Inspect the selected source and its intrinsic dimensions at the rendered size |
| Text or icons change between runs | Capture occurs before fonts, assets, or rendering has settled | Wait for the required selector or assets; use an appropriate navigation wait condition and a bounded extra wait if needed |
| External Chromium behaves differently | Puppeteer and Chromium versions may be an unsupported pair | Record both versions and use the supported pairing for the installed Puppeteer version |
| Element is cut off at a scale factor above 1 | Could be clip bounds, viewport geometry, or a version-specific issue | Reduce to a minimal reproduction and record OS, versions, element bounds, viewport, scale factor, screenshot options, and output dimensions |
A truncation report opened in 2025 used Puppeteer 24.4.0, Node 20.17.0, and Linux, but it was marked unconfirmed/not reproducible. Treat it as a reason to create a minimal reproduction, not proof of a general bug. See the report.
8. Make captures repeatable and manageable
- Pin Puppeteer and its compatible browser version in your deployment environment.
- Set viewport dimensions and scale factor explicitly for each capture profile.
- Choose viewport, full-page, or clip capture based on the required area.
- Use PNG while diagnosing sharpness; choose JPEG or WebP only when their compression tradeoff is acceptable.
- Wait for the page state your capture requires, such as a known selector or completed assets.
- Log capture options, browser versions, resulting dimensions, and file type when a job fails or differs.
- Keep scale and capture area proportional to the intended output; unusually large captures can require more memory and produce larger files.
There is no evidence in the sources for a universal resolution benchmark or a single scale factor that suits every workload. Measure your own output dimensions and downstream file size against the target use.
9. How should I choose the settings?
| Need | Starting choice |
|---|---|
| Exact responsive layout | Set the CSS viewport to the target layout dimensions; choose scale independently |
| More pixels for the same layout | Increase deviceScaleFactor, then inspect saved dimensions and file size |
| Whole long page | Use fullPage: true and verify the resulting height |
| One component or region | Use clip with the intended page coordinates and bounds |
| Best diagnostic comparison | Capture PNG first; compare lossy formats afterward |
| Unexpected behavior across machines | Align Puppeteer/Chromium versions and capture environment details |
Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server for developers. It accepts a URL in one GET request and returns a PNG, JPEG, WebP, or PDF. See the ScreenshotNeo site and 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}`);
ScreenshotNeo accepts cookie and consent banners and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each step can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits cost nothing, and response headers identify the page verdict and billing status. Its MCP server gives AI agents tools for screenshots, page information, and PDF capture. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 screenshots.
Sign up for 1,000 free screenshots a month, with no card required.
FAQ
Does Puppeteer have a DPI setting?
For screenshot pixel density, use deviceScaleFactor alongside the CSS viewport. DPI metadata is not a universal fix for raster screenshot sharpness.
Does a higher device scale make the webpage itself sharper?
It requests more output pixels for the viewport. It cannot add detail absent from source images or reverse later resizing or compression.
Does quality work with PNG?
No. Puppeteer documents quality for JPEG and WebP; PNG does not use that quality option.
Why is my screenshot truncated?
First confirm whether you intended viewport, full-page, or clipped capture. If the area is still unexpectedly cut off, reduce the case and record versions, bounds, viewport, scale, options, and output dimensions.


