declare global
Adds or patches declarations in the global scope from inside a module.
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
| Parameter | Type | Description |
|---|---|---|
$body | declarations | Declarations merged into the global scope. Only patches to existing declarations and new global bindings are allowed — the same limitations as module augmentation. |
$enclosing file | module | The 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,globalThisorNodeJS.ProcessEnvfrom 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:
- The file must be a module. Without a top-level
importorexport, TypeScript rejects the block outright. Addingexport {}fixes it. - Use
varfor global variables. Onlyvardeclarations become properties ofglobalThis;letandconstcreate bindings thatglobalThis.myGlobalcannot see. - 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
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 trueWhen 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.
// 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.
// 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