Introduction: Why Adding a Property to window is a Headache for Every TypeScript Developer?
Imagine a scenario: you are writing a modern SPA application in TypeScript. Everything goes smoothly until you need to store global state or integrate with an external script (e.g., analytics, maps, or a payment gateway). You write:
window.myAppConfig = { apiKey: '123' };And then TypeScript gives you a red error: "Property 'myAppConfig' does not exist on type 'Window & typeof globalThis'". Sound familiar? This question has gathered thousands of views on Stack Overflow, and today we will not only provide a ready-made solution but also look under the hood of TypeScript to understand why this happens and how to deal with it elegantly and safely.
In this article, we will break down several approaches — from "crutches" for beginners to professional patterns used by senior developers. Let's go!
Under the Hood: Why TypeScript Complains About window.myProp?
To understand the problem, we need to remember that TypeScript is a superset of JavaScript that adds static typing. The window object in the browser is a global object that has a predefined type (Window). This type is described in the standard TypeScript libraries (lib.dom.d.ts) and includes all standard properties: document, location, setTimeout, and hundreds of others.
When you try to assign window.myProp, TypeScript checks: "Does this property exist in the Window interface?". No — so it's an error. This is protection against typos and accidental pollution of the global scope. But what if you intentionally want to extend window?
Method 1: The Simplest but Dangerous — as any
The first option that comes to a beginner's mind is to just "shut up" TypeScript using a type assertion:
(window as any).myAppConfig = { apiKey: '123' };Pros: fast, works, requires no additional definitions.
Cons: you lose all the benefits of TypeScript. No autocompletion, no type checking. If you accidentally write myAppConfigg, there will be no error, but the application will crash at runtime. It's like turning off the airbags in a car — you can drive, but it's risky.
When to use: only for quick prototyping or if you are sure the property will be used once and then removed.
Method 2: Extending the Global Interface (Interface Merging)
TypeScript supports declaration merging. This is a key mechanism that allows you to "add" new properties to an existing Window type. To do this, you need to declare an interface with the same name in the global scope.
Create a file global.d.ts (or add it to any .d.ts file in your project):
// global.d.tsinterface Window { myAppConfig: { apiKey: string; debugMode?: boolean; }; myAnalyticsFunction: (event: string, data: object) => void;}Now you can write:
window.myAppConfig = { apiKey: '123' };window.myAnalyticsFunction('page_view', { page: '/home' });
// TypeScript will hint that apiKey is a stringconsole.log(window.myAppConfig.apiKey); // ✅ No errorsHow does this work under the hood? TypeScript automatically merges all interface declarations with the same name. If lib.dom.d.ts already has interface Window { ... }, your interface with the same name will simply add new properties to it. This is called glob