October DealsAmazon USOctober deal check: compare before you payAmazon US: current deals, useful picks and tech finds.Check DealsClean PCRecommendedOne scan can reveal what keeps slowing WindowsLook for cleanup and repair opportunities.Run ScanOctober DealsAmazon USDeal season is back - check today's better picksAmazon US: current deals, useful picks and tech finds.See Picks×
Skip to content
Node.js

How to Get a PDF Buffer from a Puppeteer Response Body

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

To get a PDF returned by a website as a Node.js buffer, wait for the matching Puppeteer HTTPResponse and call await response.buffer(). Start waiting before the click or navigation that triggers the request, then identify the PDF response by its URL, status, or Content-Type. Use page.pdf() instead only when you want Puppeteer to generate a new PDF from the rendered page.

Get the PDF response as a buffer

HTTPResponse.buffer() resolves to a Node.js Buffer containing the response body. The key is selecting the response caused by your action—not assuming that the click itself returns the PDF or that the next response is the right one.

Wait for the response, trigger the download, and save it

This CommonJS example assumes page is an already-open Puppeteer page and that clicking #download-pdf requests a PDF. It writes the returned bytes to document.pdf.

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

const responsePromise = page.waitForResponse(response => {
  const contentType = response.headers()['content-type'] || '';
  return response.status() === 200 &&
    contentType.toLowerCase().includes('application/pdf');
});

await page.click('#download-pdf');
const response = await responsePromise;
const pdfBuffer = await response.buffer();
await fs.writeFile('document.pdf', pdfBuffer);

Create the waitForResponse() promise before the click. If you click first and wait second, a fast request may already have completed before Puppeteer starts listening. Awaiting the promise after the click lets the action and response wait overlap correctly.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Match a stable URL when the content type is unreliable

If the endpoint has a known path, URL matching can be more dependable than relying on a server header. Include a status check so an error page returned from the same endpoint is not mistaken for the file.

const responsePromise = page.waitForResponse(response =>
  response.url().includes('/reports/') && response.status() === 200
);

await page.click('#download-pdf');
const response = await responsePromise;
const pdfBuffer = await response.buffer();

For a site with multiple report requests, make the predicate more specific: match the exact report path or a known query parameter, and check the content type too when the endpoint supplies it. Puppeteer responses expose url(), status(), headers(), and request() to help distinguish candidates.

Make response selection robust

Use the narrowest useful predicate

A predicate that only checks for status 200 can match images, API calls, or an HTML page. Prefer a combination suited to the site:

  • Known endpoint: match the PDF route or report identifier, plus a successful status.
  • Useful response header: require Content-Type to include application/pdf. Compare case-insensitively and allow for parameters such as a charset by checking for inclusion rather than exact equality.
  • Ambiguous route: combine URL and content-type checks; inspect the response request if the page makes similar requests for several actions.

Do not assume a filename in the URL proves the body is a PDF. Conversely, some servers return a PDF without an accurate content-type header, which is when a stable URL and later byte validation can help.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Set a timeout and handle a missing match

waitForResponse() waits for a response satisfying the predicate. If the action fails, opens a different route, or the predicate is too restrictive, the wait can time out. Catch that failure at the operation boundary and report the action and expected endpoint rather than proceeding with an undefined response.

try {
  const responsePromise = page.waitForResponse(response =>
    response.url().includes('/reports/quarterly.pdf') &&
    response.status() === 200,
    { timeout: 15000 }
  );

  await page.click('#download-pdf');
  const response = await responsePromise;
  const pdfBuffer = await response.buffer();
  await fs.writeFile('quarterly.pdf', pdfBuffer);
} catch (error) {
  console.error('Could not capture the expected PDF response:', error);
}

Choose a timeout that fits the site and your job deadline; it is not a guarantee the server will finish within that time. If a timeout is frequent, verify the click actually triggers the request and that your filter matches the observed URL, status, and headers.

Save or forward the bytes without corrupting them

Keep the result as a Buffer when writing a file or passing it to a binary-storage or upload API. Do not convert it to a UTF-8 string: arbitrary PDF bytes are not text, and decoding them as text can alter or lose byte values.

await fs.writeFile('document.pdf', pdfBuffer);

// Example shape for an API that accepts binary data:
await uploadClient.upload({ body: pdfBuffer });

Puppeteer documents that a response buffer might be re-encoded by the browser based on HTTP headers or other heuristics. If exact byte fidelity matters, check the endpoint’s headers and validate the resulting file in your application. A typical PDF begins with the byte sequence represented by %PDF-; that check is a useful sanity check, not a substitute for parsing or validating the full file.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Handle bodyless responses and listener failures

Not every browser response has a readable body. CORS preflight requests commonly use OPTIONS; status codes 204 and 304 are also cases where a body is unavailable or not expected. Avoid calling buffer() on every response in a broad listener.

Filter defensively in a response listener

A listener is useful when you need to observe responses across several actions. Filter before reading, and catch errors because a matching response body may still be unavailable.

