# How Cypress Implements Network Request Interception with cy.intercept()

> Discover how Cypress implements network request interception with cy.intercept() using a three-layer architecture with CDP or WebDriver BiDi. Learn to pause, match, and manipulate HTTP requests.

- Repository: [Cypress.io/cypress](https://github.com/cypress-io/cypress)
- Tags: internals
- Published: 2026-07-12

---

**Cypress implements `cy.intercept()` through a three-layer architecture that separates the driver command API, a browser-agnostic NetworkInterceptionCore, and browser-specific transports using Chrome DevTools Protocol (CDP) or WebDriver BiDi to pause, match, and manipulate HTTP requests.**

The `cy.intercept()` command in Cypress enables developers to stub, spy, and modify network traffic during end-to-end tests. According to the cypress-io/cypress source code, this feature relies on a sophisticated pipeline that bridges the test driver with low-level browser automation protocols. Understanding this implementation reveals how Cypress achieves consistent network stubbing across Chrome, Edge, Firefox, and WebKit without relying solely on proxy-based MITM techniques.

## The Three-Layer Architecture

Cypress’s network interception splits responsibilities across distinct layers to ensure cross-browser compatibility and maintainable code.

### Layer 1: Driver Command Registration

When a test calls `cy.intercept()`, the driver processes the command in [`packages/driver/src/cy/net-stubbing/add-command.ts`](https://github.com/cypress-io/cypress/blob/main/packages/driver/src/cy/net-stubbing/add-command.ts). This module validates arguments, creates a **RouteMatcher** object, and registers the route with the core interception engine. If the command chain includes `.as('alias')`, the [`packages/driver/src/cy/net-stubbing/aliasing.ts`](https://github.com/cypress-io/cypress/blob/main/packages/driver/src/cy/net-stubbing/aliasing.ts) module associates the route with a Cypress alias for later reference via `cy.wait()`.

### Layer 2: Browser-Agnostic Interception Core

The **NetworkInterceptionCore** in [`packages/network-interception/lib/core/index.ts`](https://github.com/cypress-io/cypress/blob/main/packages/network-interception/lib/core/index.ts) serves as the central coordination hub. When the driver calls `NetworkInterceptionCore.registerRoute(matcher, handler)`, the core instantiates an **HttpIntercept** class containing the matcher, handler function, and a unique interception ID. The core applies default policies—such as blocked-hosts handling in [`packages/network-interception/lib/policies/blocked-hosts.ts`](https://github.com/cypress-io/cypress/blob/main/packages/network-interception/lib/policies/blocked-hosts.ts)—to determine how requests should be treated before they ever reach the network transport.

### Layer 3: Browser-Specific Transport

The server creates a **NetworkInterceptionRuntime** instance (wired in [`packages/server/lib/network-runtime.ts`](https://github.com/cypress-io/cypress/blob/main/packages/server/lib/network-runtime.ts)) that pairs the core with a transport adapter specific to the browser:

- **Chrome and Edge**: Uses [`packages/server/lib/browsers/cdp-protocol/cdp-fetch-transport.ts`](https://github.com/cypress-io/cypress/blob/main/packages/server/lib/browsers/cdp-protocol/cdp-fetch-transport.ts) to enable the CDP **Fetch** domain (`Fetch.enable`) and listen for `Fetch.requestPaused` events.
- **Firefox and BiDi-capable browsers**: Uses [`packages/server/lib/browsers/bidi_automation.ts`](https://github.com/cypress-io/cypress/blob/main/packages/server/lib/browsers/bidi_automation.ts) to call `networkAddIntercept` via the WebDriver BiDi protocol and handle `beforeRequestSent` events.

Both transports convert paused browser requests into **CyHttpMessage** objects and forward them to the core for matching.

## The Request Interception Flow

The complete lifecycle of an intercepted request progresses through six distinct stages.

### 1. Command Parsing and Route Registration

The `intercept` command parses its arguments (URL, method, handler) and constructs a **RouteMatcher**. It then invokes `NetworkInterceptionCore.registerRoute()` to store the matcher and handler (which may be a function, static response, or middleware) in the core’s registry.

### 2. Core Registration and Policy Application

Upon registration, the core builds an **HttpIntercept** instance and executes `registerDefaultNetworkPolicies`. This applies global rules such as blocked-host restrictions and content-type handling, ensuring requests are filtered according to project configuration before matching occurs.

### 3. Transport Initialization

When the spec run begins, the server instantiates the runtime. For Chrome, `CdpFetchTransport` sends `Fetch.enable` to the browser and attaches a listener on `Fetch.requestPaused`. For Firefox, `BidiAutomation` calls the BiDi `networkAddIntercept` command to obtain an intercept ID and prepares to receive `beforeRequestSent` events.

### 4. Request Matching and Interception Creation

When a request occurs, the transport pauses it and queries the core via `getRoutesForRequest()`. If a **RouteMatcher** matches, the core creates an **Interception** object that tracks the full lifecycle (request, response, error). The driver’s proxy logging module ([`packages/driver/src/cypress/proxy-logging.ts`](https://github.com/cypress-io/cypress/blob/main/packages/driver/src/cypress/proxy-logging.ts)) records this event to display in the Cypress Command Log.

### 5. Handler Execution and Resolution

The core invokes the registered handler, which may:
- **Continue** the request to the destination server
- **Stub** a static response using the provided `statusCode` and `body`
- **Modify** the request headers or body before continuation

Once the handler completes, the transport either releases the request to the network or returns the stubbed response, then notifies the core to mark the interception as `completed` or `errored`.

### 6. Waiting and Assertions

Commands like `cy.wait('@alias')` utilize [`packages/driver/src/cy/net-stubbing/wait-for-route.ts`](https://github.com/cypress-io/cypress/blob/main/packages/driver/src/cy/net-stubbing/wait-for-route.ts) to poll the core’s stored interceptions. The utility waits until the interception reaches the desired state (e.g., response received) before yielding control back to the test for assertions.

## Practical Implementation Examples

```javascript
// Spy on GET requests to /api/users and log them
cy.intercept('GET', '/api/users', (req) => {
  console.log('Intercepted:', req.method, req.url)
})

```

```javascript
// Stub a JSON response without hitting the server
cy.intercept('GET', '/api/todos', {
  statusCode: 200,
  body: [{ id: 1, title: 'Write article' }],
})

```

```javascript
// Modify request body and wait for the response
cy.intercept('POST', '/api/login', (req) => {
  req.body = { ...req.body, extra: 'data' }
}).as('login')

// Later in the test
cy.wait('@login').its('response.statusCode').should('eq', 200)

```

## Key Source Files

- **[`packages/driver/src/cy/net-stubbing/add-command.ts`](https://github.com/cypress-io/cypress/blob/main/packages/driver/src/cy/net-stubbing/add-command.ts)**: Implements the `intercept` command and argument validation.
- **[`packages/driver/src/cy/net-stubbing/aliasing.ts`](https://github.com/cypress-io/cypress/blob/main/packages/driver/src/cy/net-stubbing/aliasing.ts)**: Handles `.as()` aliasing for intercepted routes.
- **[`packages/network-interception/lib/core/index.ts`](https://github.com/cypress-io/cypress/blob/main/packages/network-interception/lib/core/index.ts)**: Entry point for the browser-agnostic interception core.
- **[`packages/network-interception/lib/policies/blocked-hosts.ts`](https://github.com/cypress-io/cypress/blob/main/packages/network-interception/lib/policies/blocked-hosts.ts)**: Implements blocked-host policy enforcement.
- **[`packages/server/lib/network-runtime.ts`](https://github.com/cypress-io/cypress/blob/main/packages/server/lib/network-runtime.ts)**: Wires the core into the server’s request handling pipeline.
- **[`packages/server/lib/browsers/cdp-protocol/cdp-fetch-transport.ts`](https://github.com/cypress-io/cypress/blob/main/packages/server/lib/browsers/cdp-protocol/cdp-fetch-transport.ts)**: CDP Fetch domain implementation for Chrome/Edge.
- **[`packages/server/lib/browsers/bidi_automation.ts`](https://github.com/cypress-io/cypress/blob/main/packages/server/lib/browsers/bidi_automation.ts)**: WebDriver BiDi implementation for Firefox.
- **[`packages/driver/src/cypress/proxy-logging.ts`](https://github.com/cypress-io/cypress/blob/main/packages/driver/src/cypress/proxy-logging.ts)**: Logs interceptions to the Cypress Command Log UI.
- **[`packages/driver/src/cy/net-stubbing/wait-for-route.ts`](https://github.com/cypress-io/cypress/blob/main/packages/driver/src/cy/net-stubbing/wait-for-route.ts)**: Provides the polling mechanism for `cy.wait('@alias')`.

## Summary

- **Cypress splits network interception** into driver commands, a generic core, and browser-specific transports to maintain consistency across CDP and BiDi protocols.
- **Route registration** begins in [`add-command.ts`](https://github.com/cypress-io/cypress/blob/main/add-command.ts) with a **RouteMatcher**, but the actual interception logic lives in the **NetworkInterceptionCore**.
- **Browser transports** like `CdpFetchTransport` and `BidiAutomation` pause requests and convert them into **CyHttpMessage** objects for the core to process.
- **Handlers** attached to routes can spy, stub, or modify requests before the transport releases them or returns a mocked response.
- **Waiting and assertions** rely on [`wait-for-route.ts`](https://github.com/cypress-io/cypress/blob/main/wait-for-route.ts) polling the core’s stored **Interception** objects until they reach the desired lifecycle state.

## Frequently Asked Questions

### What is the difference between the driver layer and the transport layer in cy.intercept()?

The **driver layer** (in `packages/driver`) handles the test-side API, parsing arguments and creating **RouteMatcher** objects. The **transport layer** (in `packages/server/lib/browsers`) manages low-level browser protocols like CDP Fetch or WebDriver BiDi to actually pause and manipulate network traffic. This separation allows the core interception logic to remain browser-agnostic while the transport adapts to specific automation protocols.

### How does Cypress handle network interception in Firefox compared to Chrome?

For **Chrome and Edge**, Cypress uses the Chrome DevTools Protocol (CDP) Fetch domain via [`cdp-fetch-transport.ts`](https://github.com/cypress-io/cypress/blob/main/cdp-fetch-transport.ts), enabling `Fetch.enable` and processing `Fetch.requestPaused` events. For **Firefox**, Cypress uses WebDriver BiDi via [`bidi_automation.ts`](https://github.com/cypress-io/cypress/blob/main/bidi_automation.ts), calling `networkAddIntercept` and handling `beforeRequestSent` events. Both transports forward requests to the same **NetworkInterceptionCore**, ensuring consistent behavior across browsers.

### What happens to a request after it matches a cy.intercept() route?

Once a request matches, the core creates an **Interception** object and invokes the registered handler. The handler can modify the request, immediately return a stubbed response, or allow the request to continue to the destination server. The transport either fulfills the request from the handler’s instructions or releases the paused request, then notifies the core to update the interception status to `completed` or `errored`.

### Where does Cypress store intercepted requests for cy.wait() assertions?

Intercepted requests are stored as **Interception** objects within the **NetworkInterceptionCore**. When `cy.wait('@alias')` executes, the [`wait-for-route.ts`](https://github.com/cypress-io/cypress/blob/main/wait-for-route.ts) module polls the core’s registry via `waitForRoute`, resolving when the interception reaches the required state (such as receiving a response). This allows tests to assert on request and response properties after the network exchange has occurred.