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.
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.
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.
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.
Function expression requires a return type
Function expression requires a return type
Value of type 'string | undefined' is not iterable
How to fix "Value is not iterable" in TypeScript
Type 'undefined' is not assignable to type 'string'
How to fix "Type undefined is not assignable to type string" in TypeScript
Type narrowing from typeof check produces 'never'
How to fix "Type narrowing produces never" in TypeScript
Type parameter 'T' has conflicting constraints
How to fix "Type parameter has conflicting constraints" in TypeScript