Marius Schulz
Marius Schulz
Front End Engineer

Declaring Global Variables in TypeScript

Every now and then, you might want to statically type a global variable in TypeScript. For example, in some of my web applications, I need to pass a few properties from my markup rendered on the server to my JavaScript code running in the browser. To do that, I typically define a global variable named __INITIAL_DATA__ within an inline script and assign to it a JSON-serialized object:

<script>
  window.__INITIAL_DATA__ = {
    userID: "536891193569405430",
  };
</script>

Now, if I try to access window.__INITIAL_DATA__ in a TypeScript file, the compiler will produce a type error because it can't find a definition of the __INITIAL_DATA__ property anywhere:

// Property '__INITIAL_DATA__' does not exist
// on type 'Window & typeof globalThis'
const initialData = window.__INITIAL_DATA__;

I'm going to show you a few different approaches for letting TypeScript know about the window.__INITIAL_DATA__ property and making the type error go away.

#Using a Type Assertion

The quickest way to make the type error go away is to use the any type in a type assertion. We can treat the window object to be of type any so that we can access its __INITIAL_DATA__ property:

const initialData = (window as any).__INITIAL_DATA__;

This solution works, and we longer get a type error. This is a pragmatic approach if you need an ad-hoc way to access a property on the window object that TypeScript doesn't know about.

The (window as any).__INITIAL_DATA__ expression is of type any, and therefore initialData is of type any too. We could go one step further and use another type assertion to give the initialData variable a more specific type:

type InitialData = {
  userID: string;
};

const initialData = (window as any).__INITIAL_DATA__ as InitialData;

Now, we can access initialData.userID in a type-safe way:

const userID = initialData.userID; // Type string

Do keep in mind that this is not a guarantee that window.__INITIAL_DATA__ will be set correctly at runtime. The type checker trusts us and it is our job to make sure that we assign an object with the expected shape to window.__INITIAL_DATA__.

#Declare a Global Variable

Another approach is to declare a global variable using the declare var syntax. This way, we can let TypeScript know that it can expect to find a global variable with the given name and type:

declare var __INITIAL_DATA__: InitialData;

We can now access the __INITIAL_DATA__ variable directly …

const initialData = __INITIAL_DATA__;

… or off of the window object:

const initialData = window.__INITIAL_DATA__;

Note that the access via window.__INITIAL_DATA__ will not work from within an ECMAScript module. If your JavaScript file contains top-level import or export declarations, it is considered a module, and you will receive a type error if you try to access the __INITIAL_DATA__ on the window object.

You can declare a global variable in the global scope by using the declare global { ... } syntax to be able to access both window.__INITIAL_DATA__ as well as __INITIAL_DATA__ directly within a JavaScript module.:

export function someExportedFunction() {
  // ...
}

declare global {
  var __INITIAL_DATA__: InitialData;
}

const initialData = window.__INITIAL_DATA__;

If you need to access window.__INITIAL_DATA__ in several files or modules, it might be a good idea to create a globals.d.ts file in your project. In that file, you can declare all global variables you'll use:

declare var __INITIAL_DATA__: InitialData;

As long as globals.d.ts is part of your TypeScript project, the compiler will know that __INITIAL_DATA__ is a global variable, and it will let you access it via both __INITIAL_DATA__ as well as window.__INITIAL_DATA__.

#Augmenting the Window Interface

Lastly, you can use TypeScript's interface declaration merging to let the compiler know that it can expect to find a property named __INITIAL_DATA__ on the Window type and therefore the window object. To do that, you'll need to define an interface named Window with a property named __INITIAL_DATA__:

interface Window {
  __INITIAL_DATA__: InitialData;
}

TypeScript will merge this interface definition together with the Window interface defined in lib.dom.d.ts, resulting in a single Window type. Now, the following assignment will no longer produce a type error:

const initialData = window.__INITIAL_DATA__;

Note that once again, this approach will not work within a JavaScript module. You'll need to use the declare global { ... } syntax again in order for the window.__INITIAL_DATA__ expression to type-check correctly:

export function someExportedFunction() {
  // ...
}

declare global {
  interface Window {
    __INITIAL_DATA__: InitialData;
  }
}

const initialData = window.__INITIAL_DATA__;