How to create a type guard for string union types in TypeScript

  • Peter Parkes
    Peter Parkes
    Co-founder
We’re building a whiteboard for software engineers. Read about how

String union types are convenient, but creating type guards for them without repetition isn’t immediately obvious. This post explains how to create DRY type guards for string union types in TypeScript.

We use string union types in various places throughout Qualdesk. They’re easy to read, easy to update, and because they’re strings, they’re useful when you need to specify things like CSS classnames.

To refresh your memory, a string union type looks like this:

type ColorKey = 'red' | 'orange' | 'yellow' | 'green' | 'blue' | 'purple'

If we want to:

  1. check that a string is a ColorKey, and
  2. narrow the type of the string to ColorKey

we need to write a user-defined type guard.

For example, a simple type guard for ‘red’ might look like this:

function isRed(colorKey: ColorKey): colorKey is 'red' {
  return colorKey === 'red'
}

What if we want to check if a string is any one of the valid color keys?

We could do something like this:

function isColorKey(colorKey: string): colorKey is ColorKey {
  switch (colorKey) {
    case 'red':
    case 'orange':
    case 'yellow':
    case 'green':
    case 'blue':
    case 'purple':
      return true
    default:
      return false
  }
}

but then we’ve just repeated the entire type definition in the type guard function.

In this situation, if we add a new color, we have to update both the type and the type guard.

There’s an alternative.

First, we need to define an array of colors:

const COLORS = ['red', 'orange', 'yellow', 'green', 'blue', 'purple'] as const

You’ll notice that there’s as const at the end of this definition. This defines COLORS as a readonly array consisting of precisely the values in the declaration.

This is important in the next step, definining the ColorKey type itself:

type ColorKey = typeof COLORS[number]

What does this do? It tells the compiler that ColorKey is the type of any of the elements in COLORS. An ‘any of’ type is a union type, and so we end up with the ColorKey type definition exactly as we did above.

If we didn’t cast the COLORS array as const, the type of each of the elements in COLORS would simply be string, and so we’d end up with ColorKey === string: not very helpful.

Finally, we can rewrite the type guard using the COLORS array:

function isColorKey(colorKey: string): colorKey is ColorKey {
  return COLORS.includes(colorKey as ColorKey)
}

Now we have a DRY type guard. If we want to add a new color, we just add it to the array, and the type guard will recognize it immediately.

We’re building a whiteboard for software engineers. Read about how