This TypeScript configuration error occurs when you enable emitDecoratorMetadata in tsconfig.json without also enabling experimentalDecorators. Both options must be enabled together because decorator metadata depends on the experimental decorator feature.
The "'emitDecoratorMetadata' requires 'experimentalDecorators' to be enabled" error appears during TypeScript compilation when your tsconfig.json has "emitDecoratorMetadata": true but lacks "experimentalDecorators": true. These two compiler options are interdependent. The emitDecoratorMetadata option enables TypeScript to emit runtime type metadata for decorated declarations when using the reflect-metadata library. This metadata includes design-time type information like parameter types, return types, and property types. However, this feature only makes sense when decorators are enabled, which is why experimentalDecorators must also be set to true. This error is commonly encountered when working with frameworks like NestJS, TypeORM, Angular, or any library that relies on dependency injection or runtime reflection. These frameworks use decorators extensively and need both the decorator syntax (experimentalDecorators) and type metadata (emitDecoratorMetadata) to function properly.
Open your tsconfig.json file and add "experimentalDecorators": true alongside "emitDecoratorMetadata": true:
{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"target": "ES2020",
"module": "commonjs",
"lib": ["ES2020"],
"outDir": "./dist",
"rootDir": "./src",
"strict": true
}
}Both options must be set to true for frameworks that use decorator metadata. Save the file and the error should disappear.
After updating tsconfig.json, verify the changes by running the TypeScript compiler:
# Check for compilation errors
npx tsc --noEmit
# Or run your build command
npm run buildFor NestJS projects:
# Start the development server
npm run start:devFor TypeORM projects:
# Run TypeORM CLI commands
npx typeorm migration:runIf the error persists, ensure there are no syntax errors in your tsconfig.json (missing commas, quotes, etc.).
IDEs cache TypeScript configuration, so restart the language server to pick up changes:
VS Code:
1. Press Cmd/Ctrl + Shift + P
2. Type "TypeScript: Restart TS Server"
3. Press Enter
WebStorm/IntelliJ:
1. File → Invalidate Caches → Invalidate and Restart
Command line (if using ts-node):
# Kill any running ts-node processes
killall ts-node
# Restart your application
npx ts-node src/index.tsAfter restarting, the IDE should recognize the updated configuration and clear any red underlines.
If you're working in a monorepo or project with multiple tsconfig files, ensure all relevant configurations include both options:
# Find all tsconfig files
find . -name "tsconfig*.json" -not -path "*/node_modules/*"Project structure example:
my-monorepo/
├── tsconfig.base.json # Base config
├── apps/
│ ├── api/
│ │ └── tsconfig.json # Extends base
│ └── web/
│ └── tsconfig.json
└── packages/
└── shared/
└── tsconfig.jsontsconfig.base.json:
{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"strict": true
}
}apps/api/tsconfig.json:
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src"
}
}This ensures all packages inherit the decorator settings.
If you're using emitDecoratorMetadata, you'll need the reflect-metadata package for runtime reflection:
npm install reflect-metadataThen import it at the top of your main entry file (before any decorators are used):
For NestJS (src/main.ts):
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();For TypeORM (src/index.ts):
import "reflect-metadata";
import { createConnection } from "typeorm";
import { User } from "./entity/User";
createConnection().then(async connection => {
// Your code here
});Without reflect-metadata, decorator metadata won't be available at runtime, even though TypeScript will compile successfully.
### Legacy vs. TC39 Decorators
TypeScript currently supports two decorator proposals:
Legacy Decorators (experimentalDecorators: true):
- The original proposal, widely used in frameworks like NestJS, TypeORM, Angular
- Supports emitDecoratorMetadata for runtime type reflection
- Parameter decorators work as expected
- Will remain supported for backward compatibility
TC39 Decorators (the new standard, default in TypeScript 5.0+):
- Stage 3 ECMAScript proposal, closer to becoming a standard
- Does NOT support emitDecoratorMetadata
- Parameter decorators not supported
- Different syntax and capabilities
Most backend frameworks still require legacy decorators:
{
"compilerOptions": {
"experimentalDecorators": true, // Use legacy decorators
"emitDecoratorMetadata": true // Emit type metadata
}
}If you disable experimentalDecorators, you'll use TC39 decorators, which are incompatible with NestJS/TypeORM's current versions.
### Why Metadata Emission Matters
The emitDecoratorMetadata option makes TypeScript emit three special metadata keys for each decorated declaration:
1. design:type - The type of the property/parameter
2. design:paramtypes - Parameter types of a method/constructor
3. design:returntype - Return type of a method
Example:
import "reflect-metadata";
class UserService {
constructor(private db: Database) {}
}
// TypeScript emits this metadata:
Reflect.getMetadata("design:paramtypes", UserService);
// Returns: [Database]Dependency injection frameworks use this to automatically resolve constructor dependencies without manual wiring.
### Framework-Specific Requirements
NestJS:
- Requires both experimentalDecorators and emitDecoratorMetadata
- Uses metadata for dependency injection, parameter validation, and route handling
- Error will appear immediately if either option is missing
TypeORM:
- Uses metadata to map TypeScript classes to database tables
- Column types are inferred from TypeScript types via metadata
- Without emitDecoratorMetadata, you must specify types manually:
// Without metadata - manual type specification
@Column({ type: "varchar", length: 255 })
name: string;
// With metadata - automatic inference
@Column()
name: string; // TypeORM knows this is varcharAngular:
- Requires experimentalDecorators for dependency injection
- Doesn't strictly require emitDecoratorMetadata but benefits from it
- Uses InjectionToken when metadata isn't available
### Performance Considerations
Enabling emitDecoratorMetadata increases the size of compiled JavaScript output because type information is embedded as metadata. For large applications, this can add 10-20% to bundle size.
If you don't need runtime reflection, consider:
1. Using manual dependency injection without decorators
2. Using type-only imports: import type { User } from "./types"
3. Tree-shaking to remove unused metadata
### Migration Path for New Projects
If starting a new project, consider:
Stick with legacy decorators if:
- Using NestJS, TypeORM, or similar frameworks
- Need runtime type reflection
- Require parameter decorators
Use TC39 decorators if:
- Building a framework-agnostic library
- Want future-proof code aligned with ECMAScript
- Don't need emitDecoratorMetadata
You cannot use both simultaneously - it's an either/or choice per tsconfig.json.
### Debugging Configuration Issues
To verify your exact TypeScript configuration:
# Show effective configuration
npx tsc --showConfig
# Check which files are included
npx tsc --listFiles | head -20This shows the merged configuration from all extends and helps identify if decorator settings are properly applied.
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