Custom Parameter Decorators

Pull Anything Out of the Request With One Line

Custom Parameter Decorators

`createParamDecorator` lets you build reusable decorators like `@CurrentUser()` that hide the plumbing.

4 min read Level 3/5 #nestjs#decorators#params
What you'll learn
  • Create a parameter decorator with `createParamDecorator`
  • Read from the request inside the factory
  • Compose custom decorators with pipes

Built-in decorators (@Body, @Param, @Query) cover the obvious bits of a request. For anything project-specific — the authenticated user, the tenant ID, a parsed cookie — you can write your own.

Building @CurrentUser()

A guard normally attaches the user to the request object: req.user = {...}. Repeating req.user in every handler is noise. A custom decorator fixes that:

import { createParamDecorator, ExecutionContext } from '@nestjs/common';

export interface CurrentUserPayload {
  id: number;
  email: string;
  roles: string[];
}

export const CurrentUser = createParamDecorator(
  (_data: unknown, ctx: ExecutionContext): CurrentUserPayload => {
    const request = ctx.switchToHttp().getRequest();
    return request.user;
  },
);

createParamDecorator takes a factory that runs at request time. It receives any args passed to the decorator (data) and the ExecutionContext, from which you can pull the underlying request.

Usage feels native:

@Get('me')
me(@CurrentUser() user: CurrentUserPayload) {
  return user;
}

Accepting an Argument

The factory’s first parameter is whatever the decorator user passes. You can use it to select a sub-field:

export const CurrentUser = createParamDecorator(
  (key: keyof CurrentUserPayload | undefined, ctx: ExecutionContext) => {
    const user = ctx.switchToHttp().getRequest().user;
    return key ? user?.[key] : user;
  },
);
@Get('me')
email(@CurrentUser('email') email: string) { return email; }

Composing With Pipes

Custom decorators stack with pipes exactly like built-ins:

import { ParseIntPipe } from '@nestjs/common';

@Get('mine')
list(@CurrentUser('id', ParseIntPipe) id: number) {
  return this.posts.byOwner(id);
}

Pipes run after the factory, so you can validate or transform the extracted value before it reaches the handler.

Beyond HTTP

ExecutionContext is transport-agnostic. The same decorator can work for WebSockets or gRPC if you branch on the context type:

export const Tenant = createParamDecorator((_, ctx: ExecutionContext) => {
  if (ctx.getType() === 'http') {
    return ctx.switchToHttp().getRequest().tenant;
  }
  if (ctx.getType() === 'ws') {
    return ctx.switchToWs().getClient().data.tenant;
  }
  return undefined;
});

That’s the same secret behind most well-loved Nest libraries — a small decorator that reaches into the request, returns a clean value, and keeps controllers readable.