How to fix Type 'string | number' cannot be used to index type 'X' in TypeScript
TS2536 happens when you try to read or write a property with a broad `string | number` union but the target type only exposes named members or lacks a matching index signature.
Quick fix
Fixes TS2536
function getValue<T, K extends keyof T>(obj: T, key: K) {
return obj[key];
}
const user = { name: "alice", id: 1 };
getValue(user, "name"); // OK
getValue(user, Math.random() > 0.5 ? "name" : "id"); // OK: keyof typeof user- Constrain the accessor key with keyof
- Add an index signature or use Record when you really allow arbitrary keys
- Narrow Object.keys / runtime key lists to keyof typeof
- Guard dynamic keys before indexing
function getValue<T, K extends keyof T>(obj: T, key: K) {return obj[key];}const user = { name: "alice", id: 1 };getValue(user, "name"); // OKgetValue(user, Math.random() > 0.5 ? "name" : "id"); // OK: keyof typeof userType 'string | number' cannot be used to index type 'X'
What this error means
TypeScript only allows bracket access when it is sure the key exists on the other side of the access. A parameter typed as `string | number` (common when iterating `Object.keys()`, accepting user input, or keeping generics too wide) is not guaranteed to match every property of the object, so the compiler raises TS2536: "Type 'string | number' cannot be used to index type 'X'." The error is the compiler stopping you from reading or writing a property that may not exist at runtime; adding a `keyof` constraint, an index signature, or a runtime guard lets the compiler trust the access and removes the error.
Typical symptoms
- TypeScript fails to compile with error TS2536 and the message Type 'string | number' cannot be used to index type 'X'.
- Bracket accesses such as config[key] or state[valueName] are underlined in red in your editor when the key is typed generally.
- Object.keys(obj).map or loop bodies that call obj[key] stop compiling until you assert the keys more narrowly.
- The inferred type of the indexed expression becomes never/unknown even though the value exists in the object.
- Build scripts fail only when strict type checking (noImplicitAny/strict) is enabled, because the compiler enforces the extra safety.
Common causes
- The key expression is typed as string, number, or string | number instead of keyof the target object type.
- The target type does not declare an index signature such as [key: string]: Value or [key: number]: Value.
- You use Object.keys(obj) and keep the result as string[], so TypeScript no longer knows the keys belong to the object.
- Generic parameters are not constrained with extends keyof T, so the compiler treats them as arbitrary strings/numbers.
- A string or number from JSON, user input, or a union literal is used before you validate or narrow it against the object.
- You expect Record<string, T> semantics but the concrete type only exposes specific property names.
How to fix "Type 'string | number' cannot be used to index type 'X'"
Tell TypeScript the key is one of the valid properties before indexing. This guarantees the compiler that the key belongs to the object and fixes TS2536:
function getValue<T, K extends keyof T>(obj: T, key: K) {
return obj[key];
}
const user = { name: "alice", id: 1 };
getValue(user, "name"); // OK
getValue(user, Math.random() > 0.5 ? "name" : "id"); // OK: keyof typeof userIf the type is meant to act like a dictionary, declare an index signature or use Record so both string and number keys are accepted:
type Settings = Record<string, number>;
const config: Settings = {};
function setConfig(key: string, value: number) {
config[key] = value; // allowed because Settings has [key: string] index
}Use a number index signature (for example, [key: number]: Value) when you index arrays by number or when you really allow number indexes, because TypeScript treats number and string indexers separately even though JavaScript coerces numbers to strings.
Object.keys(obj) returns a string[], which is why the compiler rejects indexing by its result. Narrow the keys back to the known property union before use:
const state = { a: 1, b: 2 } as const;
const keys = Object.keys(state) as Array<keyof typeof state>;
keys.forEach((key) => {
state[key]; // safe: key is keyof typeof state
});Alternatively, keep the key list as a constant tuple with as const so the literal union stays precise.
When the key comes from user input or an unknown source, validate it against the object first. The in operator plus a cast to keyof keeps TypeScript happy:
function readField<T>(obj: T, key: string) {
if (key in obj) {
return obj[key as keyof T]; // safe after the guard
}
throw new Error("Invalid key");
}If you still need to use the value afterward, keep the narrowed key in a variable typed as keyof T.
Advanced notes
The compiler treats string and number indexers differently, so "string | number" unions do not automatically satisfy either index signature. When iterating a fixed list of keys, keep that list as const so TypeScript knows the union of literal names instead of general strings. If you rely on dictionaries with truly arbitrary keys, prefer Record<string, T> or Map<string, T>, which declare the index signature up front. Combining keyof typeof obj with extends keyof T is the modern idiom for keeping dynamic access type-safe.
Frequently asked questions
- What does "TS2536" mean?
- TypeScript only allows bracket access when it is sure the key exists on the other side of the access. " The error is the compiler stopping you from reading or writing a property that may not exist at runtime; adding a keyof constraint, an index signature, or a runtime guard lets the compiler trust the access and removes the error.
- How do I fix "TS2536"?
- TS2536 happens when you try to read or write a property with a broad string | number union but the target type only exposes named members or lacks a matching index signature.
- What causes "TS2536"?
- The key expression is typed as string, number, or string | number instead of keyof the target object type. The target type does not declare an index signature such as [key: string]: Value or [key: number]: Value. You use Object.keys(obj) and keep the result as string[], so TypeScript no longer knows the keys belong to the object.
- Is the "TS2536" error serious?
- It is a medium-severity error โ more of a blocker than a risk. The steps above resolve it.
Official resources & further reading
- ๐TypeScript Official Documentation
- ๐TypeScript Handbook - Interfaces and Index Signatures(Official Docs)
- ๐TypeScript Handbook - Indexed Access Types(Official Docs)