ScreenshotNeo

BlogHow-to

How to Handle Request Interception in Puppeteer

Intercept Puppeteer requests to continue, block, or mock traffic safely. Learn the event-handler pattern, cooperative priorities, and fixes for common races.

By the ScreenshotNeo team4 October 20268 min read

To intercept requests in Puppeteer, enable interception before navigation, then resolve every intercepted request by continuing it, fulfilling it with a response, or aborting it. Each intercepted request pauses until it is resolved, so an unhandled request can stall page loading. If multiple listeners or packages may handle requests, check whether a request is already resolved immediately before acting.

This guide uses Puppeteer’s JavaScript API. The same interception behavior applies whether the page is opened directly or used as part of a larger browser automation workflow. See the Puppeteer Request Interception guide and the setRequestInterception API reference.

1. Enable interception and resolve every request

Call await page.setRequestInterception(true) before page.goto() or any action that triggers requests you want to handle. Register a request listener that chooses one resolution for each request:

  • request.continue(overrides) sends the request onward, optionally with modified request properties.
  • request.respond(response) supplies a mock response instead of sending the request to the server.
  • request.abort(errorCode) blocks the request.

Here is a complete runnable example that blocks PNG files and lets other requests proceed. It also closes the browser if navigation or handling fails.

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;

    if (request.url().endsWith('.png')) {
      request.abort();
    } else {
      request.continue();
    }
  });

  await page.goto('https://example.com');
} finally {
  await browser.close();
}

This example uses the legacy immediate-resolution behavior because the calls omit a priority. It is appropriate when this listener owns request handling. The handled-state guard protects against another listener resolving a request first.

2. Choose continue, abort, or respond

Goal Method Typical use
Allow the original request continue() Default path for requests you do not need to change.
Change request properties, then allow it continue(overrides) Apply supported request overrides, such as changing headers.
Stop the request abort(errorCode?) Block a resource such as an image or a request matching a test rule.
Return a controlled response respond(response) Mock an endpoint for a test or provide a fixture response.

Block selected resources

Match the request type when possible instead of relying only on a filename suffix. This blocks image requests while allowing scripts, stylesheets, and documents to load:

page.on('request', request => {
  if (request.isInterceptResolutionHandled()) return;

  if (request.resourceType() === 'image') {
    request.abort();
  } else {
    request.continue();
  }
});

Register this listener after enabling interception and before navigating. URL rules can also be useful, but account for query strings, redirects, and URLs whose paths do not have a file extension.

Modify a request

Pass overrides to continue() when a request should still reach its destination with changed properties. For example, the API accepts an override object:

page.on('request', request => {
  if (request.isInterceptResolutionHandled()) return;

  const headers = {
    ...request.headers(),
    'x-test-run': 'fixture'
  };
  request.continue({ headers });
});

Use the request API’s supported override fields for your Puppeteer version. Header names are case-insensitive in HTTP, but avoid adding conflicting variants of the same header.

Mock a response

Use respond() to fulfill a request with a controlled response. The response object supports fields such as a status, headers, and body:

page.on('request', request => {
  if (request.isInterceptResolutionHandled()) return;

  if (request.url() === 'https://example.com/api/status') {
    request.respond({
      status: 200,
      contentType: 'application/json',
      body: JSON.stringify({ status: 'ok' })
    });
    return;
  }

  request.continue();
});

Match the endpoint precisely enough that unrelated requests are not accidentally mocked. Puppeteer documents that mocking a data: URL with respond() is unsupported and the call is a no-op.

3. Avoid “Request is already handled” errors

A request can be resolved only once. Multiple listeners, application code, or third-party packages may all receive the same request event. Before resolving it, call request.isInterceptResolutionHandled(). If the handler awaits asynchronous work, check again after the await: another handler may have resolved the request while this one was paused.

page.on('request', async request => {
  if (request.isInterceptResolutionHandled()) return;

  const shouldBlock = await checkBlockRule(request.url());

  // Another listener may have resolved this request during the await.
  if (request.isInterceptResolutionHandled()) return;

  if (shouldBlock) {
    request.abort();
  } else {
    request.continue();
  }
});

The second check must be immediately before the resolution call. Keep the check and the call in the same synchronous portion of the handler; do not await between them. If your asynchronous operation throws, make sure your handler still resolves the request when it remains unresolved, or the request can stay stalled.

4. Coordinate multiple handlers with Cooperative Intercept Mode

When several handlers need to express preferences for the same request, Puppeteer supports Cooperative Intercept Mode. Every resolution must include a numeric priority. Puppeteer waits for cooperative handlers and applies the highest-priority resolution. If priorities tie, the documented order is abort, then respond, then continue.

import puppeteer, {
  DEFAULT_INTERCEPT_RESOLUTION_PRIORITY
} from 'puppeteer';

