Metadata

The metadata module provides a low-level API for storing and retrieving metadata on classes, methods, and parameters using the reflect-metadata library. This system forms the foundation for decorators like @register, @inject, @onConstruct, and @onDispose.

Metadata is data about your code that can be attached to classes, methods, and parameters at design time. The TypeScript compiler with reflect-metadata enables reading and writing this metadata at runtime. The metadata system allows you to:

This library provides a simplified, functional API over reflect-metadata that supports accumulation patterns through mapper functions.

To use the metadata system, you need:

TypeScript
// At application entry point
import 'reflect-metadata';

// In tsconfig.json
{
"compilerOptions": {
  "experimentalDecorators": true,
  "emitDecoratorMetadata": true
}
}
// At application entry point
import 'reflect-metadata';

// In tsconfig.json
{
"compilerOptions": {
  "experimentalDecorators": true,
  "emitDecoratorMetadata": true
}
}

The metadata module exports six functions organized in pairs:

Class metadata is stored on the class constructor itself. Use this to attach configuration, tags, or registration information to classes.

setClassMetadata

Creates a class decorator that stores metadata using a mapper function. The mapper receives the previous value (if any) and returns the new value.

setClassMetadata<T>(
  key: string | symbol,
  mapFn: (prev: T | undefined) => T
): ClassDecorator

getClassMetadata

Retrieves metadata from a class constructor.

getClassMetadata<T>(
  target: object,
  key: string | symbol
): T | undefined

Example

TypeScript
const TAGS_KEY = 'tags';

@setClassMetadata(TAGS_KEY, (prev: string[] = []) => [...prev, 'service'])
@setClassMetadata(TAGS_KEY, (prev: string[] = []) => [...prev, 'api'])
class ApiService {}

const tags = getClassMetadata<string[]>(ApiService, TAGS_KEY);
console.log(tags); // ['api', 'service']
const TAGS_KEY = 'tags';

@setClassMetadata(TAGS_KEY, (prev: string[] = []) => [...prev, 'service'])
@setClassMetadata(TAGS_KEY, (prev: string[] = []) => [...prev, 'api'])
class ApiService {}

const tags = getClassMetadata<string[]>(ApiService, TAGS_KEY);
console.log(tags); // ['api', 'service']

Parameter metadata is stored as an array indexed by parameter position. This is crucial for constructor and method parameter injection.

setParameterMetadata

Creates a parameter decorator that stores metadata for a specific parameter. The mapper receives the previous value for that parameter index.

setParameterMetadata(
  key: string | symbol,
  mapFn: (prev: unknown) => unknown
): ParameterDecorator

getParameterMetadata

Retrieves parameter metadata as an array. Undecorated parameters will have undefined at their index.

getParameterMetadata(
  key: string | symbol,
  target: constructor<unknown>
): unknown[]

Example

TypeScript
const INJECT_KEY = 'inject:constructor';

class DatabaseService {
constructor(
  @setParameterMetadata(INJECT_KEY, () => 'config') config: any,
  @setParameterMetadata(INJECT_KEY, () => 'logger') logger: any,
) {}
}

const metadata = getParameterMetadata(INJECT_KEY, DatabaseService);
console.log(metadata); // ['config', 'logger']
const INJECT_KEY = 'inject:constructor';

class DatabaseService {
constructor(
  @setParameterMetadata(INJECT_KEY, () => 'config') config: any,
  @setParameterMetadata(INJECT_KEY, () => 'logger') logger: any,
) {}
}

const metadata = getParameterMetadata(INJECT_KEY, DatabaseService);
console.log(metadata); // ['config', 'logger']

Method metadata is stored per method on the class. Use this for hooks, validators, middleware, or any method-level configuration.

setMethodMetadata

Creates a method decorator that stores metadata for a specific method. The mapper receives the previous value for that method.

setMethodMetadata<T>(
  key: string,
  mapFn: (prev: T | undefined) => T
): MethodDecorator

getMethodMetadata

Retrieves metadata for a specific method.

getMethodMetadata(
  key: string,
  target: object,
  propertyKey: string
): unknown

Example

TypeScript
const MIDDLEWARE_KEY = 'middleware';

class Controller {
@setMethodMetadata(MIDDLEWARE_KEY, (prev: string[] = []) => [...prev, 'auth'])
@setMethodMetadata(MIDDLEWARE_KEY, (prev: string[] = []) => [...prev, 'validate'])
handleRequest() {}
}

const controller = new Controller();
const middleware = getMethodMetadata(MIDDLEWARE_KEY, controller, 'handleRequest');
console.log(middleware); // ['validate', 'auth']
const MIDDLEWARE_KEY = 'middleware';

class Controller {
@setMethodMetadata(MIDDLEWARE_KEY, (prev: string[] = []) => [...prev, 'auth'])
@setMethodMetadata(MIDDLEWARE_KEY, (prev: string[] = []) => [...prev, 'validate'])
handleRequest() {}
}

const controller = new Controller();
const middleware = getMethodMetadata(MIDDLEWARE_KEY, controller, 'handleRequest');
console.log(middleware); // ['validate', 'auth']

The metadata system is built on top of reflect-metadata:

The metadata system powers the core decorators:

If you want to avoid metadata, consider: