Skip to content

TypeScript errorTypeScript: “Property 'x' does not exist on type 'Y'” (TS2339)

The TypeScript compiler or your editor stops with Property 'name' does not exist on type '{}' (or on type 'Window', 'Request', 'never' or a union). The code reads a property that the type, as TypeScript knows it, does not have. Sometimes the type is wrong, sometimes the code needs to check which kind of value it has first.

Short answer

Look at the type named in the message. If it is your own interface, add the property (optional with ? if it is not always there). If it is a union such as Cat | Dog, check which one you have first with 'meow' in pet or typeof before reading the property. If it is {} or never, the variable was typed from an empty initial value: give it a real type, such as useState<User | null>(null). For window.x or Express’s req.user, add the property with a global or module augmentation. Avoid casting to any, which hides real bugs.

Why TypeScript doesn’t know the property, and the fix

  1. 01

    The property is missing from your type

    The interface or type alias does not list the property, or the name is spelled differently from the data. Add it, as optional if the API does not always send it.

    interface User {
      id: string;
      name: string;
      avatarUrl?: string; // optional: some users have none
    }
  2. 02

    A union type that has not been narrowed

    “JavaScript has an operator for determining if an object or its prototype chain has a property with a name: the in operator.” TypeScript, Narrowing: the in operator

    With a value typed as A | B, TypeScript only lets you read properties that both have. Check which one you have first, with the in operator, typeof, or a discriminant field, and TypeScript narrows the type inside that branch.

    type Pet = { meow(): void } | { bark(): void };
    function speak(pet: Pet) {
      if ('meow' in pet) pet.meow();
      else pet.bark();
    }
  3. 03

    The type was inferred from an empty value

    const [user, setUser] = useState({}) makes user type {}, and useState([]) makes an array of never, so every property is unknown. Give the state or variable the type it will hold.

    const [user, setUser] = useState<User | null>(null);
    const items: Product[] = [];
  4. 04

    A global or library type needs an extra property

    “You can also add declarations to the global scope from inside a module” TypeScript, Declaration merging: global augmentation

    window.dataLayer, a value your own middleware puts on Express’s req (req.user) or a custom field on a library type is not in that library’s types. Declare it once in a .d.ts file that your tsconfig includes, instead of casting at every use.

    // types/global.d.ts
    export {};
    declare global {
      interface Window { dataLayer: unknown[] }
      namespace Express {
        interface Request { user?: { id: string } }
      }
    }

Checked against the vendor’s docs on : TypeScript Handbook: Narrowing; TypeScript Handbook: Declaration merging (module and global augmentation).

Rather hand it over?

Fixed scope, fixed price, paid upfront. The work happens in writing; no call needed.

Related: “Cannot read properties of undefined” · TypeError: “x is not a function” · Next.js “window is not defined”

Questions

More on this error

Can I just use (obj as any).name?

It silences the error, but it also turns off every check on that value, so a typo or a missing field becomes a runtime crash. Fix the type or narrow the value instead; keep any for rare cases you document.

Why does it say “on type never”?

TypeScript decided no value can reach that line, usually because an array was typed from [] with no type, or a check already ruled out every case. Type the array, or look at the checks above that line.

Can you fix it for me?

If one bug is the whole problem, the App Fix Ticket covers it: a written diagnosis first, then the fix. If the app has more going on, the Launch Check is a written check of the whole app. Each issue found is rated critical, high, medium or low, with where it is and how it was confirmed. No issues worth fixing, no charge. If the Launch Check finds nothing rated medium or higher, Simon refunds the full $450. You keep the report either way. This applies to the first 10 Launch Checks bought after October 30, 2026.

A different error?

Describe it and get a written quote

Send the error and what you were doing. You get a written scope, a fixed price and a timeline; Simon replies by email, usually within one business day.