Proposal for ECMAScript enums
March 12, 2026 · View on GitHub
A common and oft-used feature of many languages is the concept of an Enumerated Type, or enum. Enums provide a
finite domain of constant values that are regularly used to indicate choices, discriminants, and bitwise flags. As a
popular and heavily used feature of TypeScript, this proposal seeks the adoption of a compatible form of TypeScript's
enum declaration. Where the syntax or semantics of this proposal differ from that of TypeScript, it is with the full
knowledge of the TypeScript development team and represents either a change that TypeScript is willing to adopt, or
represents a limited subset of functionality that TypeScript expands upon.
NOTE: This proposal has been heavily reworked from its prior incarnation, which can now be found at https://github.com/rbuckton/proposal-enum/tree/old
Status
Stage: 1
Champion: Ron Buckton (@rbuckton)
For more information see the TC39 proposal process.
Authors
- Ron Buckton (@rbuckton)
Motivations
Many ECMAScript hosts and libraries have various ways of distinguishing types or operations via some kind of discriminant:
- ECMAScript:
[Symbol.toStringTag]typeof
- DOM:
Node.prototype.nodeType(Node.ATTRIBUTE_NODE,Node.CDATA_SECTION_NODE, etc.)DOMException.prototype.code(DOMException.ABORT_ERR,DOMException.DATA_CLONE_ERR, etc.)XMLHttpRequest.prototype.readyState(XMLHttpRequest.DONE,XMLHttpRequest.HEADERS_RECEIVED, etc.)CSSRule.prototype.type(CSSRule.CHARSET_RULE,CSSRule.FONT_FACE_RULE, etc.)Animation.prototype.playState("idle","running","paused","finished")ApplicationCache.prototype.status(ApplicationCache.CHECKING,ApplicationCache.DOWNLOADING, etc.)
- NodeJS:
Bufferencodings ("ascii","utf8","base64", etc.)os.platform()("win32","linux","darwin", etc.)"constants"module (ENOENT,EEXIST, etc.;S_IFMT,S_IFREG, etc.)
In addition, with the recent adoption of TypeScript Type Stripping in NodeJS, there has been renewed interest in
ECMA-262 adopting a compatible form of TypeScript's enum declaration as it is one of the most frequently used
TypeScript features not supported in this mode.
An enum declaration provides several advantages over an object literal:
- Closed domain by default — The declaration is non-extensible and enum members would be non-configurable and non-writable.
- Restricted domain of allowed values — Restricts initializers to a subset of allowed JS values (
Number,String,Symbol,BigInt). - Self-references during definition — Referring to prior enum members of the same enum in the initializer of a subsequent enum member.
- Static Typing (tooling) — Static type systems like TypeScript use enum declarations to discriminate types, provide documentation in hovers, etc.
- ADT enums (future) — Potential future support for Algebraic Data Type enums (i.e., "discriminated unions").
- Decorators (future) — Potential future support for
enum-specific Decorators. - Auto-Initializers (future) — Potential future support for auto-initialized enum members.
- "shared" enums (future) — Potential future support for a
shared enumwith restrictions on inputs to align withshared struct
Prior Art
- TypeScript: Enums
- C++: Enumerations
- C#: Enumeration types
- Java: Enum types
Syntax
While a Stage 1 proposal is generally encouraged to avoid specific syntax, it is a stated goal of this proposal to
introduce an enum declaration whose syntax is compatible with TypeScript's. As such, the syntax of this proposal is
restricted to a subset of TypeScript's enum.
// enum declarations
enum Numbers {
zero = 0,
one = 1,
two = 2,
three = 3,
alsoThree = three // self reference
}
enum PlayState {
idle = "idle",
running = "running",
paused = "paused"
}
enum Symbols {
alpha = Symbol("alpha"),
beta = Symbol("beta")
}
enum Named {
Identifier = 0,
"string name" = 1,
}
// Accessing enum values:
let x = Numbers.three;
let y = Named["string name"];
// Iteration, replaces TypeScript enum "Reverse mapping" (formatting,
// debugging, diagnostics, etc):
for (const [key, value] of Numbers) ...
Semantics
While a Stage 1 proposal is generally encouraged to avoid specific semantics, it is a stated goal of this proposal to
introduce an enum declaration whose semantics are compatible with TypeScript's. As such, the semantics of this
proposal are intended to align with that of TypeScript's enum where possible.
Enum Declarations
Enum declarations consist of a finite set of enum members that define the names and values
for each member of the enum. These results are stored as properties of an enum object. An
enum object is an ordinary object whose [[Prototype]] is null. Each enum member defines
a property on the enum object.
In addition, an enum object contains a Symbol.iterator method that yields a [key, value] entry for each declared enum
member, in document order. To explain the semantics of the Symbol.iterator method, an enum object may require an
[[EnumMembers]] internal slot.
Enum Members
Enum members define the set of values that belong to the enum's domain. Each enum member consists of a name and an
initializer that defines the value associated with that name. Enum members are [[Writable]]: false and
[[Configurable]]: false.
Enum Member Names
Enum member names are currently restricted to IdentifierName and StringLiteral, as those are the only member names
currently supported by TypeScript's enum. We may opt to expand this to allow other member names like
ComputedPropertyName in the future, if there is sufficient motivation.
An enum may not have duplicate member names. We may opt to introduce restrictions on member names like constructor if
we deem it necessary to support ADT enums in the future.
If an enum member name shares the same string value as the name of the enum itself, it shadows the enum declaration name within the enum body.
Enum Member Initializers
An enum member's initializer is restricted to a subset of
ECMAScript values (i.e., Number, String, Symbol, and BigInt). This limitation allows us to consider future
support for Algebraic Data Types (ADT) in enums without the possibility of conflicting with something like an enum
member whose value is a function.
An IdentifierReference in an enum member initializer may refer to the name of a prior declaration, and to the enum
itself (much like a class).
API
Aside from the enum declaration itself, there is no other proposed API.
An enum declaration will have a Symbol.iterator method that can be used to iterate over the key/value pairs of the enum's
members.
Desugaring
An enum declaration could potentially be implemented as the following desugaring:
enum E {
A = 1,
B = 2,
C = A | E.B,
}
let E = (() => {
let E = Object.create(null), A, B, C;
Object.defineProperty(E, "A", { value: A = 1 });
Object.defineProperty(E, "B", { value: B = 2 });
Object.defineProperty(E, "C", { value: C = A | E.B });
Object.defineProperty(E, Symbol.iterator, {
value: function* () {
yield ["A", E.A];
yield ["B", E.B];
yield ["C", E.C];
}
});
Object.defineProperty(E, Symbol.toStringTag, { value: "E" });
Object.preventExtensions(E);
return E;
})();
Other Considerations
enum Expressions
While ECMAScript has both statement and expression forms for class and function declarations, this proposal does not
currently support enum expressions. There is no concept of an enum expression in TypeScript, though we may consider
enum expressions for ECMAScript if there is sufficient motivation.
export/export default
An enum declaration would support both export and export default, much like class.
Interaction with Shared Structs
In general, this proposal hopes to align enum member values to those that can be shared with a shared struct, however
it is important to note that a Symbol-valued enum that uses Symbol() and not Symbol.for() would produce values
that cannot be reconciled between two Agents. In addition, ADT enums may need to contain non-shared data, such as in an
Option or Result enum. As such, this proposal may seek to introduce a shared enum declaration that further
restricts allowed inputs.
Differences from TypeScript
There are several differences in the enum declaration for this proposal compared to TypeScript's enum:
- Auto-initializers
- Declaration merging
- Reverse mapping
const enumSymbolvaluesBigIntvaluesexport default
Auto-Initializers
TypeScript's enum supports auto-initialization of enum members:
enum Numbers {
zero, // 0
one, // 1
two // 2
}
However, this behavior is contentious amongst some TC39 delegates and has been removed from this proposal. The main concern that has been raised is that introducing new auto-initialized enum members in the middle of an existing enum has the potential to be a versioning issue in packages, and that such behavior should be harder to reach for, as opposed to being the default behavior. However, even if this capability is not supported, TypeScript will continue to support auto-initialization due to its frequent use within the developer community, but would emit explicit initializers to JavaScript. It is possible that another form of auto-initialization may be introduced in the future that could be utilized by both TypeScript and ECMAScript. For more information, please refer to the Auto-Initializers topic in the Future Directions section.
Declaration Merging
TypeScript (as of v5.8) allows enum declarations to merge with other enum (and namespace) declarations with the
same name. This is not a desirable feature for ECMAScript enums and will not be supported. TypeScript is considering
deprecating this feature in
TypeScript 6.0.
Reverse Mapping
TypeScript currently supports reverse-mapping enum values back to enum member names using E[value], but only for
Number values. This limitation is intended to avoid collisions for String-valued enum members that could potentially
overwrite other members. While this information is invaluable for debugging, diagnostics, formatting, and serialization,
it is far less frequently used compared to enum on the whole.
To avoid this inconsistency, we instead propose using iteration (by way of the Symbol.iterator built-in
symbol) to cover the "reverse mapping" case:
enum E {
A = 0,
B = "A",
}
for (const [key, value] of E) {
console.log(`${key}: ${value}`);
}
// prints:
// A: 0
// B: A
const keyForA = E[Symbol.iterator]().find(([, value]) => value === "A")[0]
console.log(keyForA); // prints: B
If adopted, TypeScript would add support for Symbol.iterator while eventually deprecating existing reverse mapping support.
const enum
TypeScript supports the concept of a const enum declaration, which is similar to a normal enum declaration except
that enum values are inlined into their use sites. Implementations are free to optimize as they see fit, and it's
entirely reasonable that an implementation may eventually support similar inlining on a normal enum declaration. As
the current const enum requires whole program knowlege and a type system, we believe it should remain a
TypeScript-specific capability at this time.
Symbol values
TypeScript does not currently support Symbol values for enums, but would add support if this feature were to be
adopted.
BigInt values
TypeScript does not currently support BigInt values for enums, but would add support if this feature were to be
adopted.
export default
TypeScript does not currently support export default on an enum, but would add support if this feature were to be
adopted.
Future Directions
While this proposal is intended to be rather limited in scope, there are several potential areas for future advancement in the form of follow-on proposals:
Algebraic Data Type (ADT) Enums
Algebraic Data Type (ADT) enums act like a discriminated union of structured types. ADT enum members describe a
constructor function that produces an object with a discriminant property. A future enhancement of an
ECMAScript enum declaration might support ADT enums in conjunction with Extractors and Pattern Matching:
enum Option {
Some(value),
None()
}
const opt = Option.Some(123);
match (opt) {
Option.Some(let value): console.log(value);
Option.None(): console.log("<no value>");
}
enum Result {
Ok(value),
Error(reason)
}
function try_(cb) {
try {
return Result.Ok(_cb());
} catch (e) {
return Result.Error(e);
}
}
const res = try_(() => obj.doWork());
match (res) {
Result.Ok(let value): ...;
Result.Error(let reason): ...;
}
Here, Option.Some might describe a "constructor" function that produces an object discriminated by either a
well-known symbol field or merely by its [[Prototype]], such that Option.Some(0) instanceof Option.Some is
true. ADT enum members could also describe more complex shapes through the use of binding patterns, such as:
enum Geometry {
Point({ x, y }),
Line(p1, p2),
}
const p1 = Geometry.Point({ x: 0, y: 1 });
p1[0].x; // 0
p1[0].y; // 1
const p2 = Geometry.Point({ x: 2, y: 3 });
const l = Geometry.Line(p1, p2);
l[0] === p1; // true
const printGeom = geom => match (geom) {
Geometry.Point({ let x, let y }): console.log(`Point({ x: ${x}, y: ${y} })`);
Geometry.Line(let p1, let p2): console.log(`Line(${printGeom(p1)}, ${printGeom(p2)})`);
};
printGeom(p1); // Point({ x: 0, y: 1 })
printGeom(l); // Line(Point({ x: 0, y: 1 }), Point({ x: 2, y: 3 }))
ADT enum members may also need a mechanism to implement prototypal or static methods on the enum, which is one
reason why we prefer Symbol.iterator to describe the domain of an enum vs. something like Object.entries().
Decorators
In the future we may opt to extend support for Decorators to enum
declarations to support serialization/deserialization, formatting, and FFI scenarios:
@WasmType("u1")
enum Role {
@Alias(["user", "person"], { ignoreCase: true })
user = 1,
@Alias(["admin", "administrator"], { ignoreCase: true })
admin = 2,
}
Auto-Initializers
While this proposal does not support TypeScript's auto-initialization semantics, we may consider introducing an
alternative syntax in a future proposal, such as the of clause described in an
older version of this proposal:
enum Numbers of Number { zero, one, two, three }
Numbers.zero; // 0
enum Colors of String { red, green, blue }
Colors.red; // "red"
Or through some form of statically recognizable syntax:
auto enum Numbers { zero, one, two, three }
Numbers.zero; // 0
History
- April 15th, 2025 &emdash; Proposed for Stage 1 (slides)
- Outcome: Advanced to Stage 1
TODO
The following is a high-level list of tasks to progress through each stage of the TC39 proposal process:
Stage 1 Entrance Criteria
- Identified a "champion" who will advance the addition.
- Prose outlining the problem or need and the general shape of a solution.
- Illustrative examples of usage.
- High-level API.
Stage 2 Entrance Criteria
- Initial specification text.
- Transpiler support (Optional).
Stage 3 Entrance Criteria
- Complete specification text.
- Designated reviewers have signed off on the current spec text.
- The ECMAScript editor has signed off on the current spec text.
Stage 4 Entrance Criteria
- Test262 acceptance tests have been written for mainline usage scenarios and merged.
- Two compatible implementations which pass the acceptance tests: [1], [2].
- A pull request has been sent to tc39/ecma262 with the integrated spec text.
- The ECMAScript editor has signed off on the pull request.