ScreenshotNeo

BlogHow-to

How to Save a Playwright Screenshot as a Buffer in Node.js

Use `await page.screenshot()` to get screenshot bytes as a Node.js Buffer. Learn how to configure, process, and troubleshoot the result.

By the ScreenshotNeo team4 October 20268 min read

In Node.js, await page.screenshot() to get a screenshot as a Buffer:

const screenshotBuffer = await page.screenshot();
console.log(Buffer.isBuffer(screenshotBuffer)); // true

Leave out the path option when you want the bytes in memory without saving an image file. Add path when you also want Playwright to write the screenshot to disk. These are separate needs: the returned buffer is useful for uploading, transforming, or attaching the image without first reading a file.

This guide uses Playwright’s Node.js API. See the official Page API reference for the current Page.screenshot() options. Some options were added in later Playwright releases, so check your installed version if an option is unavailable.

1. Create a screenshot buffer

This complete example launches Chromium, opens a page, captures the viewport as a PNG buffer, and closes the browser even if capture fails:

const { chromium } = require('playwright');

async function main() {
  const browser = await chromium.launch();
  try {
    const page = await browser.newPage();
    await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });

    const screenshotBuffer = await page.screenshot();
    console.log('Buffer:', Buffer.isBuffer(screenshotBuffer));
    console.log('Bytes:', screenshotBuffer.length);
  } finally {
    await browser.close();
  }
}

main().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});

Install Playwright and its browser if needed with npm install playwright and npx playwright install chromium. In projects already using Playwright, use the same package and browser setup as the rest of the project.

The key line is const screenshotBuffer = await page.screenshot(). Its resolved value is a Node.js Buffer. The screenshot is PNG by default, and no file is written unless you provide path.

2. Save the buffer to a file, or keep it in memory

To both get a buffer and save a copy, pass a path. Playwright still returns screenshot bytes, and the file extension can determine the image format:

const screenshotBuffer = await page.screenshot({ path: 'screenshot.png' });

To write the already captured bytes yourself, use Node’s file API:

const { writeFile } = require('node:fs/promises');

const screenshotBuffer = await page.screenshot();
await writeFile('screenshot.png', screenshotBuffer);

Use the first approach when you want Playwright to manage file output. Use the second when you need to send or inspect the buffer before deciding whether or where to store it. Avoid capturing to a file and then reading it back if the buffer alone is what you need.

3. Configure format, quality, region, and scale

The basic call captures the visible viewport as a PNG. Pass options to change its encoding or the area captured:

Option What it controls Notes
type Image encoding: png, jpeg, or webp. PNG is the default.
quality Lossy encoding quality from 0 to 100. Applies to JPEG and WebP, not PNG. JPEG defaults to 80; WebP defaults to 100, which the reference describes as lossless. Lower WebP quality is lossy.
fullPage Capture the full scrollable page. Without it, Playwright captures the viewport.
clip Capture a specified rectangle. Useful for a region of the viewport; set its coordinates and dimensions deliberately.
scale Choose output pixel scale. 'css' uses one output pixel per CSS pixel. 'device' uses device pixels and is the default, so high-DPI pages can produce larger images.

JPEG buffer with a quality setting

const jpegBuffer = await page.screenshot({
  type: 'jpeg',
  quality: 80,
});

Full-page WebP capture

const webpBuffer = await page.screenshot({
  type: 'webp',
  quality: 85,
  fullPage: true,
});

Capture a rectangle at CSS pixel scale

const regionBuffer = await page.screenshot({
  type: 'png',
  scale: 'css',
  clip: { x: 20, y: 30, width: 640, height: 360 },
});

Pick options based on how the result will be used. PNG preserves lossless detail and is a practical default for text and interface captures. JPEG or lower-quality WebP can reduce output size when lossy compression is acceptable. CSS scale makes output dimensions easier to relate to CSS layout; device scale preserves the default device-pixel behavior and can produce sharper, larger output on high-DPI displays.

4. Use the buffer with other Node.js code

A buffer can be passed directly to APIs that accept binary data. For example, send it as the body of an HTTP request with its media type:

const screenshotBuffer = await page.screenshot({ type: 'png' });

const response = await fetch('https://example.com/upload', {
  method: 'POST',
  headers: { 'content-type': 'image/png' },
  body: screenshotBuffer,
});

if (!response.ok) {
  throw new Error(`Upload failed: ${response.status} ${response.statusText}`);
}

Use the matching content type for the encoding: image/png, image/jpeg, or image/webp. If an API expects multipart form data or a named file, follow that API’s format rather than sending raw bytes.

For Base64 output, convert only when the destination requires text encoding. Base64 increases payload size compared with binary transfer:

const base64 = screenshotBuffer.toString('base64');

For a data URL, include the correct MIME type, such as data:image/png;base64,.... Do not label JPEG or WebP bytes as PNG.

5. Wait for the page before capturing

A screenshot records the rendered page state at capture time. Navigate and wait for the content your image needs before calling screenshot(). For client-rendered pages, waiting for the relevant selector is often more reliable than assuming navigation alone means the page is ready:

