How to Continue an Intercepted Request in Puppeteer
Enable Puppeteer request interception, handle every intercepted request, and continue it safely with optional overrides and priorities.
To continue an intercepted request in Puppeteer, enable interception before navigation, listen for the page’s request event, and call request.continue() for requests that should proceed. Every intercepted request must be continued, responded to, aborted, or completed through the browser cache; leaving one unresolved can stall page loading.
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch();
try {
const page = await browser.newPage();
await page.setRequestInterception(true);
page.on('request', request => {
if (request.isInterceptResolutionHandled()) return;
request.continue();
});
await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
} finally {
await browser.close();
}
This is the basic pass-through pattern in the Puppeteer request interception guide. The request event handler above deliberately continues every unresolved request. See the HTTPRequest.continue() reference and Page.setRequestInterception() reference for the API details. Check the documentation for your installed Puppeteer version if you target an older release.
1. Enable interception before navigation
Call page.setRequestInterception(true) before the navigation or other browser action that triggers requests you need to handle. Once interception is enabled, each intercepted request pauses until a handler resolves it. A handler with no special decision still needs to explicitly continue the request.
await page.setRequestInterception(true);
page.on('request', request => {
if (!request.isInterceptResolutionHandled()) {
request.continue();
}
});
await page.goto('https://example.com');
Register the listener before starting navigation so it can handle the navigation request and the page’s resource requests. If your application enables interception after a page is already active, subsequent requests are the ones the listener can handle.
2. Continue safely when handlers may overlap
Another event listener or package may resolve a request before your listener does. In that case, calling continue(), abort(), or respond() again can produce Request is already handled!. Check request.isInterceptResolutionHandled() immediately before resolving it.
If the handler awaits work before deciding, check again afterward. Another handler could resolve the request during the wait. Keep the status check and resolution call together synchronously:
page.on('request', async request => {
if (request.isInterceptResolutionHandled()) return;
const decision = await decideWhatToDo(request);
// The request may have been resolved while this handler was awaiting.
if (request.isInterceptResolutionHandled()) return;
if (decision === 'continue') {
request.continue();
} else {
request.abort();
}
});
decideWhatToDo is application code: replace it with your own decision logic. Avoid putting another await between the final status check and continue(), respond(), or abort().
3. Continue with request overrides
request.continue(overrides?) can pass supported request changes to the browser while allowing the request to proceed. A common use is changing headers. The following example adds a header and removes origin:
page.on('request', request => {
if (request.isInterceptResolutionHandled()) return;
const headers = {
...request.headers(),
'x-capture-mode': 'preview',
origin: undefined,
};
request.continue({ headers });
});
Use the installed release’s ContinueRequestOverrides type as the authority for which fields can be changed; supported overrides can vary by version. The continuation method returns a Promise<void>. Attach rejection handling if your event-handler setup needs to report failures rather than leave them unobserved.
4. Coordinate multiple interception handlers
Puppeteer has legacy and cooperative interception resolution behavior. A resolution without a priority takes effect immediately. Cooperative resolution is used only when all participating resolutions provide a numeric priority; Puppeteer then waits for asynchronous handlers and selects the highest priority. For equal priorities, abort outranks respond, which outranks continue.
For a handler that has no special opinion and should simply pass the request through, the guide recommends priority 0 or DEFAULT_INTERCEPT_RESOLUTION_PRIORITY. A higher custom priority is for a handler that deliberately intends its decision to win:
import puppeteer, {
DEFAULT_INTERCEPT_RESOLUTION_PRIORITY,
} from 'puppeteer';
const browser = await puppeteer.launch();
try {
const page = await browser.newPage();
await page.setRequestInterception(true);
page.on('request', request => {
if (request.isInterceptResolutionHandled()) return;
request.continue(
request.continueRequestOverrides(),
DEFAULT_INTERCEPT_RESOLUTION_PRIORITY,
);
});
await page.goto('https://example.com');
} finally {
await browser.close();
}
Confirm that the priority constant and method are available in your installed Puppeteer release before using this version-specific form. If another handler resolves without a priority, legacy behavior can still resolve the request immediately. Keep the handled-state check even when using priorities.
5. Choose the right resolution for each request
Continuing is the pass-through choice. An interception handler may instead respond with its own content or abort a request when application logic requires it. The essential reliability rule is that every intercepted request needs a resolution. Structure conditional branches so none can fall through without one.
page.on('request', request => {
if (request.isInterceptResolutionHandled()) return;
if (shouldBlock(request)) {
request.abort();
return;
}
request.continue();
});
shouldBlock represents your own policy. If a condition is uncertain or unavailable, explicitly choose the intended fallback instead of leaving the request pending.
6. Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Navigation or page load hangs | An intercepted request never reached continue(), respond(), or abort(). |
Audit every branch and ensure pass-through requests call continue(). Register the listener before navigation. |
Request is already handled! |
Another listener or package resolved the request first, or it did so while your async handler was waiting. | Check isInterceptResolutionHandled() immediately before resolving, and check again after every awaited decision. |
| Continuation fails because interception is disabled | request.continue() was called without first enabling interception on that page. |
Call and await page.setRequestInterception(true) before triggering the requests. |
| Several handlers produce an unexpected result | A resolution without a numeric priority invokes legacy immediate behavior instead of cooperative finalization. | Inspect every handler, including package-installed listeners. If you rely on cooperative mode, ensure all resolutions supply numeric priorities. |
| Only some resources stall | A conditional branch handles one request type but not another, such as a script, image, or navigation request. | Log request URLs and types in the handler, then give every branch an explicit resolution. |
7. Performance, reliability, and cost
Request interception adds an event-handler decision to each intercepted request. Keep handler work small when possible: expensive asynchronous decisions hold the request unresolved until they finish and can delay page progress. For asynchronous logic, use the handled-state check again after waiting, and make sure errors in the decision path do not leave a request without a resolution.
Interception is useful when you need to inspect, modify, block, or replace requests. If you only need to observe network activity, choose an observation approach that does not pause requests. Puppeteer’s cited interception references do not establish universal latency figures, reliability guarantees, or browser-hosting costs; these depend on the page, environment, and workload. Account for browser runtime and any external services your own handler calls.
8. FAQ
Does request.continue() return a promise?
Yes. The API reference documents a Promise<void> return value. The standard event-handler examples call it directly; handle rejections according to your application’s error-reporting approach.
Can I continue a request and change its headers?
Yes. Pass supported overrides, such as a headers object, to request.continue(). Check the API reference for the installed version’s supported override fields.
Does every handler need a priority?
No. Priorities are needed for cooperative resolution. Without them, legacy immediate resolution applies. Cooperative behavior requires all participating resolutions to use numeric priorities.
Or skip the browser setup
If your goal is a website screenshot rather than custom request logic, ScreenshotNeo provides a screenshot API and MCP server. Its one-call API captures a URL as an image or PDF; the ScreenshotNeo docs cover the request 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', new Uint8Array(await res.arrayBuffer()));
ScreenshotNeo removes cookie banners, newsletter popups, and chat widgets before capture. Bot checks, blank pages, and failed loads are never billed. Its MCP server lets AI agents take screenshots. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000. Sign up for 1,000 free screenshots a month, with no card required.


