# How Exception Handling Works in NestJS: A Complete Guide to the Filter Architecture

> Understand NestJS exception handling with our complete guide. Learn how the filter architecture intercepts errors and translates them into HTTP responses through its four-step pipeline.

- Repository: [nestjs/nest](https://github.com/nestjs/nest)
- Tags: deep-dive
- Published: 2026-03-01

---

**NestJS implements a layered exception-filter system that intercepts errors thrown during request processing and translates them into HTTP responses through a four-step pipeline involving RouterProxy, ExceptionsHandler, and BaseExceptionFilter.**

Exception handling in NestJS is built around a flexible, metadata-driven architecture that allows developers to catch and process errors at global, controller, or route levels. According to the nestjs/nest source code, the framework wraps every incoming request in a try/catch block and delegates errors to a chain of **Exception Filters** that determine the final response format.

## The Exception Handling Pipeline

The framework processes uncaught exceptions through four distinct architectural layers defined in `packages/core/exceptions/` and `packages/core/router/`.

### Step 1: Request Routing and Error Capture

When a request arrives, the `RouterProxy` invokes the controller method inside a try/catch block. If an error bubbles up unhandled, the router immediately forwards it to the exception filter context.

In [`packages/core/router/router-exception-filters.ts`](https://github.com/nestjs/nest/blob/main/packages/core/router/router-exception-filters.ts), the `RouterExceptionFilters.create` method (lines 23-45) constructs an **ExceptionsHandler** instance for the current request. This handler serves as the central orchestrator for all subsequent error processing.

### Step 2: Filter Resolution and Metadata

`RouterExceptionFilters` builds an ordered list of applicable filters by reading the `EXCEPTION_FILTERS_METADATA` key. This metadata is populated when you use the `@UseFilters()` decorator or register global filters.

The framework aggregates three tiers of filters:
- **Method-level** filters (highest priority)
- **Controller-level** filters
- **Global filters** from `ApplicationConfig.getGlobalFilters()` and `ApplicationConfig.getGlobalRequestFilters()`

These are stored in an `ExceptionsHandler` instance during the creation phase (lines 31-44 of [`router-exception-filters.ts`](https://github.com/nestjs/nest/blob/main/router-exception-filters.ts)).

### Step 3: Custom Filter Invocation

When an exception occurs, `ExceptionsHandler.next()` (lines 12-17 in [`packages/core/exceptions/exceptions-handler.ts`](https://github.com/nestjs/nest/blob/main/packages/core/exceptions/exceptions-handler.ts)) triggers `invokeCustomFilters()`. This method iterates through the filter list and selects the first match using `selectExceptionFilterMetadata`.

Located in [`packages/common/utils/select-exception-filter-metadata.util.ts`](https://github.com/nestjs/nest/blob/main/packages/common/utils/select-exception-filter-metadata.util.ts) (lines 4-13), this utility checks if the thrown error is an instance of any type defined in the filter's `exceptionMetatypes` array. A filter with an empty `exceptionMetatypes` array acts as a catch-all.

### Step 4: Fallback to Base Filter

If no custom filter matches the exception type, control falls to `BaseExceptionFilter.catch()` in [`packages/core/exceptions/base-exception-filter.ts`](https://github.com/nestjs/nest/blob/main/packages/core/exceptions/base-exception-filter.ts) (lines 26-48).

This base implementation distinguishes between `HttpException` instances (which have predefined status codes and responses) and unknown errors. For unrecognized exceptions, `handleUnknownError()` (lines 50-71) generates a 500 Internal Server Error response and logs the stack trace through the injected `HttpAdapterHost`.

## Key Architectural Components

Understanding the internal structure of NestJS exception handling requires familiarity with several core abstractions.

### The ExceptionFilter Interface

Every custom filter must implement the `ExceptionFilter` interface defined in [`packages/common/interfaces/exceptions/exception-filter.interface.ts`](https://github.com/nestjs/nest/blob/main/packages/common/interfaces/exceptions/exception-filter.interface.ts). The contract requires a single method:

```typescript
catch(exception: unknown, host: ArgumentsHost): void

```

The `ArgumentsHost` parameter provides access to the execution context, allowing filters to retrieve the underlying request and response objects via `host.switchToHttp()`.

### Filter Context and Scoping

`BaseExceptionFilterContext` extends `ContextCreator` to instantiate concrete filter classes. It resolves scoped providers (request-scoped or transient) and extracts `exceptionMetatypes` via the `reflectCatchExceptions` utility. This ensures that filters can leverage the full dependency injection container, including services and repositories.

### Global vs. Scoped Registration

Global filters apply to every route in the application and are retrieved through `ApplicationConfig`. Method and controller-level filters are discovered through reflection metadata and take precedence over global configurations. The framework merges these lists into a single execution chain during the request lifecycle.

## Implementing Custom Exception Filters

The following examples demonstrate how to implement and register filters at different scopes.

### Creating a Catch-All Filter

This filter intercepts every exception type and formats a consistent JSON response:

```typescript
import {
  ExceptionFilter,
  Catch,
  ArgumentsHost,
  HttpException,
  HttpStatus,
} from '@nestjs/common';

@Catch()
export class AllExceptionsFilter implements ExceptionFilter {
  catch(exception: unknown, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse();
    const request = ctx.getRequest();

    const status =
      exception instanceof HttpException
        ? exception.getStatus()
        : HttpStatus.INTERNAL_SERVER_ERROR;

    response.status(status).json({
      timestamp: new Date().toISOString(),
      path: request.url,
      error: exception instanceof Error ? exception.message : 'Unknown error',
    });
  }
}

```

### Registering Global Filters

Apply a filter to every route in your application using `useGlobalFilters()`:

```typescript
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { AllExceptionsFilter } from './all-exceptions.filter';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalFilters(new AllExceptionsFilter());
  await app.listen(3000);
}
bootstrap();

```

### Controller and Route-Level Registration

Bind filters to specific controllers or methods using the `@UseFilters()` decorator:

```typescript
import { Controller, Get, UseFilters } from '@nestjs/common';
import { AllExceptionsFilter } from './all-exceptions.filter';

@Controller('cats')
export class CatsController {
  @Get()
  @UseFilters(AllExceptionsFilter)
  findAll() {
    throw new Error('Something went wrong');
  }
}

```

When the above route throws, `ExceptionsHandler.invokeCustomFilters` selects `AllExceptionsFilter` because its empty `exceptionMetatypes` array matches all exception types.

## Filter Selection Logic

The `selectExceptionFilterMetadata` utility determines which filter handles a specific error by evaluating the `exceptionMetatypes` array in the order filters were registered. The algorithm returns the first filter where:

1. The `exceptionMetatypes` array is empty (catch-all), OR
2. The thrown error is an instance of at least one type in the `exceptionMetatypes` array

This short-circuit evaluation means the order of filter registration matters significantly, with method-level filters evaluated before controller-level and global alternatives.

## Summary

- **RouterExceptionFilters.create** constructs an **ExceptionsHandler** for each request by aggregating metadata from `EXCEPTION_FILTERS_METADATA`.
- **ExceptionsHandler.next** delegates to custom filters first via **invokeCustomFilters**, which uses **selectExceptionFilterMetadata** to find type matches.
- **BaseExceptionFilter** serves as the final fallback, converting `HttpException` instances to proper HTTP responses and unknown errors to 500 status codes.
- Custom filters implement the **ExceptionFilter** interface and access the execution context through **ArgumentsHost**.
- Registration can occur globally via `useGlobalFilters()`, or locally using the `@UseFilters()` decorator with method-level priority.

## Frequently Asked Questions

### What order does NestJS use to evaluate exception filters?

NestJS evaluates filters in the order they were registered within each scope tier: method-level filters execute first, followed by controller-level filters, and finally global filters. Within each tier, the first filter whose `exceptionMetatypes` matches the thrown error type handles the exception, and subsequent filters are skipped.

### How do I catch every exception in a NestJS application?

Create a filter with the `@Catch()` decorator without passing any exception classes, which leaves the `exceptionMetatypes` array empty. Register this filter globally using `app.useGlobalFilters()` in your bootstrap function to ensure it catches all unhandled errors across every route.

### What happens when an error is not an HttpException?

If no custom filter catches the error, `BaseExceptionFilter.handleUnknownError` processes it. The method generates a 500 Internal Server Error response, logs the error stack trace through the HttpAdapterHost, and returns a generic error message to the client to prevent information leakage.

### Can exception filters access dependency injection?

Yes. Exception filters are part of the NestJS instantiation lifecycle and can inject providers through their constructors. When using `BaseExceptionFilterContext` to create filter instances, the framework resolves all dependencies including scoped providers (request-scoped or transient) before the filter handles an exception.