Global Declarations in TypeScript
Global declarations add types to the global scope in TypeScript. Learn how to declare global variables, augment built-in types, and extend window with declare global.
TypeScript global declarations tell the compiler that a variable, function, or type is available everywhere at runtime without needing to be imported. When a value lives in the global scope, like window in browsers, process in Node.js, or a variable injected by a build tool, you need a global declaration so TypeScript can see it.
TypeScript treats files differently depending on whether they contain import or export statements. Understanding this distinction is the key to getting global declarations right.
Script Files vs Module Files
Whether a declaration ends up global or local depends on the file it lives in. The table below compares the two file kinds TypeScript recognizes.
| File kind | Trigger | Declaration visibility |
|---|---|---|
| Script | No top-level import or export | Global, no import needed |
| Module | At least one top-level import or export | Local to that file, unless exported |
A script file has no top-level import or export statements, so every declaration inside it becomes globally visible across the project.
// globals.d.ts -- SCRIPT file (no imports or exports)
// These are available everywhere in the project without importing.
declare const APP_VERSION: string;
declare function logEvent(name: string): void;A module file has at least one top-level import or export, so TypeScript treats its declarations as local to that file rather than global.
// helpers.ts -- MODULE file (has an import)
import { something } from "./other";
// This declaration is local to helpers.ts only.
declare const LOCAL_ONLY: string;If you want to add a global declaration from inside a module, you need a different approach: declare global. This builds on the same declare keyword used in ambient declarations in TypeScript.
Using declare global
A declare global block lets module files contribute declarations to the global scope. This is the only way to add globals from a file that already has imports or exports.
// app.ts (a module file because it has imports)
import express from "express";
declare global {
var APP_START_TIME: number;
function trackMetric(name: string, value: number): void;
}
// Now APP_START_TIME and trackMetric are available everywhere --
// even in other files that do not import app.ts.Without that block, those declarations would only be visible inside app.ts. The block acts as an escape hatch from module scope into the global scope.
The syntax is straightforward: wrap your declarations in a declare global block. Inside it, use the normal declare patterns for variables, functions, classes, namespaces, interfaces, and types.
Augmenting the Window Interface
The most common use case for global declarations is adding properties to the browser's window object. Third-party scripts, analytics libraries, and browser extensions often attach themselves to window, but TypeScript does not know about them.
// window.d.ts (script file -- no imports)
interface Window {
analytics: {
track(page: string): void;
identify(userId: string): void;
};
__INITIAL_STATE__: Record<string, unknown>;
}
// Now you can use them anywhere:
window.analytics.track("/home");
const state = window.__INITIAL_STATE__;You can also use a declare global block to augment the Window interface from a module file. This is the pattern to reach for once your project already imports other modules, since a plain script file is no longer an option.
// analytics-setup.ts (module file -- has imports)
import { initAnalytics } from "./analytics";
declare global {
interface Window {
analytics: ReturnType<typeof initAnalytics>;
}
}Both approaches work. The script-file approach is simpler when you have no other imports. The declare global approach is necessary when the file is already a module.
Augmenting Built-in Global Types
The global scope includes many built-in interfaces: Array, String, Number, Promise, and more. You can augment these from any file to add custom methods or properties.
// array-extensions.d.ts
interface Array<T> {
last(): T | undefined;
}
// Now Array has a last() method everywhere:
const items = [1, 2, 3];
console.log(items.last()); // 3This tells TypeScript that Array.prototype.last exists, but it does not create the method. You are responsible for shipping a runtime implementation that actually adds it to the prototype, as shown in the plain JavaScript file below.
// array-extensions.js (plain JavaScript, loaded at startup)
Array.prototype.last = function () {
return this[this.length - 1];
};The type augmentation and the runtime polyfill are separate.
The declaration file provides the types, while the JavaScript file provides the behavior. Both are required, since the declaration alone changes nothing at runtime.
Real-World Pattern: Build-Tool Globals
Build tools like Webpack, Vite, and esbuild often define global constants at build time. TypeScript does not know about these unless you declare them:
// env.d.ts (script file)
declare const __DEV__: boolean;
declare const __API_URL__: string;
// Any file can now reference them:
if (__DEV__) {
console.log("Running in development mode");
}
fetch(`${__API_URL__}/users`)
.then((res) => res.json())
.then(console.log);Keep these declarations in a dedicated declaration file in script mode so they apply project-wide. Add the file to your tsconfig.json include array if it is not already covered.
Global Declarations vs Module Declarations
It is important to choose the right scope for your declarations:
| Goal | Use |
|---|---|
| Variable available everywhere without import | Script declaration file or declare global |
| Types for an npm package | declare module with the package name |
| Types for a local JavaScript module | Module declaration file with export declare |
| Augmenting an existing package | Module augmentation |
For package-specific types, prefer declare module rather than leaking everything into the global scope. Global declarations are for things that truly exist everywhere, such as browser APIs, build constants, and analytics globals loaded by script tags.
Common Mistake: Forgetting the Script/Module Distinction
When you write a declaration in a file with imports and forget to wrap it in declare global, it stays local:
// config.ts -- this is a MODULE because of the import
import fs from "fs";
declare const CONFIG_PATH: string;
// CONFIG_PATH is only visible inside config.ts, not globally.Because the declaration stayed local to config.ts, any other file that tries to reference CONFIG_PATH fails at compile time, even though both files belong to the same project.
// server.ts
console.log(CONFIG_PATH);
// ^ Cannot find name 'CONFIG_PATH'.Fix this one of two ways: move the declaration into a dedicated script declaration file with no imports, or keep it in config.ts and wrap it in declare global so it escapes module scope, as shown below.
// config.ts
import fs from "fs";
declare global {
const CONFIG_PATH: string;
}Now every file in the project can see CONFIG_PATH.
When to Use Each Approach
Use a script declaration file (no imports or exports) when you have several project-wide globals that do not need any module imports. This is the simplest and most common pattern.
Use declare global inside a module when the global declaration depends on types imported from other modules. For example, if window.analytics has a type that comes from a third-party package you imported.
Use module augmentation when you want to add types to an existing npm package rather than the global scope.
Rune AI
Key Insights
- A .d.ts file with no imports or exports is a script, making its declarations globally visible.
- Use declare global inside a module file to add types to the global scope.
- You can augment built-in interfaces like Window, Array, and String from any file.
- Global declarations are erased at compile time and produce no JavaScript.
- Prefer script-mode .d.ts files for project-wide globals and declare global inside modules for targeted augmentation.
Frequently Asked Questions
What is declare global used for?
Why can't I just write declare var at the top of my file?
Does declare global emit any JavaScript?
Conclusion
Global declarations let you teach TypeScript about values that are available everywhere at runtime. Whether you are describing a browser API, a build-injected variable, or an analytics script loaded by a script tag, declare global and script-mode declaration files give you the right tools.
More in this topic
TypeScript Compiler Explained
The TypeScript compiler (tsc) turns .ts files into .js files while checking types. Learn what it does, how to use it, and how it fits your workflow.
Set Up ESLint for TypeScript
Install and configure ESLint with typescript-eslint to catch bugs and enforce consistent code style in your TypeScript project.
Pick Utility Type in TypeScript
Pick creates a new type by selecting only the properties you name from an existing type. Use it to build focused types for specific views, API responses, or function parameters.