error TS2339: Property 'dataLayer' does not exist on type 'Window & typeof globalThis'. is reported when code reads or writes a property that the value's type does not declare. When a declared property has a similar name, the checker reports TS2551 and suggests it instead.
JavaScript files get one allowance TypeScript files do not: assigning a property to an object literal declared in the same file declares the property, so building an object up line by line works. Reading a property nothing declared still fails, and so does adding one to window or to an object whose type comes from a JSDoc annotation.
At a glance
| Code | TS2339 |
|---|---|
| Message | Property '{0}' does not exist on type '{1}'. |
| TypeScript's category | error |
| Listed under | Unknown names and members |
| Turned on by | No option: every checked file reports it |
| 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} and {1} are filled in by tsc for each error.
What it looks like
window.dataLayer.push({ event: "add_to_cart" });error TS2339: Property 'dataLayer' does not exist on type 'Window & typeof globalThis'.
const config = { port: 3000 };
export const host = config.host ?? "localhost";error TS2339: Property 'host' does not exist on type '{ port: number; }'.jsconfig.json for this example
{
"compilerOptions": {
"checkJs": true,
"noEmit": true,
"strict": true,
"target": "es2022",
"module": "nodenext",
"types": []
},
"include": ["*.js"]
} What causes it
- A global object (
window,document,globalThis) is given a property by a third-party script, and the DOM types do not list it. - The object's type comes from an annotation or an initializer that does not include the property (
config.hostwhen the object was{ port: 3000 }). - The name is a typo that is too far from any declared name for a suggestion. This happens most with short names:
response.josn()gets TS2339, not a hint forjson.
How to fix it
Declare the property where the type is defined
For window, add an interface Window { ... } block to a .d.ts file the config includes; it merges with the DOM's own Window. For your own objects, add the property to the @typedef or the inline @type, optional if it may be absent.
window.dataLayer.push({ event: "add_to_cart" });
/** @type {{ port: number, host?: string }} */
const config = { port: 3000 };
export const host = config.host ?? "localhost";jsconfig.json for this example
{
"compilerOptions": {
"checkJs": true,
"noEmit": true,
"strict": true,
"target": "es2022",
"module": "nodenext",
"types": []
},
"include": ["*.js", "*.d.ts"]
} Build the object in the same file
In a .js file, an object literal is open-ended: const user = {}; user.name = "Ada"; declares name. A @type annotation on the declaration closes it again.
const user = {};
user.name = "Ada";
user.joined = new Date();
export const greeting = `Hello, ${user.name}`;jsconfig.json for this example
{
"compilerOptions": {
"checkJs": true,
"noEmit": true,
"strict": true,
"target": "es2022",
"module": "nodenext",
"types": []
},
"include": ["*.js"]
} Same idea, different API
Code written for one API often meets another's types. res.statusCode belongs to Node's http response; the fetch Response has status instead. TS2339 on a name you are sure exists usually means the value is a different kind of object than you think: hover over it to see its type.
Gotchas
- A property that exists only on some members of a union is TS2339 until the value is narrowed, for example with
"items" in valueor a check on a sharedkindproperty. globalThis.somethingis reported as TS7017 instead undernoImplicitAny, when no file assigns it.
Sources
- Type Checking JavaScript Files: Object literals are open-ended · TypeScript handbook
- Declaration Merging: Merging Interfaces · TypeScript handbook
- diagnosticMessages.json (TypeScript 5.9.3) · TypeScript on GitHub
Last reviewed 20 October 2026.