159

I'd like to get a string literal union from an enum.

For this enum…

enum Weekday {
    MONDAY = 'mon',
    TUESDAY = 'tue',
    WEDNESDAY = 'wed'
}

… I'd like to get this:

type WeekdayType = 'mon' | 'tue' | 'wed';

I tried typeof keyof Weekday but that resulted in 'MONDAY' | 'TUESDAY' | 'WEDNESDAY'. Feel like the solution might have to do with mapped types but I can't seem to wrap my head around it.

How do I do this?

2
  • That's unfortunate!
    – AKG
    Commented Sep 19, 2018 at 1:18
  • @jcalz when people 'answer' in comments like you did, the answers cannot be edited by the community. It's a pity, as this question can now be solved, but people coming onto this question will first read your (then correct, now incorrect) comment and may be mislead as a result. Commented Aug 19, 2023 at 9:37

4 Answers 4

228

See TS4.1 ANSWER:

type WeekdayType = `${Weekday}`;

PRE TS-4.1 ANSWER:

This can't be done programmatically... you're trying to convert the type Weekday, which is Weekday.MONDAY | Weekday.TUESDAY | Weekday.WEDNESDAY, to the type WeekdayType which is "mon" | "tue" | "wed". This conversion is a form of widening, since Weekday is a subtype of WeekdayType:

type WeekdayExtendsWeekdayType = 
  Weekday extends WeekdayType ? true : false
// type WeekdayExtendsWeekdayType = true

Unfortunately the compiler doesn't give you a handle to remove an "enum"-ness from the enum type and leave you with plain literal types.


So, workarounds? Maybe you don't actually need an enum, but can make do with an object whose property values are string literals:

const lit = <V extends keyof any>(v: V) => v;
const Weekday = {
  MONDAY: lit("mon"),
  TUESDAY: lit("tue"),
  WEDNESDAY: lit("wed")
}
type Weekday = (typeof Weekday)[keyof typeof Weekday],

If you inspect it, the value named Weekday behaves like an enum object:

console.log(Weekday.TUESDAY); // tue

while the type named Weekday behaves like the union of string values "mon" | "tue" | "wed" that you were calling WeekdayType:

const w: Weekday = "wed"; // okay
const x: Weekday = "xed"; // error

So in this workaround, there is no "enum"-ness, and therefore no need to distinguish the type Weekday from the type WeekdayType. It's a little different from an actual enum (which includes types like Weekday.MONDAY, which you'd have to represent as the cumbersome typeof Weekday.MONDAY or create a different type alias for it), but it might behave similarly enough to be useful. Does that work for you?

5
  • 11
    Update: since typescript 3.4, special lit function is no longer needed. You can use const assertions instead
    – just-boris
    Commented Sep 22, 2019 at 20:26
  • curios question @jcalz. What does <V extends keyof any> do? it prevents widening any kind of string/number literal ? thanks!
    – hotell
    Commented Aug 9, 2020 at 9:24
  • is there a way to do the opposite? Get a string enum from a type that is a union of string literals?
    – Pablo K
    Commented May 11, 2021 at 0:39
  • 1
    @Pablo I don't think so.
    – jcalz
    Commented May 11, 2021 at 0:55
  • 2
    type WeekdayType = `${Weekday}`; is wicked Commented Nov 8, 2022 at 1:11
72

TypeScript 4.1+:

As mentioned, this can be achieved by using Template Literal Types like so:

type WeekdayType = `${Weekday}`;

TypeScript 3.4+:

Following up on @jcalz answer and the comment from @just-boris, here's an example with const assertions:

const Weekday = {
  MONDAY: "mon",
  TUESDAY: "tue",
  WEDNESDAY: "wed",
} as const;

type Weekday = (typeof Weekday)[keyof typeof Weekday];

Edit:

Wrote a blog post for those who would like to dig deeper.

1
  • That should work but has a limitation. ``` type Weekday1 = (typeof Weekday)[keyof typeof Weekday]; type Weekday2 = ${Weekday}; const day1: Weekday1 = Weekday.MONDAY; => works const day2: Weekday2 = Weekday.MONDAY; => works const day3: Weekday1 = 'mon' => does not work const day4: Weekday2 = 'mon'; => works ```
    – Mina Luke
    Commented Jan 4, 2021 at 22:30
68

With Typescript 4.1, it can be done!

enum Weekday {
  MONDAY = 'mon',
  TUESDAY = 'tue',
  WEDNESDAY = 'wed'
}

type WeekdayType = `${Weekday}`;

And for number enums, thanks to @okku:

enum ThreeDigits {
  ZERO = 0,
  ONE = 1,
  TWO = 2
}

type ThreeDigitsType = `${ThreeDigits}` extends `${infer T extends number}` ? T : never;
8
  • 5
    An easier way to to this is: type WeekdayUnion = ${Weekday}; That's it! Commented Nov 27, 2020 at 8:21
  • 1
    any luck getting this to compile with ts-node?
    – Michael G
    Commented Dec 16, 2020 at 4:59
  • 2
    @MichaelG yes, you just need to update your typescript version to 4.1 in your project's package.json in your dev dependencies
    – coyotte508
    Commented Dec 17, 2020 at 20:09
  • 4
    Could someone provide documentation for this behaviour plz?!
    – sandbox992
    Commented May 30, 2021 at 15:09
  • 1
    It does work with number enums, with some extra work: type WeekdayType = ${Weekday}` extends ${infer T extends number} ? T : never;`
    – Okku
    Commented Nov 27, 2022 at 4:09
1

Typescript 4.8+ has some new features that we can use here. It works with string enums, number enums, and mixed enums too.

Typescript Playground

export type EnumToPrimitiveUnion<T> = `${T & string}` | ParseNumber<`${T & number}`>;
type ParseNumber<T> = T extends `${infer U extends number}` ? U : never;

export function typeCastEnum<E>(value: EnumToPrimitiveUnion<E>): E {
  return value as E;
}

enum Enum1 {
  A = 'a',
  B = 'b',
  X = 5,
}
enum Enum2 {
  A = 'a',
  C = 'c',
  Y = 6,
}
enum Enum3 {
  A2 = 'a',
  B2 = 'b',
  X2 = 5,
}


type PrimitiveEnum1 = EnumToPrimitiveUnion<Enum1>; // 'a' | 'b' | 5
typeCastEnum<Enum1>(Enum2.A); // ok, 'a' fits
typeCastEnum<Enum1>('b'); // ok
typeCastEnum<Enum1>(5); // ok
typeCastEnum<Enum1>(Enum3.A2 as Enum3); // ok, all values of Enum3 fit

// @ts-expect-error
typeCastEnum<Enum1>(Enum2.C); // err, 'c' does not fit
// @ts-expect-error
typeCastEnum<Enum1>('c'); // err, 'c' does not fit
// @ts-expect-error
typeCastEnum<Enum1>(Enum2.A as Enum2); // err, some values of Enum2 do not fit
// @ts-expect-error
typeCastEnum<Enum1>(Enum2.Y); // err
// @ts-expect-error
typeCastEnum<Enum1>(99); // err
// @ts-expect-error
typeCastEnum<Enum1>('foo'); // err

Not the answer you're looking for? Browse other questions tagged or ask your own question.