Question
Can You Override an Interface Property Type in TypeScript Declaration Files?
Question
In TypeScript, is it possible to change the type of an interface property that was originally defined in a .d.ts file?
For example, suppose a declaration file contains:
interface A {
property: number;
}
Can I redefine it in my own TypeScript code like this?
interface A {
property: Object;
}
Or would extending the interface work?
interface B extends A {
property: Object;
}
I tried this and it did not work on my system. I want to confirm whether overriding a property type like this is possible in TypeScript, especially when the original interface comes from a .d.ts file.
Short Answer
By the end of this page, you will understand how TypeScript handles interface declarations, why property types cannot be arbitrarily overridden, and which alternatives are safe and idiomatic. You will also learn the difference between declaration merging, extending interfaces, and creating replacement types with utilities such as Omit.
Concept
TypeScript interfaces describe the shape of values. When an interface is declared in a .d.ts file, it becomes part of the type information used by your project.
A key rule is that you cannot redefine an existing property to an incompatible type. If a property is declared as number, TypeScript will not let you later change it to Object through interface merging or by extending the interface.
Why? Because TypeScript needs type relationships to stay consistent.
If A.property is a number, then any value of type A must have a numeric property. If another declaration changed that same property to Object, the compiler would no longer know which version is correct.
What happens in the two common attempts?
1. Redeclaring the same interface
interface A {
property: number;
}
interface A {
property: Object;
}
This is called declaration merging. TypeScript does allow some interface declarations to merge, but overlapping properties must be compatible. A property cannot change from number to Object.
Mental Model
Think of an interface as a contract printed on paper.
If the contract says:
propertymust be anumber
then everyone using that contract expects a number.
You cannot take the same contract and scribble over it to say:
propertyis now anObject
because that would confuse all the people already relying on the original rules.
Instead, you create a new contract based on the old one:
- keep everything else the same
- replace only the field you need
So the mental model is:
- merge = combine compatible contract details
- extend = make a more specific contract that still obeys the original
- replace with a new type = create a fresh contract when a field must change
Syntax and Examples
Declaration merging does not allow incompatible property changes
interface A {
property: number;
}
interface A {
property: object; // Error
}
This fails because both declarations describe the same property name, but with incompatible types.
Extending also does not allow incompatible overrides
interface A {
property: number;
}
interface B extends A {
property: object; // Error
}
This fails because B must still satisfy A.
Correct approach: create a new type with a replaced property
interface A {
property: number;
name: string;
}
type ModifiedA = Omit<A, "property"> & {
property: object;
};
Step by Step Execution
Consider this code:
interface A {
property: number;
}
type ModifiedA = Omit<A, "property"> & {
property: object;
};
const a: A = { property: 10 };
const b: ModifiedA = { property: { value: 10 } };
Here is what happens step by step:
-
TypeScript reads
A.- It records that any
Amust have apropertyof typenumber.
- It records that any
-
TypeScript reads
Omit<A, "property">.- This means: take all properties from
Aexceptproperty. - Since
Aonly hasproperty, the result is an empty object type here.
- This means: take all properties from
Real World Use Cases
Working with third-party library types
A package may ship a .d.ts file where a field is too narrow for your application. Instead of changing the library type directly, you create a local derived type.
type AppUser = Omit<LibraryUser, "metadata"> & {
metadata: Record<string, unknown>;
};
Adapting API response types
An external API might document a field as a string in one layer, but your app transforms it into a structured object.
type ParsedOrder = Omit<ApiOrder, "createdAt"> & {
createdAt: Date;
};
Form models vs domain models
A domain model may store numbers, while a form temporarily stores strings.
interface Product {
price: number;
}
type ProductForm = Omit<Product, > & {
: ;
};
Real Codebase Usage
In real projects, developers rarely try to mutate third-party declaration types directly. More common patterns include:
1. Create adapted types
This is the most common approach.
type SafeConfig = Omit<ExternalConfig, "timeout"> & {
timeout: string;
};
2. Use wrapper functions
A function can accept the external type, transform it, and return your internal type.
function normalizeUser(user: ApiUser): AppUser {
return {
...user,
createdAt: new Date(user.createdAt)
};
}
3. Add guard clauses and validation
If the incoming value is uncertain, developers validate it before treating it as the adapted type.
function isNumber(value: unknown): value is number {
return typeof value === ;
}
Common Mistakes
Mistake 1: Trying to overwrite a property during interface merging
Broken code:
interface A {
property: number;
}
interface A {
property: object;
}
Why it fails:
- merged properties must be compatible
numberandobjectare not compatible here
How to avoid it:
- create a new type with
Omitand a replacement property
Mistake 2: Assuming extends means "replace anything"
Broken code:
interface A {
property: number;
}
interface B extends A {
property: object;
}
Why it fails:
Bmust still satisfyA- changing
numberto breaks that relationship
Comparisons
| Approach | Can it change an existing property type? | Typical use | Safe for this case? |
|---|---|---|---|
| Interface merging | No, not to an incompatible type | Combine compatible declarations | No |
extends | No, child must remain compatible with parent | Make a more specific subtype | No |
Omit<T, K> & { ... } | Yes, by creating a new type | Replace selected properties in a derived type | Yes |
Edit original .d.ts | Technically possible by changing source files, but not recommended for dependencies | Only when you own the declarations | Usually no |
| Generics in original design | Yes, if designed that way | Flexible reusable interfaces |
Cheat Sheet
Core rule
You cannot change an existing interface property from one incompatible type to another through merging or extending.
Not allowed
interface A {
property: number;
}
interface A {
property: object; // Error
}
interface B extends A {
property: object; // Error
}
Recommended pattern
type ModifiedA = Omit<A, "property"> & {
property: object;
};
When declaration merging works
- adding new compatible members
- augmenting global interfaces like
Window - augmenting module declarations carefully
Useful utility pattern
type ReplaceProperty<T, K extends keyof T, V> = Omit<T, K> & {
[P K]: V;
};
FAQ
Can I override a property type in an existing TypeScript interface?
Not if the new type is incompatible with the original one. TypeScript does not allow that through interface merging or inheritance.
Why does interface B extends A fail when I change the property type?
Because B must still be usable wherever A is expected. If A.property is number, then B.property must remain compatible with number.
Can declaration merging change property types in TypeScript?
Only if the declarations are compatible. Changing number to object is not compatible, so it fails.
What is the best way to replace one property type?
Create a new type using Omit and add the property back with the new type.
Should I edit a third-party .d.ts file directly?
Usually no. Your changes can be lost during updates, and it makes maintenance harder.
Is Object the same as object in TypeScript?
No. They are different. In most cases where you mean a non-primitive value, is the better choice.
Mini Project
Description
Build a small type-adaptation example for an API model. The project demonstrates how to keep a third-party or external interface unchanged while creating a safer local type with one property replaced. This mirrors a common real-world situation where API data uses one shape, but your application needs another.
Goal
Create a new TypeScript type based on an existing interface, replacing one property type without modifying the original interface.
Requirements
- Define an interface named
ApiUserwith anidof typenumberand acreatedAtof typestring. - Create a new type named
AppUserwherecreatedAtis aDateinstead of astring. - Write a function that converts an
ApiUserinto anAppUser. - Show one valid example of each type.
- Do not modify the original
ApiUserinterface.
Keep learning
Related questions
@Directive vs @Component in Angular: Differences, Use Cases, and When to Use Each
Learn the difference between @Directive and @Component in Angular, including use cases, examples, and when to choose each.
Angular (change) vs (ngModelChange): What’s the Difference?
Learn the difference between Angular (change) and (ngModelChange), when each fires, and which one to use in forms and inputs.
Angular formControl Error with Material Autocomplete: Why It Happens and How to Fix It
Learn why Angular says it cannot bind to formControl and how to fix Reactive Forms setup with Angular Material Autocomplete.