Question

Can You Override an Interface Property Type in TypeScript Declaration Files?

typescriptjavascripttypescript-typings

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:

  • property must be a number

then everyone using that contract expects a number.

You cannot take the same contract and scribble over it to say:

  • property is now an Object

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
Take Quiz

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.property is still number
  • ModifiedA.property is object
  • name is 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:

  1. TypeScript reads A.

    • It records that any A must have a property of type number.
  2. TypeScript reads Omit<A, "property">.

    • This means: take all properties from A except property.
    • Since A only has property, the result is an empty object type here.
  3. TypeScript intersects that result with { property: object }.

    • The final ModifiedA type becomes a type with property: object.
  4. const a: A = { property: 10 } is checked.

    • 10 is a number, so this is valid.
  5. const b: ModifiedA = { property: { value: 10 } } is checked.

    • { value: 10 } is an object, so this is valid.
  6. The original interface A remains unchanged.

    • Code expecting A still gets a numeric property.
    • Code using ModifiedA gets the replacement type.

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;
};
Take Quiz

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:

  • ApiUser for data from the server
  • User for domain logic
  • UserForm for 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
  • number and object are not compatible here

How to avoid it:

  • create a new type with Omit and 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:

  • B must still satisfy A
  • changing number to object breaks 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 A is used as if property were numeric

Comparisons

ApproachCan it change an existing property type?Typical useSafe for this case?
Interface mergingNo, not to an incompatible typeCombine compatible declarationsNo
extendsNo, child must remain compatible with parentMake a more specific subtypeNo
Omit<T, K> & { ... }Yes, by creating a new typeReplace selected properties in a derived typeYes
Edit original .d.tsTechnically possible by changing source files, but not recommended for dependenciesOnly when you own the declarationsUsually no
Generics in original designYes, if designed that wayFlexible reusable interfacesYes, 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 Omit creates 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.

Take Quiz

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

  • extends must preserve compatibility with the parent type
  • object is usually preferable to Object
  • avoid editing third-party .d.ts files 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.

Take Quiz

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

  1. Define an interface named ApiUser with an id of type number and a createdAt of type string.
  2. Create a new type named AppUser where createdAt is a Date instead of a string.
  3. Write a function that converts an ApiUser into an AppUser.
  4. Show one valid example of each type.
  5. Do not modify the original ApiUser interface.
Take Quiz

Keep learning

Related questions

View more