Puppeteer screenshots: full page, element & PDF

Tested Puppeteer code for the screenshots people actually need, the fixes for the usual broken output, and an honest look at when an HTTP API saves you running Chrome.

Full-page screenshot

Install with npm i puppeteer. It downloads a matching Chrome into ~/.cache/puppeteer.

const puppeteer = require("puppeteer");

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  await page.setViewport({ width: 1280, height: 800 });
  await page.goto("https://example.com", { waitUntil: "networkidle2" });
  await page.screenshot({ path: "full.png", fullPage: true });
  await browser.close();
})();

fullPage: true captures the whole scrollable height. Without it you get just the 1280×800 viewport. Puppeteer's default viewport is 800×600, so always set one.

Screenshot one element or a region

const heading = await page.waitForSelector("h1");
await heading.screenshot({ path: "heading.png" });

await page.screenshot({ path: "region.png", clip: { x: 0, y: 0, width: 600, height: 300 } });

waitForSelector returns an element handle once it's in the DOM. Its screenshot() scrolls it into view and crops to its bounding box.

Retina, JPEG, dark mode

await page.setViewport({ width: 1280, height: 800, deviceScaleFactor: 2 }); // 2560px wide
await page.emulateMediaFeatures([{ name: "prefers-color-scheme", value: "dark" }]);
await page.goto("https://example.com");
await page.screenshot({ path: "shot.jpg", type: "jpeg", quality: 80 });

type also takes "webp". quality only applies to JPEG and WebP.

PDF

await page.goto("https://example.com", { waitUntil: "networkidle2" });
await page.emulateMediaType("screen"); // optional: look like the screen, not print CSS
await page.pdf({ path: "page.pdf", format: "A4", printBackground: true,
  margin: { top: "1cm", bottom: "1cm", left: "1cm", right: "1cm" } });

Without printBackground background colors and images are dropped. By default Chrome uses your @media print styles.

Why the screenshot looks wrong (and the fixes)

The same thing as one HTTP call

If screenshots are a side feature, you may not want Chrome, fonts and a browser pool in your deployment. Shotmint runs the browser for you:

// shot.mjs, run with: SHOTMINT_KEY=... node shot.mjs
import { writeFileSync } from "node:fs";

const params = new URLSearchParams({
  url: "https://example.com",
  full_page: "true",               // scrolls first, so lazy images load
  device_scale_factor: "2",
  format: "jpeg",
  quality: "80",
  block_ads: "true",               // also hides common cookie banners
});
const r = await fetch("https://shotmint.creasework.com/v1/screenshot?" + params, {
  headers: { "X-API-Key": process.env.SHOTMINT_KEY },
});
if (!r.ok) throw new Error(await r.text());
writeFileSync("full.jpg", Buffer.from(await r.arrayBuffer()));
PuppeteerShotmint
setViewport({width, height, deviceScaleFactor})width, height, device_scale_factor
screenshot({fullPage: true})full_page=true
(await page.waitForSelector(sel)).screenshot()selector=sel
goto(url, {waitUntil})wait_until=load|domcontentloaded|networkidle, plus delay (ms, up to 10000)
emulateMediaFeatures(dark)dark_mode=true
addStyleTag({content: ".x{display:none}"})hide_selectors=.x, or block_ads=true
page.pdf({format, landscape, printBackground})/v1/pdf with format, landscape, print_background

When to keep Puppeteer

Keep it for anything interactive: logging in, clicking through flows, filling forms, scraping. Also keep it at very high volume, or for pages on a private network. The API is for when you just need the picture or the PDF. More: Puppeteer vs. an API, Playwright screenshots, Selenium in Python, plain chrome --headless. No code at all: the full-page screenshot tool.

Try it with 100 free renders

No signup, no card. Get a key in one click, then buy credits only if you need more (from €5 for 2,000, never expire).

Get a free API key Full API reference