TS2339 Property does not exist on type (tested)

error TS2339: Property 'X' does not exist on type 'Y' fires whenever you access a property TypeScript can't find on a value's declared type. The usual cause is a typo, but the same wording also shows up for union types where only some members have the property, and for a stale lib setting that hides real standard-library methods. We reproduced four distinct cases in a real TypeScript 7 project and recorded the exact text tsc prints for each.

1. A genuine typo or undeclared property

The plain case: the property was never declared.

interface User {
  id: number;
  name: string;
}
const u: User = { id: 1, name: "Ada" };
console.log(u.nmae);

tsc printed:

s1.ts(6,15): error TS2339: Property 'nmae' does not exist on type 'User'.

Fix: this is almost always a typo (compare against the interface) or a property you forgot to add to the type. Editors with TypeScript language-service support usually offer an autocomplete-based spelling suggestion right at the error location.

2. Union types: only some members have the property

We declared two interfaces that share a discriminant but not their methods, then called a method that only one of them has:

interface Cat { kind: "cat"; meow(): void; }
interface Dog { kind: "dog"; bark(): void; }
function speak(a: Cat | Dog) {
  a.meow();
}

tsc printed a TS2339 that also names the specific union member missing the property:

s2.ts(4,5): error TS2339: Property 'meow' does not exist on type 'Cat | Dog'.
  Property 'meow' does not exist on type 'Dog'.

Fix: narrow the union before accessing the property. We added a check on the shared discriminant field and the same code compiled clean:

function speak(a: Cat | Dog) {
  if (a.kind === "cat") {
    a.meow();
  } else {
    a.bark();
  }
}
// tsc: no errors

A typeof check, an instanceof check, or a user-defined type guard works the same way — TypeScript narrows the union inside each branch so only the members that actually have the property are reachable.

3. unknown vs any: not the same error

These two "escape hatch" types behave completely differently, and the mix-up is common enough to measure directly:

function f(x: unknown, y: any) {
  console.log(x.toUpperCase());
  console.log(y.toUpperCase());
}

tsc flagged only the unknown line, and with a different error code — not TS2339:

s3.ts(2,15): error TS18046: 'x' is of type 'unknown'.

The y.toUpperCase() line produced no error at all. any opts a value out of type checking entirely, so every property access is allowed, correct or not — it will only fail at runtime if the property genuinely isn't there. unknown is the type-safe alternative: it allows a value of any shape to flow in, but blocks every operation on it (TS18046, not TS2339) until you narrow it. Adding a typeof check fixed it:

function f(x: unknown) {
  if (typeof x === "string") {
    console.log(x.toUpperCase());
  }
}
// tsc: no errors

If you're searching for "property does not exist" on an unknown value and not finding your exact wording, check for TS18046 instead — it's the code unknown actually produces.

4. replaceAll "does not exist": usually a lib/target problem, not a typo

This is the case behind a lot of searches for this exact error on String.prototype.replaceAll. We compiled this with an older target:

const s: string = "a,b,c";
console.log(s.replaceAll(",", "-"));
npx tsc --target ES2020 --noEmit s4.ts
s4.ts(2,15): error TS2550: Property 'replaceAll' does not exist on type 'string'. Do you need to change your target library? Try changing the 'lib' compiler option to 'es2021' or later.

Note the code: TS2550, not TS2339, and the message tells you the fix directly. replaceAll was added to the standard library's type definitions in ES2021. With an older target (which also sets the default lib), TypeScript's bundled type definitions simply don't declare that method, so it looks exactly like a missing property even though the method exists at runtime in any modern JS engine. Two fixes, both confirmed clean:

npx tsc --target ES2021 --noEmit s4.ts
// no errors

npx tsc --target ES2020 --lib ES2021,DOM --noEmit s4.ts
// no errors

In a real project, set this once in tsconfig.json ("target": "ES2021" or higher, or an explicit "lib" array that includes "ES2021") rather than per compile.

5. Quick checklist

Frequently asked questions

Is 'property does not exist on type' always TS2339?

No. In our tests, a plain missing property and a union type missing it on one member both gave TS2339. But unknown gave TS18046, and a missing-in-this-lib standard method (like replaceAll on an old target) gave TS2550, with a different message pointing at target/lib.

Why does TypeScript let me call a method on any but not unknown?

any opts out of type checking entirely, so we measured zero errors calling toUpperCase() on a value typed any. unknown is the safe version: it blocked the same call with TS18046 until we added a typeof check to narrow it.

Why does TypeScript say replaceAll does not exist on string when it works at runtime?

In our test this was TS2550, caused by target: ES2020 (or lower), which also sets an older default lib. replaceAll's type declarations were added in ES2021's lib. Raising target to ES2021+, or adding ES2021 to an explicit lib array, fixed it with no code changes.

How do I fix TS2339 on a union type?

We confirmed narrowing fixes it: add a check (we used a discriminant field, a.kind === 'cat') before accessing a property that only some union members have, and TypeScript allows the access inside that branch.

Does a typo in a property name always get caught at compile time?

Only if the value has a known type. In our any test, a typo'd property name on an any-typed value compiled with zero errors; it would only surface as a runtime TypeError. Typed values (interfaces, unions, string, etc.) are what TS2339 protects.

Written with AI assistance. Measured on 2026-10-03 with TypeScript 7.0.2 (local npm install, not npx), Node.js 24.21.0 on Linux. Each scenario is its own minimal .ts file compiled with tsc and explicit flags; all error text below is copy-pasted from real tsc runs. PlainHeaders is our extension; curl, Python, Node.js and TypeScript are free.