---
title: Get a catch block error message with TypeScript
date: 2021-10-28
description:
  TypeScript forces you to acknowledge you can't know what was thrown making
  getting the error message a pain. Here's how you can manage it.
categories:
  - typescript
meta:
  keywords:
    - errors
    - typescript
    - error messages
translations:
  - language: 한국어
    link: https://junghyeonsu.com/posts/get-a-catch-block-error-message-with-typescript/
    author:
      name: Hyeonsu Jung
      link: https://junghyeonsu.com/
  - language: Português (do Brasil)
    link: https://ruttmann.github.io/posts/ts-error-messages/
    author:
      name: Marlon E. Ruttmann
      link: https://www.linkedin.com/in/marlon-ruttmann/
  - language: 日本語
    link: https://note.com/lada496/n/n88c5aab6d0f9
    author:
      name: Lada496
      link: https://github.com/Lada496
  - language: 简体中文
    link: https://www.boris1993.com/translation-get-a-catch-block-error-message-with-typescript.html
    author:
      name: boris1993
      link: https://github.com/boris1993
bannerCloudinaryId: unsplash/photo-1525785967371-87ba44b3e6cf
bannerAlt: brown and white cat in shallow focus shot
bannerCredit: Photo by [傅甬 华](https://unsplash.com/photos/tEMU4lzAL0w)
---

Alrighty, let's talk about this:

```js
const reportError = ({ message }) => {
	// send the error to our logging service...
}

try {
	throw new Error('Oh no!')
} catch (error) {
	// we'll proceed, but let's report it
	reportError({ message: error.message })
}
```

Good so far? Well, that's because this is JavaScript. Let's throw TypeScript at
this:

```ts lines=9
const reportError = ({ message }: { message: string }) => {
	// send the error to our logging service...
}

try {
	throw new Error('Oh no!')
} catch (error) {
	// we'll proceed, but let's report it
	reportError({ message: error.message })
}
```

That `reportError` call there isn't happy. Specifically it's the `error.message`
bit. It's because (as of recently) TypeScript defaults our `error` type to
`unknown`. Which is truly what it is! In the world of errors, there's not much
guarantees you can offer about the types of errors that are thrown. In fact,
this is the same reason you can't provide the type for the `.catch(error => {})`
of a promise rejection with the promise generic
(`Promise<ResolvedValue, NopeYouCantProvideARejectedValueType>`). In fact, it
might not even be an error that's thrown at all. It could be just about
anything:

```ts
throw 'What the!?'
throw 7
throw { wut: 'is this' }
throw null
throw new Promise(() => {})
throw undefined
```

Seriously, you can throw anything of any type. So that's easy right? We could
just add a type annotation for the error to say this code will only throw an
error right?

```ts lines=3
try {
	throw new Error('Oh no!')
} catch (error: Error) {
	// we'll proceed, but let's report it
	reportError({ message: error.message })
}
```

Not so fast! With that you'll get the following TypeScript compilation error:

```
Catch clause variable type annotation must be 'any' or 'unknown' if specified. ts(1196)
```

The reason for this is because even though in our code it looks like there's no
way anything else could be thrown, JavaScript is kinda funny and so its
perfectly possible for a third party library to do something funky like
monkey-patching the error constructor to throw something different:

```ts
Error = function () {
	throw 'Flowers'
} as any
```

So what's a dev to do? The very best they can! So how about this:

```ts lines=4-5,7
try {
	throw new Error('Oh no!')
} catch (error) {
	let message = 'Unknown Error'
	if (error instanceof Error) message = error.message
	// we'll proceed, but let's report it
	reportError({ message })
}
```

There we go! Now TypeScript isn't yelling at us and more importantly we're
handling the cases where it really could be something completely unexpected.
Maybe we could do even better though:

```ts lines=4,6
try {
	throw new Error('Oh no!')
} catch (error) {
	let message
	if (error instanceof Error) message = error.message
	else message = String(error)
	// we'll proceed, but let's report it
	reportError({ message })
}
```

So here if the error isn't an actual `Error` object, then we'll just stringify
the error and hopefully that will end up being something useful.

Then we can turn this into a utility for use in all our catch blocks:

```ts lines=1-4,14
function getErrorMessage(error: unknown) {
	if (error instanceof Error) return error.message
	return String(error)
}

const reportError = ({ message }: { message: string }) => {
	// send the error to our logging service...
}

try {
	throw new Error('Oh no!')
} catch (error) {
	// we'll proceed, but let's report it
	reportError({ message: getErrorMessage(error) })
}
```

This has been helpful for me in my projects. Hopefully it helps you as well.

Update: [Nicolas](https://github.com/npirotte) had
[a nice suggestion](https://github.com/kentcdodds/kentcdodds.com/issues/206) for
handling situations where the error object you're dealing with isn't an actual
error. And then [Jesse](https://discord.com/users/804795652252106762) had
[a suggestion](https://discord.com/channels/715220730605731931/715227739749089281/903649313228480532)
to stringify the error object if possible. So all together the combined
suggestions looks like this:

```ts
type ErrorWithMessage = {
	message: string
}

function isErrorWithMessage(error: unknown): error is ErrorWithMessage {
	return (
		typeof error === 'object' &&
		error !== null &&
		'message' in error &&
		typeof (error as Record<string, unknown>).message === 'string'
	)
}

function toErrorWithMessage(maybeError: unknown): ErrorWithMessage {
	if (isErrorWithMessage(maybeError)) return maybeError

	try {
		return new Error(JSON.stringify(maybeError))
	} catch {
		// fallback in case there's an error stringifying the maybeError
		// like with circular references for example.
		return new Error(String(maybeError))
	}
}

function getErrorMessage(error: unknown) {
	return toErrorWithMessage(error).message
}
```

Handy!

## Conclusion

I think the key takeaway here is to remember that while TypeScript has its funny
bits, don't dismiss a compilation error or warning from TypeScript just because
you think it's impossible or whatever. Most of the time it absolutely is
possible for the unexpected to happen and TypeScript does a pretty good job of
forcing you to handle those unlikely cases... And you'll probably find they're
not as unlikely as you think.
