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.
2. Extending the interface and changing the property
interface A {
property: number;
}
interface B extends A {
property: Object;
}
This also fails, because B must still be assignable to A. If A.property is a number, then B.property must also be a number or a compatible subtype. Object is not compatible with number here.
Why this matters
This rule prevents unsafe code. Imagine a function that expects A and does math with property. If another type silently changed that property to an object, the function could break at runtime.
function doubleValue(a: A) {
return a.property * 2;
}
If property stopped being a number, this code would no longer be safe.
What to do instead
If you need a similar type but with one property changed, create a new type rather than trying to mutate the original declaration.
A common pattern is:
type ModifiedA = Omit<A, "property"> & {
property: object;
};
This creates a new type based on A, but replaces property with a different type.
That is usually the cleanest solution when you do not control the original .d.ts file.
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;
};
Now:
A.propertyis stillnumberModifiedA.propertyisobjectnameis preserved
Example usage
const original: A = {
property: 42,
name: "Alice"
};
const changed: ModifiedA = {
property: { value: 42 },
name: "Alice"
};
If you control the original type
If you own the source, the best fix is often to update the original declaration instead of trying to override it later.
interface A<T = number> {
property: T;
}
const x: A<number> = { property: 1 };
const y: A<object> = { property: { value: 1 } };
This generic design is much more flexible when you can change the original type.
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
-
TypeScript intersects that result with
{ property: object }.- The final
ModifiedAtype becomes a type withproperty: object.
- The final
-
const a: A = { property: 10 }is checked.10is a number, so this is valid.
-
const b: ModifiedA = { property: { value: 10 } }is checked.{ value: 10 }is an object, so this is valid.
-
The original interface
Aremains unchanged.- Code expecting
Astill gets a numericproperty. - Code using
ModifiedAgets the replacement type.
- Code expecting
This is the safe way to "change" a property type: do not modify the original contract, create a new one.
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, "price"> & {
price: string;
};
Normalized application data
You may receive nested objects from an API but convert one field into an ID reference.
type StoredComment = Omit<ApiComment, "author"> & {
author: string;
};
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 === "number";
}
4. Keep external and internal models separate
Many codebases use a pattern like:
ApiUserfor data from the serverUserfor domain logicUserFormfor UI editing
This avoids forcing one interface to serve every purpose.
5. Use declaration merging only for compatible additions
Declaration merging is often used to add new fields, not to change existing field types.
interface Window {
myAppVersion: string;
}
That works because it adds a property instead of conflicting with an existing one.
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
numbertoobjectbreaks that relationship
How to avoid it:
- use a derived type instead of inheritance when the field must change
Mistake 3: Using Object when you really mean object
let a: Object;
let b: object;
Object and object are not the same idea in TypeScript. In modern TypeScript, object is usually the better choice when you mean a non-primitive object value.
Mistake 4: Editing library .d.ts files directly
This may appear to work temporarily, but it is fragile.
Why it is a problem:
- package updates can overwrite your changes
- other developers will not see the intent clearly
- type mismatches can spread through the codebase
Better approach:
- create local adapter types
- wrap external types with your own domain types
Mistake 5: Using type assertions to silence the problem
Broken approach:
const x = { property: { value: 1 } } as unknown as A;
Why to avoid it:
- this disables useful type checking
- runtime code may break if
Ais used as ifpropertywere numeric
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 | Yes, if you control the source |
Quick comparison: merging vs extending vs replacing
- Merging combines declarations with the same name.
- Extending creates a subtype that must still obey the parent.
- Replacing with
Omitcreates a separate type with one field changed.
If the goal is to keep most of A but change one property's type, Omit plus a new property is usually the right choice.
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 in K]: V;
};
Example:
type ModifiedA = ReplaceProperty<A, "property", object>;
Notes
extendsmust preserve compatibility with the parent typeobjectis usually preferable toObject- avoid editing third-party
.d.tsfiles directly
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, object is the better choice.
What if I control the original interface?
Consider redesigning it with generics so the property type can vary safely.
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.
Accessing Input Value from EventTarget in TypeScript
Learn why EventTarget has no value property in TypeScript and safely read values from HTML input events in Angular applications.
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.