Looking back at the TypeScript 6.0 Beta announcement from February 11, 2026, most of the coverage focused on the compiler backend story: this is the last TypeScript release built on the JavaScript codebase, the Go rewrite is coming, performance will improve dramatically. That framing is accurate, but it skips the change that will cause the most actual migration work for real codebases. The removal of experimentalDecorators and emitDecoratorMetadata is the breaking change that lands on your team’s sprint board.
How a Draft Spec Became Load-Bearing Infrastructure
TypeScript added decorator support in version 1.5, in 2015. The specification it implemented was a draft, actively changing, and clearly labeled experimental with the aptly named experimentalDecorators flag. That flag should have been a signal to avoid building critical infrastructure on top of it. Instead, it became the foundation for several of the most widely used TypeScript frameworks.
Angular’s entire component model uses @Component, @Injectable, and @NgModule. NestJS routes HTTP requests through @Controller, @Get, and @Post. TypeORM maps database columns through @Entity and @Column. The class-validator and class-transformer libraries, which power validation in thousands of Node.js APIs, use @IsEmail, @IsNotEmpty, and @Transform. All of these depend on the pre-standard decorator behavior that TypeScript 6.0 removes.
The companion flag, emitDecoratorMetadata, made things more complicated. When enabled, the TypeScript compiler injects Reflect.metadata calls into the emitted JavaScript, encoding type information that TypeScript knows at compile time but JavaScript does not. NestJS and Angular’s dependency injection systems read these design:paramtypes entries at runtime to figure out what to inject into constructors without requiring explicit configuration:
// With emitDecoratorMetadata enabled, TypeScript emits
// __metadata("design:paramtypes", [UserRepository])
// for each decorated constructor parameter.
class UserService {
constructor(private repo: UserRepository) {}
}
This was convenient. It was also a compile-time coupling between TypeScript’s type system and runtime behavior that the TC39 decorator proposal explicitly declined to standardize.
What the Standard Decorator System Actually Provides
The TC39 decorator proposal went through multiple complete redesigns before reaching Stage 3 in 2022. TypeScript 5.0 shipped the standard version, which is architecturally different from the experimental one in ways that matter for migration.
The old model had decorators execute immediately at class definition time, receiving a descriptor object or target directly. The new model has decorators return initializer functions, declare context through typed context objects, and schedule side effects through addInitializer. Here is what the difference looks like in practice:
// Old: experimentalDecorators
function deprecated(
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
const original = descriptor.value;
descriptor.value = function (...args: any[]) {
console.warn(`${propertyKey} is deprecated`);
return original.apply(this, args);
};
return descriptor;
}
// New: TC39 standard (TypeScript 5.0+)
function deprecated(
target: Function,
context: ClassMethodDecoratorContext
) {
return function (this: unknown, ...args: unknown[]) {
console.warn(`${String(context.name)} is deprecated`);
return (target as Function).apply(this, args);
};
}
The new version returns a replacement function rather than mutating a descriptor. The context object carries the method name, kind, whether it is static or private, and the addInitializer hook for setup work. This is cleaner, but the difference is significant enough that existing decorator implementations need rewriting rather than just type annotation updates.
The Metadata Problem Is the Hard Part
The harder migration is anything that depended on emitDecoratorMetadata. TypeScript 5.2 introduced decorator metadata via Symbol.metadata as the standard replacement:
function injectable(target: any, context: ClassDecoratorContext) {
context.metadata['injectable'] = true;
}
@injectable
class UserService {
constructor(private repo: UserRepository) {}
}
console.log(UserService[Symbol.metadata]); // { injectable: true }
The critical gap is that Symbol.metadata does not automatically carry type information. The old emitDecoratorMetadata mechanism encoded design:type, design:paramtypes, and design:returntype by reading the TypeScript AST at compile time. That information does not exist in the standard, because the TC39 proposal deliberately excluded runtime type reflection as too tightly coupled to TypeScript’s static type system.
Frameworks have responded differently to this gap. NestJS 10 added support for the TC39 decorator model and offers migration paths that make constructor injection work without emitDecoratorMetadata. Angular migrated its dependency injection system in version 16 with the Ivy compiler, and Ivy-based Angular projects largely do not need emitDecoratorMetadata for their first-party code. TypeORM and the class-validator stack are harder: they encode field-level type information through design:type, and migrating that requires either explicit type registration or adopting a different approach to schema definition.
Module Resolution Gets a Final Answer
Alongside the decorator changes, TypeScript 6.0 deprecates the legacy "moduleResolution": "node" setting. This option predates ESM, does not understand the exports field in package.json, and uses implicit extension searching that produces ambiguous results in modern module graphs.
For Node.js projects, the migration target is "node16" or "nodenext". These modes require explicit .js extensions on relative imports:
// Works with "moduleResolution": "node":
import { something } from './util';
// Required with "node16" or "nodenext":
import { something } from './util.js';
The .js extension on a .ts file is counterintuitive but reflects how Node.js’s ESM resolver actually works at runtime. TypeScript resolves ./util.js to ./util.ts during compilation. This is not a TypeScript invention; it is alignment with the runtime’s actual module resolution semantics. Projects that have been avoiding this migration because of the ergonomic friction now have a concrete forcing function.
How This Connects to the Go Rewrite
The removals in TypeScript 6.0 share a common thread: they eliminate features that require emit-time transformation rather than simple type erasure. The experimental decorator system required the compiler to generate Reflect.metadata calls and descriptor manipulation code. The ES3 and ES5 targets required complex downleveling of modern syntax. The legacy module resolution required special-cased implicit extension searching.
The Go compiler, which Microsoft announced in March 2025 and has been developing as microsoft/typescript-go, is designed around a model where TypeScript is erased rather than transformed. A compiler that strips type annotations has a dramatically simpler architecture than one that rewrites class syntax and emits polyfill code. TypeScript 6.0 moves the JavaScript compiler to that simpler model so the Go port can faithfully replicate it.
The other architectural connection is isolatedDeclarations, introduced in TypeScript 5.5 and elevated in 6.0 to a first-class signal in the language server and project references infrastructure. The flag requires explicit type annotations on exported declarations so that .d.ts files can be generated per-file without cross-file type inference:
// Without isolatedDeclarations: fine, return type is inferred
export function add(a: number, b: number) {
return a + b;
}
// With isolatedDeclarations: must be explicit
export function add(a: number, b: number): number {
return a + b;
}
This constraint is what enables the Go compiler to process files in parallel. When each file can produce its .d.ts without a global resolution pass, the type checker can work on all files simultaneously. The 10x build improvement cited in Microsoft’s benchmarks depends substantially on this parallelism, which depends on isolatedDeclarations being enabled.
What the Migration Actually Requires
For new TypeScript 6.0 projects, the surface is small: use TC39 decorator syntax, annotate exports explicitly, and use node16 module resolution. For existing projects with deep NestJS or TypeORM usage, the work scales with how much code relies on emitDecoratorMetadata.
NestJS projects running 10+ are in the best position, since the framework explicitly supports both decorator models during the migration window. Angular projects on Ivy (Angular 16+) mostly need to audit third-party libraries rather than first-party code. TypeORM projects should review the framework’s own migration documentation, as column type detection from design:type metadata is central to how it maps schemas.
The beta is available via:
npm install -D typescript@beta
The TypeScript 6.0 Beta is, as the announcement notes, the last version built on the JavaScript codebase. The Go rewrite coming in TypeScript 7 will be the dramatic release. TypeScript 6.0 is the release that ensures there is a clean foundation for it to replicate, by retiring a decade of experimental features that accumulated before there were better alternatives.