TS18046 'e' is of type 'unknown' (tested)
error TS18046: 'e' is of type 'unknown' fires when you access a property like .message directly on a catch variable. Since TypeScript 4.4, catch (e) {} types e as unknown by default under strict mode, instead of the old any. We reproduced the error in a real TypeScript 7 project, confirmed the two standard fixes, and confirmed the tsconfig flag that restores the old behaviour.
1. The exact error
This is the plain, common case: a try/catch that reads .message straight off the caught value.
function f() {
try {
JSON.parse("{");
} catch (e) {
console.log(e.message);
}
}
Compiled with tsc --strict, this printed:
c1.ts(5,17): error TS18046: 'e' is of type 'unknown'.
tsc exited with code 1. This is not a bug in your code's logic — it's TypeScript refusing to let you call a method on a value it cannot prove has that method. unknown accepts any value coming in, but blocks every operation on it until you narrow the type. Why this changed: since TypeScript 4.4, the compiler option useUnknownInCatchVariables defaults to true whenever strict is on, so a bare catch (e) gets type unknown instead of the old any (per the TypeScript 4.4 release notes, not independently measured — the behaviour itself we did measure above).
2. The standard fix: narrow with instanceof Error
Add a runtime check before touching any property. This is the type-safe fix and needs no cast:
function f() {
try {
JSON.parse("{");
} catch (e) {
if (e instanceof Error) {
console.log(e.message);
}
}
}
Compiled with tsc --strict: no output, exit code 0. Inside the if block, TypeScript narrows e to Error, so .message is valid. This also protects you at runtime: JavaScript lets you throw any value — a string, a number, a plain object — not just an Error. The instanceof check only runs .message when the thrown value really is an Error instance.
3. The unsafe shortcut: (e as Error).message
Some code casts the catch variable instead of checking it:
function f() {
try {
JSON.parse("{");
} catch (e) {
console.log((e as Error).message);
}
}
Compiled with tsc --strict: no output, exit code 0 — tsc accepts it, same as the instanceof version. The difference is at runtime, not compile time. A cast is just a promise to the compiler; it adds no actual check. If something throws a plain string or a custom non-Error object, (e as Error).message compiles fine but returns undefined at runtime instead of raising a type error, which can silently swallow useful error data. Prefer instanceof Error narrowing (section 2) unless you have already verified, elsewhere, that only Error objects can reach this catch.
4. Opting back into the old `any` behaviour
If a large codebase isn't ready to add instanceof checks everywhere, you can turn off the specific flag that changed in TypeScript 4.4, in tsconfig.json:
{
"compilerOptions": {
"strict": true,
"useUnknownInCatchVariables": false
}
}
We compiled the original plain e.message file (section 1) with --strict --useUnknownInCatchVariables false on the command line: no output, exit code 0. With the flag off, catch (e) is typed any again, so e.message compiles without narrowing — and without the runtime safety instanceof gives you.
Turning off strict entirely has the same side effect, but it also disables every other strict check (null checks, implicit any on parameters, and more). We don't recommend that route just to silence this one error — set useUnknownInCatchVariables: false specifically instead, if you need the old behaviour.
5. On TypeScript 7, this also fires with NO strict flag at all
Every test above used tsc --strict on purpose, to match the documented TypeScript 4.4+ behaviour (useUnknownInCatchVariables defaults to true only when strict is on). We then tested the plain e.message file from section 1 with no flags and no tsconfig.json at all:
$ tsc --noEmit c1.ts
c1.ts(5,17): error TS18046: 'e' is of type 'unknown'.
Same error, same exit code 1 — with nothing that should turn on strict. We also tried a tsconfig.json with an empty compilerOptions (no strict key at all, which should mean strict: false by documented default):
{ "compilerOptions": { "noEmit": true } }
$ tsc -p .
c1.ts(5,17): error TS18046: 'e' is of type 'unknown'.
Still TS18046. On TypeScript 7.0.2 (the current native compiler), only explicitly passing --strict false or --useUnknownInCatchVariables false restores the old any behaviour — there is no longer an implicit "off" state. This contradicts the TypeScript 4.4-era release notes (which tie this purely to strict) and is worth knowing if you hit TS18046 on a project you were sure had strict disabled: check for the explicit flag, don't assume the absence of "strict": true protects you.
Summary
| Approach | Compiles under --strict | Compiles with no flags (TS 7.0.2) | Runtime-safe |
|---|---|---|---|
e.message directly | No — TS18046 | No — TS18046 | — |
if (e instanceof Error) | Yes | Yes | Yes |
(e as Error).message | Yes | Yes | No — unchecked cast |
useUnknownInCatchVariables: false (explicit) | Yes (whole project) | Yes | No — back to any |
Related: if you're chasing a property-access error on a type generally, see TS2339 property does not exist on type. For assignment mismatches, see type is not assignable to type. For import resolution errors, see cannot find module.
Frequently asked questions
Why did this start happening after I upgraded TypeScript?
Since TypeScript 4.4, catch block variables are typed unknown by default under strict mode (the useUnknownInCatchVariables option), replacing the old any default. On TypeScript 7.0.2 we measured this firing even with no strict flag and no tsconfig.json at all, so an upgrade can surface it even in projects that never enabled strict mode.
Is TS18046 the same error as TS2339?
No. TS2339 ("property does not exist on type") fires for typos and union types with a declared type. TS18046 ("is of type 'unknown'") is specific to unknown-typed values, including catch variables, and is what you'll actually see here.
Do I have to use instanceof Error specifically?
You need some narrowing check before using the value, and instanceof Error is the standard one because almost everything thrown in practice is an Error. If your code might throw strings or plain objects too, add typeof or property checks for those cases before accessing fields.
Does this affect JavaScript files, or only .ts?
Only files TypeScript type-checks: .ts, .tsx, and .js files with checkJs enabled. Plain JavaScript without type checking never sees TS18046, since there's no compiler enforcing the catch variable's type.
Does turning off strict mode fix TS18046 on TypeScript 7?
Not by omission. We measured that leaving strict unset (no flag, no tsconfig key) still produces TS18046 on TypeScript 7.0.2. You need to explicitly pass --strict false or --useUnknownInCatchVariables false; there's no longer an implicit off state.
Written with AI assistance. Measured on 2026-10-03 with TypeScript 7.0.2 (local npm install, via npx tsc), Node.js 24.21.0 on Linux. Each scenario is its own minimal .ts file; most are compiled with `tsc --strict`, and section 5 specifically tests with no strict flag and no tsconfig.json. All error text and exit codes below are copy-pasted from real tsc runs. PlainHeaders is our extension; curl and TypeScript are free.