Library / SDK
microsoft/reflect-metadata avatar
microsoft/reflect-metadata

reflect-metadata: the polyfill behind TypeScript's legacy decorator metadata

Prototype for a Metadata Reflection API for ECMAScript

3,361 stars191 forksTypeScriptApache-2.0

At a glance

What is it?
reflect-metadata is a polyfill for the abandoned Metadata Reflection API proposal, kept alive for projects that still compile with TypeScript's --experimentalDecorators option. This article covers what it actually does, how to install and use it, and where it stops being the right choice.
Who is it for?
Adopt reflect-metadata if you are on TypeScript's legacy --experimentalDecorators path and a framework such as NestJS or TypeORM already depends on it. Do not adopt it for new code that can use standard Stage 3 decorators, and do not expect the API it implements to be standardized, because the README states it no longer is.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 37 days ago.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 30, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What reflect-metadata solves, and who it is still for

Decorators let you attach behaviour to a class as it is defined. Reading that attached information back at runtime is a separate problem, and JavaScript has no built-in API for it. reflect-metadata fills that gap: it patches the global Reflect object with defineMetadata, getMetadata, hasMetadata, getOwnMetadata, getMetadataKeys and deleteMetadata, so a decorator can write a value and later code can read it without agreeing on a private property name.

The README is explicit about the project's status. Both the Decorators proposal and the Decorator Metadata proposal reached Stage 3 in TC39, and the README states that the API proposed by this package is no longer being considered for standardization. The package continues so that projects using TypeScript's legacy --experimentalDecorators option keep working, because some of them cannot migrate to standard decorators.

That single sentence defines the audience. If you are starting a new project with standard decorators, this is not your library. If you maintain a codebase where a dependency injects constructor parameter types at runtime, you are probably already using it without having chosen it.

How the metadata store works under the hood

The README describes the intended semantics rather than the shipped implementation, but the shape is clear. An object carries a [[Metadata]] internal property holding a Map whose keys are property keys, with undefined standing in for the class itself, and whose values are Maps of metadata keys to metadata values. Class-level metadata lands in C.[[Metadata]] under undefined. Static member metadata lands in C.[[Metadata]] under the member's property key. Instance member metadata lands in C.prototype.[[Metadata]] under the member's property key.

That split is the part worth internalising. Metadata on an instance member is stored on the prototype, not on each instance, so a decorator that writes during construction is writing to a shared object. Because the store is a Map keyed by arbitrary values, metadata keys are usually Symbols such as Symbol.for("design:paramtypes"), which is how TypeScript's emitted decorator helpers name the parameter type list.

The lookup methods come in two flavours. getMetadata and hasMetadata walk the prototype chain; getOwnMetadata and hasOwnMetadata do not. The same pairing exists for key enumeration via getMetadataKeys and getOwnMetadataKeys. Choosing the wrong one is a common source of surprising inheritance behaviour, and the README does not offer guidance on which to prefer.

Installing reflect-metadata and reading your first metadata value

The README gives a single install command. There are no peer dependencies and the package.json lists an empty dependencies object, so nothing else is pulled in.

bash
npm install reflect-metadata

Importing the main entry point modifies the global Reflect object, or defines one in ES5 runtimes, and the README notes it contains internal polyfills for Map, Set and WeakMap. The /lite entry point does the same global patch but omits those polyfills and requires runtime support for "exports" in package.json.

ts
import "reflect-metadata";

With the side-effect import in place, you can define and read a value imperatively. The README shows defineMetadata with a property key as the fourth argument, and getMetadata reading it back through an instance.

ts
class C {
  method() {}
}

Reflect.defineMetadata("myKey", "myValue", C.prototype, "method");

const obj = new C();
const value = Reflect.getMetadata("myKey", obj, "method");
console.log(value); // "myValue"

The lookup succeeds on obj even though the metadata was written to C.prototype, because getMetadata walks the prototype chain. Swapping it for getOwnMetadata on obj would return undefined. That distinction is the first thing to check when metadata appears to vanish.

There is also a declarative form, where the library supplies its own decorator. The README shows Reflect.metadata applied to a class and to a method.

ts
@Reflect.metadata("myKey", "myValue")
class C {
  @Reflect.metadata("myKey", "myValue")
  method() {}
}

For browser use without a bundler, the README points at script tags loading Reflect.js for the full build or ReflectLite.js for the version without polyfills, and at standalone.d.ts as a reference path for editor types.

The prototype-chain trap and other real limitations

The most common failure is silent. getMetadata returns undefined, nothing throws, and the bug surfaces later as a missing dependency injection or a wrong type. The cause is usually one of three things: the side-effect import was dropped by a bundler that treats reflect-metadata as unused, the metadata was written to the prototype but read with an own-metadata call, or two copies of the package ended up in the tree and each patched a different Reflect.

