How to Get HTTP Request Headers in Puppeteer
Read outgoing request headers with Puppeteer’s request event and request.headers(). Learn about lowercase names, interception, extra headers, and common errors.
To read outgoing HTTP request headers in Puppeteer, listen for the page’s request event and call request.headers(). You do not need request interception just to observe requests. The returned object uses lowercase header names, so read headers['content-type'], for example.
Read headers from every page request
Install Puppeteer in a Node.js project with npm install puppeteer, then save this as headers.mjs and run it with node headers.mjs:
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch();
try {
const page = await browser.newPage();
page.on('request', request => {
console.log('URL:', request.url());
console.log('Method:', request.method());
console.log('Headers:', request.headers());
});
await page.goto('https://example.com');
} finally {
await browser.close();
}
The callback receives an HTTPRequest. Its headers() method returns a string-to-string object associated with that outgoing request. The listener sees requests the page makes, which can include document, script, image, stylesheet, and other resource requests.
Read a specific header
page.on('request', request => {
const headers = request.headers();
const contentType = headers['content-type'];
const authorization = headers['authorization'];
console.log({ contentType, hasAuthorization: Boolean(authorization) });
});
Header field names are case-insensitive in HTTP, but JavaScript object keys are case-sensitive. Puppeteer returns lowercase names, so use lowercase keys when looking up values. A lookup such as headers['Content-Type'] can return undefined even when the header exists.
CommonJS version
For a CommonJS project, use require and keep the same event pattern:
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch();
try {
const page = await browser.newPage();
page.on('request', request => {
console.log(request.url(), request.headers());
});
await page.goto('https://example.com');
} finally {
await browser.close();
}
})();
Request headers versus response headers
request.headers() reports headers associated with an outgoing request. It does not return the server’s response headers. To observe responses, listen for the page’s response event and use the response API:
page.on('response', async response => {
console.log('URL:', response.url());
console.log('Status:', response.status());
console.log('Response headers:', response.headers());
});
Choose the event based on the question: use request to inspect what the browser sends, and response to inspect what the server returns. A response status of 404 or 503 is still an HTTP response; that status alone does not mean the request emitted requestfailed. A redirect finishes one request and causes a new request to the redirected URL, so inspect each request if you need to follow the redirect chain.
Add headers to requests
To add the same extra headers to every request initiated by a page, call page.setExtraHTTPHeaders() before navigation. It accepts a string-to-string object and returns a promise:
await page.setExtraHTTPHeaders({
'x-client-tag': 'example',
'accept-language': 'en-US'
});
page.on('request', request => {
console.log(request.headers()['x-client-tag']);
});
await page.goto('https://example.com');
This is the straightforward option for a page-wide header. Header names are lowercased, and outgoing header order is not guaranteed. Do not rely on a particular order.
Change only selected requests
For per-request changes, use request interception and pass header overrides to request.continue(). Interception is for controlling requests, not required for passive logging. Once enabled, each intercepted request stalls until it is continued, answered with a response, or aborted.
await page.setRequestInterception(true);
page.on('request', request => {
const headers = {
...request.headers(),
'x-client-tag': 'selected-request'
};
request.continue({ headers });
});
await page.goto('https://example.com');
Every code path handling an intercepted request must resolve it. If multiple listeners or packages may handle requests, check request.isInterceptResolutionHandled() before acting. If your handler awaits other work before resolving the request, check again after the await because another handler may have resolved it meanwhile. Duplicate calls to continue(), abort(), or respond() can throw.
Choose the right approach
| Need | Use | Scope and tradeoff |
|---|---|---|
| Log or inspect outgoing headers | page.on('request') and request.headers() |
Passive observation; no interception required. |
| Add common headers across page traffic | page.setExtraHTTPHeaders() |
Page-wide extra headers; order is not guaranteed. |
| Change, fulfill, or block selected requests | Request interception | Per-request control; every intercepted request must be resolved. |
| Inspect server response metadata | page.on('response') |
Response event and response headers, distinct from request headers. |
Troubleshooting
- A header lookup is undefined: Check the lowercase key, such as
headers['content-type']. Also confirm that the header belongs to the outgoing request you are inspecting. - No request logs appear: Register the listener before calling
page.goto()or triggering the action that makes the request. Confirm that the request is made by this page. - The page hangs after enabling interception: At least one intercepted request is likely unresolved. Ensure every handler path calls
continue(),respond(), orabort(). - A handler throws about an already handled request: Another listener may have resolved it. Check
isInterceptResolutionHandled()before resolving, and check again after asynchronous work. - You see a 404 or 503 and expect
requestfailed: An HTTP error status is still a completed HTTP request. Inspect the response status in the response event;requestfailedindicates a request failure rather than merely an unsuccessful HTTP status. - You cannot find the response’s headers: You are reading the request event. Listen for
responseand call the response headers method instead.
Performance, reliability, and logging
For observation, use the ordinary request event and avoid interception; Puppeteer provides page network events by default. Keep event callbacks small if a page generates many requests, and filter by URL or resource type when you only need a subset. Logging every header object can produce a large amount of output.
Treat headers as potentially sensitive. Authorization values, cookies, and other session data should not be written indiscriminately to production logs. Prefer recording only the fields needed for debugging, and redact credentials when logging values.
For reliable interception, resolve requests on every branch, including error paths in asynchronous handlers. Avoid relying on header order, and inspect redirects as separate requests when the exact sequence matters.
Or skip the browser setup
If you need a screenshot rather than programmatic access to each request, ScreenshotNeo is a website screenshot API and MCP server. Its one-call API returns an image or PDF; this is not a replacement for Puppeteer when you need to inspect request headers.
See the ScreenshotNeo API documentation. For example, this cURL request saves a WebP screenshot:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
Python:
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)
Node.js:
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
const bytes = Buffer.from(await res.arrayBuffer());
await import('node:fs/promises').then(fs => fs.writeFile('shot.webp', bytes));
ScreenshotNeo accepts cookie and consent banners and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each step can be turned off. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed, and response headers identify the page verdict and billing outcome. Its MCP server gives AI agents tools for screenshots, page information, and PDF capture. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 shots. Sign up for 1,000 free screenshots a month, with no card.
FAQ
Does Puppeteer return header names in their original casing?
No. The object returned by request.headers() uses lowercase header names.
Can I get headers before navigation finishes?
Yes. Register the request listener before navigation; it runs as requests are issued, rather than waiting for the page navigation promise to finish.
Does setExtraHTTPHeaders() set a header on just one request?
No. It applies extra headers to every request initiated by that page. Use interception when you need request-specific control.
Should I enable interception to inspect headers?
No. The request event is sufficient for passive inspection. Enable interception only when you also need to modify, fulfill, or abort requests.


