Declaration Merging
What is declaration merging?
Declaration merging is a TypeScript feature where multiple declarations with the same name are combined into a single definition. The most common and practical form is interface merging, which allows you to extend existing types—especially third-party library types—without changing the library’s code.
Why declaration merging exists
In real projects, you often need to add fields to existing types: framework configuration, request objects, global settings, or plugin APIs. Declaration merging enables safe augmentation while keeping type checking consistent across the codebase.
Interface merging
Interfaces can be declared multiple times, and TypeScript merges them by combining their members.
interface Config {
debug: boolean;
}
interface Config {
env: "dev" | "prod";
}
const cfg: Config = {
debug: true,
env: "dev"
};
The resulting Config interface includes both debug and env.
How conflicts are handled
If merged declarations include a member with the same name, the types must be compatible. Otherwise, TypeScript reports an error. This prevents accidental breakage when augmentations disagree.
Merging with function overloads
Declaration merging can also apply to function declarations via overloads. Multiple function signatures can exist, and a single implementation must satisfy them.
function format(value: string): string;
function format(value: number): string;
function format(value: string | number): string {
return value.toString();
}
Module augmentation
The most production-relevant use case is module augmentation: extending types from a library you import. This is commonly used in frameworks such as Express, Next.js, and many plugin-based systems.
declare module "express-serve-static-core" {
interface Request {
userId?: number;
}
}
This extends Express’s Request type so your application can safely reference request.userId.
Global augmentation
You can also augment global types, but this should be done sparingly because it affects the entire project.
declare global {
interface Window {
appVersion: string;
}
}
Interfaces merge, type aliases do not
A key difference in TypeScript architecture is that interfaces can be merged, but type aliases cannot. This is one reason interfaces are often preferred for public, augmentable contracts.
When declaration merging is useful
- Extending third-party library types without forking the library.
- Adding fields to framework request/response objects.
- Supporting plugin systems where consumers add capabilities.
- Gradually enhancing types in legacy or migration code.
When declaration merging becomes risky
- Accidental name collisions across modules.
- Hidden global side effects that confuse teams.
- Over-augmentation that makes types unclear and hard to trace.
Production guidance
- Prefer module augmentation over global augmentation.
- Keep augmentations close to the integration layer (e.g., an express.d.ts file).
- Name and organize .d.ts files clearly so teams can find augmentations.
- Avoid using declaration merging as a replacement for proper domain types.
What’s next
Type System Deep Dive continues by applying these tools together: generics, keyof, conditional types, and mapped types. Next up, we will connect these ideas through real patterns and then move into production workflows in the following section.