stackademic

The leading education platform for anyone with an interest in software development.

TypeScript Decorators

Annotate and extend classes, methods, and fields with reusable decorators

Overview

Decorators are functions that attach reusable behavior or metadata to classes and their members. TypeScript 5 implements the ECMAScript Stage 3 decorator proposal, where a decorator receives the target and a context object and may return a replacement. They are widely used in frameworks for dependency injection, routing, and validation.

Syntax / Usage

A class-method decorator receives the original method and a ClassMethodDecoratorContext, returning a new function that wraps it.

function logged<This, Args extends any[], Return>(
  target: (this: This, ...args: Args) => Return,
  context: ClassMethodDecoratorContext<This>
) {
  const name = String(context.name)
  return function (this: This, ...args: Args): Return {
    console.log(`calling ${name}`)
    return target.apply(this, args)
  }
}

class Service {
  @logged
  fetch(id: number) {
    return { id }
  }
}

Examples

A class decorator can seal the constructor and its prototype.

function sealed(target: Function, _context: ClassDecoratorContext) {
  Object.seal(target)
  Object.seal(target.prototype)
}

@sealed
class Registry {}

Use context.addInitializer from a field decorator to bind a method.

function bound<This, V extends (...args: any[]) => any>(
  _target: undefined,
  context: ClassFieldDecoratorContext<This, V>
) {
  context.addInitializer(function (this: This) {
    const self = this as Record<string, unknown>
    self[context.name as string] = (self[context.name as string] as V).bind(this)
  })
  return undefined
}

An auto-accessor decorator can validate assignments through set.

function positive(_target: ClassAccessorDecoratorTarget<unknown, number>, _ctx: ClassAccessorDecoratorContext) {
  return {
    set(this: unknown, value: number) {
      if (value < 0) throw new RangeError("must be positive")
    },
  }
}

Common Mistakes

  • Mixing the legacy experimentalDecorators API with the Stage 3 signatures
  • Assuming decorators run per call—they run once at class definition time
  • Forgetting to return the wrapped method, so the original is not replaced
  • Reading context.name as a string without String(...), since it may be a symbol
  • Relying on parameter decorators, which are not part of the Stage 3 standard

See Also

typescript-declaration-files typescript-mapped-types interfaces