const browser = await puppeteer.launch();
try {
  const page = await browser.newPage();
  await page.setRequestInterception(true);

  // A safe default: continue unless another cooperative handler
  // expresses a higher-priority resolution.
  page.on('request', request => {
    if (request.isInterceptResolutionHandled()) return;
    request.continue({}, DEFAULT_INTERCEPT_RESOLUTION_PRIORITY);
  });

  // Example policy from another cooperative handler.
  page.on('request', request => {
    if (request.isInterceptResolutionHandled()) return;
    if (request.url().endsWith('.png')) {
      request.abort('failed', 1);
    }
  });

  await page.goto('https://example.com');
} finally {
  await browser.close();
}

The default intercept resolution priority is zero. A custom priority is an intentional preference: use it only when your handler should outrank other cooperative handlers. Check the API signature for the Puppeteer version in your project when using the optional priority argument.

Legacy behavior versus cooperative behavior

  • If a resolution call omits its priority, it uses legacy immediate handling. That call can settle the request before other handlers contribute.
  • Cooperative aggregation applies only when every resolution includes a numeric priority.
  • In cooperative mode, higher numeric priority wins. Equal priorities use abort, respond, then continue.
  • Even with cooperative handlers, retain handled-state checks, especially around asynchronous work and code that may also run with legacy handlers.

Use priority zero when a handler simply wants to continue by default. Choose a nonzero priority only when you intentionally want its opinion to outrank another handler.

5. Practical setup checklist

  1. Enable interception before navigation or the action that triggers the requests of interest.
  2. Attach the request listener before triggering those requests.
  3. Give every request a resolution path: continue, respond, or abort.
  4. Check isInterceptResolutionHandled() immediately before resolving.
  5. After every await in a request handler, check the handled state again.
  6. If handlers should cooperate, pass a numeric priority to every resolution call.
  7. Close the browser in a finally block so errors do not leave the browser process running.

6. Performance, reliability, and cost

Interception adds request-handler work to page loading. Keep matching rules local and inexpensive when possible. An awaited network or database lookup in the request listener delays that request and can make navigation slower; it also creates a race with other handlers, so repeat the handled-state check afterward.

Blocking resources may reduce transferred work, but can change page behavior or appearance. Blocking images can affect layout; blocking scripts can prevent the page from rendering or functioning. Mocking an endpoint makes tests more controlled, but the fixture must match the response the page expects, including status and content type.

For reliable automation, resolve every intercepted request, avoid accidental double resolution, and test rules against redirects, query strings, and the request types your page actually makes. Interception itself does not define a monetary price; browser compute, network traffic, and any external services used by your automation determine operational cost.

7. Troubleshooting

Symptom Likely cause Fix
Navigation hangs or times out after enabling interception A request was intercepted but not resolved, or an async handler is taking too long. Ensure every request reaches continue, respond, or abort. Add a fallback path and keep async work short.
Request is already handled or a resolution-state error Another listener or package resolved the request first. Check isInterceptResolutionHandled() immediately before the resolution call and check again after awaits.
A cooperative handler’s preferred action does not win Another handler used a higher priority, or a handler omitted priority and switched that resolution to legacy behavior. Make all resolutions cooperative with numeric priorities, then review priority values and tie behavior.
Mock response has no effect for a data URL Puppeteer does not support mocking a data URL with respond(); the call is a no-op. Use a request that can be intercepted and fulfilled, or construct the data directly in the page or test fixture.
Some requests are not blocked by a suffix rule The URL may include a query string, use a different extension, or be served through a redirect. Inspect request.url() and request.resourceType(); use a rule that matches the actual request.
The page looks broken after blocking resources A blocked script, stylesheet, font, or image is required for the page’s rendering or behavior. Narrow the rule, block by resource type only when appropriate, and allow essential resources through.
An async handler sometimes stalls requests The async operation throws or returns along a path that never resolves the request. Use try/catch/finally around asynchronous policy work and provide a safe resolution when the request is still unhandled.

8. Or skip the browser setup

If the goal is a screenshot rather than custom request policy, ScreenshotNeo provides a website screenshot API and MCP server for developers. Its API takes a URL and returns a PNG, JPEG, WebP, or PDF. See the ScreenshotNeo API documentation for parameters and setup.

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);

ScreenshotNeo accepts cookie and consent banners like a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each step can be turned off. Bot checks and CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, with the page verdict and billing status included in response headers. Its MCP server provides take_screenshot, get_page_info, and capture_pdf for Claude, Cursor, and other MCP clients. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 screenshots. Every feature is on every plan.

Create a free ScreenshotNeo account for 1,000 screenshots a month, with no card required.

9. FAQ

Does interception include requests made before I enable it?

No. Enable it before the navigation or page action that triggers the requests you need to handle.

Can I disable interception later?

Yes. page.setRequestInterception(false) disables it; the method returns a promise, so await it.

Should I give every handler a custom priority?

No. Use the default priority for a cooperative safe default. Set a custom priority only when you mean for one handler’s decision to take precedence.

Can I use request interception just to observe traffic?

Interception pauses requests for resolution. If you only need visibility, use Puppeteer’s request events and network APIs suited to observation, and avoid enabling interception unless you intend to control request outcomes.