An enum gives a set of named constants one type. Numeric enums count up from 0 and also map numbers back to names; string enums need a literal per member and have no reverse mapping; const enums are inlined and vanish. Unlike most TypeScript, regular enums emit real JavaScript, so many teams prefer as const objects.
| Style | Emits JavaScript? | Reverse mapping | Accepts a raw literal? |
|---|---|---|---|
| Numeric enum | Yes, an object | Yes, number to name | Numbers that match a member |
| String enum | Yes, an object | No | No, must use Enum.Member |
| const enum | No, values inlined | No | As its enum kind |
| as const object | Yes, a plain object | No (write one if needed) | Yes |
| Union of literals | No, erased | No | Yes |
Updated
Type member names into the generator and switch between styles. It writes the TypeScript declaration and the JavaScript the compiler would output, so you can see the cost of each choice before it lands in your bundle.
enum Status { Draft, Review, Live }var Status;
(function (Status) {
Status[Status["Draft"] = 0] = "Draft";
Status[Status["Review"] = 1] = "Review";
Status[Status["Live"] = 2] = "Live";
})(Status || (Status = {}));Mid and High continue counting from Low. The output object maps both ways.
enum Level { Low = 1, Mid, High }
// tsc output
var Level;
(function (Level) {
Level[Level["Low"] = 1] = "Low";
Level[Level["Mid"] = 2] = "Mid";
Level[Level["High"] = 3] = "High";
})(Level || (Level = {}));Each member is assigned once, with no reverse entry.
enum Theme { Light = "light", Dark = "dark" }
// tsc output
var Theme;
(function (Theme) {
Theme["Light"] = "light";
Theme["Dark"] = "dark";
})(Theme || (Theme = {}));The declaration disappears and each use becomes its value.
const enum Size { Small, Large }
const s = Size.Large;
// tsc output
const s = 1 /* Size.Large */;Works with type strippers, iterates with Object.values, and accepts the raw string.
const Role = {
Admin: "admin",
Editor: "editor",
Viewer: "viewer",
} as const;
type Role = (typeof Role)[keyof typeof Role]; // "admin" | "editor" | "viewer"
function canPublish(role: Role) {
return role !== Role.Viewer;
}
canPublish("editor"); // fine
canPublish(Role.Admin); // fine
Object.values(Role); // ["admin", "editor", "viewer"]Erased entirely; the compiler still rejects any other string.
type Size = "sm" | "md" | "lg";
function padding(size: Size) {
return size === "sm" ? 4 : size === "md" ? 8 : 12;
}With this option, an enum declaration is a compile error.
{
"compilerOptions": {
"erasableSyntaxOnly": true,
"verbatimModuleSyntax": true
}
}
Without initialisers, members are numbered 0, 1, 2 in order. Give the first one a value, such as Low = 1, and the rest continue from there. The emitted object holds both name to number and number to name.
That compactness is also the risk. The numbers mean nothing outside your code, and adding a member in the middle silently renumbers everything after it.
Every member needs a string literal, which makes values self-describing in logs, URLs and JSON. The strictness cuts both ways: a value read from an API is a string, not the enum, so you still need a check before treating it as one.
An object literal marked as const keeps its exact string values as types. Pair it with type Role = (typeof Role)[keyof typeof Role] and you get a runtime object for iteration plus a union type that accepts both Role.Admin and the literal admin.
This is plain JavaScript with annotations, so every type stripper handles it, and nothing extra is generated beyond the object you wrote.
For numeric enums tsc stores both directions on the object, so Direction.Up is 0 and Direction[0] is the string Up. String members get no reverse entry, because the value could collide with another member's name.
String enum members are distinct types. The compiler reports that the literal is not assignable to the enum, so callers must write Status.Draft. If you want callers to pass the literal, use a union of string literals instead.
For a string enum, Object.values(MyEnum) returns the values. For a numeric enum the object also holds the reverse keys, so filter: Object.values(MyEnum).filter((v) => typeof v === 'number').
No. They remain part of TypeScript. The newer erasableSyntaxOnly option, added in TypeScript 5.8, simply lets projects that rely on type stripping forbid them, because an enum cannot be removed by deleting annotations.
Not with default type stripping. Node's documentation lists enum declarations among the features that need code generation and are not supported in that mode, so compile them with tsc or another transformer, or switch to an as const object.
Related: TypeScript vs JavaScript, TypeScript interfaces, React with TypeScript, JSON to TypeScript converter.
Start for free or see all free tools. We.Inc is free to start, with no card needed. AI building is on paid plans from $20 a month.
We.Inc is an AI-powered website builder you can resell under your own brand. Launch a branded client dashboard, bill on Stripe Connect, and deliver AI-generated websites in minutes. White-label plans start at $99 a month for 25 client sites, with a 7-day free trial and no per-site fees.