How to Modify and Continue an HTTP Request in Puppeteer
Enable request interception, change a URL, header, method, or body, and continue safely. Includes race guards, troubleshooting, and a ScreenshotNeo shortcut.
To modify and continue an HTTP request in Puppeteer, enable interception before the request starts, listen for the page’s request event, and call request.continue(overrides). The documented overrides are headers, method, postData, and url. Every intercepted request must be resolved by continuing it, fulfilling it with respond(), aborting it, or being served from browser cache; otherwise it can stall.
1. Enable interception and continue requests
This runnable example adds a header to every request, then navigates to a page. Install Puppeteer in your project with npm install puppeteer and run the file with Node.js.
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch({ headless: true });
try {
const page = await browser.newPage();
await page.setRequestInterception(true);
page.on('request', request => {
// Resolve synchronously after this check to avoid a handler race.
if (request.isInterceptResolutionHandled()) return;
const headers = {
...request.headers(),
'x-example-header': 'example-value',
};
void request.continue({ headers }).catch(error => {
console.error(`Could not continue ${request.url()}:`, error);
});
});
await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
console.log(await page.title());
} finally {
await browser.close();
}
})().catch(error => {
console.error(error);
process.exitCode = 1;
});
Register the listener and enable interception before goto() or before the click, form submission, or other action that triggers the request. The handler above explicitly continues every request, including requests that do not match any special rule.
2. Change only matching requests
Branch on properties such as URL, method, or resource type. The fallback path still needs to resolve the request. This example changes one API URL and adds a header only for matching requests.
await page.setRequestInterception(true);
page.on('request', request => {
if (request.isInterceptResolutionHandled()) return;
const isTarget = request.url() === 'https://example.com/api/items';
if (!isTarget) {
void request.continue().catch(console.error);
return;
}
void request.continue({
url: 'https://example.com/api/items?source=puppeteer',
headers: {
...request.headers(),
'x-debug-source': 'puppeteer',
},
}).catch(console.error);
});
Use exact URL matching when the rule should apply to only one endpoint. For broader matching, parse the URL with new URL(request.url()) and inspect its hostname, pathname, or query parameters rather than relying on a loose substring that could match an unrelated host.
3. Available overrides and edge cases
| Override | What it changes | Things to check |
|---|---|---|
headers |
Headers sent with the request. | request.headers() returns lowercase header names. Copy it before adding or removing fields. Set a field to undefined to remove it, as in the documented example below. |
method |
HTTP method, such as GET or POST. |
Changing the method can alter server behavior. Ensure the body and headers make sense for the new method. |
postData |
Request body data. | Body encoding and content type must agree. The current API reference deprecates postData() for reading a body and directs users to fetchPostData(). |
url |
Destination URL for the request. | Changing it is not an HTTP redirect. The browser sends the intercepted request to the replacement URL; the original server does not issue a redirect. |
To remove a header while preserving the rest, copy the current map and set the unwanted name to undefined:
const headers = {
...request.headers(),
origin: undefined,
};
await request.continue({ headers });
hasPostData() can be true even when postData() is unavailable because the body is large or cannot be decoded. For that case, consult the installed version’s fetchPostData() API and avoid assuming the body is a small UTF-8 string. If you rewrite a body, make sure the receiving server gets compatible content headers.
4. Avoid resolving a request twice
Application code and libraries can register more than one request handler. In the default legacy behavior, the first call to continue(), respond(), or abort() resolves the interception. Another handler that tries to resolve it can encounter “Request is already handled!”.
Check isInterceptResolutionHandled() immediately before resolving. If the handler performs asynchronous work, check again after the await, because a different handler may have resolved the request while it was waiting:
page.on('request', async request => {
if (request.isInterceptResolutionHandled()) return;
const shouldRewrite = await decideWhetherToRewrite(request.url());
// Another handler may have resolved it during the await.
if (request.isInterceptResolutionHandled()) return;
if (shouldRewrite) {
await request.continue({
url: rewriteUrl(request.url()),
});
} else {
await request.continue();
}
});
For a synchronous handler, keep the check and resolution together without an intervening await. Also make sure errors from asynchronous handler work are caught and that the request still gets resolved when appropriate; an unhandled exception can leave it pending.
Cooperative interception
Puppeteer also supports cooperative interception, where handlers provide a numeric priority and Puppeteer waits for participating handlers before choosing a resolution. This convention only applies when every resolution specifies a priority. A handler that resolves without one switches the request to legacy immediate behavior.
const { DEFAULT_INTERCEPT_RESOLUTION_PRIORITY } = require('puppeteer');
page.on('request', request => {
if (request.isInterceptResolutionHandled()) return;
void request.continue(
{ headers: { ...request.headers(), 'x-example-header': 'value' } },
DEFAULT_INTERCEPT_RESOLUTION_PRIORITY,
).catch(console.error);
});
Use the default priority (or 0) for an ordinary continuation. Use a custom numeric priority only when handlers intentionally compete. Higher priority wins; tied priorities are resolved in this order: abort, respond, then continue. Coordinate the convention across all handlers, including dependencies, or rely on the legacy guard pattern instead. Confirm the exported constant and method signatures in the API docs for the Puppeteer version installed in your project.
5. Continue, fulfill, or abort?
| Method | Use it when |
|---|---|
continue(overrides) |
The request should go to the network, possibly with changed fields. |
respond(response) |
The handler should provide a response directly instead of fetching the destination. |
abort() |
The request should be stopped. |
These methods resolve the interception. Do not call more than one for the same request. A server response with status 404 or 503 is still a completed HTTP request; it is not the same as a transport failure. Puppeteer reports transport-level failures through requestfailed, while HTTP responses such as 404 and 503 can produce requestfinished. A redirect finishes the original request and starts another request.
6. Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Navigation hangs or times out after interception is enabled. | A request was intercepted but no handler resolved it. | Ensure every branch calls continue(), respond(), or abort(). Enable interception before navigation and inspect all registered handlers. |
Request is already handled! |
Another listener or package resolved the request first. | Check isInterceptResolutionHandled() just before resolution. Repeat the check after each awaited operation. |
| The changed header does not appear at the destination. | The handler used a partial header object or a different matching branch, or the request was redirected. | Start with { ...request.headers(), ...changes }, verify the matching URL and method, and inspect each redirected request separately. |
| Changing the URL did not produce a redirect response. | continue({ url }) changes the request destination; it does not ask the original server to redirect. |
Use the replacement destination intentionally, or allow the original server to return its redirect response. |
| Request body is missing or unreadable. | The body may be large or undecodable; hasPostData() alone does not guarantee postData() can return it. |
Use fetchPostData() where supported by the installed version and handle unavailable data explicitly. |
| A 404 or 503 was mistaken for interception failure. | HTTP error status and transport failure are different outcomes. | Inspect the response status for HTTP errors and use requestfailed for transport failures. |
| Cooperative priority seems ignored. | At least one handler resolved without a numeric priority, causing legacy behavior. | Make every participating handler use a priority, or use legacy resolution guards consistently. |
7. Performance, reliability, and cost
- Performance: Interception stalls requests until they are resolved, so keep handlers short. Avoid slow network calls or expensive processing in the request callback. If asynchronous decisions are necessary, measure their effect on navigation and resolve promptly.
- Reliability: Cover every request path, catch rejected continuation promises in event handlers, and guard against other listeners. Test redirect behavior and the request types your page uses. Browser cache may serve a request without the ordinary network path, so do not assume every resource always results in a network transfer.
- Cost: Puppeteer itself is an open-source browser automation library; the practical cost depends on where Chromium runs and the compute, storage, and network resources your workload uses. This is separate from any website screenshot API pricing.
8. Or skip the browser setup
If your goal is to capture a page rather than customize arbitrary browser traffic, ScreenshotNeo provides a website screenshot API and MCP server for developers. One GET request returns an image or PDF. See the ScreenshotNeo API 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 fs = require('node:fs/promises');
await fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));
- Cookie banners are accepted like a visitor and 60+ known consent platforms, newsletter popups, and chat widgets can be removed before capture; each step can be turned off.
- Bot checks and CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed. Responses identify the page verdict and billing status in headers.
- An MCP server gives AI agents such as Claude and Cursor tools for screenshots, page information, and PDF capture.
- The free plan includes 1,000 shots per month with no card. Paid plans start at $5 for 3,000 shots.
Create a free ScreenshotNeo account for 1,000 screenshots a month with no card.
9. FAQ
Does continuing a request change the response body?
No. continue() sends the request onward with optional request overrides. Use respond() when the interception handler should supply the response.
Can I change just one header?
Yes. Pass a copied header map with the changed value. Copying preserves the other headers you intend to send.
Is request interception required to observe traffic?
Interception is needed for continue(), respond(), and abort() handling. If you only need lifecycle notifications, review Puppeteer’s request and response events for your use case.
Does changing a URL with continue() create a redirect?
No. It changes the destination of that intercepted request. A redirect is a response from a server that leads the browser to issue another request.
Official references
- Puppeteer: Request interception guide
- Puppeteer: HTTPRequest.continue()
- Puppeteer: HTTPRequest API
- Puppeteer: HTTPRequest.headers()
API details can differ between Puppeteer releases. Check the documentation matching the version installed in your project.


