TypeScript Decorators and why you should use them

// 30-08-2022

If you are coming from a Java Background, you are most definitely familiar with the concept of Annotations. They are used to provide metadata to a Class/Method/Property and in a further step even functionality.

JavaScript features a similar concept called Decorators.

Those are currently a part of the ESlatest specification which consists of experimental features which are not implemented by current browsers yet. You can track the progress on the proposal on the [official GitHub repository]

Even though, the current generations of browsers cannot understand Decorators, you can still use them by the magic of TypeScript! Although, this is still only in experimental support in TypeScript as well, don’t be hesitant to use these features, as it is already an industry standard and frameworks like Angular heavily depend on them.

Technical Details

  • Fundamentally, a decorator is just a normal method with specific parameters
  • They can be applied to Classes, Properties, Methods, Accessors and Parameters
  • Multiple decorators can be applied to the same target and will be evaluated one after the other (so order matters)
  • You can also add metadata using an extra node package


To get started, all you need to do is enable the experimentalDecorators flag in your tsconfig.json.

This option is opt-in and not enabled by default, unless you are using a framework like Angular which already enables it by default.

When this flag is enabled you can use decorators all over your project without any further import statements. To show you what you can do with this power, let’s have a look at this small example, which wraps a class method and prints „Hello“ whenever the mentioned method is called:

const log = (
  target: (new () => Test) | Test, // The constructor of the applied class (for static methods) or prototype (for non-static)
  propertyKey: string, // The name of the applied property in the class
  descriptor: PropertyDescriptor // Describes the value of the applied property
) => {
  // Save the given property value
  const original = descriptor.value;

  // Intercept the given property value
  descriptor.value = function () {

  // Return the descriptor with the new property value
  return descriptor;

This decorator can now be applied to any method, like in the following snippet:

class Test {
  public static method() {

// => Hello
//    World

Class vs. Property vs. Method/Accessor vs. Parameter decorators

There are some minor but important differences between the various decorator types which you must know if you want to use them (but don’t worry, you can always look up this information online or locally in the type definitions).

They mainly differ in the parameters passed into the decorator. For example, a class decorator only has access to the given class (the target) while a property decorator also has access to the property key.

To get a quick glance of the 4 different decorator types, I have created this little snippet:

class Test {
  private _property: string = "";

  public method(@parameterDecorator parameter: string) {
    console.log(`${this._property} ${parameter} World!`);

  @methodDecorator // method decorator can be applied to accessor
  set property(value: string) {
    this._property = value;

If you are interested in the exact types of them, I have listed them here as well, but as I already mentioned you can always look them up in your local type definitions.

// Types form the official TypeScript library (
// Every decorator needs to be assignable to one of those 4 types
type ClassDecorator = <T extends Function>(target: T) => T | void;

declare type PropertyDecorator = (
  target: Object,
  propertyKey: string | symbol
) => void;

declare type MethodDecorator = <T>(
  target: Object,
  propertyKey: string | symbol,
  descriptor: TypedPropertyDescriptor<T>
) => TypedPropertyDescriptor<T> | void;

declare type ParameterDecorator = (
  target: Object,
  propertyKey: string | symbol,
  parameterIndex: number
) => void;

const methodDecorator: MethodDecorator = (target, propertyKey, descriptor) => {
  // target = if static method: constructor of applied class
  //          if instance method: prototype of applied class
  // propertyKey = name of the method
  // descriptor = metadata of member (value, accessors, enumerable, ...)

  return descriptor;

const propertyDecorator: PropertyDecorator = (target, propertyKey) => {
  // target = if static property: constructor of applied property,
  //          if instance property: prototype of applied property
  // propertyKey = name of the property

const classDecorator: ClassDecorator = (target) => {
  // target = if static property: constructor of applied property,

const parameterDecorator: ParameterDecorator = (
) => {
  // target = if static method: constructor of applied method parameter,
  //          if instance method: prototype of applied method parameter
  // propertyKey = name of the method parameter
  // parameterIndex = ordinal index of the parameter in the arguments list


As decorators are nothing more than function references, you can easily utilize the factory pattern while leveraging [JavaScripts closures] to create parametrized decorators.

// This is a function that returns a MethodDecorator
// The passed variable "logLevel" is accessible from inside the returned decorator
// thanks to JavaScripts closures
const log = (logLevel: "log" | "warn" | "error"): MethodDecorator => {
  console.log("Init with logLevel:", logLevel);

  return (_, _2, descriptor: PropertyDescriptor) => {
    const originalValue = descriptor.value!;

    descriptor.value = () => {
      console[logLevel]("Method called");

    return descriptor;

class Test {
  public static method() {}

// => Init with logLevel: error
//    Method called
//    Method called


There is one problem with decorators that makes them different to what you might have expected from Java – they don’t provide any metadata to the applied method. That means, you cannot simply read the applied decorators of a property. But there is a possibility to bring back some of those features by enabling another flag in the tsconfig.json – emitDecoratorMetadata.

Once enabled, decorators emit some metadata to every field. Let’s have a look at the compiled output of the last example:

Custom Metadata

But to really leverage the metadata API, you can install the node package [reflect-metadata].

This package works flawlessly with TypeScripts decorator and metadata capabilities and adds the capabilities to add custom metadata which is then readable and even queryable!

Let’s take a look how you can use the package by giving a small example:

import "reflect-metadata"; // You need to import the library once

class Test {
  @Reflect.metadata("magic", 42) // Then you can use the decorators
  static text = "The magic number is: ";

  static doSomething() {
    // Then you can check if a property has some metadata
    // Keep in mind, that design metadata is also queried
    // e.g. "design:type", "design:paramtypes", etc.
    const metadataKeys = Reflect.getOwnMetadataKeys(Test, "text");

    // And then read from that metadata key
    // Note: in this example we directly access [1],
    // because we know there is only a single metadata property specified
    // and the first one is the design:type metadata
    const magicNumber = Reflect.getMetadata(metadataKeys[1], Test, "text");

    console.log(Test.text + magicNumber);

// => The magic number is: 42

There are a few things to keep in mind using this package:

  • Once the decorators API will be part of the ECMAScript standard, the metadata API will be proposed for adoption, potentially making the reflect-metadata package obsolete.
  • There is an object that was added with ES6 sharing a name with this package [Reflect], which has among others the following methods: Reflect.get(…) and Reflect.set(…).
  • They look similar, but are actually used for setting properties on objects, while this package is capable of adding metadata to object properties, methods, and so on.


TypeScript decorators (and hopefully in the near future „JavaScript decorators“), are a great tool for mainly interceptor type functionality, but can also be used for much more with a bit of tinkering.

They are broadly adapted and heavily used by some of the biggest frameworks, like Angular. So if you are working in a TypeScript environment, and there are some use-cases for you (e.g. a custom performance logger or a permissions check for backend methods), don’t be afraid of using them.

In case you want to have a look at, or tinker with the examples from this blog entry, you can clone this entire article, with the examples included (as a node package) from GitHub

// Autor:in