Siddhant DevalAuthor
Senior Full-Stack Engineer·Aug 27, 2026·11 min read
Declaration Merging, Module Augmentation & Ambient Types
Declaration merging is how TypeScript resolves multiple declarations of the same name. It is the mechanism that lets you extend third-party library types safely — adding properties to Express's Request, typing process.env, or declaring non-JS file imports — without forking anything.
Technical Series
TypeScript Mastery
Part 8 of 11
Declaration Merging, Module Augmentation & Ambient Types
Types are a specification language — not an annotation layer. When a third-party library's types are incomplete or wrong, the instinct is to cast:
(req as any).user. That cast is a lie. The correct tool is declaration merging — TypeScript's mechanism for reopening and extending existing type declarations. It is how you add properties to Express's Request, type process.env, and declare that .svg imports return strings, all without modifying the library source.1. Declaration Merging — Interface vs. Type
The foundational rule: only
interface declarations merge. type aliases do not.typescript
When two interfaces merge, all members are combined. If two merged declarations include the same method signature, the later signature is checked first (function overload resolution order). This is intentional — it allows augmenters to override or specialize behavior.

Expand
2. Module Augmentation — Extending Third-Party Types
Module augmentation is the targeted form of declaration merging — you reopen a specific module's namespace and add to it:
2.1 Extending Express's Request
typescript
Architectural Note
Express augments
express-serve-static-core, not express directly — check the library's type definitions to find the correct module name for augmentation.
Expand
3. Global Augmentation — Extending the Global Scope
Sometimes you need to extend the global namespace —
window, globalThis, or process.env:typescript
Crucial Requirement
The distinction between global scope and module scope is critical: a
.d.ts file with no import or export statements is ambient (global) by default. Adding any import or export makes it a module — after which you must use declare global { } to write to the global scope. Always add export {} when in doubt.4. d.ts Files — Global vs. Module Scope
typescript
The scope rule comes directly from the TypeScript module resolution spec: a file is a module if and only if it contains at least one top-level
import or export. Otherwise it is a script (ambient / global).5. Ambient Modules — Typing Non-JS Imports
Bundlers like Webpack and Vite transform non-JS files before TypeScript sees them. Without declarations, TypeScript complains about these imports:
typescript
![Three-row diagram. Each row shows: a file import statement (left), the matching wildcard ambient module declaration (center), and the resulting inferred type (right). Row 1: import logo from './logo.svg' → declare module '*.svg' { const content: string } → logo: string. Row 2: import styles from './app.module.css' → declare module '*.module.css' { const styles: { [k: string]: string } } → styles: { [k: string]: string }. Row 3: import data from './data.json' → declare module '*.json' { const value: unknown; export default value } → data: unknown. Caption: 'Wildcard ambient modules provide TypeScript with a typed declaration for any file matching the glob pattern — the bundler handles the actual transformation at runtime'.](/assets/blog/frontend/typescript-declaration-merging-module-augmentation/fig-03.png)
Expand
6. DefinitelyTyped as Real-World Examples
The
@types/* packages on npm are all real-world ambient declaration files. Reading them is the fastest way to see these patterns in production use:@types/express— uses module augmentation to compose theRequestandResponsetypes across multiple files@types/node— usesdeclare globalanddeclare namespace NodeJSto typeprocess,Buffer, and__dirname@types/react— uses ambient module declarations for JSX and namespace merging for React hooks
bash
Summary
| Concept | Rule |
|---|---|
| Interface merging | Same-name interface declarations merge; type aliases do not |
| Module augmentation | declare module 'library' { interface X { ... } } reopens the module's namespace |
| Global augmentation | declare global { ... } extends the global scope from inside a module file |
| File scope rule | No import/export → ambient (global); any import/export → module (local) |
export {} | Minimal way to make a .d.ts file a module when no other exports exist |
| Ambient modules | declare module '*.svg' types all .svg imports matching the pattern |
declare namespace NodeJS | The correct way to extend process.env with custom environment variable types |
What's Next
In Part 9, we cover Runtime Type Safety — because TypeScript's types are erased at runtime andas Tdoes nothing at runtime. Every network boundary, localStorage read, and environment variable is an untrusted source. The correct tool is a schema parser like Zod or Valibot — not a type cast.
Research & Synthesis Note
This article was developed with AI-assisted deep search, specification cross-referencing, and technical research synthesis.
#TypeScript#Declaration Merging#Module Augmentation#Ambient Types#d.ts