Skip to content
TypeScript ts basic-types 4 min read

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

Aspectanyunknown
Position in type systemDisables the systemTop type
Assign anything to itYesYes
Assign it to other typesYes (unsafe)Only after narrowing
Use without checkingYesNo
Type safety preservedNoYes
Best forAvoidUntrusted / dynamic input

Reach for unknown at every untyped boundary — JSON.parse, fetch bodies, localStorage, message events, and catch clauses — then validate once and work with a real type from there.

Best Practices

  • Default to unknown instead of any for any value of undetermined type.
  • Always narrow with typeof, instanceof, in, or a type guard before using the value.
  • Annotate JSON.parse results and external input as unknown, then validate them.
  • Enable useUnknownInCatchVariables (via strict) so caught errors are unknown.
  • Write reusable value is T type guards to convert unknown into domain types.
  • Validate untrusted data with a schema library, then trust the inferred output type.
  • Never assert unknown straight to a concrete type without a runtime check first.
Last updated June 29, 2026
Was this helpful?