Open-source project
tc39/proposal-decorators avatar
tc39/proposal-decorators

tc39/proposal-decorators: What Stage 2.7 Means for Class Decorators in JavaScript

Decorators for ES6 classes

2,976 stars117 forksUnknownLicense varies

At a glance

What is it?
The TC39 decorators proposal replaces the old static decorators design with plain functions that can only swap a value for one with matching semantics. Here is what the spec text actually commits to, and where it still leaves you exposed.
Who is it for?
Adopt it if you already write decorated classes through a transpiler and want your source to match where the language is heading; the proposal's plain-function model and the accessor keyword are what the README commits to. Do not adopt it if you need a shipped runtime today, because the repository is a specification with no build, no package and no release, and the README itself calls the proposal a work in progress.
Can I use it commercially?
Not without permission. GitHub finds no licence file in the repository, and without a licence all rights are reserved by default: you may read the code but not reuse it. Check the README, or ask the authors, before using it.
Is it still maintained?
Yes. The repository last received commits 99 days ago.
What is it written in?
GitHub does not report a main language for this repository.

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

The gap in JavaScript metaprogramming that decorators fill

JavaScript already supports the decorator pattern for functions. The README shows a logResult helper that wraps a function, returns a new function, and logs the result on each call. That works because closures let you wrap anything callable. The same trick does not extend to class fields. The README walks through the manual version: declare a private #x, write a getter and a setter that log, and repeat for every property you care about. The alternative it shows is calling Object.defineProperty on the prototype after the class is defined, which breaks as soon as the class uses a field, because the field overwrites the prototype accessor.

That is the concrete problem the proposal targets. The audience is not application developers looking for a convenience syntax. It is people who maintain decorator libraries, transpiler plugins and framework code, plus the runtime implementers who have to analyze decorated classes statically. The README states that decorators are widely adopted among developers in transpiler environments, which is the honest framing: the syntax already exists in practice, and the proposal is about making the standardized version something implementers can reason about.

Replace, access, initialize: the three capabilities the proposal grants

A decorator in this proposal is a function called on a class, a class element, or another JavaScript syntax form during definition. The README lists three capabilities. A decorator can replace the decorated value with a matching value that has the same semantics. It can provide access to the decorated value through accessor functions that it may share. It can initialize the value, running additional code after the value is fully defined; for a class member, that initialization happens once per instance.

The word matching carries the design weight. The README is explicit that this proposal differs from earlier iterations, where a decorator could swap the decorated value for a completely different type. Here a method can only become a method, a field only a field, a class only a class. Two stated goals follow from that restriction: decorators stay easy to write because they are plain functions, and decorated values stay analyzable because they do not silently change type. That second goal is aimed at runtimes as much as at developers.

The decoratable surface is limited and enumerable: classes, public, private and static fields, public, private and static methods, and public, private and static accessors. Nothing else. If your mental model comes from a framework that decorates parameters or arbitrary expressions, this proposal does not cover that.

The accessor keyword is the part most teams will actually touch

The proposal introduces one new class element: the auto accessor, written by putting the accessor keyword in front of a class field. The README gives the shape as a class Example with a decorator applied to accessor myBool = false. Unlike a plain field, an auto accessor has a real getter and setter, with the value stored on a private slot equivalent to a private class field.

The README is candid about why this element is in the proposal at all. It states that auto accessors can be used independently and have semantics separate from decorator usage, and that they are included primarily because decorator use cases require their semantics, given that a decorator can only replace an element with a corresponding element of the same semantics. Those use cases are described as common in the existing decorators ecosystem. Read that as an admission: the language is adding a class element to make a decorator pattern expressible, not because the element was independently demanded. That is a defensible trade, but it does mean the feature set is shaped by library authors rather than by ordinary class definitions.

There is nothing to install, and that is the first thing to understand

The repository contains a README, an EXTENSIONS.md file, a meetings directory and a .gitignore. There is no package manifest, no build script, no test runner and no release. The README does not give installation steps, and it should not: this is a proposal document, not a library. The README states plainly that it describes the current decorators proposal and that the proposal is a work in progress.

What you can do is read the specification text and run the syntax through a transpiler that implements a decorators transform. The README's own example is the best starting point for reading, because it shows a decorator applied to a class and to an auto accessor at once:

js
@defineElement("my-class")
class C extends HTMLElement {
  @reactive accessor clicked = false;
}

Nothing in that snippet runs on its own. defineElement and reactive are decorator functions the surrounding code must supply; the proposal defines how they are called and what they may return, not what they do. If you want to verify behavior, the README points to the commit history of the repository for earlier iterations of the proposal, and the homepage field points at an ecma262 comparison page for a pull request. Those are the places to check what changed between versions, because the README does not maintain a changelog.

