Global Type Augmentation

Problem

TypeScript doesn’t know about custom Window properties, custom events, or non-standard module imports (like SCSS). This causes type errors even when the runtime behavior is correct.

Solution

Create types/*.d.ts files with interface augmentation to extend global types.

Code Example

// types/quartz.d.ts
 
// Type definitions for nominal types
type SlugLike<T extends string> = string & { __brand: T };
type FullSlug = SlugLike<'full'>;
 
// SCSS module declarations
declare module '*.scss' {
  const content: Record<string, string>;
  export default content;
}
 
// Custom Window properties and events
declare global {
  interface CustomEventMap {
    nav: CustomEvent<{ url: FullSlug }>;
    prenav: CustomEvent<Record<string, never>>;
    themechange: CustomEvent<{ theme: 'dark' | 'light' }>;
    readermodechange: CustomEvent<{ mode: 'on' | 'off' }>;
  }
 
  interface Window {
    addCleanup(fn: () => void): void;
    spaNavigate(url: URL | FullSlug): void;
  }
 
  interface DocumentEventMap extends CustomEventMap {
    nav: CustomEventMap['nav'];
    prenav: CustomEventMap['prenav'];
    themechange: CustomEventMap['themechange'];
    readermodechange: CustomEventMap['readermodechange'];
  }
}
 
// Content data types
declare global {
  interface ContentDetails {
    title?: string;
    slug?: FullSlug;
    content?: string;
    tags?: string[];
    [key: string]: unknown;
  }
 
  type ContentIndex = { [key: string]: ContentDetails };
 
  function fetchData(): Promise<ContentIndex>;
}
 
export {};

Usage

1. Create the Declaration File

Place in types/quartz.d.ts (or any .d.ts file in a types/ directory).

2. Configure TypeScript

Ensure tsconfig.json includes the types directory:

{
  "compilerOptions": {
    "typeRoots": ["./types", "./node_modules/@types"]
  },
  "include": ["types/**/*.d.ts", "**/*.ts"]
}

3. Use Extended Types

// Now TypeScript knows about these
window.addCleanup(() => cleanup());
document.addEventListener('nav', (e) => {
  const slug: FullSlug = e.detail.url;
});

Trade-offs

When to Use

  • Extending global objects - Window, Document, Navigator
  • Custom events with typed payloads - Ensures e.detail is properly typed
  • Non-standard module imports - SCSS, images, JSON files
  • Third-party libraries without types - When @types/* doesn’t exist

When NOT to Use

  • Types that can be inferred - Let TypeScript do its job
  • Local-scope-only types - Use regular interface or type declarations
  • Types for your own modules - Export types from the source file

Key Points

  1. declare global {} - Required to augment global scope from a module
  2. export {} - Makes the file a module (required for declare global)
  3. Interface merging - Interfaces with same name combine their members
  4. declare module "*.scss" - Wildcard module declarations for import patterns

Source

Extracted from Knowledge-Vault during type safety audit (January 2026).