This Crazy Syntax Lets You Get An Array Element's Type
Learn how to extract the type of an array element in TypeScript using the powerful Array[number]
trick.
In pretty much any frontend application, you'll likely have encountered this error:
Property 'X' does not exist on type 'Window & typeof globalThis'.
window .X ;Property 'X' does not exist on type 'Window & typeof globalThis'.2339Property 'X' does not exist on type 'Window & typeof globalThis'.
In this article, we'll lay out several different solutions to this issue.
The Window
interface is defined globally in a file called lib.dom.d.ts
. You can change it using various techniques:
.ts
or .tsx
file, you can use declare global
and interface Window
:declare global {
interface Window {
X : number;
}
}
window .X ;
.d.ts
file, you can just specify interface Window
.interface Window {
X: number;
}
declare const window
in a .ts
or .tsx
file:declare const window : {
X : number;
} & Window ;
window .X ;
The interface Window
lives in the global scope in TypeScript. It ships as part of the DOM types in lib.dom.d.ts
, which describes what methods are available in the browser.
The issue comes up when a third-party script (or perhaps our own code) adds something to the window:
export {};
declare const window: {
X: number;
} & Window;
// ---cut---
window.X = Date.now();
This is a problem because TypeScript doesn't know about the X
property, and so it throws an error when you try to access it:
window .X ;Property 'X' does not exist on type 'Window & typeof globalThis'.2339Property 'X' does not exist on type 'Window & typeof globalThis'.
So, we need to somehow change the global definition of Window
to include the new property that TypeScript doesn't know about.
declare global
in a .ts
or .tsx
fileThe first solution is to use declare global
to change the global definition of Window
:
declare global {
interface Window {
X : number;
}
}
window .X ;
This works for two reasons. First, declare global
tells TypeScript that anything inside it should be added to the global scope.
Second, we take advantage of declaration merging. This is a rule in TypeScript where interfaces with the same name in the same scope get merged. So by redeclaring Window
in the same scope, we can append new properties to it.
This wouldn't work if we used type
because types don't support declaration merging.
declare global {
type Window = {Duplicate identifier 'Window'.2300Duplicate identifier 'Window'. X : number;
};
}
This solution only works inside a .ts
or .tsx
file because declare global
only works inside a module. So this solution is a little awkward in terms of where you place the definition in your project. In its own module? Colocated with something else?
.d.ts
fileA neater solution is to use interface Window
in a .d.ts
file:
// window.d.ts (choose any filename you like)
interface Window {
X: number;
}
// your-app.ts
window .X ;
This works because anything you place in a .d.ts
automatically goes into the global scope, so there's no need for declare global
.
It also lets you place the global definition in a single file, on its own, which feels a little cleaner than trying to figure out which .ts
file to put it in.
If you're concerned about adding types into the global scope, you can use declare const window
to override the type of window
in a single module:
declare const window : {
X : number;
} & Window ;
window .X ;
declare const window
acts like const window
, but on the type level. So it only acts on the current scope — in this case, the module. We declare the type as the current Window
, plus the new property X
.
This solution gets a little annoying if you need to access window.X
in multiple files because you'll need to add the declare const window
line to each file.
Personally, I tend to reach for solution 2 — a .d.ts
file. It's the fewest lines of code and the easiest to place in your project.
I don't mind adding types into the global scope. By actually changing the type of Window
, you're more accurately describing the environment your code executes in. In my book, that's a bonus.
But if you're really concerned about it, use the declare const
solution.
Share this article with your friends
Learn how to extract the type of an array element in TypeScript using the powerful Array[number]
trick.
Learn how to publish a package to npm with a complete setup including, TypeScript, Prettier, Vitest, GitHub Actions, and versioning with Changesets.
Enums in TypeScript can be confusing, with differences between numeric and string enums causing unexpected behaviors.
Is TypeScript just a linter? No, but yes.
It's a massive ship day. We're launching a free TypeScript book, new course, giveaway, price cut, and sale.
Learn why the order you specify object properties in TypeScript matters and how it can affect type inference in your functions.