Quick answer
- You used an
unknownvalue without proving what it is. TypeScript blocks every operation onunknownuntil you narrow it. - Caught error? (the usual cause) —
if (e instanceof Error) e.message. - Arbitrary value? — guard with
typeof,in, or a custom type-predicate function. - External JSON? — validate it into a real type (a type guard or a schema like Zod), don't blind-cast.
- The bare-identifier form is the near-identical
TS18046: 'x' is of type 'unknown'— same cause, same fixes.
The exact error string
try {
doSomething();
} catch (e) {
console.log(e.message);
// ~~~~~~~~~
// error TS2571: Object is of type 'unknown'.
}
For a plain named variable, recent TypeScript versions print the sibling wording TS18046: 'e' is of type 'unknown'. It's the same situation — e is unknown and you tried to read .message off it before establishing that it has one. Everything on this page fixes both codes.
unknown is TypeScript's safe top type. Any value is assignable to an unknown, but you can do almost nothing with an unknown — no property access, no call, no arithmetic — until you narrow it to something more specific. TS2571 is the compiler refusing to let you touch a value whose shape it can't guarantee. That's the opposite of any, which lets you do anything and catches nothing.
Where did the unknown come from? (triage)
Every fix starts with identifying the source. There are three common ones:
- A caught error —
catch (e)givese: unknownsince TypeScript 4.4. This is by far the most frequent trigger. - An explicitly
unknownvalue — you or a library typed somethingunknownon purpose (a saferany), e.g. a function parameter, a generic default, or the return of a deliberately-untyped API. - External data — you parsed JSON or read a response and stored it as
unknownrather thanany, to force validation before use.
Match your case to the matching fix below.
Fix 1: narrow a caught error before using it
Because JavaScript can throw any value — a string, a number, an object, not just an Error — TypeScript can't assume a caught value has .message or .stack. Narrow it with instanceof:
try {
doSomething();
} catch (e) {
if (e instanceof Error) {
console.log(e.message); // ✅ e is Error here
} else {
console.log(String(e)); // ✅ handle non-Error throws
}
}
For a one-liner where you just want a message string, a conditional expression is enough: const msg = e instanceof Error ? e.message : String(e);. If you catch a lot of errors, factor the narrowing into a helper:
function toError(e: unknown): Error {
return e instanceof Error ? e : new Error(String(e));
}
// usage
catch (e) { logger.error(toError(e).message); }
Fix 2: type-guard an arbitrary unknown value
When the value isn't an Error but some data whose shape you know at runtime, use the standard narrowing operators — typeof for primitives, Array.isArray, and the in operator (or a property check) for object shape:
function len(value: unknown): number {
if (typeof value === "string") return value.length; // ✅ string here
if (Array.isArray(value)) return value.length; // ✅ unknown[] here
return 0;
}
function getId(value: unknown): string | null {
// narrow to an object that has a string 'id'
if (typeof value === "object" && value !== null && "id" in value
&& typeof (value as { id: unknown }).id === "string") {
return (value as { id: string }).id; // ✅ checked at runtime
}
return null;
}
For anything you reuse, wrap the check in a type predicate so the narrowing is named and reusable — the value is User return type teaches the compiler what a true result means:
interface User { id: string; name: string; }
function isUser(v: unknown): v is User {
return typeof v === "object" && v !== null
&& typeof (v as any).id === "string"
&& typeof (v as any).name === "string";
}
if (isUser(data)) {
console.log(data.name); // ✅ data is User inside this block
}
Fix 3: validate external / JSON data into a real type
The reason to type parsed JSON as unknown (rather than any) is precisely so TS2571 forces you to validate it. Note that JSON.parse itself returns any, so the unknown usually comes from your own annotation or a typed fetch wrapper — and that annotation is a good habit, because data from the network is not guaranteed to match your interface. Rather than writing guards by hand for large shapes, define the target type once and validate against it:
// annotate the parsed result as unknown to force a check
const data: unknown = JSON.parse(rawBody);
// hand-rolled assertion function (throws if the shape is wrong)
function assertUser(v: unknown): asserts v is User {
if (!isUser(v)) throw new Error("response is not a User");
}
assertUser(data);
console.log(data.name); // ✅ data is User from here on
For real payloads a schema validator such as Zod is less error-prone than a hand-written guard, because one schema gives you both the runtime check and the static type (z.infer). Whichever you choose, start from an accurate interface: paste a sample response into JSON to TypeScript to generate the User type, then write your guard or schema against it. That keeps the shape you validate identical to the shape the rest of your code relies on — the same discipline that prevents a downstream TS2532 "Object is possibly 'undefined'" when an optional field is missing.
Fix 4: assert the type only when you're certain
If you genuinely know the type — for example the value came from your own code a line earlier — a specific assertion is acceptable and still far better than any, because every use after the assertion is fully type-checked:
const user = data as User; // asserts the type; no runtime check happens
console.log(user.name); // ✅ checked against User from here
The trade-off is honest: an assertion is a promise you make to the compiler and is not verified at runtime, so if the value isn't really a User, the bug surfaces later as undefined access. Use an assertion for values you control; use validation (Fix 3) for values you don't.
What NOT to do
Two tempting shortcuts undo the safety the unknown was giving you:
- Casting to
any((e as any).message) silences TS2571 but turns off checking for that value entirely — a misspelled property or a wrong method now sails through to runtime. If you must assert, assert to a specific type, notany. - Disabling
useUnknownInCatchVariablesto make caught errorsanyagain re-introduces the exact class of bug the flag was added to prevent (assuming every throw is anError). Keep it on and narrow withinstanceof— it's two lines.
Debugging checklist
- ✓ Identify the source of the
unknown— caught error, explicitunknown, or external data - ✓ Caught error? Narrow with
e instanceof Errorand handle theelse - ✓ Arbitrary value? Use
typeof/Array.isArray/in, or av is Ttype predicate - ✓ JSON / API data? Validate into a real type (assertion function or a Zod schema), don't blind-cast
- ✓ Generate the target interface from a sample with JSON to TypeScript, then guard against it
- ✓ Certain of the type from your own code? A specific
as Tassertion is fine — neveras any - ✓ Same message as
TS18046: 'x' is of type 'unknown'? Identical cause — same fixes apply
Frequently Asked Questions
What does TS2571: Object is of type 'unknown' mean?
You tried to use a value whose type is unknown — read a property, call it, or index into it — without first proving what it is. unknown is TypeScript's safe top type: any value is assignable to it, but you can do almost nothing with it until you narrow it. The fix is to narrow the value with a type guard, validation, or an assertion before you use it.
Why is my caught error of type unknown?
Since TypeScript 4.4, catch-clause variables are typed unknown by default under strict mode (useUnknownInCatchVariables). This is deliberate — anything can be thrown in JavaScript, not just Error objects. Narrow it before use: if (e instanceof Error) { console.log(e.message) } else { console.log(String(e)) }.
What is the difference between TS2571 and TS18046?
They are the same root cause with slightly different wording. TS2571 'Object is of type unknown' fires when you use an unknown-typed expression; TS18046 "'x' is of type unknown" fires for a bare named identifier in recent TypeScript versions. Both mean the value is unknown and both are fixed the same way — narrow it before using it.
How do I fix 'Object is of type unknown' for JSON or API data?
Validate the data into a known type instead of asserting it blindly. Write a type-guard function or use a schema validator (like Zod) that checks the shape at runtime and returns a typed value. Generate the target interface first with a tool like JSON to TypeScript, then validate the parsed data against it so the rest of your code is fully typed.
Should I just cast unknown to any to fix TS2571?
No. Casting to any disables all checking for that value, which defeats the reason it was unknown in the first place — TypeScript can no longer catch a wrong property or method on it. Prefer a type guard or validation. If you are genuinely certain of the type, a specific assertion (value as User) is far safer than any because it still type-checks every use afterward.
Why is unknown safer than any?
any switches type-checking off — you can do anything to an any value and TypeScript stays silent, so bugs slip through to runtime. unknown keeps checking on: you can hold any value in it, but you must narrow it before doing anything, so the compiler forces you to handle the actual shape. TS2571 is that safety net doing its job.
Type your JSON, skip the guards
Generate an accurate interface from a sample response, then validate against it — and browse the full TypeScript error reference.