page.on('response', async response => {
  if (response.request().method() === 'OPTIONS') return;
  if ([204, 304].includes(response.status())) return;

  const contentType = response.headers()['content-type'] || '';
  if (!contentType.toLowerCase().includes('application/pdf')) return;

  try {
    const pdfBuffer = await response.buffer();
    await fs.writeFile('captured.pdf', pdfBuffer);
  } catch (error) {
    console.error('PDF body unavailable:', response.url(), error);
  }
});

For a single user action that should produce one PDF, waitForResponse() is usually easier to reason about: it gives the operation one explicit response to await. A listener can observe multiple matching PDFs, so production code may need its own deduplication or destination naming.

Choose between a server PDF and a Puppeteer-generated PDF

response.buffer() captures bytes the server returned. page.pdf() asks Puppeteer to render the current page as a new PDF. These are different sources of truth; rendering a page is not a way to retrieve the original server file.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Question response.buffer() page.pdf()
What are the bytes? The body of a selected network response, such as a PDF download. A newly generated PDF of the current rendered page.
When to use it The server already creates and returns the PDF you need. You want a PDF representation of the page DOM as rendered by Puppeteer.
Return type Promise<Buffer>. Promise<Uint8Array>; convert to a Node buffer with Buffer.from() if required.
Rendering behavior Does not create a print layout; it reads the response body. Generates a PDF using the print CSS media type.

Generate a PDF from the current page

const pdfBytes = await page.pdf({
  format: 'A4',
  printBackground: true
});
const pdfBuffer = Buffer.from(pdfBytes);
await fs.writeFile('page.pdf', pdfBuffer);

Use the response route when you need the server’s finished document, including its server-side report content or authorization behavior. Use page rendering when the browser page itself is the document you intend to print and a print-style rendering is acceptable.

Or skip the browser setup

If your goal is to create a PDF capture of a webpage rather than retrieve a PDF file that a site already serves, ScreenshotNeo offers a screenshot API with PDF output. It is not a replacement for collecting an existing server response; it captures a page as a new document. For API parameters and setup, see the ScreenshotNeo documentation.

One GET request can return a PDF. For example, using the documented endpoint and adapting the target URL:

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.com -o page.pdf
  • Cookie and consent banners, newsletter popups, and chat widgets are removed before capture; each cleanup step can be turned off.
  • Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed; response headers identify the page verdict and billing status.
  • An MCP server provides screenshot tools for AI agents and MCP clients.
  • The Free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 shots.

Create a free ScreenshotNeo account to start with 1,000 screenshots a month and no card.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Troubleshoot common problems

The wait times out

  • Cause: The click did not trigger a network request, the request failed before a matching response, or the predicate excludes the actual response.
  • Fix: Confirm the selector and action, inspect the endpoint and response headers, then relax or refine the predicate based on what the site actually returns. Keep the wait promise registered before triggering the action.

The saved file is HTML or an error document

  • Cause: The matched response may be a login page, an error response, or another request to a similar URL.
  • Fix: Check response.status(), response.url(), and the content type before saving. Match the report identifier or endpoint more narrowly; validate the resulting bytes when the server’s headers are unreliable.

buffer() fails or produces no usable body

  • Cause: The response may be bodyless, unavailable, or not the intended PDF; broad listeners can also encounter preflight and other non-document traffic.
  • Fix: Filter out OPTIONS, 204, and 304 cases where appropriate, and catch read errors around response.buffer().

The file exists but a PDF reader rejects it

  • Cause: The response may not have been a PDF, or browser/header-driven re-encoding may have affected the response buffer.
  • Fix: Verify status and headers, inspect whether the output begins with %PDF-, and validate the document using your application’s PDF handling. If exact fidelity is required, investigate the endpoint’s response headers and whether its bytes are being transformed.

The wrong PDF is saved when several requests happen

  • Cause: A generic content-type predicate can match an unrelated PDF request on the same page.
  • Fix: Combine the content type with an exact route, report ID, or other stable request detail, and use distinct filenames when capturing multiple documents.

Performance and reliability considerations

Reading a response into a buffer means the complete response body is held in memory before the file write completes. For a large PDF or many simultaneous captures, limit concurrent work to suit the memory available to the process and avoid retaining buffers after they have been persisted or uploaded. A response listener that saves every matching document can multiply memory and disk use if the page makes repeated requests.

Reliability depends on identifying the right response and handling failures at each boundary: the triggering action, response wait, body read, and persistence step. Log the selected response URL and status alongside errors, but avoid logging credentials or sensitive document contents. Retry only when the action is safe to repeat; a click that creates a report or otherwise changes server state may not be idempotent.

Frequently Asked Questions

Can I use response.buffer() after the page has navigated?

Yes, if you retained the matching HTTPResponse object and its body is still available. In practice, register the response wait before the navigation or action so the relevant response is not missed.

Does response.buffer() create a PDF from the page?

No. It reads bytes from a network response. To render the current page into a PDF, use page.pdf().

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.

Leave a Reply

Your email address will not be published. Required fields are marked *

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Read next

Recommended PC Tool
Recommended PC Tool
Windows Errors? Fix Them Before They SpreadFree repair scan
Crashes, No Sound, or Screen Glitches?Free driver scan

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.