How to Convert a String to an Enum in TypeScript

To convert a string to an enum in TypeScript, check the string against the enum’s values with a type guard, then return the enum member or undefined. A plain as cast checks nothing:

const isStatus = (v: string): v is OrderStatus =>
  (Object.values(OrderStatus) as string[]).includes(v);

My test setup for this post: TypeScript 7.0.2 and Node.js v22.22.2, strict mode on, and nothing in the outputs below is edited by hand.

You’ll see string enums, member-name lookups with keyof typeof, numeric enum traps, a generic helper, and how you handle input that doesn’t match.

Why can’t you assign a string to an enum in TypeScript?

Say order statuses arrive from an API as text. Even a matching string is refused:

enum OrderStatus {
  Pending = "PENDING",
  Shipped = "SHIPPED",
  Delivered = "DELIVERED",
  Cancelled = "CANCELLED",
}

const fromApi: string = "SHIPPED";

const orderStatus: OrderStatus = fromApi;

Output:

enum_assign.ts(10,7): error TS2322: Type 'string' is not assignable to type 'OrderStatus'.
Command Prompt output of tsc error TS2322: Type string is not assignable to type OrderStatus when converting a string to an enum
TS2322: a string could be anything, so TypeScript won’t treat it as an OrderStatus.

Only enum members are allowed, and a string could hold "REFUNDED", so you must prove it’s valid first. New to enums? Start with my TypeScript enums guide.

Cast a string to an enum with as (no validation)

An assertion tells the compiler to trust you. Here’s what happens with a status the enum doesn’t have:

enum OrderStatus {
  Pending = "PENDING",
  Shipped = "SHIPPED",
  Delivered = "DELIVERED",
  Cancelled = "CANCELLED",
}

const labels: Record<OrderStatus, string> = {
  PENDING: "Waiting to ship", SHIPPED: "On the truck",
  DELIVERED: "Delivered", CANCELLED: "Cancelled",
};

for (const raw of ["SHIPPED", "REFUNDED"]) {
  const status = raw as OrderStatus;      // compiles, checks nothing
  const text: string = labels[status];
  console.log(raw.padEnd(9), "->", text);
}

Output:

SHIPPED   -> On the truck
REFUNDED  -> undefined

"REFUNDED" passed the cast, and text is undefined although its type says string. So only cast after you’ve checked the value. The same hole bites switch statements on TypeScript enums.

Convert a string to a string enum with a type guard

The safe pattern is a type guard that checks Object.values(). When it returns true, TypeScript narrows the string to the enum type. Here it cleans up API data:

enum OrderStatus {
  Pending = "PENDING",
  Shipped = "SHIPPED",
  Delivered = "DELIVERED",
  Cancelled = "CANCELLED",
}

const statusValues: string[] = Object.values(OrderStatus);

function isOrderStatus(value: string): value is OrderStatus {
  return statusValues.includes(value);
}

function parseOrderStatus(value: string): OrderStatus | undefined {
  const clean = value.trim().toUpperCase();
  return isOrderStatus(clean) ? clean : undefined;
}

const orders = JSON.parse(`[
  { "id": 5011, "city": "Austin, TX",  "total": 84.5,   "status": "shipped" },
  { "id": 5012, "city": "Denver, CO",  "total": 129.99, "status": "DELIVERED " },
  { "id": 5013, "city": "Boston, MA",  "total": 42,     "status": "refunded" }
]`) as { id: number; city: string; total: number; status: string }[];

for (const o of orders) {
  const status = parseOrderStatus(o.status) ?? OrderStatus.Pending;
  console.log(o.id, o.city.padEnd(11), `$${o.total.toFixed(2)}`.padStart(8), JSON.stringify(o.status).padEnd(13), "->", status);
}

Output:

5011 Austin, TX    $84.50 "shipped"     -> SHIPPED
5012 Denver, CO   $129.99 "DELIVERED "  -> DELIVERED
5013 Boston, MA    $42.00 "refunded"    -> PENDING
Command Prompt output of a TypeScript type guard converting API status strings to an OrderStatus enum with a fallback for invalid input
Lowercase and padded values are normalized, and the unknown "refunded" falls back to PENDING.

I widen the values to string[] once, so includes() accepts any string. The value is OrderStatus return type is what does the narrowing, and I explain it in the is keyword in TypeScript.

A default like Pending suits display code; for database writes, reject the record instead. For plain yes/no checks, see how to check if a string is in an enum.

Match the enum member name with keyof typeof

Sometimes your input holds the member name ("Shipped"), not the value ("SHIPPED"). Index the enum with keyof typeof after you check the key exists:

enum OrderStatus {
  Pending = "PENDING",
  Shipped = "SHIPPED",
  Delivered = "DELIVERED",
  Cancelled = "CANCELLED",
}

type StatusKey = keyof typeof OrderStatus;     // "Pending" | "Shipped" | "Delivered" | "Cancelled"

