unknown
Accepts any value but permits no operation on it until you narrow it to a real type.
function safeParse(s: string): unknown- Added in TypeScript 3.0
Errors
Using an unknown value without narrowing is a compile error: "'x' is of type 'unknown'".
Changelog
- PHP 3.0unknown introduced as the type-safe counterpart of any. Anything is assignable to unknown; unknown is assignable only to unknown and any.
- PHP 4.0catch clause variables may be annotated `catch (e: unknown)`.
- PHP 4.4useUnknownInCatchVariables makes caught errors unknown by default under strict, replacing the old implicit any.
unknown is the top type: every value is assignable to it, and it is assignable to nothing but unknown and any. That asymmetry is the whole feature. It lets a function accept anything while refusing to let the body do anything with the value until a check has proved what it is.
On an unknown, TypeScript rejects property access, indexing, calling, construction, and every arithmetic or relational operator. Only equality comparisons are allowed. To use the value, narrow it with typeof, instanceof, an in check, or a user-defined type predicate.
Where it belongs
Anywhere data crosses into your program from outside the type system: JSON.parse results, request bodies, message-channel payloads, configuration files, and caught errors. Type such a boundary unknown, validate once, and everything downstream can be a precise type instead of a hopeful one.
Note that unknown absorbs in unions (unknown | string is unknown) and is absorbed in intersections (unknown & string is string). A common trap is reaching for as to silence the error — that just recreates any with extra syntax. Write the predicate instead.
Example
When to use it
- Typing the return of JSON.parse so a validator has to run before the data is used
- Accepting an error in a catch block and checking `e instanceof Error` before reading `.message`
- Writing a logging or serialisation helper that takes any value without weakening its callers
Frequently asked questions
How do I fix "Object is of type 'unknown'" in TypeScript?
Narrow the value before using it — the error means you tried to read or call something on a value whose type is not yet known. Use `typeof x === "string"`, `x instanceof Error`, an `in` check, or a user-defined predicate `function isThing(v: unknown): v is Thing`. Casting with `as` removes the error but also removes the safety, so only do it after a real runtime check.
Should I use unknown instead of any?
Yes, wherever you only need to accept arbitrary input. `unknown` takes exactly the same values as `any` but blocks every operation until narrowing proves the type, so mistakes surface at compile time instead of runtime. Keep `any` for incremental JavaScript migrations, not for API boundaries.
What type does JSON.parse return in TypeScript?
`any`, which is why parsed data flows unchecked into typed code. Wrap it in a helper declared to return `unknown` and validate the result with a type predicate, or use a schema validator. That way the parsed object cannot reach the rest of your program until its shape has been confirmed.
More examples
unknown in a catch block
Output is `TypeError: bad input`, `thrown string: plain string`, `thrown number`, `ok`. JavaScript lets you throw any value, so a caught error is genuinely unknown; the three branches show why reading `.message` directly is unsafe. Since 4.4 `useUnknownInCatchVariables` gives caught errors this type by default under `strict`.
function describe(e: unknown): string {
if (e instanceof Error) return `${e.name}: ${e.message}`;
if (typeof e === "string") return `thrown string: ${e}`;
return `thrown ${typeof e}`;
}
function attempt(fn: () => void): string {
try {
fn();
return "ok";
} catch (e: unknown) {
// e.message here would be: 'e' is of type 'unknown'.
return describe(e);
}
}
console.log(attempt(() => { throw new TypeError("bad input"); }));
console.log(attempt(() => { throw "plain string"; }));
console.log(attempt(() => { throw 42; }));
console.log(attempt(() => {}));unknown versus any at an API boundary
`loadUnsafe` returns the malformed object untouched, so `.toFixed?.(0)` prints `undefined` — the `Config` annotation never checked anything. `loadSafe` prints `retries must be a number` and then the valid config, because `unknown` forced the checks to exist before the value could be returned.
interface Config { retries: number; endpoint: string }
function loadUnsafe(raw: any): Config {
return raw; // compiles, checks nothing
}
function loadSafe(raw: unknown): Config {
if (typeof raw !== "object" || raw === null) throw new Error("not an object");
const o = raw as Record<string, unknown>;
if (typeof o.retries !== "number") throw new Error("retries must be a number");
if (typeof o.endpoint !== "string") throw new Error("endpoint must be a string");
return { retries: o.retries, endpoint: o.endpoint };
}
const bad = { retries: "three", endpoint: "/api" };
console.log(loadUnsafe(bad).retries.toFixed?.(0));
try { loadSafe(bad); } catch (e) { console.log((e as Error).message); }
console.log(loadSafe({ retries: 3, endpoint: "/api" }));
Discussion