How to Override Puppeteer Request Continuation Options
Change headers, method, body, or URL on an intercepted Puppeteer request. Learn the safe handler pattern, resolution priorities, and common fixes.
To override a Puppeteer request while allowing it to proceed, enable interception with await page.setRequestInterception(true), then call request.continue({ ... }) in the request handler. Puppeteer documents four override fields: headers, method, postData, and url. Every intercepted request must be resolved, so continue unchanged requests too, unless another handler has already resolved them.
This guide covers the interception lifecycle, complete examples, priorities, edge cases, and debugging. The API details below follow Puppeteer’s [HTTPRequest.continue()](https://pptr.dev/api/puppeteer.httprequest.continue), [ContinueRequestOverrides](https://pptr.dev/api/puppeteer.continuerequestoverrides), and [request interception guide](https://pptr.dev/guides/network-interception). The surfaced method and interface references show different documentation version labels, so check your installed Puppeteer release when exact behavior matters.
1. Enable interception and override a request
Interception is a prerequisite. When enabled, a request stalls until it is continued, answered with a response, aborted, or completed using the browser cache. This runnable example adds a header to requests for one host and continues all other requests unchanged.
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch({ headless: true });
try {
const page = await browser.newPage();
await page.setRequestInterception(true);
page.on('request', request => {
const isTarget = new URL(request.url()).hostname === 'example.com';
const headers = {
...request.headers(),
'x-example-client': 'puppeteer',
};
void request.continue(isTarget ? { headers } : {}).catch(error => {
console.error('Could not continue request:', request.url(), error);
});
});
await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
await page.screenshot({ path: 'page.png' });
} finally {
await browser.close();
}
Save it as an ES module file, install Puppeteer in the project, and run it with Node.js. The handler deliberately resolves requests outside the target host as well. Without that branch, those requests would remain stalled.
Why copy the existing headers?
request.headers() provides the current headers with lower-case names. Spreading them into a new object preserves them while adding or replacing one value. Puppeteer’s official example uses this pattern. Use a plain object for the override; do not assume a partial headers object will be merged with the original.
2. The four continuation override fields
| Field | What it changes | Example | Things to check |
|---|---|---|---|
headers |
Request headers | { headers: { ...request.headers(), 'x-trace': 'abc' } } |
Preserve headers you need; names from request.headers() are lower-case. |
method |
HTTP method | { method: 'POST' } |
Changing the method may make the existing body or headers inappropriate. |
postData |
Request body string | { postData: 'name=value' } |
Encode the body in the format expected by the destination and set relevant content headers where needed. |
url |
Request URL | { url: 'https://example.test/alternate' } |
Puppeteer documents this as a URL change, not a redirect. |
These are the documented continuation fields. Avoid assuming that arbitrary properties in the object alter the request.
Change or remove headers
page.on('request', request => {
const headers = {
...request.headers(),
'x-debug-mode': '1',
origin: undefined, // Remove the existing origin header.
};
void request.continue({ headers }).catch(console.error);
});
The Puppeteer documentation’s example uses undefined to remove a header. Only remove a header when the destination and request semantics permit it; some servers rely on headers such as content-type or authorization.
Change method, body, or URL
page.on('request', request => {
if (request.url() === 'https://example.test/submit') {
void request.continue({
url: 'https://example.test/alternate',
method: 'POST',
postData: 'mode=preview',
headers: {
...request.headers(),
'content-type': 'application/x-www-form-urlencoded',
},
}).catch(console.error);
return;
}
void request.continue().catch(console.error);
});
This example shows the documented override shape; it does not imply that changing these values will be suitable for every request. A body format, method, and content headers must agree with the endpoint’s expectations. Changing url does not issue an HTTP redirect response.
3. A safe pattern when handlers are composed
When multiple request handlers or libraries can resolve the same interception, a handler should check whether resolution has already been handled before acting. Puppeteer’s interception guide demonstrates request.isInterceptResolutionHandled(). Keep the check and the resolution together in the same synchronous section of the handler; do not await unrelated work between checking and resolving.
await page.setRequestInterception(true);
page.on('request', request => {
if (request.isInterceptResolutionHandled()) return;
const shouldModify = request.url().includes('/api/');
const overrides = shouldModify
? { headers: { ...request.headers(), 'x-client': 'capture' } }
: {};
void request.continue(overrides, 0).catch(error => {
console.error('Request resolution failed:', error);
});
});
The optional second argument is a priority used for cooperative interception. Puppeteer’s guide recommends priority 0 or DEFAULT_INTERCEPT_RESOLUTION_PRIORITY for an unopinionated continuation. A handler using priority to force a decision over another handler’s lower-priority abort or response is making an opinionated choice; coordinate priorities across handlers rather than adding a high value blindly.
The guide also states that request.continue() must be called explicitly or the request will hang. The guard above is appropriate when other handlers may resolve the request. In a single-handler setup, use a straightforward continue path and make sure every branch reaches a resolution.
4. cURL, Python, and Node.js context
Request continuation is a Puppeteer browser interception API. cURL and ordinary Python or Node.js HTTP clients do not have Puppeteer’s page-level intercepted request lifecycle, so they cannot call HTTPRequest.continue(). For a one-off HTTP request, set the desired method, headers, body, and URL directly in that client. Use the Puppeteer examples above when you need to alter requests made by a browser page.
cURL: make a request with chosen values
curl 'https://example.test/submit' \
-X POST \
-H 'content-type: application/x-www-form-urlencoded' \
-H 'x-client: capture' \
--data 'mode=preview'
Python: make a request with chosen values
import requests
response = requests.post(
'https://example.test/submit',
headers={'x-client': 'capture'},
data={'mode': 'preview'},
timeout=30,
)
response.raise_for_status()
print(response.status_code, response.url)
Node.js: make a request with chosen values
const response = await fetch('https://example.test/submit', {
method: 'POST',
headers: {
'content-type': 'application/x-www-form-urlencoded',
'x-client': 'capture',
},
body: 'mode=preview',
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
console.log(response.status, response.url);
5. Request body and matching edge cases
- Original body inspection:
request.postData()may be undefined when a body is too long or is not readily available in decoded form. The HTTPRequest reference points tofetchPostData()for that situation. This is about reading the original body; the continuation override accepts apostDatastring. - Match precisely: Avoid broad substring checks when a host, path, or method check can target the intended request. Parse URLs with
new URL(request.url())so host matching does not accidentally include lookalike domains. - Keep headers consistent: If you replace a body or method, make sure the content type and other relevant headers still describe the request correctly.
- Preserve unmodified behavior: For requests that need no changes, call
request.continue()orrequest.continue({}). - Handle asynchronous work carefully: If a handler must await work before resolving, check resolution state again afterward because another handler may have resolved in the meantime.
6. Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
continue() throws that interception is not enabled |
The page has not enabled request interception. | Call and await page.setRequestInterception(true) before navigation or before the request you want to intercept. |
| Navigation or page requests hang | A request was intercepted but no handler resolved it. | Ensure every event path calls continue(), abort(), or respond(); inspect early returns and exceptions. |
| “Request is already handled” or duplicate resolution error | Two handlers tried to resolve the same request. | Coordinate handler ownership and check isInterceptResolutionHandled() before resolving, especially after asynchronous work. |
| A header disappears unexpectedly | The override headers object omitted it. | Start with { ...request.headers() }, then edit only the intended fields. |
| A header will not be sent | Header names or values may be malformed, or another handler may override the request. | Use lower-case names for consistency, inspect the final request at the destination, and review all interception handlers and priorities. |
| The destination rejects a changed body | Body encoding, method, or content headers do not match what the endpoint expects. | Use the endpoint’s required encoding and ensure method and content type agree with the body. |
Changing url did not produce a redirect |
A continuation URL override is not an HTTP redirect. | Use it to send the request to the replacement URL; use server redirect behavior when an actual redirect response is required. |
postData() returns undefined |
The original body may be too long or unavailable in decoded form. | Consult the installed release’s HTTPRequest reference and use fetchPostData() where appropriate. |
7. Performance, reliability, and cost
Interception adds work to the request path because the browser pauses intercepted requests until they are resolved. Keep handlers narrow and synchronous when possible, and avoid waiting on slow external work before continuing. Enable interception only for pages and flows that need request changes. A missing resolution is a reliability problem as well as a performance problem because it can stall page loading.
There are no benchmark figures in the cited documentation, so throughput impact depends on the page, handler work, and request volume. Puppeteer itself is browser automation software; infrastructure and browser runtime costs depend on where and how you run it. For screenshots, an API can remove the browser setup and operational work.
8. Or skip the browser setup
If your goal is a screenshot rather than request interception itself, ScreenshotNeo provides a website screenshot API and MCP server. It is made by Yorker Media. One GET request returns a PNG, JPEG, WebP, or PDF; see the API documentation for parameters and options.
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}`);
await Bun.write('shot.webp', res);
- Cookie banners, popups, and chat widgets are removed before the shot, with each cleanup step configurable.
- Bot checks, blank pages, timeouts, and failed loads are never billed; cache hits are also free. Responses identify the page verdict and billing status in headers.
- An MCP server lets AI agents, including Claude and Cursor, take screenshots with tools for screenshots, page information, and PDF capture.
- 1,000 screenshots a month are free with no card; paid plans start at $5 for 3,000. All features are available on every plan, and yearly billing gives two months free.
Sign up for ScreenshotNeo’s free plan to get 1,000 screenshots a month with no card.
9. Frequently asked questions
Can I override more than one field at a time?
Yes. Pass multiple documented fields in the same object, for example { headers, method, postData }.
Does changing the request URL make the browser follow a redirect?
No. Puppeteer documents a URL override as a changed request URL, not a redirect response.
Do I need a priority for every continuation?
No. The priority is optional. Use the cooperative priority guidance when several handlers may resolve the same request.
Why does the method reference show a different version from the overrides reference?
The surfaced official pages carried different version labels. Check the documentation corresponding to the Puppeteer version installed in your project when release-specific behavior matters.


