Suggest an editImprove this articleRefine the answer for “How to create and use custom decorators in NestJS?”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**Custom decorators in NestJS** are TypeScript functions that extract values from the request context or attach metadata for guards and pipes to read at runtime. ```typescript export const CurrentUser = createParamDecorator( (data: string, ctx: ExecutionContext) => { const request = ctx.switchToHttp().getRequest(); return data ? request.user?.[data] : request.user; }, ); ``` **Key idea:** use `createParamDecorator` for request data extraction, `SetMetadata` for guard metadata, and `applyDecorators` to compose multiple decorators into one.Shown above the full answer for quick recall.Answer (EN)Image**Custom decorators in NestJS** are TypeScript functions that either extract values from the request context or store metadata for guards and pipes to read, using `Reflect.metadata` for runtime reflection. ## Theory ### TL;DR - Custom decorators work like labels on assembly line parts: attach one to a method or parameter, and NestJS reads it at runtime to adjust behavior automatically - Two main types: param decorators (pull data from the request) and metadata decorators (store data for guards to read later) - `createParamDecorator` for request extraction, `SetMetadata` for guard metadata, `applyDecorators` to bundle both - Decision rule: if you copy `req.user` or `SetMetadata('roles', ...)` in 2+ places, create a decorator; for one-offs, use guards or pipes directly ### Quick example ```typescript import { createParamDecorator, ExecutionContext } from '@nestjs/common'; export const CurrentUser = createParamDecorator( (data: string, ctx: ExecutionContext) => { const request = ctx.switchToHttp().getRequest(); const user = request.user; // populated by JwtAuthGuard return data ? user?.[data] ?? null : user ?? null; }, ); // Controller usage @Get('me') getMe(@CurrentUser() user: User) { return user; // full user object from request.user } @Get('email') getEmail(@CurrentUser('email') email: string) { return { email }; // just the email field } ``` `data` carries the argument you pass inside the decorator call. `@CurrentUser('email')` sets `data` to `'email'`. No argument returns the full object. Returning `null` instead of `undefined` keeps TypeScript types honest in the controller. ### Key difference: param vs metadata decorators Param decorators run per request inside `createParamDecorator` and extract a value from `ExecutionContext`. Metadata decorators run once at class definition time via `SetMetadata`, storing key-value pairs that guards read later with `Reflector`. If you mix these up, the metadata is `undefined` at runtime and the guard silently allows or blocks everything, which is hard to trace. ### When to use - Repeated `request.user` extraction across 2+ controllers: param decorator - Role or permission checks on multiple routes: metadata decorator paired with a guard - Custom field validation: property decorator with `registerDecorator` - Four decorators stacked on every protected endpoint: composed decorator with `applyDecorators` - Single-use logic: skip the decorator, use guards or pipes directly ### Decorator types and their APIs **Parameter decorator** runs per request and receives `ExecutionContext`: ```typescript // current-user.decorator.ts import { createParamDecorator, ExecutionContext } from '@nestjs/common'; export const CurrentUser = createParamDecorator( (data: string, ctx: ExecutionContext) => { const request = ctx.switchToHttp().getRequest(); const user = request.user; return data ? user?.[data] ?? null : user ?? null; }, ); ``` **Metadata decorator** stores static data that guards read: ```typescript // roles.decorator.ts import { SetMetadata } from '@nestjs/common'; export const ROLES_KEY = 'roles'; export const Roles = (...roles: string[]) => SetMetadata(ROLES_KEY, roles); ``` The guard reads this with `Reflector.getAllAndOverride`: ```typescript // roles.guard.ts @Injectable() export class RolesGuard implements CanActivate { constructor(private reflector: Reflector) {} canActivate(context: ExecutionContext): boolean { const requiredRoles = this.reflector.getAllAndOverride<string[]>(ROLES_KEY, [ context.getHandler(), // method-level first context.getClass(), // then class-level ]); if (!requiredRoles) return true; const { user } = context.switchToHttp().getRequest(); return requiredRoles.some((role) => user?.roles?.includes(role)); } } ``` **Composed decorator** bundles multiple decorators into one call: ```typescript // auth.decorator.ts import { applyDecorators, UseGuards, SetMetadata } from '@nestjs/common'; import { ApiBearerAuth, ApiUnauthorizedResponse } from '@nestjs/swagger'; export function Auth(...roles: string[]) { return applyDecorators( SetMetadata(ROLES_KEY, roles), UseGuards(JwtAuthGuard, RolesGuard), ApiBearerAuth(), ApiUnauthorizedResponse({ description: 'Unauthorized' }), ); } ``` Instead of stacking four decorators on every protected route, you write `@Auth('admin')` once. **Property decorator** for custom validation rules: ```typescript import { registerDecorator, ValidationOptions } from 'class-validator'; export function IsStrongPassword(validationOptions?: ValidationOptions) { return function (object: object, propertyName: string) { registerDecorator({ name: 'isStrongPassword', target: object.constructor, propertyName, options: validationOptions, validator: { validate(value: string) { return /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&]).{8,}$/.test(value); }, defaultMessage() { return 'Password must include uppercase, lowercase, number, and special character'; }, }, }); }; } ``` ### How it works internally TypeScript compiles decorators as static function calls that run when the class is defined, not when a request arrives. They store key-value pairs via `Reflect.defineMetadata` in a WeakMap tied to the class or method. NestJS scans these during app bootstrap and wires the metadata into the DI system. When a request comes in, guards and pipes call `Reflect.getMetadata` to read what was stored. `Reflector.getAllAndOverride` checks method-level metadata first, then class-level. Method wins when both are set. That is why you can put `@Roles('admin')` on a controller and override a specific route with `@Roles('user')`. The closest definition wins. ### Common mistakes **Assuming HTTP context in all param decorators:** ```typescript // Wrong: crashes in WebSocket or microservice transport export const UserId = createParamDecorator( (_, ctx) => ctx.switchToHttp().getRequest().user.id ); // Right: check context type first export const UserId = createParamDecorator( (_, ctx) => { if (ctx.getType() === 'http') { return ctx.switchToHttp().getRequest().user?.id; } return ctx.switchToRpc().getData().user?.id; } ); ``` **Returning `undefined` when the user is missing:** ```typescript // Wrong: types say User, runtime gives undefined -> null ref errors export const CurrentUser = createParamDecorator( (_, ctx) => ctx.switchToHttp().getRequest().user ); // Right: return null explicitly so callers handle it export const CurrentUser = createParamDecorator( (_, ctx) => ctx.switchToHttp().getRequest().user ?? null ); ``` **Missing `reflect-metadata` polyfill:** NestJS auto-imports `reflect-metadata` in `main.ts`. But if you move a decorator into a shared library without that import, `Reflector.getMetadata` returns `undefined` everywhere and guards stop working. Always confirm `import 'reflect-metadata'` runs before any decorator code. **Naming conflicts with NestJS built-ins:** ```typescript // Wrong: shadows @Body() from @nestjs/common, breaks validation pipes export const Body = createParamDecorator(...); // Right: use a unique prefix export const ParsedBody = createParamDecorator(...); ``` **Inline `SetMetadata` instead of a named constant:** ```typescript // Wrong: typo-prone, duplicated string across guard and decorator @SetMetadata('roles', ['admin']) // Right: export the key, share it between decorator and guard export const ROLES_KEY = 'roles'; export const Roles = (...roles: string[]) => SetMetadata(ROLES_KEY, roles); ``` ### Real-world usage - `@nestjs/passport` uses a `@User()` param decorator for JWT user extraction - NestJS GraphQL examples use `@CurrentUser()` inside resolvers, switching to `GqlExecutionContext` instead of HTTP - RBAC in e-commerce backends (orders, inventory) uses `@Roles()` with `RolesGuard` for access control - API platforms combine everything into `@Auth()` so `@UseGuards`, `@ApiBearerAuth()`, and `@SetMetadata` stay in sync on every route ### Follow-up questions **Q:** What is the difference between `createParamDecorator` and a method decorator? **A:** `createParamDecorator` runs per request and extracts a value from `ExecutionContext`. A method decorator runs once at class definition time, typically to set static metadata via `SetMetadata`. **Q:** Can the factory function inside `createParamDecorator` be async? **A:** Yes. The function can `await` inside, for example to fetch a user from a database. It runs after guards complete, so `request.user` is already set if your auth guard ran first. **Q:** How do you unit test a custom param decorator? **A:** Create a mock `ExecutionContext` with the request data you need, then call the decorator factory directly. No full NestJS app is required for this. **Q:** Why does `Reflector.getAllAndOverride` check both handler and class? **A:** It merges metadata across the prototype chain, and the closest definition wins. This lets you set a default `@Roles('admin')` on a controller and override specific routes with a different value. **Q (senior):** If `@Roles('admin')` is on the controller class and `@Roles('user')` is on a method, what does `getAllAndOverride` return? **A:** It returns `['user']`. `getAllAndOverride` starts from the handler (method) and picks the first defined value, so method-level metadata always beats class-level when both exist. ## Examples ### Basic: CurrentUser param decorator ```typescript // current-user.decorator.ts import { createParamDecorator, ExecutionContext } from '@nestjs/common'; export const CurrentUser = createParamDecorator( (data: string, ctx: ExecutionContext) => { const request = ctx.switchToHttp().getRequest(); const user = request.user; // set by JwtAuthGuard before this runs return data ? user?.[data] ?? null : user ?? null; }, ); // profile.controller.ts @Controller('profile') @UseGuards(JwtAuthGuard) export class ProfileController { @Get() getMe(@CurrentUser() user: User) { return user; } @Get('email') getEmail(@CurrentUser('email') email: string) { return { email }; } } ``` The `data` argument is whatever you pass inside the decorator call. Without it you get the full user object. Returning `null` instead of `undefined` means TypeScript callers see an honest type instead of a runtime surprise. ### Intermediate: Roles decorator with RolesGuard ```typescript // roles.decorator.ts import { SetMetadata } from '@nestjs/common'; export const ROLES_KEY = 'roles'; export const Roles = (...roles: string[]) => SetMetadata(ROLES_KEY, roles); // roles.guard.ts @Injectable() export class RolesGuard implements CanActivate { constructor(private reflector: Reflector) {} canActivate(context: ExecutionContext): boolean { const requiredRoles = this.reflector.getAllAndOverride<string[]>(ROLES_KEY, [ context.getHandler(), context.getClass(), ]); if (!requiredRoles) return true; const { user } = context.switchToHttp().getRequest(); return requiredRoles.some((role) => user?.roles?.includes(role)); } } // orders.controller.ts @Controller('orders') @UseGuards(JwtAuthGuard, RolesGuard) export class OrdersController { @Post() @Roles('customer', 'admin') // 403 if user.roles has neither create(@Body() dto: CreateOrderDto) { return this.ordersService.create(dto); } @Delete(':id') @Roles('admin') // overrides class-level if one was set remove(@Param('id') id: string) { return this.ordersService.remove(id); } } ``` `getAllAndOverride` checks `getHandler()` first (the method), then `getClass()` (the controller). Whichever is defined closest to the route wins. ### Advanced: Composed Auth decorator ```typescript // auth.decorator.ts import { applyDecorators, UseGuards, SetMetadata } from '@nestjs/common'; import { ApiBearerAuth, ApiUnauthorizedResponse } from '@nestjs/swagger'; import { JwtAuthGuard } from './guards/jwt-auth.guard'; import { RolesGuard } from './guards/roles.guard'; export const ROLES_KEY = 'roles'; export function Auth(...roles: string[]) { return applyDecorators( SetMetadata(ROLES_KEY, roles), UseGuards(JwtAuthGuard, RolesGuard), ApiBearerAuth(), ApiUnauthorizedResponse({ description: 'Unauthorized' }), ); } // users.controller.ts @Controller('users') export class UsersController { @Get() @Auth('admin') findAll() { return this.usersService.findAll(); } @Get('me') @Auth('customer', 'admin') getProfile(@CurrentUser() user: User) { return user; } } ``` Without `@Auth`, each route needs four separate decorators. With it, the controller stays readable and Swagger docs stay accurate on every route. I have seen projects where forgetting `@ApiBearerAuth()` on a new route broke OpenAPI client generation - this pattern makes that impossible.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.