This TypeScript error occurs when you use parameter decorators without enabling the emitDecoratorMetadata compiler option. Parameter decorators rely on metadata reflection to function properly, which requires both experimentalDecorators and emitDecoratorMetadata to be enabled in tsconfig.json.
Parameter decorators are a TypeScript feature that allows you to attach metadata to function or method parameters. They are commonly used in dependency injection frameworks like NestJS, Angular, and InversifyJS to automatically resolve dependencies at runtime. When TypeScript compiles code with parameter decorators, it needs to emit type metadata that describes the parameter types. This metadata is stored using the reflect-metadata library and accessed at runtime. Without the emitDecoratorMetadata option enabled, TypeScript cannot emit this critical type information, causing the compiler to reject parameter decorators entirely. This error is distinct from general decorator errors because parameter decorators specifically require metadata reflection. While class, method, and property decorators can work without emitDecoratorMetadata, parameter decorators cannot function without knowing the runtime types of parameters—information only available through metadata emission. The error commonly appears in dependency injection scenarios where frameworks need to know what types to inject into constructor or method parameters.
Open your tsconfig.json file and ensure both experimentalDecorators and emitDecoratorMetadata are enabled:
{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"target": "ES2020",
"module": "commonjs"
}
}Both flags are required:
- experimentalDecorators: Enables decorator syntax
- emitDecoratorMetadata: Emits type metadata for parameters
Save the file and restart your TypeScript server in your IDE (VS Code: Cmd+Shift+P → "TypeScript: Restart TS Server").
Parameter decorators require the reflect-metadata polyfill to store and retrieve type information at runtime.
Install it as a dependency (not devDependency):
npm install reflect-metadata
# or
yarn add reflect-metadata
# or
pnpm add reflect-metadataThen import it at the very top of your application entry file (usually main.ts, index.ts, or app.ts):
// main.ts - FIRST import, before anything else
import "reflect-metadata";
import { NestFactory } from "@nestjs/core";
import { AppModule } from "./app.module";
async function bootstrap() {
const app = await NestFactory.create(AppModule);
await app.listen(3000);
}
bootstrap();This polyfill must be loaded before any decorators are evaluated.
Ensure TypeScript is actually reading your tsconfig.json file:
# Check which config file TypeScript is using
npx tsc --showConfigCommon issues:
Wrong directory: Make sure you're running tsc from the directory containing tsconfig.json
Extends chain broken: If you use extends, verify the base config exists:
{
"extends": "./tsconfig.base.json", // Must exist
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}Multiple configs in monorepo: Ensure the correct tsconfig is being used for your package:
# Compile with specific config
npx tsc -p packages/api/tsconfig.jsonTypeScript 5.0 introduced standard ECMAScript decorators, which are NOT compatible with parameter decorators or emitDecoratorMetadata.
If you're using TypeScript 5.0+, you MUST use legacy decorators (experimentalDecorators) for parameter decorators to work:
{
"compilerOptions": {
"experimentalDecorators": true, // REQUIRED for parameter decorators
"emitDecoratorMetadata": true,
"target": "ES2022"
}
}Important: Standard decorators (without experimentalDecorators) do NOT support:
- Parameter decorators at all
- emitDecoratorMetadata
- Design-time type reflection
If you need parameter decorators (for dependency injection), you must use legacy decorators.
Ensure your build tools respect tsconfig.json decorator settings:
For ts-node:
// tsconfig.json
{
"ts-node": {
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}
}Or use command line flags:
ts-node --experimental-decorators --emit-decorator-metadata src/main.tsFor webpack with ts-loader:
// webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.tsx?$/,
loader: "ts-loader",
options: {
compilerOptions: {
experimentalDecorators: true,
emitDecoratorMetadata: true,
},
},
},
],
},
};For NestJS: The default NestJS tsconfig already includes these settings. If you customized it, restore them.
If you're using isolatedModules: true (common with Babel, esbuild, or SWC), you may encounter limitations with decorators.
Check your configuration:
{
"compilerOptions": {
"isolatedModules": true, // Can cause issues
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}Workaround options:
1. Use tsc for type checking, fast bundler for builds:
// tsconfig.build.json (for esbuild/swc)
{
"extends": "./tsconfig.json",
"compilerOptions": {
"isolatedModules": false // Only for type checking
}
}2. Use SWC with decorator support:
// .swcrc
{
"jsc": {
"parser": {
"syntax": "typescript",
"decorators": true
},
"transform": {
"legacyDecorator": true,
"decoratorMetadata": true
}
}
}Run type checking separately: npx tsc --noEmit
### How emitDecoratorMetadata Works
When enabled, TypeScript emits three types of metadata for decorated elements:
class UserService {
constructor(@Inject() private db: DatabaseService) {}
}
// TypeScript emits (conceptually):
// design:type - the type of the decorated element
// design:paramtypes - parameter types (for constructors/methods)
// design:returntype - return type (for methods)For the constructor above, TypeScript emits:
__metadata("design:paramtypes", [DatabaseService])The dependency injection framework reads this at runtime to know to inject a DatabaseService instance.
### NestJS and Dependency Injection
NestJS heavily relies on parameter decorators for dependency injection:
import "reflect-metadata";
import { Injectable, Controller, Get, Param } from "@nestjs/common";
@Injectable()
export class UsersService {
findOne(id: string) {
return { id, name: "John" };
}
}
@Controller("users")
export class UsersController {
// Parameter decorator @Param requires emitDecoratorMetadata
constructor(private usersService: UsersService) {}
@Get(":id")
findOne(@Param("id") id: string) {
return this.usersService.findOne(id);
}
}Without emitDecoratorMetadata, NestJS cannot determine that UsersService should be injected into the constructor.
### Standard Decorators vs Legacy Decorators
TypeScript 5.0 introduced standard ECMAScript decorators, but they have critical limitations:
| Feature | Legacy (experimentalDecorators) | Standard (TS 5.0+) |
|---------|--------------------------------|---------------------|
| Parameter decorators | ✅ Supported | ❌ Not supported |
| emitDecoratorMetadata | ✅ Supported | ❌ Not supported |
| Type reflection | ✅ Available | ❌ Not available |
| Future standard | ⚠️ May change | ✅ ES standard |
For dependency injection frameworks, you MUST use legacy decorators.
### Monorepo Considerations
In monorepos with multiple packages, ensure each package that uses parameter decorators has the correct tsconfig:
monorepo/
├── packages/
│ ├── api/
│ │ └── tsconfig.json ← Must have flags
│ ├── shared/
│ │ └── tsconfig.json ← Must have flags
│ └── web/
│ └── tsconfig.json ← May not need them
└── tsconfig.base.json ← Can set here tooBase config approach:
// tsconfig.base.json
{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}
// packages/api/tsconfig.json
{
"extends": "../../tsconfig.base.json"
}### Performance Implications
emitDecoratorMetadata adds metadata to your compiled JavaScript, slightly increasing bundle size. For most applications this is negligible, but if bundle size is critical:
1. Use tree-shaking to remove unused metadata
2. Only enable in packages that need it
3. Consider alternative DI patterns that don't require decorators
### Debugging Metadata
To verify metadata is being emitted, inspect compiled JavaScript:
npx tsc --outFile output.js src/main.ts
cat output.js | grep "design:paramtypes"You should see lines like:
__metadata("design:paramtypes", [DatabaseService, LoggerService])If these are missing, metadata emission failed.
Function expression requires a return type
Function expression requires a return type
Value of type 'string | undefined' is not iterable
How to fix "Value is not iterable" in TypeScript
Type 'undefined' is not assignable to type 'string'
How to fix "Type undefined is not assignable to type string" in TypeScript
Type narrowing from typeof check produces 'never'
How to fix "Type narrowing produces never" in TypeScript
Type parameter 'T' has conflicting constraints
How to fix "Type parameter has conflicting constraints" in TypeScript