keyof

Produces the union of an object type's property names as string or numeric literal types.

Signature
keyof Type
  • Added in TypeScript 2.1

Parameters

ParameterTypeDescription
$TypetypeThe object type, interface, class, tuple or type parameter whose keys are collected.

Return value

A union of string, number and symbol literal typesThe union of every known key of Type. For a type with a number index signature the result is number; for a string index signature it is string | number, because JavaScript coerces numeric keys to strings.

Errors

Passing a key that is not in the union is a compile-time error, such as Argument of type '"wargarbl"' is not assignable to parameter of type '"foo" | "bar"'.

Changelog

  • PHP 2.1Introduced alongside indexed access (lookup) types.
  • PHP 2.9keyof now yields number- and symbol-named keys as well as string keys. Code that assumed keyof T was assignable to string began failing and needs Extract<keyof T, string> or string & keyof T.

keyof takes an object type and hands back a union of its key names. keyof { x: number; y: number } is "x" | "y". On its own that is mildly interesting; paired with a generic parameter it becomes the backbone of every type-safe accessor you will write.

When to reach for it

Whenever a function accepts a property name as an argument. Constrain the parameter with K extends keyof T and the compiler rejects misspelled keys at the call site and knows the return type is T[K] rather than a shrugging union of every value type.

It is also the input side of every mapped type: [P in keyof T] is how Partial, Readonly and friends enumerate properties.

What trips people up

Index signatures do not behave like literal keys. A number index signature gives you plain number, and a string index signature gives you string | number — not string — because obj[0] and obj["0"] are the same property in JavaScript. That surprise number is the single most common reason a keyof result refuses to go where a string is expected.

The other trap is keyof on a value. It only takes types. To get the keys of an object you actually have, write keyof typeof myObject.

Example

Try it yourself
Loading editor…
Press Run to execute the code.

When to use it

  • Constrain a get/set helper so only real property names compile
  • Build a column picker whose allowed values track the row interface automatically
  • Derive the allowed keys of a config object for a validation function

Frequently asked questions

What is the difference between keyof T and Object.keys(t)?

keyof T is a compile-time type and Object.keys is a runtime function, and they disagree on purpose. Object.keys returns string[] rather than (keyof T)[] because a value may carry extra properties beyond the ones its type declares, so TypeScript refuses to promise the keys are exactly the declared ones.

Why does keyof give me string | number instead of string?

Because the type has a string index signature. JavaScript coerces numeric keys to strings, so obj[0] and obj["0"] are the same slot and TypeScript includes number in the union. Use Extract<keyof T, string> or string & keyof T to drop it.

How do I get the keys of an object I already have?

Combine it with a type query: keyof typeof myObject. keyof only accepts a type, and a variable is a value, so typeof lifts the value into type space first. Pair it with as const if you also want the values as literal types.

More examples

Index signatures change the answer

Point has two declared properties, so keyof is a two-member literal union. Arrayish has a numeric index signature and collapses to number. Mapish is the surprise: a string index signature produces string | number because numeric property access is string access underneath. Extract is the usual fix when a downstream API insists on string.

Example · typescript
type Point = { x: number; y: number };
type P = keyof Point;
// type P = "x" | "y"

type Arrayish = { [n: number]: unknown };
type A = keyof Arrayish;
// type A = number

type Mapish = { [k: string]: boolean };
type M = keyof Mapish;
// type M = string | number

type OnlyStrings = Extract<keyof Mapish, string>;
// type OnlyStrings = string

A type-safe pluck over an array of rows

The return type tracks the key that was passed: asking for "name" gives string[] and asking for "id" gives number[], so .join and .reduce are both legal without a cast. The commented line is the payoff — a typo in the key is a compile error rather than an array of undefined at runtime.

Example · typescript
type Row = { id: number; name: string; active: boolean };

function pluck<T, K extends keyof T>(rows: T[], key: K): T[K][] {
  return rows.map((row) => row[key]);
}

const rows: Row[] = [
  { id: 1, name: "alice", active: true },
  { id: 2, name: "bob", active: false },
];

const names = pluck(rows, "name"); // string[]
const ids = pluck(rows, "id");     // number[]
console.log(names.join(","), ids.reduce((a, b) => a + b, 0));
// alice,bob 3

// pluck(rows, "nmae"); // error: not assignable to "id" | "name" | "active"

Discussion

  • Be the first to comment on this lesson.