Learn why TypeScript throws TS7053 when a string key indexes an object with no matching index signature, and how to fix it with keyof typeof or Record.
TS7053 fires when you read or write a property with bracket notation — stats[key] — and TypeScript cannot work out which property you mean. The static type of key (usually string, but sometimes a literal like "user-1") is not one of the known property names of the object, and the object's type has no index signature that would accept it.
Because the compiler cannot determine the element type, the only type it could give the expression is any. Under noImplicitAny — which strict turns on — an implicit any is an error rather than a silent fallback. That is why this error is scoped to strict projects: with noImplicitAny: false the same code compiles and the element quietly becomes any.
The error usually arrives as two lines. The second one, TS7054, is the elaboration and is often what you see quoted in an editor tooltip:
// The general shape of the error:
// Element implicitly has an 'any' type because expression of type 'string'
// can't be used to index type '{ views: number; likes: number; }'.
// No index signature with a parameter of type 'string' was found on type
// '{ views: number; likes: number; }'.TS7053 was introduced in TypeScript 3.5, when string-keyed indexing stopped being implicitly any — which is why it shows up in nearly every "we upgraded and now the build is red" thread from that era.
Watch out for the neighbouring codes: bracket access on window or an array-like type reports TS7015 (the index expression must be a number there), dotted access on globalThis reports TS7017, and a missing property reached with a dot gives TS2339. TS2536 is the non-implicit-any sibling — it fires when an index type is unusable regardless of noImplicitAny.
string VariableThis is the classic one. Object.keys() is typed as string[], so the key you get back has lost every trace of which object it came from.
// ❌ Broken
const postStats = { views: 120, likes: 8, shares: 3 }
Object.keys(postStats).forEach((key) => {
console.log(`${key}: ${postStats[key]}`)
// ~~~~~~~~~~~~~~ Error: Element implicitly has an 'any' type
// because expression of type 'string' can't be used to index
// type '{ views: number; likes: number; shares: number; }'.
})// ✅ Fixed — narrow the keys to the ones the object actually has
const postStats = { views: 120, likes: 8, shares: 3 }
const statKeys = Object.keys(postStats) as Array<keyof typeof postStats>
statKeys.forEach((key) => {
console.log(`${key}: ${postStats[key]}`) // number
})
// Or skip the keys entirely — Object.entries hands you the value already typed
Object.entries(postStats).forEach(([key, value]) => {
console.log(`${key}: ${value}`)
})const profileCache = {} gives the variable the type {}, which has no properties at all. Every key fails — even a string literal you typed by hand.
// ❌ Broken
interface UserProfile {
id: string
displayName: string
}
const profileCache = {}
function rememberProfile(profile: UserProfile) {
profileCache['user-1'] = profile
// ~~~~~~~~~~~~~~~~~~~ Error: Element implicitly has an 'any' type because
// expression of type '"user-1"' can't be used to index type '{}'.
}// ✅ Fixed — declare what the cache actually holds
interface UserProfile {
id: string
displayName: string
}
const profileCache: Record<string, UserProfile> = {}
function rememberProfile(profile: UserProfile) {
profileCache[profile.id] = profile
}An API returns "shipped" and you want the matching enum member. Indexing typeof OrderStatus with a plain string is exactly the pattern TS7053 rejects.
// ❌ Broken
enum OrderStatus {
Pending = 'pending',
Shipped = 'shipped',
Delivered = 'delivered',
}
function parseStatus(raw: string) {
return OrderStatus[raw]
// ~~~~~~~~~~~~~~~ Error: expression of type 'string' can't be used to
// index type 'typeof OrderStatus'.
}// ✅ Fixed — prove the key exists before you use it
enum OrderStatus {
Pending = 'pending',
Shipped = 'shipped',
Delivered = 'delivered',
}
function parseStatus(raw: string): OrderStatus | undefined {
return raw in OrderStatus ? OrderStatus[raw as keyof typeof OrderStatus] : undefined
}The in check is doing real work here: it means the assertion on the next line is backed by a runtime guarantee instead of wishful thinking.
object Instead of a Genericobject means "some non-primitive value" — it promises no properties, so nothing can index it usefully.
// ❌ Broken
function pluck(source: object, field: string) {
return source[field]
// ~~~~~~~~~~~~~ Error: expression of type 'string' can't be used to
// index type '{}'.
}// ✅ Fixed — let the caller's types flow through the helper
function pluck<T extends object, K extends keyof T>(source: T, field: K): T[K] {
return source[field]
}
const order = { id: 'ord-91', total: 42 }
const total = pluck(order, 'total') // number, not anyNarrow the key, don't widen the object. The object's type is usually correct — it is the key that has been flattened to string. Reach for keyof typeof someObject first, and only change the object's type if the keys really are open-ended.
Use a generic when you are writing a helper. function read<T, K extends keyof T>(source: T, key: K): T[K] keeps the precise return type for every caller, where object plus string throws all of it away.
Guard with in when the key comes from outside your program. Request bodies, route params and form field names are string for a reason — you genuinely do not know they are valid keys, so check first and return undefined for the rest:
function readStat(stats: PageStats, key: string): number | undefined {
if (key in stats) {
return stats[key as keyof PageStats]
}
return undefined
}Give the object an honest index signature when the keys are dynamic. A translation table, a feature-flag map or a counter keyed by user id should say so with Record<string, T> or [locale: string]: string — that is not a workaround, it is the accurate type.
Don't turn noImplicitAny off, and don't cast to any. suppressImplicitAnyIndexErrors used to silence exactly this error, but it was deprecated in TypeScript 5.0 and removed in 5.5 — code that relies on it will not compile on a modern toolchain. Fixing the key type once is cheaper than the runtime undefined you are otherwise deferring.
TS7053 fires under noImplicitAny when you write obj[key] and the static type of key is not one of the known property names of obj, and obj has no index signature that accepts it. TypeScript cannot compute the element type, so it would have to fall back to any — which strict mode refuses to do silently. The key is most often a string that came from Object.keys(), a route param or a form field name.
Narrow the key rather than loosening the object. Inside a function you own, use keyof typeof someObject or a generic constrained with K extends keyof T; for a key that arrives at runtime, guard it with key in someObject before asserting. If the object legitimately has arbitrary keys — a cache, a lookup table, a counter — type it as Record<string, ValueType> and the indexing becomes valid on its own.
Because TypeScript is structurally typed: a value assigned to PageStats may carry extra properties at runtime, so a keyof PageStats[] return type would be a lie the compiler could not back up. The team has kept Object.keys as string[] deliberately. When you own the object and know it has no extras, Object.keys(obj) as Array<keyof typeof obj> is a reasonable assertion; otherwise prefer Object.entries, which gives you the value directly and sidesteps the index altogether.
Put your understanding to the test with these related challenges.
Or browse all TypeScript practice challenges to keep sharpening your type-level skills.
Track your progress through 100+ hands-on challenges. Free, sign in with GitHub.
Or start solving right away: explore all TypeScript challenges