What DPI Does Puppeteer Use for PDFs and Screenshots?
Puppeteer has no single DPI: PDFs use print geometry, while screenshots use viewport pixels and deviceScaleFactor.
Short answer: Puppeteer has no single DPI setting shared by PDFs and screenshots. A PDF uses print layout: CSS is converted at 96 CSS pixels per inch and 72 points per inch, while page.pdf() exposes paper size, margins, CSS page sizing, and a scale from 0.1 to 2. A screenshot is a raster image: its pixels come from the viewport dimensions multiplied by deviceScaleFactor (default 1). Its physical DPI is only knowable after you choose a display or print size.
That distinction prevents two common mistakes: calling every Puppeteer PDF “300 DPI,” and calling a screenshot “96 DPI” without stating its physical output size. Chromium contains a kDefaultPdfDpi = 300 printing constant, but Puppeteer’s public PDF options do not expose that constant as a universal output-DPI control.
PDFs and screenshots use different rendering paths
| Output | What determines output | How to control it | How to discuss DPI |
|---|---|---|---|
| Print layout and physical page geometry | format or width/height, margins, @page, preferCSSPageSize, and scale |
Discuss page dimensions and CSS/print units; there is no general Puppeteer DPI option | |
| Screenshot | Rasterization of a viewport | Viewport width/height and deviceScaleFactor, plus screenshot type, clip, and full-page options |
Compute pixels per physical inch only after fixing the display or print size |
What DPI does Puppeteer use for PDFs?
page.pdf() generates print-media output by default. Puppeteer uses the browser’s print geometry rather than a user-selected raster DPI. Chromium’s documented conversion is 96 CSS pixels per inch and 72 points per inch, so a CSS length can be converted to print points, but that does not mean the resulting PDF has one universal pixel density.
PDF settings that affect output
format: a named paper size such as A4 or Letter.width/height: explicit page dimensions with units such asin,mm, orpx.margin: printable margins; include them in page-size calculations.preferCSSPageSize: let CSS@pagesize win instead of fitting content to the selected format.scale: rendering scale from 0.1 through 2, default 1. It changes layout size; it is not a DPI field.printBackground: include background colors and images when required.
Runnable PDF example
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('https://example.com', { waitUntil: 'networkidle0' });
await page.pdf({
path: 'report.pdf',
format: 'A4',
printBackground: true,
margin: { top: '16mm', right: '16mm', bottom: '16mm', left: '16mm' },
preferCSSPageSize: true,
scale: 1
});
await browser.close();
Use screen styles only when intentional:
await page.emulateMediaType('screen');
await page.pdf({ path: 'screen-styled.pdf', format: 'A4' });
CSS page sizing
@page {
size: A4;
margin: 12mm;
}
@media print {
.navigation, .chat-widget { display: none; }
}
With preferCSSPageSize: true, the browser honors CSS page size. Keep page size, margins, and scale fixed when comparing PDFs; changing any of them changes pagination and apparent print density.
What DPI does Puppeteer use for screenshots?
A screenshot is a bitmap. Puppeteer’s viewport is expressed in CSS pixels; deviceScaleFactor controls how many output pixels represent each CSS pixel and defaults to 1. A viewport of 1,200 by 800 CSS pixels produces about 1,200 by 800 image pixels at scale 1, or 2,400 by 1,600 at scale 2.
Physical DPI is image pixels ÷ physical inches. A 2,400-pixel-wide image printed 12 inches wide is 200 PPI; displayed 24 inches wide it is 100 PPI. The file did not change, so “Puppeteer screenshots are 96 DPI” is incomplete.
Runnable screenshot example
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.setViewport({ width: 1200, height: 800, deviceScaleFactor: 2 });
await page.goto('https://example.com', { waitUntil: 'networkidle0' });
await page.screenshot({ path: 'retina.png', type: 'png' });
await browser.close();
For a full page use fullPage: true. For one element, capture its bounding box:
await page.screenshot({ path: 'full.webp', type: 'webp', fullPage: true });
const card = await page.$('.invoice');
const clip = await card.boundingBox();
if (!clip) throw new Error('Invoice element is not visible');
await page.screenshot({ path: 'invoice.png', clip });
Does deviceScaleFactor change PDF DPI?
No documented Puppeteer behavior connects viewport deviceScaleFactor to a universal PDF DPI. Set viewport scale for screenshots. Set PDF paper geometry, CSS @page, margins, print media, and PDF scale for PDFs.
How to choose settings
- Choose the deliverable. Use PDF for selectable, paginated print output; use PNG, JPEG, or WebP for a fixed raster image.
- For PDF, define physical geometry. Pick paper size, margins, and
@page. UsepreferCSSPageSizewhen the document owns its page size. - For screenshots, define CSS layout first. Set viewport width and height, then choose device scale for target pixels.
- Wait for visual completeness. Wait for a selector, deliberate delay, or network idle when fonts, charts, or lazy content are involved.
- Measure the artifact. Inspect PDF page boxes and screenshot pixel dimensions. Do not infer physical DPI from pixels alone.
Resolution, quality, and performance
- Increasing
deviceScaleFactorincreases bitmap dimensions and memory use. It can sharpen text and thin lines but cannot repair low-resolution source images. - Full-page screenshots can become very tall. Split long pages when downstream systems have image-size limits.
- PDF
scalechanges layout fitting and pagination. Use paper dimensions and CSS page rules first. - Network idle can wait indefinitely on analytics or streaming connections. Prefer a readiness selector or bounded delay when needed.
- Reuse a browser process for batches, create a fresh page per capture, and close pages to avoid memory growth. Pin Puppeteer and browser versions when exact rendering matters.
Common errors and fixes
| Symptom | Cause | Fix |
|---|---|---|
| PDF is not 300 DPI | Chromium’s internal constant was treated as a public Puppeteer setting. | Set paper size, margins, CSS page rules, and scale; verify physical print size. |
| Screenshot has too few pixels | Viewport is in CSS pixels and scale remained 1. | Set deviceScaleFactor before navigation and capture. |
| PDF differs from browser | PDF uses print media by default. | Add print CSS or call page.emulateMediaType('screen'). |
| Content is clipped | Paper size, margins, @page, and scale conflict. |
Use one page-size source, enable preferCSSPageSize, and remove unnecessary scale changes. |
| Blank or incomplete capture | Navigation, fonts, lazy images, or client rendering had not finished. | Check the response, wait for a readiness selector, and use a bounded timeout. |
boundingBox() is null |
Element is hidden, detached, or not rendered. | Wait for the selector, ensure visibility, and read the box immediately before capture. |
Or skip the browser setup
If you need an image or PDF from a URL without maintaining Chromium, ScreenshotNeo provides a website screenshot API and MCP server. Cookie and consent banners, newsletter popups, and chat widgets are removed before capture; bot checks, blank pages, failed loads, timeouts, and cache hits are not billed, and X-Page-Verdict and X-Billed identify the result.
See the ScreenshotNeo API documentation for 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}`);
ScreenshotNeo supports full-page and element capture, dark mode, device presets or custom viewports, retina scale, PDF paper and margin controls, custom CSS and JavaScript, waits, request blocking, headers and cookies, geolocation, caching, signed links, async jobs, bulk capture, usage data, and an MCP server for Claude, Cursor, and other MCP clients. Every feature is on every plan: 1,000 screenshots per month are free with no card; paid plans start at $5 for 3,000.
Create a free ScreenshotNeo account and start with 1,000 screenshots a month at no charge.
FAQ
Is PDF DPI the same as PPI?
No. A PDF stores page geometry and vector or text instructions; PPI or DPI becomes meaningful when raster content is displayed or printed.
Can I set 300 DPI in page.pdf()?
There is no general DPI option in Puppeteer’s documented PDF API. Configure dimensions, margins, CSS page sizing, and scale instead.
What is the highest useful screenshot scale?
Use the scale that produces the pixel dimensions your consumer needs. Higher values increase memory and transfer costs and may exceed downstream limits.
Should I use PNG or JPEG?
PNG preserves sharp text and flat UI edges. JPEG is smaller for photographic pages and introduces lossy compression. WebP can be smaller when supported.


