Puppeteer guide

Puppeteer Screenshot Guide: Full Page, Elements and Base64

By SnapshotFlow ·

Use page.screenshot() to capture a website with Puppeteer. Add fullPage: true for the document, call element.screenshot() for a component, or request Base64 when the next system needs an encoded image. This guide starts with a runnable Node.js script and builds each variation from that same setup.

Puppeteer screenshot guide showing viewport, full-page and element captures from one sample page

1. Install Puppeteer and take your first screenshot

Use Node.js 24 LTS for this walkthrough. The current Puppeteer system requirements specify Node.js 22.12 or newer. The examples below use JavaScript ES modules; saving the file with a .mjs extension avoids changing your package configuration.

mkdir puppeteer-capture
cd puppeteer-capture
npm init -y
npm install puppeteer

The puppeteer package downloads a compatible Chrome for Testing browser during installation. puppeteer-core leaves browser installation and configuration to you, so use the regular package for this first capture.

Save the following as capture.mjs, or download the script. It creates an output directory, checks the navigation response, and closes Chrome even if the capture fails.

import puppeteer from "puppeteer";
import { mkdir } from "node:fs/promises";

const url = process.argv[2] ?? "https://example.com";
await mkdir("screenshots", { recursive: true });
const browser = await puppeteer.launch();

try {
  const page = await browser.newPage();
  await page.setViewport({
    width: 1440,
    height: 900,
    deviceScaleFactor: 1,
  });

  const response = await page.goto(url, {
    waitUntil: "load",
    timeout: 30_000,
  });
  if (response && !response.ok()) {
    throw new Error(`HTTP ${response.status()} for ${url}`);
  }

  await page.screenshot({ path: "screenshots/viewport.png" });
} finally {
  await browser.close();
}
node capture.mjs https://example.com

Open screenshots/viewport.png in your current directory. The capture is 1440 × 900 image pixels at the scale used here. page.screenshot() also returns the bytes, so you can send them elsewhere without saving a file. See the Page.screenshot() reference for its return types.

A successful HTTP response does not prove an application has finished rendering. The basic script waits for the load event; the readiness section below shows how to wait for application content.

The Puppeteer recipes were checked on a local static demo with Puppeteer 24.42.0, Chrome 147 and Node.js 24.15.0. Rendering on other websites depends on their content and loading behavior.

2. Choose the capture area and output

Choose the area first, then the encoding. A full-page PNG and a viewport PNG can have the same width while containing very different amounts of the page.

You needUseWhat you receive
The visible page areapage.screenshot()A viewport image
The whole documentfullPage: trueA taller image of the document
One componentelement.screenshot()An image cropped to the element
An image filepath: "page.png"A file plus the returned bytes
Image bytes in memoryNo pathA Uint8Array
A Base64 stringencoding: "base64"Encoded image data without a MIME prefix

The recipes below belong inside the try block of the first script, after navigation and before browser.close(). They reuse its page and screenshots directory. The viewport setup in section 8 belongs before navigation.

3. Take a full-page screenshot

await page.screenshot({
  path: "screenshots/full-page.png",
  fullPage: true,
});

The viewport width still controls responsive layout. With a 1440-pixel viewport, a full-page screenshot shows the desktop layout over the document’s height. It does not switch the site into a mobile layout or make the viewport wider.

Viewport: 1440 × 900

Viewport screenshot of the demo page; the lower content falls below the capture boundary

Full page: 1440 × 1800

Full-page screenshot of the same demo, including the content and footer below the fold
Two actual Puppeteer captures of the same local demonstration page at deviceScaleFactor: 1. The sample is fixed at 1800 CSS pixels tall.

fullPage is not a page-loading strategy. Lazy images or content that appears only after scrolling may still be missing. A panel with overflow: auto also keeps its own scrolling behavior. For those failures, use our Puppeteer screenshot troubleshooting guide rather than repeatedly increasing a delay.

4. Capture an individual element

// Replace "h1" with a selector from your own page.
const element = await page.waitForSelector("h1", {
  visible: true,
  timeout: 10_000,
});
if (!element) throw new Error("Screenshot element was not found");

await element.screenshot({ path: "screenshots/element.png" });

The example uses h1 because it exists on example.com. For your own site, select the component you need, such as #pricing-card or [data-testid="invoice"]. A selector matching several nodes selects the first match here; make it specific when the choice matters.

Puppeteer scrolls the element into view before capturing it. The method can still fail if the node is detached during a rerender. Waiting for visibility also does not prove that text, charts, or images inside the element have finished loading. The ElementHandle.screenshot() reference describes the scrolling and detached-node behavior.

Use this for a component preview or a report section. For a reusable API workflow, see capturing elements with CSS selectors.

5. Save PNG, JPEG or WebP

await page.screenshot({
  path: "screenshots/page.jpg",
  type: "jpeg",
  quality: 80,
});

await page.screenshot({
  path: "screenshots/page.webp",
  type: "webp",
  quality: 80,
});

PNG is a useful starting point for UI screenshots because it preserves sharp text without lossy compression. JPEG is useful when file size matters and an opaque background is acceptable. WebP is another option for web delivery; compare the actual output on your page before choosing a quality setting.

quality accepts values from 0 to 100 for JPEG and WebP. Passing it for PNG raises an error, so omit it for PNG captures. The value 80 above is an example setting, not a promise of a particular size or visual quality. Puppeteer can infer the format from the path extension, but specifying type makes the output explicit. Keep it consistent with the filename.

