The unknown Type
unknown is TypeScript’s top type — the type-safe counterpart of any. Every value is assignable to unknown, which makes it the right home for anything whose type you cannot trust at compile time: parsed JSON, network responses, caught exceptions, and values crossing a JavaScript boundary. Unlike any, unknown does not let you do anything with the value until you prove what it is. That forced narrowing is the entire point: you keep flexibility on the way in and regain full type safety before you act. This page covers assignability, narrowing techniques, and the situations where unknown shines.
unknown accepts everything
Any value can be assigned to a variable of type unknown. In that direction it behaves just like any.
let value: unknown;
value = 42;
value = "hello";
value = { id: 1 };
value = [1, 2, 3];
value = () => {};
// every assignment is allowed
This makes unknown the universal supertype: a parameter typed unknown can receive a value of any other type. The discipline appears only when you try to use that value.
You must narrow before use
Operations on an unknown value are rejected until you narrow it to a more specific type. The compiler refuses to assume anything about it.
function process(value: unknown) {
// ❌ Error: 'value' is of type 'unknown'.
value.toUpperCase();
// ❌ Error: 'value' is of type 'unknown'.
const n: number = value;
if (typeof value === "string") {
value.toUpperCase(); // ✅ narrowed to string
}
}
unknown is assignable to nothing except unknown and any. That is the exact inverse of any, and it is what prevents the silent runtime crashes that any allows. Compare them side by side in unknown vs any.
Narrowing techniques
You narrow unknown with the same tools you use for unions: typeof, instanceof, property checks, and custom type guards.
function describe(value: unknown): string {
if (typeof value === "string") return `string: ${value}`;
if (typeof value === "number") return `number: ${value.toFixed(2)}`;
if (value instanceof Date) return `date: ${value.toISOString()}`;
if (Array.isArray(value)) return `array of ${value.length}`;
if (value && typeof value === "object" && "id" in value) {
return `object with id`;
}
return "unknown shape";
}
Each guard tells the compiler more about the value within that block, unlocking the corresponding methods. See typeof Narrowing for the full set of narrowing patterns.
Custom type guards for shapes
For object shapes, a user-defined type guard (a function returning value is T) turns runtime validation into compile-time knowledge.
interface User {
id: number;
name: string;
}
function isUser(value: unknown): value is User {
return (
typeof value === "object" &&
value !== null &&
"id" in value &&
typeof (value as Record<string, unknown>).id === "number" &&
"name" in value &&
typeof (value as Record<string, unknown>).name === "string"
);
}
const data: unknown = JSON.parse('{"id":1,"name":"Ada"}');
if (isUser(data)) {
console.log(data.name); // ✅ data is User here
}
The guard validates the structure at runtime and refines unknown to User for the rest of the block. This is the safe bridge between untrusted input and your typed domain model — see Custom Type Guards.
Ideal use cases
unknown is the correct type wherever data enters your program without a guaranteed shape. JSON.parse returns any, so annotating its result as unknown re-enables safety.
const raw: unknown = JSON.parse(payload); // safer than the default any
// catch variables are `unknown` with useUnknownInCatchVariables
try {
doWork();
} catch (err: unknown) {
if (err instanceof Error) {
console.error(err.message); // ✅ narrowed
} else {
console.error("Unknown error", err);
}
}
With useUnknownInCatchVariables (on under strict), the catch binding is typed unknown instead of any, forcing you to check before assuming it is an Error. This pairs well with Async Error Handling.
How unknown compares
| Aspect | any | unknown |
|---|---|---|
| Position in type system | Disables the system | Top type |
| Assign anything to it | Yes | Yes |
| Assign it to other types | Yes (unsafe) | Only after narrowing |
| Use without checking | Yes | No |
| Type safety preserved | No | Yes |
| Best for | Avoid | Untrusted / dynamic input |
Reach for
unknownat every untyped boundary —JSON.parse,fetchbodies,localStorage, message events, andcatchclauses — then validate once and work with a real type from there.
Best Practices
- Default to
unknowninstead ofanyfor any value of undetermined type. - Always narrow with
typeof,instanceof,in, or a type guard before using the value. - Annotate
JSON.parseresults and external input asunknown, then validate them. - Enable
useUnknownInCatchVariables(viastrict) so caught errors areunknown. - Write reusable
value is Ttype guards to convertunknowninto domain types. - Validate untrusted data with a schema library, then trust the inferred output type.
- Never assert
unknownstraight to a concrete type without a runtime check first.