TypeScript TS18046 — Error Is of Type unknown
Fix TS18046 by narrowing unknown values with type guards, using instanceof Error, and opting into useUnknownInCatchVariables deliberately.
Symptoms
- error TS18046: 'e' is of type 'unknown'
- The error appeared after enabling strict mode
- Accessing e.message in a catch block fails to compile
Possible causes
- Strict mode or useUnknownInCatchVariables makes catch bindings unknown
- An index access returns unknown under noUncheckedIndexedAccess
- JSON.parse or a generic API returns unknown and is used without narrowing
Diagnosis steps
Reproduce the compile error
npx tsc --noEmit 2>&1 | grep TS18046Interpretation
The message names the variable and the operation, showing where narrowing is needed.
Narrow with a guard
if (e instanceof Error) console.log(e.message);Interpretation
instanceof Error narrows unknown to Error so message and stack are accessible.
Root cause
With useUnknownInCatchVariables (implied by strict), catch clauses bind unknown because anything can be thrown. TypeScript forbids member access on unknown until a guard proves a narrower type.
Fix
Narrow before use: instanceof Error for error-like values, a custom isApiError guard for shapes, and a helper that converts unknown to a message safely.
Risk note
Casting with `as Error` skips the check and reintroduces the runtime bug the strict setting prevents.
Verify the fix
Run tsc --noEmit and confirm TS18046 is gone; add a test throwing a string and an Error and assert both produce a readable message.
Symptoms
When you hit “TypeScript TS18046 — Error Is of Type unknown”, guessing wastes time. The guide below follows evidence: reproduce the symptom, run targeted diagnostics, identify the root cause, apply the fix, and verify it with concrete checks.
Observed symptoms include:
- error TS18046: ‘e’ is of type ‘unknown’
- The error appeared after enabling strict mode
- Accessing e.message in a catch block fails to compile
Reproduce and Diagnose
Reproduce the failure in isolation before changing any code. Work through the checks below in order; each one narrows the search space.
Step 1: Reproduce the compile error
npx tsc --noEmit 2>&1 | grep TS18046
The message names the variable and the operation, showing where narrowing is needed.
Step 2: Narrow with a guard
if (e instanceof Error) console.log(e.message);
instanceof Error narrows unknown to Error so message and stack are accessible.
Likely contributors, in order of frequency:
- Strict mode or useUnknownInCatchVariables makes catch bindings unknown
- An index access returns unknown under noUncheckedIndexedAccess
- JSON.parse or a generic API returns unknown and is used without narrowing
Root Cause
With useUnknownInCatchVariables (implied by strict), catch clauses bind unknown because anything can be thrown. TypeScript forbids member access on unknown until a guard proves a narrower type.
The Fix
Narrow before use: instanceof Error for error-like values, a custom isApiError guard for shapes, and a helper that converts unknown to a message safely.
Watch out: Casting with as Error skips the check and reintroduces the runtime bug the strict setting prevents.
function messageOf(e: unknown): string {
if (e instanceof Error) return e.message;
if (typeof e === 'string') return e;
try { return JSON.stringify(e); } catch { return String(e); }
}
try { risky(); } catch (e: unknown) {
console.error(messageOf(e));
}
Verify and Prevent
Run tsc –noEmit and confirm TS18046 is gone; add a test throwing a string and an Error and assert both produce a readable message.
Once verified, add a regression test that reproduces the original symptom so the failure cannot silently return.
This class of failure is usually environmental rather than pure logic, so capture the exact command, the environment variables, and the working directory in your bug report. Reproduce it in the smallest possible script, then widen the scope only once the minimal case is understood. A short written checklist for this failure mode saves hours the next time it appears.