Master

Declaration Merging and Augmentation

Extend third-party types and combine declarations using merging and module augmentation.

What you will see

[ 1, 2, 3 ]

About 2 min of reading

Quick check

Before this new lesson, one small question about the lesson you just finished. Recall it, do not look it up.

Try It

Declaration merging lets multiple declarations with the same name or module combine into one. Interfaces merge, and functions can carry properties. This is real JavaScript behavior at runtime.

1

Interface declaration merging

const user = { name: "Ada", age: 30 }; console.log(user.name, user.age);

Before you run it, say out loud what it will print. Then open the answer.

What it prints
Ada 30
2

Function + interface merging

function counter() { counter.calls += 1; return counter.calls; } counter.calls = 0; console.log(counter(), counter(), counter());

Before you run it, say out loud what it will print. Then open the answer.

What it prints
1 2 3
3

Augmenting global types

Array.prototype.unique = function () { return this.filter((item, pos) => this.indexOf(item) === pos); }; console.log([1, 2, 2, 3, 1, 3].unique());

Before you run it, say out loud what it will print. Then open the answer.

What it prints
[ 1, 2, 3 ]

Put it in order

Before you write anything, put these lines back in the right order. Click a line to move it up one step. This support step is easier than writing code from scratch, and it is here to make the last example stick.

Data Flow

The compiler collects all declarations sharing a name or module path and merges them into one unified type.
1 Multiple declarations share a name or module
↓
2 Compiler unions their members
↓
3 Augmentation appends to an existing module
↓
4 Every file sees the merged result
Why this works
Why declaration merging

JavaScript lets you attach properties to almost anything; TypeScript mirrors that with merging:

interface Options { timeout: number; }
interface Options { retries: number; }  // { timeout; retries }

Multiple authors can contribute to the same contract. Libraries use it to let you extend config or event shapes without forking the source.

Module augmentation

Add fields to a library's exports without rewriting it:

declare module "express" {
  interface Request {
    user?: { id: string };
  }
}

This augments the express module, so req.user becomes known to the whole project. It is the standard way to plug custom middleware types into third-party libraries.

Examples

Interface declaration merging

Merge same-named interfaces

interface User { name: string; }
interface User { age: number; }   // merges
const u: User = { name: "Ada", age: 30 };

Two interfaces with the same name are merged into one. This lets libraries and your code add properties to a shared type without touching the original.

Function + interface merging

Attach properties to functions

function counter(): number {
  counter.calls += 1;
  return counter.calls;
}
counter.calls = 0;

A function and a variable can share a name to attach state. This pattern is common for small self-tracking helpers.

Augmenting global types

Extend Node's globals

declare global {
  interface Array<T> {
    unique(): T[];
  }
}
export {};

declare global plus an empty export {} turns a file into a module so you can augment existing global types. Use it sparingly, it affects the whole program.

Words this lesson uses

Each one is defined here on this site. Open the entry for the full definition and the other words it points at.

← Modules and Namespaces
Previous Lesson
Modules and Namespaces
Next Lesson
Advanced Inference and Conditional Types
Advanced Inference and Conditional Types →