declare global

Adds or patches declarations in the global scope from inside a module.

Signature
export {}; // the file must be a module

declare global {
  interface Window { myProp: string }
  interface Array<T> { last(): T | undefined }
  var myGlobal: SomeType;   // must be var, not let or const
  function myFn(): void;
  namespace NodeJS { interface ProcessEnv { API_URL: string } }
}
  • Added in TypeScript 1.8

Parameters

ParameterTypeDescription
$bodydeclarationsDeclarations merged into the global scope. Only patches to existing declarations and new global bindings are allowed — the same limitations as module augmentation.
$enclosing filemoduleThe containing file must be a module, i.e. have a top-level import or export. `export {}` is the conventional way to force this in a file that exports nothing.

Errors

Errors at compile time with 'Augmentations for the global scope can only be directly nested in external modules or ambient module declarations' when used in a file that is not a module, and when a `let` or `const` global is expected to appear on globalThis.

Changelog

  • PHP 1.8Introduced alongside module augmentation. Both share the rule that an augmentation can only patch existing declarations, not introduce new top-level items into another module.
  • PHP 3.4globalThis was added to the standard library, so `var` declarations inside declare global become visible as properties of globalThis.

Inside a module, every declaration is local. declare global opens a hole back into the global scope so a module can describe globals it creates as a side effect — a polyfill, a prototype extension, a value assigned to window.

When to reach for it

  • Adding a property to Window, globalThis or NodeJS.ProcessEnv from a file that already imports something.
  • Typing a prototype method your module installs, such as Array.prototype.last.
  • Declaring a global your bundler injects, when the surrounding file cannot be a plain script.

What trips people up

Three rules cause nearly every failure:

  1. The file must be a module. Without a top-level import or export, TypeScript rejects the block outright. Adding export {} fixes it.
  2. Use var for global variables. Only var declarations become properties of globalThis; let and const create bindings that globalThis.myGlobal cannot see.
  3. You can only patch, not create, inside other declarations. Augmentations extend existing interfaces; they cannot add a new top-level export to another module, and default exports cannot be augmented at all.

Also remember this widens the type for the entire program. If you add last() to Array, every array everywhere claims to have it, whether or not your polyfill was imported.

Example

Example · typescript
export {}; // makes this file a module — required for declare global

declare global {
  interface Array<T> {
    last(): T | undefined;
  }
  interface Window {
    __APP_VERSION__: string;
  }
  var featureFlags: Record<string, boolean>; // var, so it lands on globalThis
}

Array.prototype.last = function () {
  return this[this.length - 1];
};

globalThis.featureFlags = { darkMode: true };
console.log([1, 2, 3].last(), featureFlags.darkMode);
// 3 true

When to use it

  • Type a value your app assigns to window during bootstrap so consumers stop casting to any.
  • Give process.env named, non-optional keys instead of a loose string index signature.
  • Declare the method a polyfill module installs onto a built-in prototype.
  • Add a test-only global such as a fetch mock without turning the setup file into a script.

Frequently asked questions

How do I add a property to the Window type in TypeScript?

Put `declare global { interface Window { myProp: string } }` in a file that is a module, adding `export {}` if it has no other imports or exports. Because Window is an interface, your declaration merges with the DOM's rather than replacing it.

Why does declare global say it can only be nested in external modules?

Because the containing file is being treated as a global script rather than a module. Add a top-level `export {}` to turn it into a module — in a plain script file you do not need declare global at all, since bare declarations there are already global.

Why can't TypeScript see my global variable on globalThis?

Declare it with `var` rather than `let` or `const` — only `var` declarations become properties of globalThis. `let` and `const` create global-scope bindings that are reachable by bare name but never appear on the globalThis object.

More examples

Typing environment variables in Node

ProcessEnv is an interface in @types/node, so merging into it inside declare global replaces the loose index signature with named, precisely typed keys. Autocomplete now lists the real variables and a typo becomes a compile error. The declaration makes no promise the variables are actually set, so validate them at startup if a missing value would crash.

Example · typescript
// env.d.ts
export {};

declare global {
  namespace NodeJS {
    interface ProcessEnv {
      API_URL: string;
      LOG_LEVEL: "debug" | "info" | "error";
      PORT?: string;
    }
  }
}

// server.ts
const url = process.env.API_URL;         // string, not string | undefined
const level = process.env.LOG_LEVEL;     // the literal union
// process.env.TYPO;                     // Property 'TYPO' does not exist

console.log(`${level}: connecting to ${url}`);

Module augmentation versus global augmentation

declare module "./observable" targets one module's exports; declare global targets the global scope. Both obey the same limitation — they patch existing declarations rather than adding new top-level ones. The bare `import "./map"` in the consumer is essential: it is what actually installs the prototype method, and forgetting it leaves code that type-checks but throws at runtime.

Example · typescript
// observable.ts
export class Observable<T> {
  constructor(public value: T) {}
}

// map.ts — patches the module's own exported class
import { Observable } from "./observable";

declare module "./observable" {
  interface Observable<T> {
    map<U>(f: (x: T) => U): Observable<U>;
  }
}

Observable.prototype.map = function (f) {
  return new Observable(f(this.value));
};

// consumer.ts
import { Observable } from "./observable";
import "./map"; // the side-effect import that installs the method

const o = new Observable(21);
console.log(o.map((x) => x * 2).value);
// 42

Discussion

  • Be the first to comment on this lesson.