This TypeScript error occurs when you try to extend or implement an interface that has incompatible property signatures with a declaration file. The fix involves aligning property types, making properties optional, or using type assertions.
The "Interface 'X' is not compatible with declaration file" error (TS2420) appears when TypeScript detects that an interface you're declaring doesn't match the expected shape defined in a declaration file (.d.ts). This typically happens when: 1. **Extending built-in interfaces**: You're trying to extend a global interface (like Window, Document, or NodeJS.Process) with incompatible properties 2. **Module augmentation**: You're augmenting a third-party module's types with properties that don't match the original declaration 3. **Declaration merging**: You're merging interfaces but the property signatures conflict TypeScript's declaration files define the shape of JavaScript libraries and built-in APIs. When you try to extend these definitions, TypeScript ensures your additions are compatible with the original type definitions. If your properties have different types, are missing required properties, or have conflicting optionality, you'll see this error. This is a type safety feature that prevents you from incorrectly extending existing types, which could lead to runtime errors.
TypeScript will tell you which property is incompatible. Look at the full error message:
// Example error:
// Interface 'Window' is not compatible with declaration file.
// Types of property 'myCustomProp' are incompatible.
// Type 'string' is not assignable to type 'number'.The error shows:
1. Which interface has the problem (Window)
2. Which property is incompatible (myCustomProp)
3. What the type mismatch is (string vs number)
First, examine the original declaration to understand what type is expected:
// Check what the declaration file expects
// For global interfaces, check lib.dom.d.ts or @types packages
// For third-party modules, check node_modules/@types/package-name/index.d.tsYou can also use TypeScript's quick info in your IDE (hover over the interface name) to see the original definition.
Once you know the expected type, update your interface to match:
// BEFORE: Type mismatch
declare global {
interface Window {
myCustomProp: string; // Error: expects number
}
}
// AFTER: Match the expected type
declare global {
interface Window {
myCustomProp: number; // Correct: now matches declaration
}
}
// If you need to support multiple types, use a union:
declare global {
interface Window {
myCustomProp: string | number; // Union type works if declaration allows it
}
}
// For optional properties, match optionality:
declare global {
interface Window {
optionalProp?: string; // ? makes it optional
requiredProp: string; // No ? means required
}
}Remember: Your extension must be assignable to the original type. This means:
- Your property types must be subtypes of the original types
- You can make properties more specific (narrower), but not more general (wider)
- Optional properties in the original can be made required in your extension
- Required properties in the original cannot be made optional in your extension
For augmenting third-party module types, use proper module augmentation syntax:
// BEFORE: Incorrect - trying to redeclare the module
declare module "some-library" {
interface SomeInterface {
newProp: string;
}
}
// AFTER: Correct module augmentation
declare module "some-library" {
// Extend the existing interface
interface SomeInterface {
newProp: string; // This adds to the existing interface
}
}
// For adding to exported classes/functions:
declare module "express" {
interface Request {
user?: User; // Add custom property to Express Request
}
}
// Make sure you're augmenting the right module:
// Check node_modules/some-library/package.json for the main entry point
// Or check @types/some-library/index.d.ts for type definitionsCommon patterns:
- Use declare module "module-name" for npm packages
- Use declare global for global scope augmentation
- Place augmentations in .d.ts files or regular .ts files with proper declarations
Readonly properties and methods have special rules:
// Readonly properties: Can't make readonly properties writable
declare global {
interface Document {
// WRONG: document.location is readonly
location: string; // Error: location is readonly in lib.dom.d.ts
// CORRECT: Keep it as Location object
location: Location; // Must match original type
}
}
// Method signatures: Must be compatible
declare global {
interface Array<T> {
// WRONG: Incompatible method signature
map<U>(callback: (value: T) => U): U[]; // Missing index and array parameters
// CORRECT: Match the original signature
map<U>(
callback: (value: T, index: number, array: T[]) => U,
thisArg?: any
): U[];
}
}
// For adding new methods (not overriding):
declare global {
interface Array<T> {
// This is fine - new method doesn't conflict
customMethod(): void;
}
}
// Check method compatibility:
// - Same number of parameters
// - Parameter types must be assignable
// - Return type must be assignable
// - Optional parameters must matchUse TypeScript's extends keyword to test compatibility:
// Test if your extension is compatible
type IsCompatible = YourInterface extends OriginalInterface ? true : false;If you can't fix the type conflict, consider alternatives:
// Option 1: Type assertions (use with caution)
declare global {
interface Window {
myProp: any; // Use 'any' as last resort
}
}
// Then cast when using:
const value = (window as any).myProp as string;
// Option 2: Use intersection types instead of extension
type ExtendedWindow = Window & {
myCustomProp: string;
};
// Then use ExtendedWindow type instead of augmenting Window globally
function useWindow(win: ExtendedWindow) {
console.log(win.myCustomProp);
}
// Option 3: Create a wrapper interface
interface MyWindowExtensions {
myCustomProp: string;
}
// Use both types together
function processWindow(win: Window & MyWindowExtensions) {
// Access both Window properties and your custom properties
}
// Option 4: Use declaration files with proper merging
// Create a file: src/types/window.d.ts
declare global {
interface Window {
myCustomProp: string;
}
}
export {}; // This makes it a module for proper merging
// Option 5: Check if you're extending the right interface
// Sometimes the error means you should extend a different interface
// or create a new interface instead of extendingBest practices:
- Prefer type-safe solutions over any
- Use proper module augmentation syntax
- Keep augmentations close to where they're used
- Document why you need the augmentation
Ensure your TypeScript configuration includes the declaration files:
// tsconfig.json
{
"compilerOptions": {
// Include type definitions
"typeRoots": ["./node_modules/@types", "./src/types"],
"types": ["node", "express"], // Explicitly include types
// Declaration file settings
"declaration": true,
"declarationMap": true,
// Module resolution
"moduleResolution": "node",
"esModuleInterop": true
},
"include": [
"src/**/*.ts",
"src/**/*.d.ts", // Include your .d.ts files
"types/**/*.d.ts"
],
"exclude": ["node_modules"]
}Common configuration issues:
1. Missing typeRoots: Your custom .d.ts files aren't being included
2. Wrong types array: Missing necessary global type definitions
3. Exclude patterns: Your declaration files are being excluded
4. Module resolution: Can't find the original declarations to extend
Check file locations:
- Global augmentations: src/types/global.d.ts or src/global.d.ts
- Module augmentations: src/types/module-name.d.ts
- Third-party types: node_modules/@types/module-name/index.d.ts
Restart TypeScript server after configuration changes:
- VS Code: Ctrl/Cmd + Shift + P → "TypeScript: Restart TS Server"
### Declaration Merging Fundamentals
TypeScript supports three kinds of declaration merging:
1. Interface Merging: Multiple interface declarations with the same name merge:
interface Box { height: number; }
interface Box { width: number; }
// Result: interface Box { height: number; width: number; }2. Namespace Merging: Namespaces with the same name merge their exports.
3. Module Augmentation: Adding to existing module declarations using declare module.
### Global vs Module Augmentation
Global Augmentation (for built-ins like Window, Document):
// In a .d.ts file or .ts file with 'declare global'
declare global {
interface Window {
myGlobal: string;
}
}Module Augmentation (for npm packages):
// Must match the module name exactly
declare module "express" {
interface Request {
user?: User;
}
}### Type Compatibility Rules
When extending interfaces, remember these assignability rules:
- Property types: Your type must be assignable to the base type (covariant)
- Method parameters: Your parameters must be assignable from the base parameters (contravariant)
- Return types: Your return type must be assignable to the base return type (covariant)
- Optionality: You can add optional properties, but can't remove required ones
### Common Pitfalls
1. Extending lib.dom.d.ts interfaces: Window, Document, HTMLElement have strict definitions
2. Node.js globals: Process, Console, Buffer have specific types in @types/node
3. Third-party version mismatches: Your @types version might not match the runtime library version
4. Declaration file location: .d.ts files must be included in tsconfig.json's "include" array
### Debugging Tools
1. TypeScript compiler diagnostics:
npx tsc --diagnostics
npx tsc --explainFiles2. Check what TypeScript sees:
type Keys = keyof Window; // See all Window properties
type PropType = Window['location']; // Get type of specific property3. Use conditional types to test compatibility:
type IsAssignable<T, U> = T extends U ? true : false;
type Test = IsAssignable<YourWindow, Window>; // true or false### When to Use Declaration Merging vs Alternatives
Use declaration merging when:
- Adding properties to existing types you don't control
- Global type extensions needed throughout your app
- Integrating with third-party libraries that expect certain types
Consider alternatives when:
- You control the original type definitions (just modify them)
- The extension is only needed in specific parts of your code (use intersection types)
- You're frequently running into compatibility issues (create wrapper types)
### Version Compatibility
TypeScript's declaration merging behavior has evolved:
- TypeScript 2.0+: Enhanced module augmentation
- TypeScript 3.7+: Better error messages for incompatible declarations
- TypeScript 4.0+: Variadic tuple types can affect function signature compatibility
Always check if your TypeScript version matches the @types package versions.
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