The limitation that matters: replacement is constrained, and the README says so

The same-semantics rule is the proposal's central constraint, and it is also the reason some existing decorator code will not port. A decorator that used to turn a method into a property, or attach an unrelated value to a class, cannot do that here. The README frames this as a fix, arguing that previous iterations let decorators change decorated values unpredictably and add unrelated values, which hurt both static analysis and developer expectations.

That is a real improvement in predictability and a real loss in expressiveness, and the README does not pretend otherwise. It also concedes the general case against the feature, noting that decorators are a powerful metaprogramming feature that can simplify code but can also feel magical by hiding details from the user, and that like all abstractions they can sometimes become more trouble than they are worth. That sentence is the honest boundary. If your team's problem is that class behavior is hard to trace, adding decorators moves in the wrong direction.

The second limitation is status. Stage 2.7 is not finished. The README calls the document a work in progress and directs readers to the commit history for earlier iterations. Until the proposal advances, any syntax you write depends on a transpiler's interpretation of a moving document, and the semantics of an edge case can change between drafts. The repository's last push was on 2026-06-23, so the document is not frozen.

How this differs from TypeScript experimental decorators

The closest thing most teams already use is the TypeScript experimental decorators implementation, which predates this proposal and was built around an earlier design. The difference is in what a decorator receives and what it may return. In this proposal, a decorator is a plain function and can only replace a value with one of matching semantics, and it reaches the decorated value through accessor functions plus an initialization hook that runs once per instance for class members. The older model allowed the decorated value to become a different kind of value, which is exactly the behavior the README says this proposal was written to remove.

The practical consequence is portability. Code written against the older model may rely on type-changing replacement, and that code has no equivalent here. Code written for this proposal may not be accepted by a toolchain still implementing the earlier design. Because the README provides no migration guide and no compatibility table, the only reliable way to know where a given toolchain sits is to check its own documentation against the capabilities listed in this proposal: replace with matching semantics, access via accessor functions, initialize after definition.

Licence, maintenance and what upgrading costs you

The repository metadata does not state a licence, and there is no LICENSE file among the top-level entries, which are .gitignore, EXTENSIONS.md, README.md and meetings. Do not assume a permissive grant that is not written down; if you intend to copy specification text into your own project, treat the absence of a licence file as an open question to resolve rather than a formality. This is not legal advice, just a reading of what the repository does and does not contain.

Maintenance here means editorial maintenance of a specification, not software releases. There are no releases, so there is no version to pin and no changelog to read. The last push was on 2026-06-23. Upgrades are therefore conceptual: when the proposal text changes, your decorator implementations may need to change with it, and the README's only pointer for tracking that is the repository's commit history. EXTENSIONS.md exists at the top level and is not described in the README, so anyone relying on it should read it directly rather than assume it is part of the core proposal.

Editorial conclusion

Adopt it if you already write decorated classes through a transpiler and want your source to match where the language is heading; the proposal's plain-function model and the accessor keyword are what the README commits to. Do not adopt it if you need a shipped runtime today, because the repository is a specification with no build, no package and no release, and the README itself calls the proposal a work in progress. Before wiring anything into a codebase, check the Stage 2.7 status line and the commit history of the repository, since the README points there for previous iterations and that history is the only record of what changed.

Frequently asked questions

Is tc39/proposal-decorators a library I can install?

No. The repository holds a proposal document plus EXTENSIONS.md, a meetings directory and a .gitignore, with no package manifest, build script or release. The README describes the current decorators proposal and calls it a work in progress.

What does the accessor keyword do in the tc39/proposal-decorators proposal?

It defines an auto accessor, a class field that has a real getter and setter instead of defaulting to a private storage slot, as shown by the README's example of a decorator applied to accessor myBool = false. The README states auto accessors can be used independently and have semantics separate from decorator usage.

Can a tc39/proposal-decorators decorator change a method into a different kind of value?

No. A decorator can only replace the decorated value with a matching value that has the same semantics, so a method can only become a method and a class only a class. The README says this restriction is deliberate, to keep decorated values statically analyzable and to avoid non-local effects.

Which class elements can be decorated under tc39/proposal-decorators?

Classes, public, private and static fields, public, private and static methods, public, private and static accessors, and the new auto accessor element. The README lists these as the complete set of decoratable values.

Official sources

  1. Issues
  2. Project website
  3. README
  4. tc39/proposal-decorators on GitHub
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/tc39-proposal-decorators.svg)](https://hysenlabs.com/projects/tc39-proposal-decorators)