await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
await page.locator('main').waitFor({ state: 'visible' });
const screenshotBuffer = await page.screenshot({ fullPage: true });

Choose a readiness condition that reflects the page: a visible element, a known application state, or another documented wait condition. A fixed delay can help with a known animation or delayed content, but it adds time and may still be too short or unnecessarily long.

6. Relevant screenshot options and edge cases

Beyond format, region, and scale, the Page API includes options for behavior around the capture. Use only the ones that solve a concrete requirement:

  • Animation: control whether animations are disabled or allowed during capture. This can help make repeated captures stable, but changes what the page looks like at that moment.
  • Caret: control whether the text caret is hidden in the output.
  • Background: omit the default background when supported by the selected format. Consider format transparency support if transparent output matters.
  • Masks: cover selected page elements in the screenshot, for example when a changing region should not appear. Ensure the selectors match the intended elements.
  • Stylesheet injection: apply a stylesheet for the capture, such as hiding an element or adjusting presentation.
  • Timeout and abort signal: bound or cancel screenshot work where the installed Playwright version supports the relevant option.

Consult the current API reference for exact option types and version availability. Screenshot dimensions, option support, and output bytes depend on the page, browser, device scale, and Playwright version. A clip rectangle must make sense for the rendered page; for full-page capture, expect the output dimensions and memory use to grow with page height.

7. Troubleshooting

Symptom Likely cause Fix
The value is not a Buffer or is undefined. The promise was not awaited, or a wrapper returned something else. Use const screenshotBuffer = await page.screenshot() and check that the code is calling Playwright’s Page method.
No file appears. No path was provided. That is expected for an in-memory capture. Add a path or write the returned buffer with writeFile.
The image has the wrong format. The call used the default PNG or the file extension and explicit type do not reflect the desired encoding. Set type to 'png', 'jpeg', or 'webp' as needed, and use a matching extension and MIME type when saving or uploading.
The capture is blank or missing page content. The page had not rendered the target content when capture began, or the content is outside the viewport. Wait for a meaningful selector or page state; use fullPage: true when you need the full scrollable document.
The buffer is unexpectedly large. Full-page capture, device-pixel scaling, or a lossless/high-detail format produced many pixels. Capture only the needed region, consider scale: 'css', or use JPEG/WebP quality settings if lossy output fits the use case.
A screenshot option is rejected or missing. The installed Playwright version may predate that option. Check the current reference and the version in the project; update Playwright if the project can adopt the newer API.
The screenshot call times out. The page or capture operation is slow, or the timeout is too short. Wait for the necessary content before capture, avoid capturing more area than required, and configure timeout only where supported and appropriate.

8. Performance, reliability, and cost

Capturing into a buffer avoids a file write when you only need bytes, but the image still has to be rendered and encoded. Large full-page captures consume more memory and take longer to process than smaller viewport or clipped images. Device-pixel output can multiply pixel count on high-DPI pages. If captures run in a service, avoid retaining many large buffers at once; upload or process them and release references as soon as practical.

For repeatable results, wait for the content that matters, choose animation behavior intentionally, and keep browser cleanup in a finally block. If capture is part of a batch, handle per-page failures so one failed navigation does not silently invalidate the whole batch. Set limits appropriate to your own workload for navigation and capture time.

Playwright is browser automation software: your application or infrastructure supplies the browser execution and must account for its resource use and any hosting costs. The cited screenshot API does not establish a universal cost or performance benchmark, so measure against your own pages, capture sizes, and deployment environment.

9. Or skip the browser setup

If you need screenshot bytes without launching and maintaining a browser in your Node.js application, ScreenshotNeo provides a website screenshot API and MCP server. Its one-request endpoint returns PNG, JPEG, WebP, or PDF output. See the ScreenshotNeo API documentation for request options.

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
const screenshotBuffer = Buffer.from(await res.arrayBuffer());

The response body is bytes, so convert its array buffer to a Node.js Buffer as shown. ScreenshotNeo removes cookie banners, popups, and chat widgets before the shot; bot checks, blank pages, and failed loads are never billed. Its MCP server lets AI agents take screenshots. The free plan includes 1,000 screenshots a month with no card, and paid plans start at $5 for 3,000. Create a free account and get 1,000 screenshots a month with no card.

10. FAQ

Does page.screenshot() return a Buffer or a file path?

In the Node.js API, awaiting it returns a Buffer. A supplied path additionally tells Playwright to save an image file.

Can I use the buffer without saving it?

Yes. Pass it to code that accepts binary data, such as an upload request or an image-processing library.

Is PNG the only supported format?

No. The documented types are PNG, JPEG, and WebP. PNG is the default; quality applies to JPEG and WebP.

Why is a screenshot larger on one machine?

Device-pixel scaling is the default and can produce more pixels on high-DPI devices. Use scale: 'css' when one output pixel per CSS pixel is preferable.

Can I get a screenshot buffer from a full page?

Yes. Set fullPage: true; the result remains a Buffer, with dimensions based on the full scrollable page.