ScreenshotNeo

BlogHow-to

How to Intercept HTTP Requests with Puppeteer

Learn how to continue, block, and mock browser requests with Puppeteer, handle multiple interception listeners safely, and diagnose request failures.

By the ScreenshotNeo team4 October 20267 min read

Puppeteer intercepts browser requests with page.setRequestInterception(true) and a request event handler. For each intercepted request, call request.continue() to send it, request.abort() to block it, or request.respond() to return a mock response. Enable interception before navigation, and make sure each request is resolved exactly once.

The Puppeteer documentation warns: “Once request interception is enabled, every request will stall unless it’s continued, responded or aborted; or completed using the browser cache.” See the official request interception guide and API reference for current details.

Set up interception and continue requests

This complete ES module example logs outgoing requests, blocks image requests, and continues all others. Save it as intercept.mjs, install Puppeteer with npm install puppeteer, then run node intercept.mjs.

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;

    console.log(request.method(), request.url(), request.resourceType());
    if (request.resourceType() === 'image') {
      void request.abort();
    } else {
      void request.continue();
    }
  });

  await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
  console.log('Title:', await page.title());
} finally {
  await browser.close();
}

Register the handler and enable interception before goto(), so it can govern the navigation and its subresources. Match the request property relevant to your task. Checking resourceType() is more robust for blocking images than assuming every image URL ends in .png or .jpg; URLs may have query strings, extensionless paths, or other formats.

Choose how to resolve a request

Method Effect Typical use
continue() Sends the request to its destination. Pass through requests that do not match a rule; optionally override supported request properties such as headers.
abort() Cancels the request. Block a resource or endpoint.
respond() Fulfills the request with a response you supply. Mock an API response or serve controlled content.

When interception is enabled, ordinary requests still need a resolution. If a rule is conditional, include a pass-through branch. The current continue API documents the supported override fields; check it when upgrading rather than assuming arbitrary request properties can be changed.

Block requests by URL or resource type

Use a predicate that reflects the scope of the rule. This example blocks a specific host and all image resources:

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

  const url = new URL(request.url());
  const shouldBlock = url.hostname === 'analytics.example' ||
    request.resourceType() === 'image';

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

Be precise with URL matching. A substring test can accidentally block unrelated hosts, and a path check should account for query parameters if they matter. Blocking scripts, stylesheets, or fonts can change page layout and behavior; restrict rules to the resources you intend to suppress.

Change request headers

continue() accepts an overrides object for supported request properties. For example, to add a header to each request:

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

  void request.continue({
    headers: {
      ...request.headers(),
      'x-debug-run': 'puppeteer'
    }
  });
});

Header names are case-insensitive in HTTP, but avoid setting conflicting casing variants. Review the official override reference for the supported fields and behavior. Forwarding the existing headers avoids unintentionally discarding headers when you add one.

Mock a response with respond()

Use respond() to fulfill a matching request locally. The example below returns JSON for one API path and lets other requests proceed:

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

  const url = new URL(request.url());
  if (url.pathname === '/api/status') {
    void request.respond({
      status: 200,
      contentType: 'application/json',
      body: JSON.stringify({ ok: true, source: 'mock' })
    });
  } else {
    void request.continue();
  }
});

Set a suitable status, content type, and body for the consumer. A mock only applies to requests that reach this handler; make sure interception is active before the request is made. A mock is useful for deterministic page behavior, but it does not validate the real server or network path.

Handle asynchronous rules and multiple listeners

Another event listener, including one installed by a package, may resolve the request first. Check isInterceptResolutionHandled() immediately before resolving it. If the handler awaits an asynchronous policy lookup, check again after the await: another listener may have acted while this handler was paused.

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

  const allowed = await checkRequestPolicy(request.url());

  // Recheck after the await and resolve synchronously with this check.
  if (request.isInterceptResolutionHandled()) return;
  if (allowed) {
    void request.continue();
  } else {
    void request.abort();
  }
});

Keep the final check next to continue(), abort(), or respond(), with no further await between them. Otherwise another handler can resolve the request during the gap.

Cooperative interception priorities

