How to Read the Content of a Puppeteer Response
Read Puppeteer response bodies as text, JSON, or bytes. Learn how to capture the right response, check its status, and handle common errors.
To read a Puppeteer response body, call and await a body-reading method on its HTTPResponse object. Use text() for UTF-8 text, json() for a parsed JavaScript value, and content() or buffer() for bytes. There is no synchronous response.body property.
const response = await page.goto('https://example.com/api/data');
if (!response) throw new Error('Navigation returned no response');
console.log('status:', response.status());
console.log(await response.text());
This guide uses Puppeteer’s HTTPResponse API. The methods are asynchronous, so await them before using the result.
1. Install Puppeteer and read a navigation response
For a local Node.js project, install Puppeteer:
npm install puppeteer
Save this as read-response.js and run it with node read-response.js. It launches a browser, navigates to an endpoint, checks the HTTP status, reads the body as text, and closes the browser even if something goes wrong.
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch({ headless: true });
try {
const page = await browser.newPage();
const response = await page.goto('https://example.com/api/data', {
waitUntil: 'domcontentloaded',
timeout: 30000,
});
// page.goto() can return null for special navigations such as about:blank
// or a same-URL hash change.
if (!response) {
throw new Error('Navigation completed without a main-resource response');
}
console.log('URL:', response.url());
console.log('Status:', response.status());
console.log('OK:', response.ok());
console.log('Headers:', response.headers());
const body = await response.text();
console.log(body);
if (!response.ok()) {
throw new Error(`HTTP ${response.status()} from ${response.url()}`);
}
} finally {
await browser.close();
}
})().catch(error => {
console.error(error);
process.exitCode = 1;
});
Replace the example URL with the endpoint you want to inspect. page.goto() returns the main navigation resource’s response, not every request made by the page. It resolves with the final response after redirects. An HTTP status such as 404 or 500 does not by itself make navigation throw; inspect status() or ok().
2. Choose the right body reader
| Method | Result | Use it for | Failure or caveat |
|---|---|---|---|
text() |
String | HTML, plain text, or inspecting an unexpected payload | Can throw if the body is not valid UTF-8. |
json() |
Parsed JavaScript value | A response body that contains valid JSON | Rejects if the body cannot be parsed as JSON. |
content() |
Uint8Array |
Raw body bytes or binary content | Browser decoding can re-encode bytes based on headers or heuristics. |
buffer() |
Node.js Buffer |
Node-specific byte operations and APIs that expect a Buffer | Has the same browser byte-encoding caveat as content(). |
The current API documents content() as returning a Uint8Array; use buffer() when you specifically need Node’s Buffer methods. Do not assume the browser’s returned bytes are always identical to the original wire representation.
Read a JSON response
const response = await page.goto('https://example.com/api/data');
if (!response) throw new Error('No main resource response');
if (!response.ok()) {
const errorBody = await response.text();
throw new Error(`HTTP ${response.status()}: ${errorBody}`);
}
try {
const data = await response.json();
console.log(data);
} catch (error) {
console.error('Response was not valid JSON:', error);
}
A JSON content type is only a server claim; it does not prove that the body is valid JSON. Reading the body with text() is useful when diagnosing a parse failure.
Read a binary response
const response = await page.goto('https://example.com/file');
if (!response) throw new Error('No main resource response');
const bytes = await response.content();
console.log('Byte length:', bytes.byteLength);
// If downstream Node.js code requires Buffer methods:
const buffer = await response.buffer();
console.log('Buffer length:', buffer.length);
Use bytes for binary payloads rather than converting them to text. If exact wire bytes matter, account for Puppeteer’s documented behavior: browser decoding may re-encode data based on response headers or heuristics.
3. Capture a response triggered after navigation
When page JavaScript or a user action triggers the request, wait for the response before triggering the action. This avoids racing past a fast response. Use a predicate specific to the endpoint so you do not accidentally select a script, image, or unrelated API call.
const responsePromise = page.waitForResponse(response => {
const request = response.request();
return response.url().includes('/api/data') &&
request.method() === 'GET';
}, { timeout: 15000 });
await page.click('button.load-data');
const response = await responsePromise;
console.log('Status:', response.status());
const data = await response.json();
console.log(data);
waitForResponse() is documented on Puppeteer’s Page API. If the request uses a different method, query parameter, or endpoint path, adjust the predicate to match it.
Filter response events
You can also listen for responses as the page receives them. This pattern is useful for observation, but an async event listener is not automatically awaited by the page. Catch errors inside the listener and store results if later code needs to wait for them.
const matchingBodies = [];
page.on('response', response => {
if (!response.url().includes('/api/data')) return;
matchingBodies.push(
response.text().then(body => ({
status: response.status(),
url: response.url(),
body,
})).catch(error => ({ error: error.message }))
);
});
await page.click('button.load-data');
const results = await Promise.all(matchingBodies);
console.log(results);
For a single known interaction, the explicit waitForResponse() pattern is usually easier to reason about. Puppeteer documents both the response event and response waiting in its PageEvent reference.
4. Inspect response metadata before interpreting the body
An HTTPResponse provides more than its body. These calls help establish which response you captured and how to interpret it:
response.status()returns the HTTP status code.response.ok()is true for status codes in the 200–299 range.response.headers()returns response headers.response.url()returns the response URL.response.request()returns the request that produced this response; inspect it to filter by method or request details.
Check status separately from whether a response object exists. A 404 response has a body that may explain the error, and it is still an HTTP response. Puppeteer’s documentation distinguishes HTTP error responses from request failures: 404 and 503 responses complete as responses, while failures such as timeouts are network-level failures.
5. Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
response is null after page.goto() |
The navigation has no main-resource response in documented special cases, including about:blank or a same-URL hash change. |
Check for null before reading the body. If you need a later API call, wait for it with waitForResponse(). |
response.json() rejects |
The body is malformed JSON, empty, or an error page despite a JSON content type. | Read text() to inspect the actual payload; check status and endpoint selection. |
response.text() rejects |
The body is not valid UTF-8. | Use content() or buffer() and handle the bytes according to the actual format. |
| You get an unexpected body | The waiter matched a different request, such as an asset or a second API call. | Filter on URL and, when useful, request method or other request properties. |
| The code treats 404 or 500 as a thrown navigation error | HTTP error status was confused with a failed network request. | Inspect status() and read the response body. Use ok() for a 2xx check. |
waitForResponse() times out |
The action did not trigger the expected request, or the predicate does not match its URL or method. | Confirm the action and endpoint, broaden the predicate temporarily for diagnosis, then narrow it again. |
| Returned bytes differ from an expected file | The browser may re-encode body data according to headers or heuristics. | Do not assume the returned bytes are the original wire bytes; verify whether browser-decoded content suits the task. |
6. Performance, reliability, and cost
Reading a response body is asynchronous and requires the body to be available. Keep the capture focused: waiting for a specific response avoids collecting and processing unrelated page traffic. Set timeouts for navigation and response waits so a missing endpoint does not leave a job waiting indefinitely.
Close the browser in a finally block so failures during navigation or body parsing do not leave browser processes running. Check status before treating content as success, and handle JSON and text decoding errors at the boundary where the body is read. The supplied Puppeteer documentation gives no benchmark or fixed cost figure; resource use depends on the browser session and page activity.
Or skip the browser setup
If your goal is a visual screenshot rather than inspecting an HTTP response body, ScreenshotNeo provides a website screenshot API and MCP server. A single GET request returns an image or PDF. The API options and response details are in the ScreenshotNeo documentation.
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)
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 request failed: ${res.status}`);
const bytes = new Uint8Array(await res.arrayBuffer());
await import('node:fs/promises').then(fs => fs.writeFile('shot.webp', bytes));
Cookie banners, popups, and chat widgets are removed before the shot. Bot checks, blank pages, and failed loads are never billed. An MCP server gives AI agents screenshot tools. The Free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000.
Sign up for ScreenshotNeo and get 1,000 free screenshots a month, no card required.
FAQ
Is response.body available in Puppeteer?
No. Call an asynchronous reader such as text(), json(), content(), or buffer().
Does page.goto() return every response from the page?
No. It returns the main navigation resource response. Use a response waiter or response event for later network requests.
Does a 404 mean Puppeteer failed to receive a response?
No. It is an HTTP response with an error status. Check its status and body; network failures are a separate case.
Should I use content() or buffer()?
Use content() for a Uint8Array and buffer() when Node.js Buffer operations are needed.


