# How to Implement Custom Pipes for Request Validation in NestJS

> Learn to implement custom pipes in NestJS for robust request validation. Create a PipeTransform class to validate incoming data and ensure valid payloads reach your route handlers.

- Repository: [nestjs/nest](https://github.com/nestjs/nest)
- Tags: how-to-guide
- Published: 2026-03-01

---

**Implement custom pipes in NestJS by creating a class that implements the `PipeTransform` interface, decorating it with `@Injectable()`, and defining a `transform()` method that validates incoming request data and throws exceptions for invalid payloads before they reach the route handler.**

Custom pipes are essential building blocks in the `nestjs/nest` ecosystem for enforcing request validation logic. They intercept incoming data at the boundary of your application, allowing you to sanitize inputs, verify business rules, and prevent invalid requests from ever reaching your controllers. Whether you need simple type coercion or complex async database lookups, implementing the `PipeTransform` interface gives you full control over request preprocessing.

## Understanding the Pipe Architecture

NestJS treats pipes as **request-processing units** that execute in a specific sequence before your route handler runs. According to the source code in [`packages/common/interfaces/pipes/pipe-transform.interface.ts`](https://github.com/nestjs/nest/blob/main/packages/common/interfaces/pipes/pipe-transform.interface.ts), every pipe must implement a single `transform(value: any, metadata: ArgumentMetadata)` method that receives the raw value and metadata describing the route parameter.

When a request enters the system, Nest constructs a processing pipeline that runs in this order:

1. **Global pipes** (registered in [`main.ts`](https://github.com/nestjs/nest/blob/main/main.ts))
2. **Controller-level pipes** (applied via `@UsePipes()`)
3. **Method-level pipes** (passed to parameter decorators)
4. **Route handler**

If any pipe throws an exception—typically a `BadRequestException`—the request stops immediately and the error response returns to the client. The built-in `ValidationPipe` located at [`packages/common/pipes/validation.pipe.ts`](https://github.com/nestjs/nest/blob/main/packages/common/pipes/validation.pipe.ts) demonstrates this pattern using `class-validator` and `class-transformer`, while simpler examples like `ParseIntPipe` in [`packages/common/pipes/parse-int.pipe.ts`](https://github.com/nestjs/nest/blob/main/packages/common/pipes/parse-int.pipe.ts) show lightweight type transformation.

## Why Create Custom Validation Pipes?

While NestJS provides robust built-in validation via `ValidationPipe`, custom pipes solve specific architectural needs:

- **Domain-specific validation** that exceeds decorator capabilities, such as checking database uniqueness or consulting external APIs
- **Reusable business logic** you can apply across multiple routes without duplicating controller code
- **Performance optimization** through lightweight synchronous checks that run before heavier class-validator logic
- **Dependency injection** for async operations, as shown in [`integration/scopes/src/transient/users/user-by-id.pipe.ts`](https://github.com/nestjs/nest/blob/main/integration/scopes/src/transient/users/user-by-id.pipe.ts)

## Implementing a Synchronous Custom Validation Pipe

Create a basic validation pipe by implementing `PipeTransform` with a concrete type. This example validates that a route parameter is a positive integer:

```typescript
import {
  PipeTransform,
  Injectable,
  ArgumentMetadata,
  BadRequestException,
} from '@nestjs/common';

@Injectable()
export class PositiveIntPipe implements PipeTransform<number> {
  transform(value: any, metadata: ArgumentMetadata) {
    const val = parseInt(value, 10);
    if (isNaN(val) || val <= 0) {
      throw new BadRequestException(
        `${metadata.metatype?.name} must be a positive integer`,
      );
    }
    return val;
  }
}

```

Apply this pipe at the method level to validate parameters before they enter your handler:

```typescript
@Get('items/:limit')
findMany(@Param('limit', PositiveIntPipe) limit: number) {
  // `limit` is guaranteed to be a positive integer here
}

```

## Implementing Async Custom Pipes with Dependency Injection

Custom pipes fully support NestJS's dependency injection system, enabling async validation that queries databases or external services. The `UserByIdPipe` in the integration tests demonstrates this pattern, and you can replicate it for email uniqueness checks:

```typescript
import {
  PipeTransform,
  Injectable,
  ArgumentMetadata,
  ConflictException,
} from '@nestjs/common';
import { UsersService } from '../users/users.service';

@Injectable()
export class UniqueEmailPipe implements PipeTransform<string> {
  constructor(private readonly usersService: UsersService) {}

  async transform(value: string, metadata: ArgumentMetadata) {
    const exists = await this.usersService.findByEmail(value);
    if (exists) {
      throw new ConflictException('Email already in use');
    }
    return value;
  }
}

```

Because the pipe is `@Injectable()`, Nest resolves the `UsersService` dependency automatically, letting you perform async business validation before the controller method executes.

## Registering and Applying Custom Pipes

You can bind pipes at three different scopes depending on your validation needs.

**Method-level binding** targets specific parameters using the `@Body()`, `@Query()`, or `@Param()` decorators:

```typescript
@Post('users')
async create(@Body(UniqueEmailPipe) email: string) {
  // Email is guaranteed unique
}

```

**Controller-level binding** applies pipes to every route in a controller using `@UsePipes()`:

```typescript
@Controller('users')
@UsePipes(PositiveIntPipe)
export class UsersController {
  // All routes inherit this pipe logic
}

```

**Global registration** in your [`main.ts`](https://github.com/nestjs/nest/blob/main/main.ts) ensures all routes use the same validation strategy:

```typescript
async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalPipes(new ValidationPipe());
  await app.listen(3000);
}
bootstrap();

```

## Combining Built-in and Custom Pipes

NestJS allows you to chain multiple pipes for layered validation. For example, use the built-in `ValidationPipe` to enforce DTO structure, then apply a custom pipe for business-rule validation:

```typescript
@Post('register')
async register(
  @Body(new ValidationPipe({ whitelist: true, forbidNonWhitelisted: true }))
  createUserDto: CreateUserDto,
  @Body(UniqueEmailPipe) _: any,
) {
  // DTO is structurally valid AND email is unique
}

```

The cats-app sample in [`sample/01-cats-app/src/common/pipes/parse-int.pipe.ts`](https://github.com/nestjs/nest/blob/main/sample/01-cats-app/src/common/pipes/parse-int.pipe.ts) provides a reference implementation of this pattern, demonstrating how to throw `BadRequestException` with descriptive messages when validation fails.

## Summary

- **Implement `PipeTransform`**: All custom pipes must define a `transform(value, metadata)` method and be decorated with `@Injectable()`.
- **Source files**: Study [`packages/common/interfaces/pipes/pipe-transform.interface.ts`](https://github.com/nestjs/nest/blob/main/packages/common/interfaces/pipes/pipe-transform.interface.ts) for the contract and [`packages/common/pipes/validation.pipe.ts`](https://github.com/nestjs/nest/blob/main/packages/common/pipes/validation.pipe.ts) for complex validation patterns.
- **Dependency injection**: Constructor parameters in pipes are automatically resolved by Nest, enabling async database checks as shown in [`integration/scopes/src/transient/users/user-by-id.pipe.ts`](https://github.com/nestjs/nest/blob/main/integration/scopes/src/transient/users/user-by-id.pipe.ts).
- **Application scopes**: Bind pipes globally, at the controller level, or to individual parameters using `useGlobalPipes()`, `@UsePipes()`, or parameter decorators.
- **Error handling**: Throw `BadRequestException` or domain-specific exceptions to halt request processing when validation fails.

## Frequently Asked Questions

### How do I access the request object inside a custom pipe?

**You cannot directly access the request object inside a pipe.** The `PipeTransform` interface only receives the `value` being processed and `ArgumentMetadata`. If you need request-level data (headers, IP address), use a **Guard** or **Interceptor** instead, or pass the required data through decorators and the `metadata` argument.

### Can I use multiple pipes on a single parameter?

**Yes, NestJS processes pipes left-to-right when declared in an array.** For example, `@Param('id', ParseIntPipe, PositiveIntPipe)` first converts the string to an integer, then validates it is positive. Each pipe receives the output of the previous pipe in the chain.

### What is the difference between `ValidationPipe` and a custom validation pipe?

**`ValidationPipe` is a generic, class-validator-based implementation** located at [`packages/common/pipes/validation.pipe.ts`](https://github.com/nestjs/nest/blob/main/packages/common/pipes/validation.pipe.ts) that validates DTO objects using decorators like `@IsString()`. **Custom pipes implement specific business logic** that decorators cannot express, such as checking database uniqueness or transforming data using custom algorithms. Use `ValidationPipe` for structural validation and custom pipes for domain-specific rules.

### How do I test a custom pipe in isolation?

**Unit test pipes by instantiating them directly** and calling the `transform()` method with mock values and metadata. Since pipes are plain classes with a single method, you do not need to bootstrap an entire NestJS application. Mock any injected dependencies in the constructor to test async validation logic without database connections.