Use await page.screenshot() without a path, then convert the returned Uint8Array with Buffer.from() when your next API specifically requires a Node.js Buffer. Puppeteer’s default result is binary image data; it is not documented as a Buffer return value. Omitting path keeps the screenshot in memory instead of writing a file.
The direct answer
In Puppeteer 25.12.0, the default overload of page.screenshot() resolves to a Uint8Array. Node.js Buffer is interoperable with Uint8Array, so this is the usual conversion:
const screenshotBytes = await page.screenshot();
const screenshotBuffer = Buffer.from(screenshotBytes);
Do not pass a path if you want an in-memory result. A path asks Puppeteer to write the image; with no path, you receive the bytes and can upload, store, transform, or return them yourself.
The official Puppeteer API describes the method simply as “Captures a screenshot of this page.” The important implementation detail is the return type: binary bytes by default, or a base64 string when you explicitly request base64 encoding.
#1 Best Overall
A complete Node.js example
This example launches Chromium, navigates to a page, waits for the navigation to settle, captures a PNG in memory, converts it to a Buffer, and closes the browser even if capture fails.
import puppeteer from 'puppeteer';
import { Buffer } from 'node:buffer';
const browser = await puppeteer.launch();
try {
const page = await browser.newPage();
await page.goto('https://example.com', { waitUntil: 'networkidle2' });
const screenshotBytes = await page.screenshot();
const screenshotBuffer = Buffer.from(screenshotBytes);
console.log({
byteLength: screenshotBuffer.length,
contentType: 'image/png'
});
// Pass screenshotBuffer to an upload, image processor, or HTTP response.
} finally {
await browser.close();
}
The conversion does not create a file. screenshotBuffer.length reports the encoded image size in bytes, not the width or height of the page. The default screenshot format is PNG unless you select another type.
CommonJS version
If your project uses CommonJS rather than ES modules, load Puppeteer with require and use Node’s global Buffer:
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch();
try {
const page = await browser.newPage();
await page.goto('https://example.com', { waitUntil: 'networkidle2' });
const screenshotBuffer = Buffer.from(await page.screenshot());
console.log(screenshotBuffer.length);
} finally {
await browser.close();
}
})();
Why the result is a Uint8Array
A screenshot is an encoded PNG, JPEG, or WebP file represented as bytes. Puppeteer documents the default return as Promise<Uint8Array>, which is a portable JavaScript typed-array representation. Node’s Buffer API is built to work with that byte data. Convert at the boundary where a dependency asks for a Buffer rather than converting automatically everywhere.
Free tools Windows power users keep installed
One-click scans. No signup required.
- Use the original
Uint8Arraywhen the receiving API accepts typed arrays or generic byte data. - Use
Buffer.from(bytes)when a library checks for a Node.js Buffer or exposes Buffer-specific methods. - Keep the value binary. Do not call
toString()unless you intentionally need text or an encoded representation.
Buffer.from() is a memory operation; it does not save the image. Writing requires an explicit filesystem call such as writeFile, while sending the bytes in an HTTP response or upload does not.
Capture without writing to disk
The path option is optional. Leave it out for an in-memory screenshot:
const bytes = await page.screenshot({ type: 'webp' });
const buffer = Buffer.from(bytes);
If you provide path: 'shot.webp', Puppeteer writes the encoded image to that location. Depending on the API and your options, you may still receive bytes, but a path introduces filesystem work and permissions that are unnecessary for an upload pipeline.
Rank #2
- HTML CSS Design and Build Web Sites
- Comes with secure packaging
- It can be a gift option
Returning a Buffer from an HTTP route
For an Express-style handler, set the matching content type and send the Buffer directly:
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
app.get('/preview', async (req, res, next) => {
try {
const page = await browser.newPage();
try {
await page.goto('https://example.com', { waitUntil: 'networkidle2' });
const image = Buffer.from(await page.screenshot({ type: 'png' }));
res.type('png').send(image);
} finally {
await page.close();
}
} catch (error) {
next(error);
}
});
Do not convert the Buffer to UTF-8 text. Binary image bytes can be corrupted by a text encoding step.
Select the image you actually need
Most screenshot problems come from choosing the wrong capture scope or format, not from the Buffer conversion.
| Need | Option | Behavior |
|---|---|---|
| Visible viewport only | Omit fullPage |
The documented default is false; captures the current viewport. |
| Entire scrollable page | fullPage: true |
Captures the full page rather than only the viewport. |
| One rectangle | clip: { x, y, width, height } |
Captures the specified region in page coordinates. |
| PNG | type: 'png' |
Lossless output; quality does not apply to PNG. |
| JPEG | type: 'jpeg', quality: 80 |
Lossy output; quality is a value from 0 to 100. |
| WebP | type: 'webp' |
Use when your downstream image stack accepts WebP. |
| Transparent background | omitBackground: true |
Hides Puppeteer’s default white background. |
Full-page capture
const buffer = Buffer.from(await page.screenshot({
fullPage: true,
type: 'png'
}));
Full-page mode can produce a much larger image than a viewport capture. If the page has lazy-loaded content, make sure the content you need has been loaded before taking the screenshot; Puppeteer’s screenshot call does not guarantee that every application-specific lazy-load trigger has fired.
Element or rectangular region
For a known element, obtain its bounding box and pass it to clip:
const element = await page.$('.invoice');
if (!element) throw new Error('Invoice element was not found');
const box = await element.boundingBox();
if (!box) throw new Error('Invoice element is not visible');
const buffer = Buffer.from(await page.screenshot({
clip: box,
type: 'png'
}));
A missing element and a null bounding box are different failures: the selector may be wrong, or the element may exist but be hidden or outside a renderable state.
JPEG quality and transparency
Use quality only with a lossy format such as JPEG. A PNG ignores that setting. Use omitBackground: true when the consumer needs alpha transparency; otherwise the page is rendered against the normal white background.
Rank #3
Binary bytes versus base64
Puppeteer has a separate overload for base64:
const base64 = await page.screenshot({ encoding: 'base64' });
// base64 is a string, not a Uint8Array or Buffer
const buffer = Buffer.from(base64, 'base64');
Base64 is useful when a protocol explicitly requires text, such as a JSON field or a data URL. It is larger than the original binary representation, so keep the default byte result for file uploads, object storage, HTTP responses, and image-processing libraries that accept bytes.
Wait for the page state you intend to capture
page.goto() only tells you that the selected navigation condition was reached. Dynamic pages may still update after that point. Choose a wait strategy that matches the page:
await page.goto(url, { waitUntil: 'domcontentloaded' });
await page.waitForSelector('.report-ready');
await page.screenshot();
Use a selector that represents the finished UI rather than an arbitrary delay whenever possible. A fixed delay can be appropriate for an animation or a third-party widget, but it increases latency and can still race a slow request.
Browser and page lifecycle
Always close pages and the browser in finally blocks in a service that captures repeatedly. A failed navigation, selector timeout, or screenshot protocol error should not leave Chromium processes running.
const browser = await puppeteer.launch();
try {
for (const url of urls) {
const page = await browser.newPage();
try {
await page.goto(url, { waitUntil: 'networkidle2', timeout: 45_000 });
const bytes = await page.screenshot({ fullPage: true });
await saveOrUpload(Buffer.from(bytes));
} finally {
await page.close();
}
}
} finally {
await browser.close();
}
Puppeteer notes that, within a BrowserContext, some page-creation and close methods wait for an in-progress screenshot to finish. Page.bringToFront() does not wait for existing screenshot operations. Coordinate concurrent work with that behavior in mind, and avoid sharing one page between overlapping captures unless your sequencing is explicit.
TypeScript typing
The default call is typed as a byte array, while the base64 overload is typed as a string. Keep the branches separate so the compiler can catch accidental text handling:
Do these 3 things before closing this tab:
1Scan for outdated or missing drivers - takes under a minute2Clear out junk files and repair common Windows errors3Fix the driver behind crashes, sound loss and screen glitchesimport puppeteer from 'puppeteer';
import { Buffer } from 'node:buffer';
const browser = await puppeteer.launch();
try {
const page = await browser.newPage();
const bytes = await page.screenshot({ type: 'png' });
const image: Buffer = Buffer.from(bytes);
console.log(image.byteLength);
} finally {
await browser.close();
}
Troubleshooting
Buffer is not defined
This normally means the code is running in a browser bundle or an environment without Node’s Buffer global. Run the capture in Node.js, or import it explicitly with import { Buffer } from 'node:buffer'.
Rank #4
- Brand: Wiley
- Set of 2 Volumes
- A handy two-book set that uniquely combines related technologies Highly visual format and accessible language makes these books highly effective learning tools Perfect for beginning web designers and front-end developers
The value is a string
Check for encoding: 'base64'. Remove that option for binary output, or decode deliberately with Buffer.from(value, 'base64').
The screenshot is saved instead of returned
Inspect the options for a path value. Remove it when the pipeline should remain in memory.
The page is blank or incomplete
Wait for the application’s ready selector, verify that navigation did not fail, and check whether content is lazy-loaded or hidden until interaction. A successful screenshot call only means Chromium produced an image; it does not prove that the page contained the data you expected.
Outdated Drivers Are Slowing You Down
One free scan finds every outdated or missing driver and matches the right update for your exact hardware.Free scan · exact hardware matchWindows Errors? Fix Them Before They Spread
Repair common Windows errors and clear accumulated junk for a smoother, more stable PC - no reinstall needed.Free scan · no reinstallNode is either not clickable or the target is missing
For element captures, wait for the selector, confirm the selector matches the intended element, and check boundingBox(). Hidden elements do not have a useful capture rectangle.
Memory usage grows during batch jobs
Close each page after its upload or processing step, and close the browser when the batch ends. Limit concurrency to what the host can support; full-page images and high-resolution viewports require more memory than small viewport captures. There is no universal screenshot-size or memory limit established by the API documentation, so measure your own pages and runtime.
Performance, reliability, and cost considerations
Screenshot latency is determined by browser startup, navigation, page JavaScript, network requests, waiting rules, image dimensions, and encoding. Reuse a browser for a batch instead of launching one process per URL, while still isolating captures in separate pages when practical. Set navigation and selector timeouts appropriate to your service and record which stage failed.
Keep PNG for text-heavy or lossless requirements. JPEG can reduce payload size for photographic pages, but quality changes the visual result. Base64 adds an encoding step and should be reserved for text-only transports. Since Puppeteer runs a real browser, your deployment also needs a compatible Chromium installation and enough CPU and memory for the chosen concurrency.
Best Value
Or skip the browser setup
If you do not need to manage Chromium, navigation waits, cleanup, or byte conversion yourself, ScreenshotNeo returns a screenshot from one HTTP request. Its clean-shot pipeline accepts cookie and consent banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each step can be disabled. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and every response identifies the result with X-Page-Verdict and X-Billed headers. It also provides an MCP server for AI agents such as Claude, Cursor, and other MCP clients.
See the parameter reference in the ScreenshotNeo documentation. A basic request can write the returned bytes directly to a file or pass them to your own Buffer-based code:
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)
r.raise_for_status()
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}`);
if (!res.ok) throw new Error(`Screenshot failed: ${res.status}`);
const bytes = Buffer.from(await res.arrayBuffer());
ScreenshotNeo includes full-page capture with lazy images loaded, CSS-selector element capture, dark mode, 12 device presets plus custom viewports, retina scale, PDF output, custom CSS and JavaScript, click-before-capture, selector waits, delays, network-idle waits, ad and tracker blocking, custom headers and cookies, user-agent and Authorization settings, timezone and geolocation, transparent backgrounds, resizing, configurable caching, signed links, asynchronous jobs with signed webhooks, bulk capture for up to 100 URLs per call, a usage API, and an OpenAPI specification. Parameter names used by other screenshot APIs are also accepted to ease migration.
| Plan | Included screenshots | Price |
|---|---|---|
| Free | 1,000 per month | $0, no card |
| Starter | 3,000 | $5 |
| Growth | 15,000 | $15 |
| Pro | 60,000 | $39 |
| Scale | 250,000 | $99 |
| Business | 1,000,000 | $249 |
Every feature is available on every plan, and yearly billing gives two months free. You can create a free ScreenshotNeo account with 1,000 screenshots a month and no card required.
Quick wins for a faster PC:
Repair Windows errors before they cause bigger problemsFix Now →Scan for outdated or missing drivers - takes under a minuteDriver Scan →Clear out junk files and repair common Windows errorsFree Scan →Frequently Asked Questions
Can I pass Puppeteer’s Uint8Array directly to an upload API?
Yes, when that API accepts Uint8Array or another byte-oriented input. Convert with Buffer.from only when the API requires Node’s Buffer type or Buffer-specific methods.
Does converting the screenshot to Buffer change its image format?
No. Buffer.from preserves the encoded bytes produced by Puppeteer; the format is selected by the screenshot options, such as png, jpeg, or webp.
When should I choose base64 instead of a Buffer?
Choose base64 only when the receiving protocol requires text, for example a JSON property or data URL. Use binary bytes for normal uploads and HTTP image responses.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.
The Tool Desk
Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →

