Property does not exist on type.
Three fixes, three causes.
The compiler says a property doesn't exist. Sometimes it's right — your type is missing a field. Sometimes it's wrong — the property exists at runtime but your type doesn't know. Sometimes the property exists on only one member of a union. Each cause needs a different fix. Here are all three, with the actual error message and the actual solution.
Short answer: Read the error — it tells you the type and the missing property. If the property should be on the type, add it to the interface. If the object is one of several shapes, narrow with a type guard (typeof, in, or a custom guard). If the object is genuinely dynamic, use Record or an index signature. Never use as any — it hides every other type error in the line.
✅ Solution 1 — Add the missing property to your type
The most common case. The type is incomplete.
You're accessing user.name, but your User interface only declares id and email. The compiler is correct — the type doesn't have name. Fix: add it to the interface. If the property is optional at runtime, mark it with ?.
// ─── The error ─────────────────────────────── interface User { id: string; email: string; } const user: User = { id: '1', email: 'a@x.com' }; // console.log(user.name); // ❌ Property 'name' does not exist on type 'User' // ─── Fix 1: add the property ───────────────── interface User2 { id: string; email: string; name: string; // ← added } const user2: User2 = { id: '1', email: 'a@x.com', name: 'Alice' }; console.log(user2.name); // ✅ no error // ─── Fix 1b: optional property ─────────────── interface User3 { id: string; email: string; name?: string; // ← optional — may be missing at runtime } const user3: User3 = { id: '1', email: 'a@x.com' }; // console.log(user3.name.length); // ❌ 'name' possibly undefined console.log(user3.name?.length); // ✅ optional chaining // ─── Fix 1c: when the property isn't known upfront // If the response shape varies, model it as a union: interface BaseUser { id: string; email: string; } interface PersonalUser extends BaseUser { kind: 'personal'; name: string; } interface BusinessUser extends BaseUser { kind: 'business'; companyName: string; } type AnyUser = PersonalUser | BusinessUser; function displayName(u: AnyUser): string { if (u.kind === 'personal') return u.name; // ✅ narrowed return u.companyName; // ✅ narrowed } console.log(user2.name, user3.name, displayName({ id: '2', email: 'b@x.com', kind: 'personal', name: 'Bob' }));
// Interfaces erased. All runtime values remain. const user = { id: '1', email: 'a@x.com' }; const user2 = { id: '1', email: 'a@x.com', name: 'Alice' }; console.log(user2.name); const user3 = { id: '1', email: 'a@x.com' }; console.log(user3.name?.length); function displayName(u) { if (u.kind === 'personal') return u.name; return u.companyName; } console.log(user2.name, user3.name, displayName({ id: '2', email: 'b@x.com', kind: 'personal', name: 'Bob' }));
Read the error
Property 'name' does not exist on type 'User' — names both the property and the type. The fix is usually "add name to User."
Optional with ?
If the field is sometimes missing at runtime, use name?: string. The type becomes string | undefined, and you'll need to narrow before use.
Union for varying shapes
If a response can have different fields in different cases, model it as a discriminated union — not one giant optional-field interface.
Keep types close to data
Interface drift happens when types live far from where data is created. Fix: define types next to the code that uses them.
✅ Solution 2 — Narrow with a type guard
The property exists on only some members of a union.
If your type is a union (Circle | Square), the compiler only allows access to properties that exist on every member. Accessing .radius on the union fails because Square doesn't have it. Fix: narrow to the specific member with a discriminant, typeof, in, or instanceof.
// ─── The error ─────────────────────────────── type Shape = | { kind: 'circle'; radius: number } | { kind: 'square'; side: number }; function area(s: Shape): number { // return s.radius; // ❌ Property 'radius' does not exist on type 'Shape'. // Property 'radius' does not exist on type '{ kind: "square"; side: number }'. // ─── Fix 1: discriminate by kind ────────── if (s.kind === 'circle') return Math.PI * s.radius ** 2; // ✅ narrowed return s.side ** 2; // ✅ narrowed } // ─── Fix 2: use in for shape-based narrowing function describe(s: Shape): string { if ('radius' in s) return circle with r=${s.radius}square with side=${s.side}; // ✅ narrowed } // ─── Fix 3: typeof narrowing for primitive unions function process(x: string | number): string { // return x.toUpperCase(); // ❌ Property 'toUpperCase' does not exist on type 'string | number' if (typeof x === 'string') return x.toUpperCase(); // ✅ narrowed to string return x.toFixed(2); // ✅ narrowed to number } // ─── Fix 4: custom type guard ──────────────── interface Circle { radius: number } interface Square { side: number } function isCircle(s: Circle | Square): s is Circle { return 'radius' in s; } function area2(s: Circle | Square): number { if (isCircle(s)) return Math.PI * s.radius ** 2; // ✅ narrowed return s.side ** 2; } console.log(area({ kind: 'circle', radius: 2 })); console.log(describe({ kind: 'square', side: 3 })); console.log(process(3.14));
function area(s) { if (s.kind === 'circle') return Math.PI * s.radius ** 2; return s.side ** 2; } function describe(s) { if ('radius' in s) return circle with r=${s.radius}square with side=${s.side}; } function process(x) { if (typeof x === 'string') return x.toUpperCase(); return x.toFixed(2); } function isCircle(s) { return 'radius' in s; } function area2(s) { if (isCircle(s)) return Math.PI * s.radius ** 2; return s.side ** 2; } console.log(area({ kind: 'circle', radius: 2 })); console.log(describe({ kind: 'square', side: 3 })); console.log(process(3.14));
Union property rule
On a union type, you can only access properties that exist on every member. If a property is specific to one member, narrow first.
Discriminant tag
if (s.kind === 'circle') narrows to Circle. The best pattern when each member has a distinct kind/type field.
in operator
if ('radius' in s) narrows by property presence. Works when members have distinct property names but no discriminant.
Custom guards
A function returning x is T narrows when called in an if. Use when you can't express narrowing with typeof or in.
✅ Solution 3 — Use Record or an index signature
For genuinely dynamic objects (parsed JSON, dictionaries).
If the object truly has arbitrary keys — like a JSON response you haven't validated yet — the right type is a dictionary, not a fixed interface. Record or { [key: string]: V } allows property access to any key. This isn't a workaround; it's the correct type for that kind of data.
// ─── The error: unknown JSON shape ─────────── interface User { id: string; name: string; } const data: User = JSON.parse('{"id":"1","name":"Alice","age":28}'); // const age = data.age; // ❌ Property 'age' does not exist on type 'User'. // The JSON has it, but your type doesn't. // ─── Fix 1: extend the type (if the field is known) interface User2 { id: string; name: string; age?: number; // optional — response may or may not include it } const data2: User2 = JSON.parse('{"id":"1","name":"Alice","age":28}'); console.log(data2.age); // ✅ optional access // ─── Fix 2: treat as a dictionary ──────────── const dict: Record<string, unknown> = JSON.parse('{"id":"1","name":"Alice","age":28}'); console.log(dict.age); // ✅ allowed — any key returns unknown // ─── Better fix: validate at the boundary ──── // Don't cast JSON — narrow it with a type guard. function isUser(x: unknown): x is User2 { if (typeof x !== 'object' || x === null) return false; const u = x as Record<string, unknown>; return typeof u.id === 'string' && typeof u.name === 'string'; } const raw: unknown = JSON.parse('{"id":"1","name":"Alice","age":28}'); if (isUser(raw)) { // raw is User2 here — age is number | undefined console.log(raw.name, raw.age); } console.log(data2.age, dict.age);
const data = JSON.parse('{"id":"1","name":"Alice","age":28}'); const data2 = JSON.parse('{"id":"1","name":"Alice","age":28}'); console.log(data2.age); const dict = JSON.parse('{"id":"1","name":"Alice","age":28}'); console.log(dict.age); function isUser(x) { if (typeof x !== 'object' || x === null) return false; const u = x; return typeof u.id === 'string' && typeof u.name === 'string'; } const raw = JSON.parse('{"id":"1","name":"Alice","age":28}'); if (isUser(raw)) { console.log(raw.name, raw.age); } console.log(data2.age, dict.age);
Extend when known
If the field is part of the shape, add it to the interface — age?: number. This is more precise than making everything Record.
Record for truly dynamic
Record is the correct type for parsed JSON before validation. It permits any key while forcing you to handle unknown.
Validate, don't cast
JSON.parse(...) as User is a lie. Use as unknown, then a type guard. Only narrowed values are safe to use.
Index signature vs Record
Both allow arbitrary keys. Record is preferred — it works with utility types and reads more clearly in function signatures.
⚠️ Common mistakes
The patterns that cause this error (and the fixes).
as any to silence the error(user as any).name hides the error — and every other type error in that expression. Fix: add the property, narrow, or use Record.
JSON.parse(text) as User is a lie — the actual shape might not match. Fix: as unknown, then a type guard. Validate before accessing.
(s: Circle | Square).radius fails because Square has no radius. Fix: narrow with a discriminant or in.
?user.name?.length; still fails if name is optional but you treat it as string. Fix: use user.name?.length; or narrow first.
user['name'] works only if the type has an index signature. Otherwise it's the same error. Fix: understand why the error exists, then fix the type.
Your API returns name, but your interface doesn't have it. Fix: update the interface, or generate types from your API schema.
Making every field ?string is the lazy fix — it hides the problem and forces null checks everywhere. Fix: use optional only for genuinely optional fields.
Property does not exist = wrong type. Object is possibly undefined = nullable value. Fix each with the right technique.
❓ Frequently asked
How do I fix "Property does not exist on type" in TypeScript?
Three approaches depending on the cause. (1) If your type is missing a property it should have, fix the type. (2) If the property exists only on some union members, narrow with typeof, in, or a type guard. (3) If the object is truly a dynamic dictionary, use Record or an index signature so the property access is legal.
Why does the property exist at runtime but not in TypeScript?
Because TypeScript only knows about the properties declared in your types. If a JSON response has a 'name' field but your interface doesn't declare it, accessing obj.name is a compile error — even though it works at runtime. Fix: add the property to the interface, or use a type guard.
What does "Object is possibly undefined" mean alongside this error?
It's a related but different error. "Property does not exist on type" means the type doesn't have that property. "Object is possibly undefined" means the variable might be undefined at access time. Both require narrowing — use optional chaining, early returns, or null coalescing.
Should I use as any to fix this error?
No — as any disables type checking for the entire expression, hiding every other type error in that line. Fix the actual type instead: add the property to the interface, narrow with a type guard, or use an index signature if the object is genuinely dynamic.