declare and Global Augmentation
Introduction
The declare keyword tells TypeScript about values and types that exist outside your compiled files—globals, injected constants, or third-party scripts. Module augmentation extends existing module types without forking libraries. Used carefully, these features bridge legacy scripts and modern modules; overuse hides real structure.
Prerequisites
declare Variables and Functions
Ambient declarations (no emit):
// env.d.ts
declare const API_BASE_URL: string;
declare function trackEvent(name: string, payload?: Record<string, unknown>): void;Your bundler or HTML must define API_BASE_URL and trackEvent at runtime—TypeScript only trusts the types.
declare namespace (Legacy Globals)
Older scripts used namespaces:
declare namespace AppConfig {
const version: string;
const features: {
darkMode: boolean;
};
}Prefer ES modules for new code. Namespaces remain in typings for legacy APIs.
Extending the global Scope
// global.d.ts
export {};
declare global {
interface Window {
appSettings?: {
locale: string;
};
}
}Code explanation:
export {}makes the file a module sodeclare globalis allowedWindowgains optionalappSettingsin the browser
Use in application code:
window.appSettings?.locale;Module Augmentation
Add types to an existing module:
// express-augment.d.ts
import "express";
declare module "express-serve-static-core" {
interface Request {
userId?: string;
}
}After auth middleware sets req.userId, TypeScript knows the property:
import type { Request, Response } from "express";
function handler(req: Request, res: Response): void {
console.log(req.userId);
}Exact module path depends on Express version—follow library docs for augmentation targets.
Augmenting your own modules
// store.ts
export interface StoreState {
count: number;
}
// store-augment.ts (or in same file with care)
declare module "./store.js" {
interface StoreState {
label: string;
}
}Prefer defining the full interface in one place for app code; augmentation suits plugins extending a core interface.
declare class / enum (Ambient)
Describe script-tag globals:
declare class LegacyChart {
constructor(element: HTMLElement);
render(data: number[]): void;
}<script src="/legacy-chart.js"></script>const chart = new LegacyChart(document.getElementById("chart")!);Triple-Slash Directives (Awareness)
Older files reference types or files explicitly:
/// <reference types="node" />
/// <reference path="./globals.d.ts" />Modern projects use import and include in tsconfig instead. You may see triple-slash in generated or legacy .d.ts files.
const enum and Ambient const (Brief)
declare const enum Color {
Red,
Green,
Blue,
}const enum inlines at compile time in some setups—advanced topic; prefer string unions in new code.
Safe Practices
- Colocate ambient files (
src/types/global.d.ts) and include intsconfig - Document runtime sources for every
declareglobal - Avoid augmenting modules you do not own unless necessary
- Prefer wrapping untyped JS in a typed module you export
- One augmentation per concern—merge interfaces, do not duplicate module names
Warning
declare Does Not Create Runtime Values
If API_BASE_URL is missing at runtime, the app still crashes—declare only satisfies the type checker.
Practical Example: Vite import.meta.env
// vite-env.d.ts
/// <reference types="vite/client" />
interface ImportMetaEnv {
readonly VITE_API_URL: string;
}
interface ImportMeta {
readonly env: ImportMetaEnv;
}const url = import.meta.env.VITE_API_URL;Vite provides base types; you extend ImportMetaEnv for your env vars.
FAQ
declare vs export declare?
In .d.ts modules, export declare marks exported members. Ambient globals omit export unless inside declare global.
Can I augment node_modules types?
Yes—same declare module "pkg" pattern; changes apply project-wide. Upstream updates may break augmentations.
global.d.ts not working?
Ensure the file is in include, and use export {} when using declare global.
declare vs import type?
declare describes existing runtime symbols. import type imports types only from modules.
Should apps use many globals?
No. Prefer explicit imports and configuration objects passed through your app.
Difference from DefinitelyTyped?
DefinitelyTyped publishes .d.ts packages; declare is what those files are made of—you write smaller local versions when needed.