function statusFromKey(key: string): OrderStatus | undefined {
  return Object.hasOwn(OrderStatus, key) ? OrderStatus[key as StatusKey] : undefined;
}

console.log(statusFromKey("Shipped"));
console.log(statusFromKey("SHIPPED"));
console.log(statusFromKey("toString"));

Output:

SHIPPED
undefined
undefined

Object.hasOwn() skips inherited names like toString, and lookups are case sensitive. My keyof operator guide explains the typeof part; the reverse is in getting an enum key by value.

Convert a string to a numeric enum

Numeric enums also map each number back to its name at runtime. That reverse mapping creates three traps:

enum Priority {
  Low = 1,
  Medium,
  High,
}

console.log("Object.values:", Object.values(Priority));
console.log('Priority["High"]:', Priority["High" as keyof typeof Priority]);
console.log('Priority["2"]:   ', Priority["2" as keyof typeof Priority]);
console.log('"toString" in Priority:', "toString" in Priority);

function parsePriority(input: string): Priority | undefined {
  const key = input.trim();
  if (/^\d+$/.test(key)) {
    const n = Number(key);
    return Priority[n] !== undefined ? (n as Priority) : undefined;
  }
  const value = Object.hasOwn(Priority, key) ? Priority[key as keyof typeof Priority] : undefined;
  return typeof value === "number" ? value : undefined;
}

for (const s of ["High", "2", "9", "toString", "high"]) {
  console.log(`parsePriority(${JSON.stringify(s)}) =`, parsePriority(s));
}

Output:

Object.values: [ 'Low', 'Medium', 'High', 1, 2, 3 ]
Priority["High"]: 3
Priority["2"]:    Medium
"toString" in Priority: true
parsePriority("High") = 3
parsePriority("2") = 2
parsePriority("9") = undefined
parsePriority("toString") = undefined
parsePriority("high") = undefined
Command Prompt output of converting strings to a TypeScript numeric enum, showing reverse mapping and a safe parsePriority function
Real output of the numeric enum lookups and the safe parsePriority().
  • Object.values(Priority) includes the names, so a value check would accept "High" as a value.
  • Priority["2"] gives "Medium", a string, because of the reverse mapping.
  • "toString" in Priority is true, so the in operator isn’t a safe key check.

parsePriority() handles both forms: digits go through the number check, and names must map to a number. The mapping itself is explained in reverse mapping in TypeScript enums.

A generic helper to convert any string to an enum

With several string enums, write the check once. This generic returns the correct enum type for whichever enum you pass in:

enum OrderStatus {
  Pending = "PENDING",
  Shipped = "SHIPPED",
  Delivered = "DELIVERED",
  Cancelled = "CANCELLED",
}

enum Region {
  West = "WEST",
  Midwest = "MIDWEST",
  South = "SOUTH",
  Northeast = "NORTHEAST",
}

function toEnum<E extends Record<string, string>>(enumObj: E, value: string): E[keyof E] | undefined {
  const values: string[] = Object.values(enumObj);
  return values.includes(value) ? (value as E[keyof E]) : undefined;
}

const s: OrderStatus | undefined = toEnum(OrderStatus, "DELIVERED");
const r: Region | undefined = toEnum(Region, "MIDWEST");
const bad = toEnum(Region, "Pacific");
console.log(s, r, bad ?? "rejected: Pacific");

Output:

DELIVERED MIDWEST rejected: Pacific
Command Prompt output of a generic TypeScript toEnum function converting strings to OrderStatus and Region enums and rejecting invalid input
One helper, two enums, and "Pacific" is rejected.

Record<string, string> keeps numeric enums out, since reverse mapping would fool the check. To list valid options in an error, see getting all enum values in TypeScript.

Which string to enum method should you use?

MethodValidates?Use it when
value as MyEnumNoThe value was already checked
Type guard on Object.values()YesInput is the enum value, like "SHIPPED"
Object.hasOwn + keyof typeofYesInput is the member name, like "Shipped"
Generic toEnum()YesSeveral string enums in one codebase

Tired of converters? A string literal union may fit better; I compare both in enums vs string literal unions. Also see enums with multiple values in TypeScript.

Here are more enum tutorials worth a look:

Frequently asked questions

How do I convert a string to an enum in TypeScript?

Check it with a type guard such as Object.values(MyEnum).includes(value), then return the narrowed value, or undefined when it doesn’t match.

Why does value as MyEnum not catch invalid strings?

A type assertion only changes the compile-time type. Nothing runs at runtime, so "REFUNDED" as OrderStatus stays the string "REFUNDED".

How do I convert an enum name like “Shipped” to its value?

Check Object.hasOwn(MyEnum, key), then read MyEnum[key as keyof typeof MyEnum].

How do I make the conversion case insensitive?

Normalize first, for example value.trim().toUpperCase() when every enum value is uppercase, then run the type guard.

Why does Object.values() show names for a numeric enum?

Numeric enums also store number-to-name entries (reverse mapping), so Object.values() returns both names and numbers.

Where are TypeScript enums documented?