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):

typescript
// 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:

typescript
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

typescript
// global.d.ts
export {};
 
declare global {
  interface Window {
    appSettings?: {
      locale: string;
    };
  }
}

Code explanation:

  • export {} makes the file a module so declare global is allowed
  • Window gains optional appSettings in the browser

Use in application code:

typescript
window.appSettings?.locale;

Module Augmentation

Add types to an existing module:

typescript
// 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:

typescript
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

typescript
// 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:

typescript
declare class LegacyChart {
  constructor(element: HTMLElement);
  render(data: number[]): void;
}
html
<script src="/legacy-chart.js"></script>
typescript
const chart = new LegacyChart(document.getElementById("chart")!);

Triple-Slash Directives (Awareness)

Older files reference types or files explicitly:

typescript
/// <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)

typescript
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

  1. Colocate ambient files (src/types/global.d.ts) and include in tsconfig
  2. Document runtime sources for every declare global
  3. Avoid augmenting modules you do not own unless necessary
  4. Prefer wrapping untyped JS in a typed module you export
  5. 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

typescript
// vite-env.d.ts
/// <reference types="vite/client" />
 
interface ImportMetaEnv {
  readonly VITE_API_URL: string;
}
 
interface ImportMeta {
  readonly env: ImportMetaEnv;
}
typescript
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.