Question
I have the following TypeScript type:
type Event = {
name: string;
dateCreated: string;
type: string;
};
I want to create a new type based on it by adding another property, for example:
type UserEvent extends Event = {
userId: string;
};
This does not work. How can I correctly extend a type in TypeScript?
Short Answer
By the end of this page, you will understand how to build new types from existing ones in TypeScript. You will learn the correct way to extend object shapes using interface extends and type intersections with &, when to use each approach, and how this pattern appears in real codebases.
Concept
In TypeScript, type aliases and interface declarations are similar, but they are not extended in exactly the same way.
If you define an object shape with type, you cannot write type NewType extends OldType = .... That syntax is not valid TypeScript.
To create a new type from an existing type, the usual approach is to use an intersection type with &:
type Event = {
name: string;
dateCreated: string;
type: string;
};
type UserEvent = Event & {
userId: string;
};
This means: take everything from Event and combine it with userId.
Another option is to use interface, which does support extends directly:
interface Event {
name: string;
dateCreated: string;
type: string;
}
interface UserEvent extends Event {
userId: string;
}
Why this matters:
- Real applications often build larger types from smaller reusable ones.
- API responses often share common fields like
id,createdAt, orstatus. - Reusing types avoids duplication and reduces bugs.
- It makes your code easier to maintain when data models change.
A good rule is:
- Use
interface extendswhen you are working with interfaces. - Use
&when you are combiningtypealiases.
Mental Model
Think of a TypeScript object type like a checklist of required fields.
Event says:
- must have
name - must have
dateCreated - must have
type
Now imagine you want a stricter checklist for a user-specific event. You do not throw away the original checklist. You stack another checklist on top of it.
So UserEvent means:
- everything required by
Event - plus
userId
Using & is like merging two checklists together.
Using interface extends is like saying: “Start with this checklist, then add one more rule.”
Syntax and Examples
Extending a type with &
type Event = {
name: string;
dateCreated: string;
type: string;
};
type UserEvent = Event & {
userId: string;
};
UserEvent now requires all four properties.
const loginEvent: UserEvent = {
name: "User Login",
dateCreated: "2026-05-04",
type: "auth",
userId: "u123"
};
Using interface extends
interface Event {
name: string;
dateCreated: string;
type: string;
}
interface UserEvent extends Event {
userId: string;
}
This produces a very similar result.
Adding multiple properties
type AdminEvent = Event & {
userId: string;
permissionLevel: string;
};
Reusing shared pieces
type Event = {
name: string;
dateCreated: string;
type: string;
};
type WithUser = {
userId: string;
};
type UserEvent = Event & WithUser;
This is useful when multiple types share the same extra fields.
Step by Step Execution
Consider this example:
type Event = {
name: string;
dateCreated: string;
type: string;
};
type UserEvent = Event & {
userId: string;
};
const event: UserEvent = {
name: "Purchase",
dateCreated: "2026-05-04",
type: "order",
userId: "user-42"
};
Here is what happens step by step:
Eventis defined as an object type with 3 required string properties.UserEventis created by combining:- all properties from
Event - one extra property:
userId
- all properties from
- When TypeScript checks the
eventobject, it expects:namedateCreatedtypeuserId
- The object contains all required properties, so it is valid.
If you leave one out:
const badEvent: UserEvent = {
name: "Purchase",
dateCreated: "2026-05-04",
type: "order"
};
TypeScript reports an error because userId is missing.
If you add the wrong type:
const badEvent2: UserEvent = {
name: "Purchase",
dateCreated: "2026-05-04",
type: "order",
userId: 123
};
TypeScript reports an error because userId must be a string, not a number.
Real World Use Cases
This pattern is very common in real applications.
API models
A base response type may contain common fields:
type BaseRecord = {
id: string;
createdAt: string;
};
type User = BaseRecord & {
email: string;
};
type Product = BaseRecord & {
price: number;
};
Event tracking
Analytics systems often have shared event fields plus event-specific fields.
type BaseEvent = {
timestamp: string;
eventType: string;
};
type ClickEvent = BaseEvent & {
elementId: string;
};
Backend validation models
You may define a base entity and extend it for different use cases such as create, update, or response types.
UI state objects
A component may reuse a base data shape and add local UI properties like loading state or error messages.
Real Codebase Usage
In real projects, developers usually combine small reusable types instead of repeating full object definitions.
Common patterns
Base domain models
type BaseEntity = {
id: string;
createdAt: string;
updatedAt: string;
};
type Post = BaseEntity & {
title: string;
body: string;
};
Error handling
type ApiError = {
message: string;
code: string;
};
type FieldError = ApiError & {
field: string;
};
Configuration objects
type BaseConfig = {
env: string;
};
type DbConfig = BaseConfig & {
connectionString: string;
};
Guarding and validating data
Developers often define a broad base type, then create more specific versions for validated data.
type Event = {
name: string;
dateCreated: string;
type: string;
};
type ValidatedEvent = Event & {
id: string;
};
This helps express the difference between raw input and processed data.
Choosing between type and interface
In many codebases:
interfaceis used for object shapes that may be extended often.typeis used when combining unions, intersections, primitives, tuples, or utility types.
Both are valid. Consistency matters more than strict preference.
Common Mistakes
1. Trying to use extends with type
This is the exact problem in the question.
// Incorrect
type UserEvent extends Event = {
userId: string;
};
Use this instead:
type UserEvent = Event & {
userId: string;
};
2. Confusing type and interface
This works:
interface Event {
name: string;
}
interface UserEvent extends Event {
userId: string;
}
But this does not:
type Event = {
name: string;
};
// Invalid syntax
type UserEvent extends Event = {
userId: string;
};
3. Overwriting properties with incompatible types
type Event = {
type: string;
};
type BadUserEvent = Event & {
type: number;
};
This creates an impossible type for type, because it would need to be both string and number.
Avoid redefining the same property with conflicting types.
4. Forgetting that all required fields still apply
type Event = {
name: string;
dateCreated: string;
type: string;
};
type UserEvent = Event & {
userId: string;
};
const e: UserEvent = {
userId: "u1"
};
This is invalid because name, dateCreated, and type are still required.
5. Using the name Event in browser code without thinking
In DOM-related TypeScript projects, Event is already a built-in type used for browser events. Naming your own type Event can be confusing.
Safer alternatives:
type AppEvent = {
name: string;
dateCreated: string;
type: string;
};
Comparisons
| Approach | Syntax | Best for | Notes |
|---|---|---|---|
Extend with interface | interface UserEvent extends Event { ... } | Object shapes | Clean and readable for object inheritance |
Combine with type intersection | type UserEvent = Event & { ... } | Reusing type aliases | Works well with unions and utility types |
| Duplicate fields manually | Write all properties again | Almost never ideal | Repetition increases maintenance cost |
type vs interface
| Feature | type | interface |
|---|---|---|
| Object shapes | Yes | Yes |
| Union types | Yes | No |
| Intersection types | Yes | Limited through composition |
extends keyword | No, not in alias declaration | Yes |
| Declaration merging | No | Yes |
Which should you use here?
If you already have:
type Event = { ... }
Use:
type UserEvent = Event & { userId: string }
If you prefer inheritance-style syntax, define both as interfaces instead.
Cheat Sheet
// Base type
type Event = {
name: string;
dateCreated: string;
type: string;
};
// Correct: extend a type alias using &
type UserEvent = Event & {
userId: string;
};
// Alternative: use interfaces
interface Event {
name: string;
dateCreated: string;
type: string;
}
interface UserEvent extends Event {
userId: string;
}
Rules to remember
type X extends Y = ...is invalid.- Use
&to combinetypealiases. - Use
extendswithinterface. - Combined types must satisfy all required properties.
- Avoid conflicting property names with different types.
- Be careful with the name
Eventin browser projects.
Quick pattern
type NewType = ExistingType & {
extra: string;
};
Edge case
type A = { value: string };
type B = A & { value: number };
This is not useful because value cannot be both string and number at the same time.
FAQ
Can a TypeScript type extend another type?
Not with extends syntax in the alias declaration. Use an intersection with & instead.
How do I add properties to an existing type in TypeScript?
Create a new type using &:
type UserEvent = Event & { userId: string };
Should I use type or interface in TypeScript?
Use either consistently. interface is great for object shapes and extends; type is more flexible for unions, intersections, and other compositions.
Is interface extends the same as type &?
They are often similar for object shapes, but they are not identical in every advanced case. For this use case, both can model the same result.
Why does type UserEvent extends Event = ... fail?
Because TypeScript does not allow extends in a type alias declaration like that.
Can I override a property when extending a type?
You can redefine properties, but if the types conflict, the result may become unusable. It is better to avoid incompatible redefinitions.
Is Event a bad type name in TypeScript?
It can be confusing in frontend projects because the DOM already has a built-in Event type.
Mini Project
Description
Build a small event model for an application that tracks different kinds of events. You will start with a shared base type and create more specific event types by extending it. This demonstrates how real TypeScript projects reuse type definitions instead of duplicating fields.
Goal
Create reusable event types where each specific event includes all base event fields plus its own additional properties.
Requirements
- Create a base event type with
name,dateCreated, andtype. - Create a
UserEventtype that addsuserId. - Create a
PurchaseEventtype that addsuserIdandamount. - Declare one valid object for each specific type.
- Write a function that accepts a
UserEventand logs its information.
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.