
Custom Decorators
- 2 installs
- 1 repo stars
- Updated January 2, 2026
- ramziddin/ccplugins
Reference for NestJS custom decorators: extracting request data, setting metadata, and composing parameter, method, and class decorators.
About
Explains how to create custom decorators in NestJS to extract request data, attach metadata, and compose reusable abstractions. A developer uses it to reduce controller boilerplate or build domain-specific decorators.
- Covers parameter, method, class, and composed decorators
- Uses built-in parameter decorators as a foundation
Custom Decorators by the numbers
- 2 all-time installs (skills.sh)
- Ranked #3,774 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/ramziddin/ccplugins --skill custom-decoratorsAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 2 |
|---|---|
| repo stars | ★ 1 |
| Last updated | January 2, 2026 |
| Repository | ramziddin/ccplugins ↗ |
What it does
Reference for NestJS custom decorators: extracting request data, setting metadata, and composing parameter, method, and class decorators.
Files
NestJS Custom Decorators
When to Use This Skill
Use this skill when:
- Extracting specific properties from request objects
- Creating shortcuts for common parameter patterns
- Setting metadata for guards, interceptors, or filters
- Composing multiple decorators into one
- Implementing role-based access control
- Creating domain-specific abstractions
- Reducing boilerplate in controllers
- Building reusable decorator libraries
What are Custom Decorators?
Decorators are TypeScript/ES2016 features that allow you to add metadata and modify classes, methods, or parameters. NestJS extensively uses decorators and provides utilities to create your own.
Types of Custom Decorators
1. Parameter decorators - Extract data from request/context 2. Method decorators - Add metadata to route handlers 3. Class decorators - Add metadata to controllers 4. Composed decorators - Combine multiple decorators
Built-in Parameter Decorators
NestJS provides these built-in decorators:
@Request(), @Req() // req
@Response(), @Res() // res
@Next() // next
@Session() // req.session
@Param(key?: string) // req.params / req.params[key]
@Body(key?: string) // req.body / req.body[key]
@Query(key?: string) // req.query / req.query[key]
@Headers(key?: string) // req.headers / req.headers[key]
@Ip() // req.ip
@HostParam() // req.hostsCreating Parameter Decorators
Basic User Decorator
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
export const User = createParamDecorator(
(data: unknown, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
return request.user;
},
);Usage:
@Controller('users')
export class UsersController {
@Get('profile')
getProfile(@User() user: UserEntity) {
return user;
}
}Decorator with Data Parameter
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
export const User = createParamDecorator(
(data: string, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
const user = request.user;
return data ? user?.[data] : user;
},
);Usage:
@Get('profile')
getProfile(@User('id') userId: string) {
return `User ID: ${userId}`;
}
@Get('email')
getEmail(@User('email') email: string) {
return `Email: ${email}`;
}Current User ID Decorator
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
export const CurrentUserId = createParamDecorator(
(data: unknown, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
return request.user?.id;
},
);Usage:
@Get('posts')
getUserPosts(@CurrentUserId() userId: string) {
return this.postsService.findByUser(userId);
}IP Address Decorator
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
export const IpAddress = createParamDecorator(
(data: unknown, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
return request.ip || request.connection.remoteAddress;
},
);Usage:
@Post('login')
login(@IpAddress() ip: string) {
return `Login from IP: ${ip}`;
}Working with Pipes
Custom decorators work seamlessly with pipes:
@Get()
findOne(@User(ValidationPipe) user: UserEntity) {
return user;
}
@Get()
findOne(@User('id', ParseIntPipe) userId: number) {
return userId;
}Setting Metadata with Decorators
Roles Decorator
import { SetMetadata } from '@nestjs/common';
export const ROLES_KEY = 'roles';
export const Roles = (...roles: string[]) => SetMetadata(ROLES_KEY, roles);Usage:
@Controller('cats')
export class CatsController {
@Post()
@Roles('admin')
create(@Body() createCatDto: CreateCatDto) {
return 'Only admins can create cats';
}
}Public Route Decorator
import { SetMetadata } from '@nestjs/common';
export const IS_PUBLIC_KEY = 'isPublic';
export const Public = () => SetMetadata(IS_PUBLIC_KEY, true);Usage:
@Controller('auth')
export class AuthController {
@Public()
@Post('login')
login() {
return 'This route is public';
}
}Permissions Decorator
import { SetMetadata } from '@nestjs/common';
export const PERMISSIONS_KEY = 'permissions';
export const RequirePermissions = (...permissions: string[]) =>
SetMetadata(PERMISSIONS_KEY, permissions);Usage:
@Controller('posts')
export class PostsController {
@Get()
@RequirePermissions('posts:read')
findAll() {
return [];
}
@Delete(':id')
@RequirePermissions('posts:delete', 'posts:admin')
remove(@Param('id') id: string) {
return `Deleted post ${id}`;
}
}Decorator Composition
Combine multiple decorators into a single decorator:
import { applyDecorators } from '@nestjs/common';
import { ApiResponse, ApiOperation } from '@nestjs/swagger';
export function ApiStandardResponse(description: string) {
return applyDecorators(
ApiOperation({ summary: description }),
ApiResponse({ status: 200, description: 'Success' }),
ApiResponse({ status: 400, description: 'Bad Request' }),
ApiResponse({ status: 401, description: 'Unauthorized' }),
ApiResponse({ status: 500, description: 'Internal Server Error' }),
);
}Usage:
@Get()
@ApiStandardResponse('Get all cats')
findAll() {
return [];
}Auth Decorator Composition
import { applyDecorators, UseGuards } from '@nestjs/common';
import { AuthGuard } from './guards/auth.guard';
import { RolesGuard } from './guards/roles.guard';
import { Roles } from './decorators/roles.decorator';
export function Auth(...roles: string[]) {
return applyDecorators(
UseGuards(AuthGuard, RolesGuard),
Roles(...roles),
);
}Usage:
@Controller('admin')
export class AdminController {
@Get('dashboard')
@Auth('admin', 'superadmin')
getDashboard() {
return 'Admin dashboard';
}
}API Documentation Decorator
import { applyDecorators, Type } from '@nestjs/common';
import {
ApiOkResponse,
ApiCreatedResponse,
ApiBadRequestResponse,
ApiNotFoundResponse,
ApiTags,
} from '@nestjs/swagger';
export const ApiController = (tag: string) => {
return applyDecorators(ApiTags(tag));
};
export const ApiCreate = <TModel extends Type<any>>(model: TModel) => {
return applyDecorators(
ApiCreatedResponse({ type: model }),
ApiBadRequestResponse({ description: 'Invalid input' }),
);
};
export const ApiFindOne = <TModel extends Type<any>>(model: TModel) => {
return applyDecorators(
ApiOkResponse({ type: model }),
ApiNotFoundResponse({ description: 'Not found' }),
);
};Usage:
@Controller('cats')
@ApiController('cats')
export class CatsController {
@Post()
@ApiCreate(Cat)
create(@Body() createCatDto: CreateCatDto) {
return this.catsService.create(createCatDto);
}
@Get(':id')
@ApiFindOne(Cat)
findOne(@Param('id') id: string) {
return this.catsService.findOne(id);
}
}Request Headers Decorator
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
export const Headers = createParamDecorator(
(data: string, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
return data ? request.headers[data.toLowerCase()] : request.headers;
},
);Usage:
@Get()
findAll(@Headers('authorization') auth: string) {
return `Auth header: ${auth}`;
}Cookies Decorator
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
export const Cookies = createParamDecorator(
(data: string, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
return data ? request.cookies?.[data] : request.cookies;
},
);Usage:
@Get()
findAll(@Cookies('sessionId') sessionId: string) {
return `Session ID: ${sessionId}`;
}Protocol Decorator
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
export const Protocol = createParamDecorator(
(data: unknown, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
return request.protocol;
},
);Usage:
@Get()
findAll(@Protocol() protocol: string) {
return `Protocol: ${protocol}`;
}Hostname Decorator
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
export const Hostname = createParamDecorator(
(data: unknown, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
return request.hostname;
},
);Usage:
@Get()
findAll(@Hostname() hostname: string) {
return `Hostname: ${hostname}`;
}Request Context Decorator
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
export interface RequestContext {
user: any;
ip: string;
userAgent: string;
timestamp: Date;
}
export const ReqContext = createParamDecorator(
(data: unknown, ctx: ExecutionContext): RequestContext => {
const request = ctx.switchToHttp().getRequest();
return {
user: request.user,
ip: request.ip,
userAgent: request.headers['user-agent'],
timestamp: new Date(),
};
},
);Usage:
@Get()
findAll(@ReqContext() context: RequestContext) {
console.log('Request from:', context.ip, 'at', context.timestamp);
return context.user;
}Pagination Decorator
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
export interface PaginationParams {
page: number;
limit: number;
offset: number;
}
export const Pagination = createParamDecorator(
(data: unknown, ctx: ExecutionContext): PaginationParams => {
const request = ctx.switchToHttp().getRequest();
const page = parseInt(request.query.page) || 1;
const limit = parseInt(request.query.limit) || 10;
return {
page,
limit,
offset: (page - 1) * limit,
};
},
);Usage:
@Get()
findAll(@Pagination() pagination: PaginationParams) {
return this.catsService.findAll(pagination.limit, pagination.offset);
}Combining Parameter Decorators
@Get('profile')
getProfile(
@User('id') userId: string,
@IpAddress() ip: string,
@Headers('user-agent') userAgent: string,
) {
return {
userId,
ip,
userAgent,
};
}Timeout Decorator
import { applyDecorators, UseInterceptors } from '@nestjs/common';
import { TimeoutInterceptor } from './interceptors/timeout.interceptor';
export function Timeout(milliseconds: number) {
return applyDecorators(UseInterceptors(new TimeoutInterceptor(milliseconds)));
}Usage:
@Get()
@Timeout(5000)
findAll() {
return this.catsService.findAll();
}Cache Decorator
import { applyDecorators, UseInterceptors } from '@nestjs/common';
import { CacheInterceptor, CacheTTL } from '@nestjs/cache-manager';
export function Cache(ttl?: number) {
const decorators = [UseInterceptors(CacheInterceptor)];
if (ttl) {
decorators.push(CacheTTL(ttl));
}
return applyDecorators(...decorators);
}Usage:
@Get()
@Cache(60000) // Cache for 60 seconds
findAll() {
return this.catsService.findAll();
}Complete Example
// decorators/user.decorator.ts
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
export const User = createParamDecorator(
(data: string, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
const user = request.user;
return data ? user?.[data] : user;
},
);
// decorators/roles.decorator.ts
import { SetMetadata } from '@nestjs/common';
export const ROLES_KEY = 'roles';
export const Roles = (...roles: string[]) => SetMetadata(ROLES_KEY, roles);
// decorators/public.decorator.ts
import { SetMetadata } from '@nestjs/common';
export const IS_PUBLIC_KEY = 'isPublic';
export const Public = () => SetMetadata(IS_PUBLIC_KEY, true);
// decorators/auth.decorator.ts
import { applyDecorators, UseGuards } from '@nestjs/common';
import { AuthGuard } from '../guards/auth.guard';
import { RolesGuard } from '../guards/roles.guard';
import { Roles } from './roles.decorator';
export function Auth(...roles: string[]) {
return applyDecorators(
UseGuards(AuthGuard, RolesGuard),
Roles(...roles),
);
}
// decorators/api-response.decorator.ts
import { applyDecorators, Type } from '@nestjs/common';
import {
ApiOkResponse,
ApiCreatedResponse,
ApiBadRequestResponse,
ApiUnauthorizedResponse,
ApiNotFoundResponse,
} from '@nestjs/swagger';
export function ApiStandardResponses() {
return applyDecorators(
ApiBadRequestResponse({ description: 'Bad Request' }),
ApiUnauthorizedResponse({ description: 'Unauthorized' }),
);
}
export function ApiCreateResponse<TModel extends Type<any>>(model: TModel) {
return applyDecorators(
ApiStandardResponses(),
ApiCreatedResponse({ type: model }),
);
}
export function ApiFindOneResponse<TModel extends Type<any>>(model: TModel) {
return applyDecorators(
ApiStandardResponses(),
ApiOkResponse({ type: model }),
ApiNotFoundResponse({ description: 'Not Found' }),
);
}
// Usage in controller
import { Controller, Get, Post, Body, Param } from '@nestjs/common';
import { ApiTags } from '@nestjs/swagger';
import { User } from './decorators/user.decorator';
import { Public } from './decorators/public.decorator';
import { Auth } from './decorators/auth.decorator';
import { ApiCreateResponse, ApiFindOneResponse } from './decorators/api-response.decorator';
import { Cat } from './entities/cat.entity';
import { CreateCatDto } from './dto/create-cat.dto';
@Controller('cats')
@ApiTags('cats')
export class CatsController {
@Get()
@Public()
findAll() {
return this.catsService.findAll();
}
@Get(':id')
@ApiFindOneResponse(Cat)
findOne(@Param('id') id: string) {
return this.catsService.findOne(id);
}
@Post()
@Auth('admin')
@ApiCreateResponse(Cat)
create(
@Body() createCatDto: CreateCatDto,
@User('id') userId: string,
) {
return this.catsService.create(createCatDto, userId);
}
@Get('my-cats')
@Auth('user')
getMyCats(@User('id') userId: string) {
return this.catsService.findByUser(userId);
}
}Best Practices
1. Keep decorators simple - Each decorator should have a single purpose 2. Use descriptive names - Make decorator purpose clear from the name 3. Document decorators - Provide JSDoc comments explaining usage 4. Type decorators properly - Use TypeScript generics for type safety 5. Compose when appropriate - Combine related decorators 6. Extract reusable logic - Create decorators for common patterns 7. Use metadata constants - Export metadata keys for consistency 8. Validate decorator inputs - Check parameters before setting metadata 9. Consider backwards compatibility - Don't break existing decorator usage 10. Test decorators - Unit test custom decorator behavior
Decorator Naming Conventions
- Parameter decorators: Nouns (User, IpAddress, Pagination)
- Metadata decorators: Verbs or adjectives (Roles, Public, RequirePermissions)
- Composed decorators: Descriptive combinations (Auth, ApiStandardResponse)
TypeScript Decorator Types
// Parameter decorator
function paramDecorator(
target: Object,
propertyKey: string | symbol,
parameterIndex: number
): void {}
// Method decorator
function methodDecorator(
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
): PropertyDescriptor {}
// Class decorator
function classDecorator<T extends { new(...args: any[]): {} }>(
constructor: T
): T {}
// Property decorator
function propertyDecorator(
target: Object,
propertyKey: string | symbol
): void {}Advanced Patterns
Conditional Decorator
export function ConditionalAuth(condition: boolean) {
if (condition) {
return Auth('admin');
}
return () => {};
}Dynamic Metadata
export function DynamicRoles(getRoles: () => string[]) {
const roles = getRoles();
return Roles(...roles);
}Common Use Cases
1. Authentication - Extract user from request 2. Authorization - Set roles and permissions 3. API Documentation - Compose Swagger decorators 4. Request Context - Extract IP, user agent, headers 5. Pagination - Parse page and limit parameters 6. Logging - Add metadata for logging interceptors 7. Caching - Control cache behavior 8. Rate Limiting - Set rate limit metadata
Custom decorators are powerful tools for creating clean, reusable, and maintainable NestJS applications. They help reduce boilerplate and create domain-specific abstractions that make your code more expressive and easier to understand.