The decorators proposal is at stage 2.7 and only replaces like with like
Decorators for ES6 classes
At a glance
- What is it?
- This is TC39's rewrite of a feature that transpilers have shipped for years. Its central rule is that a decorator may only replace a value with one of matching semantics, which is what lets an engine analyse decorated code without guessing, and which is also the reason the proposal had to introduce auto accessors, because a class field would otherwise overwrite the accessor you wrote.
- Who is it for?
- Read this proposal if you maintain a transpiler, a framework that relies on the older decorator semantics, or a static analysis tool, because the rule that decorators must replace like with like changes what those tools can assume. Three things to check before you plan around it.
- 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 144 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 November 13, 2026, and from our analysis. They are not legal advice.
Star history
- tc39/proposal-decorators2,974 ★ · -2
| Repository | 2026-09-24 | 2026-11-13 |
|---|---|---|
| tc39/proposal-decorators | 3k | 3k |
-2 Stars in the last 50 days, measured from the daily GitHub snapshot. Compare with other projects
Editorial analysis
Stage 2.7, and five years of earlier drafts in the history
The page opens with a stage number and then a paragraph of history.
The stage is 2.7, and the paragraph says that decorators are a proposal for extending JavaScript classes which is widely adopted among developers in transpiler environments, with broad interest in standardization. It adds that TC39 has been iterating on decorators proposals for over five years, and that this document describes a new proposal based on elements from all past proposals.
Then comes the sentence that tells you how to treat the page. It describes the current proposal as a work in progress, and says previous iterations should be found in the commit history of this repository.
So the repository is a design document rather than an implementation, and its history is part of its contents. A proposal that has been rewritten five times is a proposal where the syntax you have read about in blog posts may not be the syntax on this page.
The homepage link is worth noting for the same reason: it points at a third-party tool for comparing the specification text against the existing language specification, with a proposal number in the query string, rather than at a specification document of its own.
Three capabilities, and the third one runs once per instance
The definition is short: decorators are functions called on classes, class elements, or other JavaScript syntax forms during definition. The example shows a decorator on a class and a decorator on an accessor inside it:
@defineElement("my-class")
class C extends HTMLElement {
@reactive accessor clicked = false;
}Three capabilities are then listed, and they are the whole model.
The first is replacement: a decorator can replace the value being decorated with a matching value that has the same semantics, so a method with another method, a field with another field, a class with another class.
The second is access: a decorator can get at the value it decorates through accessor functions, and can then choose to share those.
The third is initialization: a decorator can initialise the value, running extra code after the value is fully defined, and when the value is a class member that initialisation happens once per instance.
The last clause is the one that decides a lot of design questions. Anything a decorator does on initialisation is per instance rather than per class, which is what makes the third capability useful for reactive state and useless for one-time setup.
The same-semantics rule is what makes decorators analysable
The rule that a replacement must match the original semantics is not a limitation the page apologises for. It is presented as the answer to a difference from earlier proposals, where decorators could replace a value with a completely different type of value.
Two design goals are named for it.
The first is that it should be easy both to use decorators and to write your own. Earlier iterations, and the static decorators proposal in particular, are described as complicated for authors and implementers. In this proposal decorators are plain functions, accessible and easy to write.
The second is that decorators should affect the thing they decorate and avoid confusing or non-local effects. Earlier proposals could change a decorated value in unpredictable ways and add completely new values that were unrelated to it, and that was a problem in two directions. For runtimes, it meant decorated values could not be analysed statically. For developers, it meant a value could turn into a completely different type with no indicator to the user.
So the constraint buys static analysability at the cost of expressiveness, and the page's own framing is that decorators metaprogram and add functionality to a value without fundamentally changing its external behaviour.
That is also why the syntax surface is as narrow as it is: four kinds of thing, each in three visibilities.
Four kinds of thing can be decorated, in three visibilities each
The list of what a decorator may be applied to is deliberately short. Classes. Class fields, public, private and static. Class methods, public, private and static. And class accessors, public, private and static.
So there are four categories and, counting the visibilities, ten positions in a class body. Anything outside that list is not decoratable under this proposal.
Two of the categories are worth pausing on. Private fields and private methods are in the list, which means decorators work on the private surface as well as the public one. And accessors are in the list as their own category, separately from fields, which is a consequence of the same-semantics rule: a field and an accessor do not have the same semantics, so a decorator on a field may not return an accessor and vice versa.
That distinction is the reason the proposal also adds new syntax, which is the subject of the next section. The category list and the new element type are not independent decisions; the second exists because the first is a rule.
Auto accessors were added because class fields overwrite the prototype
The proposal introduces one new kind of class element, and its own explanation of why is the most concrete argument on the page.
Class auto accessors are defined by applying the accessor keyword to a class field. Unlike a field, an auto accessor has a getter and a setter; a plain field defaults to getting and setting a value on a private storage slot, equivalent to a private class field.
The stated reason for inclusion is the same-semantics rule. Because a decorator can only replace an element with a corresponding element of the same semantics, any decorator use case that needs getter-and-setter semantics needs a target that has them. The page says those use cases are common in the existing decorators ecosystem, which is how the argument closes: not that auto accessors are elegant, but that a set of decorators people already use cannot be expressed without them.
It also says the new element can be used independently, with its own semantics separate from decorators. So the proposal is shipping a language feature that has a life outside the feature it is bundled with, which is unusual and is worth keeping in mind when reading the stage number.
You can already do this to functions, and the pattern is the point
The motivation section makes an argument that is worth following in order, because it starts from something you can already do.
Here is a function that wraps another function and logs the result:
function logResult(fn) {
return function(...args) {
let result;
try {
result = fn.call(this, ...args);
console.log(result);
} catch (e) {
console.error(e);
throw e;
}
return result;
}
}
const plusOne = logResult((x) => x + 1);
plusOne(1); // 2The page calls this the decorator pattern implemented in plain JavaScript, notes that in other languages such as Python decorators are syntactic sugar for exactly this shape, and points out that in JavaScript today you can already apply it to functions, just without the at-sign syntax.
Then it makes the argument that matters for a language feature. The pattern is declarative, and that is important: there is no step between the definition of a function and its decoration, so it is not possible for someone to accidentally use the undecorated version, which would cause major bugs and be very difficult to debug.
That property is what the at-sign buys, and it is the same property the proposal wants to extend to objects and classes.
For a field, the old workaround works until the field overwrites it
The gap the proposal fills is demonstrated with a class that has one field, and the demonstration is in three steps of increasing convenience.
First, the manual version. To log every read and write of a field you write a getter and a setter over a private field, and the page shows it in full, with the console calls in both directions.
Then the honest admission: doing that a lot would be a pain, because you would be adding all those accessors everywhere.
Then the helper. A function that takes a class and a property name and defines a getter and setter on the prototype for you, followed by a call passing the class and the field name. And then the problem, which is where the page stops mid-sentence.
What it says is that this works, but that if the class uses a class field, the field would overwrite the getter and setter defined on the prototype, so something has to be moved. That ordering hazard is the entire reason auto accessors exist, and it is a good example of the proposal's method: find a case where the existing language cannot express what decorators need, and change the language rather than the decorator.
It is also why the new element is a keyword rather than a convention. There is no way to mark a class field as accessor-shaped without new syntax.
Extensions and meeting notes are the two other things in the tree
The repository has four entries at the top level: an ignore file, the readme, a document called extensions, and a directory of meetings.
The meetings directory is the ordinary furniture of a specification proposal, since a proposal advances in a committee and the discussion is not in the code.
The extensions document is the more interesting one, and its name is plural and unspecific. A decorators proposal necessarily generates questions about how it interacts with the rest of the language, and the extensions file is where those interactions would be recorded. What it currently contains is not described anywhere on the page, so a reader looking for how decorators interact with, say, class inheritance or private state has to open a second document that the readme never mentions.
Combined with the homepage pointing at a comparison tool rather than a specification, the pattern is consistent: the readme is the design narrative, and the normative detail lives in files a reader has to find for themselves. For a proposal at stage 2.7, where the syntax is still expected to move, that is a reasonable arrangement. For anyone writing tooling against it, it means the readme is a starting point and not a specification.
Editorial conclusion
Read this proposal if you maintain a transpiler, a framework that relies on the older decorator semantics, or a static analysis tool, because the rule that decorators must replace like with like changes what those tools can assume. Three things to check before you plan around it. The stage is 2.7 and the page calls itself a work in progress with earlier iterations in the commit history, so the syntax described here is not what your transpiler emits today. Note that the repository's own homepage is a third-party spec comparison tool rather than a specification document. And note what the proposal adds, since auto accessors are new syntax shipped inside a decorators proposal, which means the two features land or fall together.
Frequently asked questions
What stage is the TC39 decorators proposal at?
The page states stage 2.7. It also says TC39 has been iterating on decorators proposals for over five years, that this document is based on elements from all past proposals, and that the page describes a work in progress whose earlier iterations are in the commit history.
What can a decorator do in this proposal?
Three things: replace the decorated value with a matching value of the same semantics, get access to it through accessor functions which it may share, and initialise it by running extra code after the value is fully defined, which for a class member happens once per instance.
Why must a decorator replace a value with the same semantics?
For two stated goals. Decorators stay easy to write, since earlier iterations such as the static decorators proposal were complicated for authors and implementers. And effects stay local, because earlier proposals changed values in unpredictable ways and added unrelated ones, which meant runtimes could not analyse decorated values statically and developers had no indication a value had become a different type.
What is a class auto accessor?
A new class element created by applying the accessor keyword to a class field. Unlike a plain field it has a getter and a setter. It can be used independently of decorators, and it is included because decorators can only replace an element with one of the same semantics and existing decorator use cases need those semantics.
Which syntax can decorators be applied to?
Classes; class fields; class methods; and class accessors, each of the last three in public, private and static form. Fields, methods and accessors are separate categories because a decorator may not replace one with another, since they do not have matching semantics.
Why can decorators not be written today in plain JavaScript?
For functions they can, by wrapping them, and that pattern is declarative so there is no gap between definition and decoration. The gap is objects and classes: logging every access to a field means a manual getter and setter over a private field, or a helper that defines them on the prototype, which a class field then overwrites.
Official sources
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.
[](https://hysenlabs.com/projects/tc39-proposal-decorators)Star history chart for your README
[](https://hysenlabs.com/projects/tc39-proposal-decorators)Was this useful?