When multiple handlers need to propose resolutions, Puppeteer supports Cooperative Intercept Mode. Each handler supplies a numeric priority to continue(), abort(), or respond(); the highest priority wins. At equal priority, abort takes precedence over respond, which takes precedence over continue. Use priority zero when a handler has no strong reason to give its decision a different weight, as the guide recommends.

Every resolution must include a numeric priority for cooperative arbitration. If even one handler resolves without a priority, legacy immediate resolution applies, and the other handlers may not participate in the expected arbitration. Continue to guard against requests already handled, especially when third-party listeners might use legacy resolution. Priorities add little value when there is only one handler.

Observe responses and diagnose failures

Request interception is for deciding what happens to outgoing requests. For diagnostics, Puppeteer also emits response, requestfinished, and requestfailed events. A completed HTTP response with status 404 or 503 is still a response; it is not automatically a requestfailed event. Track HTTP status separately from transport or loading failures.

page.on('response', response => {
  if (response.status() >= 400) {
    console.warn('HTTP error status:', response.status(), response.url());
  }
});

page.on('requestfailed', request => {
  console.warn('Request failed:', request.url(), request.failure()?.errorText);
});

See Puppeteer’s page events reference for event details.

Common errors and fixes

Symptom Likely cause Fix
Navigation hangs after enabling interception. A request was left unresolved. Ensure every path in every handler calls continue(), abort(), or respond(). Check that interception is enabled before navigation and that a rule has a pass-through branch.
“Request is already handled” or a resolution error. Two listeners tried to resolve the same request, or a handler resumed after another listener acted. Check isInterceptResolutionHandled() before acting and again after every asynchronous wait, immediately before resolution.
Some image URLs are not blocked. URL suffix matching missed query strings, extensionless URLs, or another image format. Use request.resourceType() === 'image' when the intended rule is based on resource type, or match the actual URL structure carefully.
A page breaks after blocking resources. A required script, stylesheet, font, or API request was blocked. Narrow the predicate, log matching URLs and types, and allow required resources through.
Expected priority arbitration does not happen. At least one handler used legacy resolution without a numeric priority. Use numeric priorities on all participating resolutions, or simplify to one handler.
A 404 appears as a successful request completion. An HTTP error status is being confused with a transport failure. Inspect response.status() for HTTP errors and use requestfailed for loading failures.

Performance, reliability, and cost

Interception adds a handler decision for each request, so keep predicates and synchronous work small. Avoid slow network lookups in the request event: they delay resolution and can stall page loading. If policy checks must be asynchronous, minimize their latency and use the post-await handled check shown above. Block only resources whose absence is acceptable for the page task.

For reliability, install handlers before navigation, resolve every path, and close the browser in a finally block. Be aware that other listeners can change resolution behavior. Keep diagnostics for request failures and HTTP status codes distinct so a server response is not mistaken for a network failure.

Puppeteer request interception itself has no per-request fee; operational cost comes from the machine and browser time used by your application and any network services it calls. Blocking unnecessary large resources may reduce transferred data, but the benefit depends on the page and workload. No fixed performance gain applies to every site.

Or skip the browser setup

If your goal is to get a page image rather than control arbitrary browser traffic, ScreenshotNeo provides a one-call screenshot API. It accepts a URL and returns PNG, JPEG, WebP, or PDF. See the ScreenshotNeo API documentation for parameters.

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 import('node:fs/promises').then(fs => fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer())));

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, blank pages, failed loads, timeouts, and cache hits are not billed, and response headers report the page verdict and billing status. Its MCP server lets AI agents use take_screenshot, get_page_info, and capture_pdf. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 screenshots.

Create a free ScreenshotNeo account and get 1,000 screenshots a month with no card.

FAQ

Can I intercept requests after navigation starts?

You can enable interception later, but requests already made are not retroactively intercepted. Enable it before the navigation or action that triggers the requests you need to handle.

Does a 404 trigger requestfailed?

Not by itself. A 404 is an HTTP response; inspect its status. requestfailed reports a request that failed to load.

Can I use interception just to monitor requests?

Yes. Log request details in the handler, then continue each request so it proceeds normally.

Can interception mock an endpoint without changing the server?

Yes. Use respond() for matching requests and return a controlled status, content type, and body.