A .jpg path still uses type: "jpeg", not "jpg". If you omit both type and a file extension, the default format is PNG. These options are documented in ScreenshotOptions.

6. Return Base64 or bytes without writing a file

const base64 = await page.screenshot({
  type: "png",
  encoding: "base64",
});
const dataUrl = `data:image/png;base64,${base64}`;

// Decode or write bytes when the next system expects a file.
const bytes = Buffer.from(base64, "base64");

The Base64 result is a string. A data URL adds the MIME prefix, which must match the captured format: image/png, image/jpeg or image/webp. Never prepend a PNG prefix to JPEG bytes.

With the default binary encoding, the documented result is a Uint8Array. Convert it explicitly if another Node.js API expects a Buffer, or if you want to use Buffer’s Base64 encoding:

const bytes = await page.screenshot({ type: "png" });
const base64 = Buffer.from(bytes).toString("base64");

Calling toString() directly on a plain Uint8Array does not Base64-encode it. Base64 also adds roughly one third to the raw byte count for larger images; it is a transport encoding, not compression. Use binary uploads when the receiving system accepts them.

7. Use a transparent background

await page.screenshot({
  path: "screenshots/transparent.png",
  type: "png",
  omitBackground: true,
});

omitBackground removes the browser’s default white background. It does not erase a white background painted by the page’s CSS. If body or a component sets an opaque background, that color stays in the screenshot.

Use PNG when you need alpha transparency. If you own the page, make the intended background transparent in your capture stylesheet. Changing that CSS changes the appearance of the captured page, so it should be a deliberate presentation choice.

8. Control viewport size and resolution

// Set this before page.goto(). Keep the same CSS viewport.
await page.setViewport({
  width: 1440,
  height: 900,
  deviceScaleFactor: 2,
});
await page.screenshot({ path: "screenshots/retina.png" });

At a scale factor of 2, a 1440 × 900 CSS-pixel viewport produces a 2880 × 1800 viewport image. The layout width remains 1440 CSS pixels. Increasing viewport width instead can trigger a different responsive layout.

Doubling both image dimensions creates four times as many pixels before compression. It does not guarantee a file exactly four times larger. Choose scale 1 for compact report images, or try scale 2 when the screenshot will be displayed on a high-density screen.

Set the viewport before navigation because changing mobile-related viewport settings can reload a page. For a mobile layout, choose an appropriate narrower viewport or device emulation; scaling a desktop screenshot does not turn it into a mobile capture. See Page.setViewport().

9. Wait for the page state you actually need

For a static page, the load event can be enough. A dashboard may load its charts after that event, while a news site may keep making requests indefinitely. There is no single navigation event that means every website is visually complete.

// Run after page.goto(). Choose a signal your app owns.
await page.waitForFunction(() =>
  document.documentElement.dataset.captureReady === "true",
  { timeout: 15_000 },
);
await page.evaluate(() => document.fonts.ready.then(() => true));
await page.screenshot({ path: "screenshots/ready.png" });

This recipe requires your application to set document.documentElement.dataset.captureReady = "true" after its data and relevant images finish rendering. Without that signal, it times out. If you do not control the application, wait for a specific visible component and, where necessary, a concrete value or loaded-image state instead.

Waiting for document.fonts.ready covers fonts used by the document at that point. It does not wait for future application updates. Network-idle checks can help on some pages, but they do not replace an application signal, trigger lazy loading, or make an animated chart stop moving.

If an image is blank, clipped or inconsistent, continue with the symptom-by-symptom fixes. That guide covers the failure cases; this walkthrough establishes the capture options.

10. Use SnapshotFlow when you want to outsource browser operation

Puppeteer is a good fit when you need browser interactions you control or already operate a browser automation setup. A scheduled preview service also needs browser capacity, timeouts, retries and updates. If you would rather send a URL to a managed renderer, SnapshotFlow provides that path.

Install snapshotflow, set SNAPSHOTFLOW_API_KEY in your server environment, and run this as a separate .mjs script:

import { SnapshotFlow } from "snapshotflow";

const apiKey = process.env.SNAPSHOTFLOW_API_KEY;
if (!apiKey) throw new Error("Set SNAPSHOTFLOW_API_KEY");

const client = new SnapshotFlow({ apiKey });
const shot = await client.take({
  url: "https://example.com",
  width: 1440,
  height: 900,
  fullPage: true,
  format: "png",
});
await shot.save("example-full.png");

This request returns a full-page PNG without launching Chrome in the calling Node.js process. It uses the SDK’s fullPage option; the raw HTTP API uses full_page. Keep API credentials on the server.

For the operational trade-offs, read Puppeteer versus a screenshot API. For a comparison with Playwright and a fuller SDK walkthrough, see website screenshots in Node.js.

Frequently asked questions

Does Puppeteer save a screenshot if I leave out path?

No. Without path, screenshot() returns the image bytes, or a Base64 string when encoding is set to base64, without writing a file.

Why does fullPage miss part of a dashboard?

fullPage captures the document. A fixed-height panel with its own scrollbar remains a separate scroll container. Capture the panel separately or deliberately change its layout before capture.

Can I use quality with a PNG screenshot?

No. Puppeteer throws an error if you set quality for PNG. Use quality only with JPEG or WebP. To reduce PNG size, capture a smaller area or use a lower device scale factor.

Is a Base64 screenshot a data URL?

No. encoding: "base64" returns the encoded image without a MIME prefix. For a PNG data URL, prepend data:image/png;base64,.

Sources and related guides