This error occurs when trying to merge incompatible declarations in TypeScript. Declaration merging has strict rules about type compatibility, and attempting to merge conflicting declarations causes this error. Common causes include mismatched types between interfaces, unsafe class-interface merging, and attempting to merge across different declaration kinds.
TypeScript declaration merging is a feature that allows you to declare the same name multiple times in the same scope, and TypeScript will combine them into a single declaration. However, this feature has strict rules about what can be merged together. This error occurs when you attempt to merge declarations that violate these rules. For example, you cannot merge two interfaces where a non-function member has the same name but different types, you cannot safely merge classes with interfaces (which can hide uninitialized properties), and you cannot merge declarations across certain boundaries. The error message will typically include the name of the declaration that failed to merge, helping you identify which specific declaration conflict needs to be resolved. Understanding TypeScript's declaration merging rules is essential for using advanced patterns like module augmentation and extending third-party types.
The most common cause is trying to merge interfaces where a non-function member has the same name but different types. Ensure all merged members have compatible types:
// ❌ WRONG - Merging interfaces with different types for same member
interface User {
id: string;
}
interface User {
id: number; // Type mismatch! Cannot merge
}
// ✅ CORRECT - Same type for merged members
interface User {
id: string;
}
interface User {
id: string; // Types match, merge succeeds
email: string;
}Ensure all properties in each interface you're trying to merge have the exact same types across all declarations.
TypeScript doesn't safely support merging classes with interfaces because it can't verify that class properties are properly initialized. Use composition or separate interfaces instead:
// ❌ WRONG - Unsafe class-interface merging
class User {}
interface User {
name: string; // May not be initialized in class
}
// ✅ CORRECT - Use separate interface
interface UserData {
name: string;
}
class User implements UserData {
name: string;
constructor(name: string) {
this.name = name;
}
}
// ✅ ALSO CORRECT - Extend with namespace
class Config {}
namespace Config {
export const defaults = {};
}If you need to augment a class type, use a namespace instead of interface merging.
Namespace-to-namespace merging across multiple files is not supported in TypeScript. All merged namespace declarations must be in the same file:
// ❌ WRONG - Merging namespaces across files
// file1.ts
namespace MyNamespace {
export const a = 1;
}
// file2.ts
namespace MyNamespace { // Cannot merge across files
export const b = 2;
}
// ✅ CORRECT - Keep all namespace declarations in one file
// myNamespace.ts
namespace MyNamespace {
export const a = 1;
}
namespace MyNamespace {
export const b = 2;
}For cross-file type augmentation, use module augmentation with interfaces or use ES modules with explicit imports/exports instead of namespaces.
When you need to extend types from external libraries, use module augmentation instead of direct merging:
// ✅ CORRECT - Module augmentation
declare module 'express' {
interface Request {
userId?: string;
}
}
export {};
// In another file:
import express from 'express';
const app = express();
app.get('/', (req: express.Request) => {
console.log(req.userId); // Works
});Module augmentation allows safe extension of external types without direct declaration merging conflicts.
When merging function declarations, ensure overload signatures are compatible:
// ❌ WRONG - Conflicting function overloads
function process(x: string): string;
function process(x: string): number; // Conflicting return type
// ✅ CORRECT - Compatible overloads
function process(x: string): string;
function process(x: number): number;
function process(x: string | number): string | number {
if (typeof x === 'string') return x.toUpperCase();
return x * 2;
}
// ✅ ALSO CORRECT - Same signature, different implementation
function getValue(key: string): string;
function getValue(key: string, defaultValue: string): string;
function getValue(key: string, defaultValue?: string): string {
return defaultValue || 'default';
}Function overloads must have compatible parameters and return types to merge successfully.
When merging declarations, ensure that visibility rules won't expose non-exported members:
// ❌ PROBLEMATIC - Non-exported member visibility issues
interface Helper {
private process(): void; // Private members
}
interface Helper {
public execute(): void; // Merging may cause visibility conflicts
}
// ✅ CORRECT - Clear visibility
export interface Helper {
process(): void; // Public, accessible
execute(): void; // Public, accessible
}
// ✅ ALSO CORRECT - Separate namespaces for visibility
namespace Internal {
export interface Helper {
private process(): void;
}
}
namespace Public {
export interface Helper {
execute(): void;
}
}Ensure exported members are consistently exported across all merged declarations.
Declaration merging in TypeScript follows specific rules depending on the declaration type. Interfaces can merge freely with other interfaces as long as non-function members have compatible types. Namespaces can merge with classes, functions, and enums, but namespace-to-namespace merging across files isn't supported—use ES modules instead.
Class-interface merging is particularly unsafe because TypeScript cannot verify that properties declared in the interface are actually initialized in the class constructor. This can lead to runtime errors where properties are undefined despite being declared.
Function overloads (which are a form of declaration merging) must be listed in order of most specific to most general, and specialized signatures with string literal parameters must appear before more general signatures.
Module augmentation (using declare module) is the recommended way to extend types from external libraries or the global scope. It uses declaration merging internally but applies it safely within the module augmentation boundary.
If you're using TypeScript 4.7 or earlier, you may encounter issues with decorators and declaration merging. Ensure your tsconfig.json has the correct target and lib settings. Starting with TypeScript 5.0, the relationship between declaration merging and symbols changed, potentially affecting some advanced patterns.
When working with .d.ts files, ensure all declarations you're trying to merge are properly exported. Non-exported declarations in type definition files follow different merging rules.
Function expression requires a return type
Function expression requires a return type
Value of type 'string | undefined' is not iterable
How to fix "Value is not iterable" in TypeScript
Type 'undefined' is not assignable to type 'string'
How to fix "Type undefined is not assignable to type string" in TypeScript
Type narrowing from typeof check produces 'never'
How to fix "Type narrowing produces never" in TypeScript
Type parameter 'T' has conflicting constraints
How to fix "Type parameter has conflicting constraints" in TypeScript