The README does not document rollback, and it does not describe what happens when the package is loaded twice. The no-conflict entry point exists, exported as reflect-metadata/no-conflict with its own type declarations, but the README excerpt does not explain its semantics, so the behaviour has to be read from the source.

The design is also global by construction. Patching Reflect affects every library in the process, including ones that never asked for it. In a browser page shared with third-party scripts, that is a real surface. The lite build removes the Map, Set and WeakMap polyfills but keeps the global patch.

Finally, the semantics section describes internal methods that a Proxy can override to support additional traps. That is a statement about the proposal, not a guarantee about the polyfill's behaviour on every engine, and the README does not claim otherwise.

reflect-metadata vs ts-morph, and the property-based alternative

The README's own alternatives section argues against a separate API. The suggested approach is to hang metadata off properties directly, using Symbols such as Symbol.for("design:paramtypes") and Symbol.for("design:properties") on the target, with a hasOwnProperty check before extending an existing list. The README concedes the obvious downside: it is a lot of code. The upside is no global patch and no dependency, at the cost of every consumer agreeing on the same Symbol names.

ts-morph is a different kind of tool, and the comparison is worth stating precisely because search results often pair them. ts-morph works on source code through the TypeScript compiler API: it parses and transforms files at build time. reflect-metadata works on live objects at runtime. If you need to know a parameter's type while the program is running, a build-time transform does not answer the question unless you also emit the answer into the output, which is exactly the job reflect-metadata's store performs.

Standard Stage 3 decorators are the other direction. They are the path the README points away from this package toward, and a project that can adopt them does not need a runtime metadata store for the same purpose.

Maintenance, licensing and the upgrade calculus

The repository is not archived, and the last push was on 2026-08-24. The most recent release listed is v0.2.1 from 2023-12-14, while package.json in the repository declares version 0.2.2, so the published line trails the working tree. The project is not abandoned, but the release cadence is slow and the README frames the work as support for existing users rather than new standardisation effort.

Upgrade cost is low in the ordinary case. There are no runtime dependencies, so a version bump does not cascade. The risk sits in the entry points: the exports map defines ".", "./lite", "./no-conflict", "./Reflect" and "./Reflect.js". Moving a project from the default import to /lite changes which polyfills are present, which matters on older runtimes. The /lite and /no-conflict entries both require runtime support for "exports" in package.json, so a toolchain that ignores that field will fail to resolve them.

Licensing is Apache-2.0, with AUTHORS.md and CopyrightNotice.txt at the repository root. Apache-2.0 includes an express patent grant and requires that notices be preserved when redistributing. Because the package patches a global object at import time, it is typically shipped inside a bundle rather than as a separate artifact, and the notice obligations travel with it. This is a description of the licence text, not legal advice; check your own distribution requirements.

Editorial conclusion

Adopt reflect-metadata if you are on TypeScript's legacy --experimentalDecorators path and a framework such as NestJS or TypeORM already depends on it. Do not adopt it for new code that can use standard Stage 3 decorators, and do not expect the API it implements to be standardized, because the README states it no longer is. Before committing, verify which entry point your bundler resolves (reflect-metadata, reflect-metadata/lite or the no-conflict build), and confirm that the global Reflect object is not already patched by another copy in your dependency tree.

Frequently asked questions

What is reflect-metadata used for?

It patches the global Reflect object with methods such as defineMetadata and getMetadata so decorators can attach values to a class or its members and other code can read them back at runtime. The README names dependency injection, runtime type assertions and reflection among the use cases.

What does reflect-metadata do to the global Reflect object?

Importing the package modifies the global Reflect object, or defines one on ES5 runtimes, adding defineMetadata, getMetadata, hasMetadata, getOwnMetadata, getMetadataKeys and deleteMetadata. The README notes the main entry point also contains internal polyfills for Map, Set and WeakMap, while reflect-metadata/lite does not.

What is reflect-metadata?

It is a polyfill for a Metadata Reflection API proposed for ECMAScript, published as the reflect-metadata package under Apache-2.0. The README states that the proposed API is no longer being considered for standardization, and that the package continues to support projects using TypeScript's legacy --experimentalDecorators option.

Is there an alternative to reflect-metadata?

The README's own alternatives section suggests storing metadata on properties directly, using Symbols such as Symbol.for("design:paramtypes"), and acknowledges that this takes a lot of code. Standard Stage 3 decorators are the other route, since the README says the API this package implements is no longer being standardized.

How is reflect-metadata different from ts-morph?

ts-morph works on source code through the TypeScript compiler API at build time, while reflect-metadata reads and writes metadata on live objects at runtime. A build-time transform cannot answer a runtime question about a parameter's type unless the answer is also emitted into the output.

Official sources

  1. License: Apache-2.0
  2. microsoft/reflect-metadata on GitHub
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/microsoft-reflect-metadata.svg)](https://hysenlabs.com/projects/microsoft-reflect-metadata)