error TS2538: Type 'undefined' cannot be used as an index type. is reported for object[key] when the key's type includes something that cannot be a property key. In JavaScript projects that something is nearly always undefined, from an optional property used as the key.
At run time the code would quietly use the key "undefined": totals["undefined"] collects every item without a category. The checker reports it instead.
At a glance
| Code | TS2538 |
|---|---|
| Message | Type '{0}' cannot be used as an index type. |
| TypeScript's category | error |
| Listed under | Null, undefined and unknown |
| Turned on by | strictNullChecks, strict |
| In .js files | Reported when the file is checked: checkJs in jsconfig.json, or // @ts-check at the top of the file |
The message is TypeScript's own text from its diagnostic messages file (Apache-2.0); {0} is filled in by tsc for each error.
What it looks like
/** @typedef {{ name: string, price: number, category?: string }} LineItem */
/** @param {LineItem[]} items */
export function totalsByCategory(items) {
/** @type {Record<string, number>} */
const totals = {};
for (const item of items) {
totals[item.category] = (totals[item.category] ?? 0) + item.price;error TS2538: Type 'undefined' cannot be used as an index type.error TS2538: Type 'undefined' cannot be used as an index type.
}
return totals;
}jsconfig.json for this example
{
"compilerOptions": {
"checkJs": true,
"noEmit": true,
"strict": true,
"target": "es2022",
"module": "nodenext",
"types": []
},
"include": ["*.js"]
} What causes it
- The key comes from an optional property or parameter (
item.categorywherecategory?: string). - The key's type is an object or another type that is not
string,numberorsymbol.
How to fix it
Choose a fallback key, or skip the item
const key = item.category ?? "other" gives every item a real key; if (!item.category) continue; leaves uncategorized items out. Either way the decision is in the code instead of in an accidental "undefined" entry.
/** @typedef {{ name: string, price: number, category?: string }} LineItem */
/** @param {LineItem[]} items */
export function totalsByCategory(items) {
/** @type {Record<string, number>} */
const totals = {};
for (const item of items) {
const key = item.category ?? "other";
totals[key] = (totals[key] ?? 0) + item.price;
}
return totals;
}jsconfig.json for this example
{
"compilerOptions": {
"checkJs": true,
"noEmit": true,
"strict": true,
"target": "es2022",
"module": "nodenext",
"types": []
},
"include": ["*.js"]
} Gotchas
- Without
noUncheckedIndexedAccess, readingtotals[key]from aRecord<string, number>is typednumbereven for a key that was never set, which is why the example writes?? 0itself. - Under
noImplicitAny, astringkey on an object without an index signature is TS7053, a different error.
Sources
- Object Types: Index Signatures · TypeScript handbook
- Property accessors · MDN Web Docs
- diagnosticMessages.json (TypeScript 5.9.3) · TypeScript on GitHub
Last reviewed 20 October 2026.