Back to Blog
Advanced2026-04-05

TC39 Decorators in 2026: The Definitive Guide

A complete guide to the TC39 Stage 3 decorators proposal with practical examples for classes, methods, and fields.

Decorators Have Arrived

After years of proposals and revisions, TC39 decorators have reached Stage 3 and are shipping in modern JavaScript engines. Unlike the legacy TypeScript experimental decorators, the new standard decorators have a clean, well-defined API. If you've used decorators in TypeScript or Babel, the new syntax will feel familiar but the semantics are different.

Decorator Basics

A decorator is a function that receives the decorated value and a context object, and optionally returns a replacement value.

``javascript // Method decorator function logged(originalMethod, context) { const methodName = context.name;

function replacementMethod(...args) { console.log(\Calling \${methodName} with\, args); const result = originalMethod.call(this, ...args); console.log(\\${methodName} returned\, result); return result; }

return replacementMethod; }

class Calculator { @logged add(a, b) { return a + b; } }

const calc = new Calculator(); calc.add(2, 3); // "Calling add with [2, 3]" // "add returned 5" `

Field Decorators

Field decorators use the \context.access\ object and an initializer to transform field values.

`javascript function clamp(min, max) { return function (value, context) { return function (initialValue) { return Math.min(max, Math.max(min, initialValue)); }; }; }

function readonly(value, context) { context.addInitializer(function () { Object.defineProperty(this, context.name, { writable: false, value: this[context.name] }); }); }

class Config { @clamp(0, 100) volume = 150; // Will be clamped to 100

@readonly version = '1.0.0'; }

const config = new Config(); console.log(config.volume); // 100 config.version = '2.0.0'; // TypeError in strict mode `

Class Decorators

Class decorators can wrap or replace the entire class.

`javascript function singleton(Class, context) { let instance;

return class extends Class { constructor(...args) { if (instance) return instance; super(...args); instance = this; } }; }

@singleton class Database { constructor(url) { this.url = url; console.log('Creating database connection'); } }

const db1 = new Database('postgres://localhost'); const db2 = new Database('postgres://remote'); console.log(db1 === db2); // true `

Composing Decorators

Decorators compose naturally — they apply bottom-up (closest to the target first).

`javascript class UserService { @logged @cached(60_000) @rateLimit(100) async getUser(id) { return await fetch(\/api/users/\${id}\).then(r => r.json()); } } ``

The execution order is: rateLimit wraps first, then cached wraps that, then logged wraps the outermost layer. TC39 decorators are a game-changer for writing clean, reusable cross-cutting concerns in JavaScript.