How to Add a property to an Object in TypeScript

avatar
Borislav Hadzhiev

Last updated: Feb 27, 2024
10 min

banner

# Table of Contents

  1. Add a property to an Object in TypeScript
  2. Dynamically add Properties to an Object in TypeScript
  3. Set an Object's property name from a Variable in TypeScript
  4. Constructing the object's property name from multiple variables
  5. Using Object.assign() in TypeScript

# Add a property to an Object in TypeScript

To add a property to an object in TypeScript:

  1. Mark the property on the interface or type as optional.
  2. Use the interface to type the object.
  3. Add the property to the object.
index.ts
interface Person { name: string; age?: number; // ๐Ÿ‘ˆ๏ธ mark as optional so you can add it later } const obj: Person = { name: 'Bobby Hadz', }; obj.age = 30;

add property to object

The code for this article is available on GitHub

We set the age property on the Person interface to optional.

The age property can either be undefined or a number. This approach is used when you know the name of the property and the type of its value ahead of time as it keeps things type-safe.

Now you can initialize the object without the property and set it later on.

index.ts
interface Person { name: string; age?: number; // ๐Ÿ‘ˆ๏ธ mark as optional } const obj: Person = { name: 'Bobby Hadz', }; obj.age = 30; // โ›” Error: Type 'string' is not assignable to type 'number'. obj.age = 'hello';

If you try to assign a non-numeric value to the age property, you'd get an error.

If you need to make all properties of an object optional, use the Partial utility type.

# Add any property to an object in TypeScript

You can use the Record utility type to add any property of any type to an object.

index.ts
const obj: Record<string, any> = {}; // ๐Ÿ‘‡๏ธ can now add any property to the object obj.name = 'Bobby Hadz'; obj.age = 30; // ๐Ÿ‘‡๏ธ { name: 'Bobby Hadz', age: 30 } console.log(obj);

add any property to object in typescript

The code for this article is available on GitHub

The Record utility type allows us to enforce the type of an object's values in TypeScript, e.g. type Animal = Record<string, string>.

The Record utility type constructs an object type, whose keys and values are of the specified type.

We used a type of any for the values and a type of string for the keys in the object.

This is very broad and allows us to add any property of any type to the object.

It's best to be as specific as possible when typing an object in TypeScript. We should try to leverage the language as much as possible.

# Add any property but type key-value pairs you know in advance

You can specify the properties and the types in an object that you know about and use the Record utility type to allow the user to add other properties.

index.ts
interface Animal extends Record<string, any> { name: string; age: number; } const obj: Animal = { name: 'Alfred', age: 3 }; // ๐Ÿ‘‡๏ธ Can now add any property, but name and age are typed obj.type = 'Dog';

add any property but type known key value pairs

The code for this article is available on GitHub

The interface we created requires the name and age properties but also extends a type that allows any string properties with values of any type.

This is better than just using the Record utility type with string keys and any values because we get type safety when it comes to the properties we explicitly specified.

index.ts
interface Animal extends Record<string, any> { name: string; age: number; } const obj: Animal = { name: 'Alfred', age: 3 }; // โ›”๏ธ Type 'number' is not assignable to type 'string'. obj.name = 5;

Setting a property to the incorrect type causes an error because we've already declared the name property to be of type string in the Animal interface.

I've also written an article on how to get an object's key by value in TS.

# Dynamically add Properties to an Object in TypeScript

Use an index signature to dynamically add properties to an object.

Index signatures are used when we don't know all of the names of a type's properties and the type of their values ahead of time.

index.ts
interface Person { [key: string]: any; } const obj: Person = {}; obj.name = 'Bobby Hadz'; obj.age = 30;

dynamically add properties to object in typescript

The code for this article is available on GitHub

The {[key: string]: any} syntax is an index signature in TypeScript and is used when we don't know all the names of a type's properties and the shape of the values ahead of time.

The index signature in the examples means that when the object is indexed with a string, it will return a value of any type.

You might also see the index signature {[key: string]: string} in examples. It represents a key-value structure that when indexed with a string returns a value of type string.

I've also written an article on how to dynamically access an object's property.

# Explicitly typing specific properties

If the value of the string keys were set to any in the index signature, you could add properties to the object of any type, since anything is more specific than any.

index.ts
interface Person { [key: string]: any; age: number; name: string; country?: string; } const obj: Person = { name: 'Bobby Hadz', age: 30, }; obj.country = 'Chile'; obj.language = 'TypeScript';

explicitly typing specific properties

The code for this article is available on GitHub

This is a good way to narrow down the type of some of the properties that you know ahead of time.

For example, if I try to set the age property to a string, the type checker would throw an error because it expects a number.

index.ts
interface Person { [key: string]: any; age: number; name: string; country?: string; } const obj: Person = { name: 'Bobby Hadz', age: 30, }; // โ›”๏ธ Error: Type 'string' is not assignable to type 'number'. obj.age = '100';

We've set the age property to have a type of number, so the type checker helps us spot an error in our application.

# Using a union to dynamically add properties to an object

You can also use a union type to dynamically add properties to an object.

index.ts
interface Person { // ๐Ÿ‘‡๏ธ key value [key: string]: string | number; } const obj: Person = { name: 'Bobby Hadz', }; obj.age = 30; obj.country = 'Chile';
The code for this article is available on GitHub
The index signature in the example means that when the object is indexed with astring, it will return a value of typestring or number.

The keys in the object can either have a type of string or number.

Both types are allowed and you can use the union | syntax to include as many types as necessary.

If we try to set a property with a different value on the object, we'd get an error.

index.ts
interface Person { // ๐Ÿ‘‡๏ธ key value [key: string]: string | number; } const obj: Person = { name: 'Bobby Hadz', }; // โ›”๏ธ Type 'boolean' is not assignable to type 'string | number'.ts(2322) obj.isProgrammer = true;

# Dynamically adding properties to an object with the Record type

The Record utility type constructs an object type whose keys and values are of a specified type.

index.ts
const obj: Record<string, any> = { name: 'Bobby Hadz', }; obj.age = 30; obj.country = 'Chile';
The code for this article is available on GitHub

We typed the object above to have keys of type string and values of type any.

The first type we passed to the generic is the type for the Keys, and the second is the type for the values.

If you know the type of some of the values ahead of time, specify them in an interface for better type safety.

index.ts
interface EmployeeData extends Record<string, any> { role: string; salary: number; color?: string; } const employees: Record<string, EmployeeData> = { tom: { role: 'accountant', salary: 10, color: 'blue' }, alice: { role: 'manager', salary: 15 }, }; // โ›”๏ธ Type 'number' is not assignable to type 'string'.ts(2322) employees.tom.role = 100; // โœ… still works employees.tom.hello = 'world';

The interface EmployeeData extends from the Record constructed type with string keys and any type values.

The interface sets a specific type for the 3 properties that we know about in advance.

If we try to set the role, salary or color properties to an incompatible type, we'd get an error.

# Set an Object's property name from a Variable in TypeScript

Use computed property names to set an object's property name from a variable in TypeScript.

index.ts
type Person = { name: string; country: string; }; const myVar = 'name'; const obj: Person = { [myVar]: 'Bobby Hadz', country: 'Chile', }; // ๐Ÿ‘‡๏ธ { name: 'Bobby Hadz', country: 'Chile' } console.log(obj);
The code for this article is available on GitHub

We set an object's property name from a variable using computed properties.

All we had to do was wrap the variable in square brackets [] for it to get evaluated before being assigned as a property on the object.

Here is an example that uses a function.

index.ts
type Person = { name: string; country: string; }; const myVar = 'name'; function getObj(): Person { return { [myVar]: 'Bobby Hadz', country: 'Chile', }; } const obj = getObj(); // ๐Ÿ‘‡๏ธ {name: 'Bobby Hadz', country: 'Chile'} console.log(obj);

# Constructing the object's property name from multiple variables

The expression between the square brackets gets evaluated, so you could construct the object's property name by using multiple variables or concatenating strings.

index.ts
const myVar = 'na'; const myVar2 = 'me'; // ๐Ÿ‘‡๏ธ const obj: { [x: string]: string; country: string;} const obj = { [myVar + myVar2]: 'Bobby Hadz', country: 'Chile', }; // ๐Ÿ‘‡ {name: 'Bobby Hadz', country: 'Chile'} console.log(obj);
The code for this article is available on GitHub

You can also use a template literal.

index.ts
const myVar = 'na'; const myVar2 = 'me'; // ๐Ÿ‘‡๏ธ const obj: { [x: string]: string; country: string;} const obj = { [`${myVar}${myVar2}`]: 'Bobby Hadz', country: 'Chile', }; // ๐Ÿ‘‡ {name: 'Bobby Hadz', country: 'Chile'} console.log(obj);

However, notice that TypeScript was not able to type the property in the object as name and instead typed it as a more generic index signature of [x: string] (any string property).

If you are sure about the value the expression will evaluate to, use a type assertion.

index.ts
const myVar = 'na'; const myVar2 = 'me'; // ๐Ÿ‘‡๏ธ const obj: const obj: { name: string; country: string;} const obj = { [(myVar + myVar2) as 'name']: 'Bobby Hadz', country: 'Chile', }; // ๐Ÿ‘‡๏ธ {name: 'Bobby Hadz', country: 'Chile'} console.log(obj);

Now the object is typed correctly and we can still use the dynamic nature of the computed property names feature.

# Using Object.assign() in TypeScript

To use the Object.assign() method in TypeScript, pass a target object as the first parameter to the method and one or more source objects.

The method will copy the properties from the source objects to the target object.

index.ts
const obj1 = { name: 'Bobby Hadz' }; const obj2 = { country: 'Chile' }; // ๐Ÿ‘‡๏ธ const result: { name: string; } & { country: string; } const result = Object.assign({}, obj1, obj2); // ๐Ÿ‘‡๏ธ { name: 'Bobby Hadz', country: 'Chile' } console.log(result);
The code for this article is available on GitHub

We used the Object.assign method to merge two source objects into a target object.

The first parameter the method takes is a target object to which the properties of the source objects are applied.

The next parameters are source objects - objects containing the properties you want to apply to the target.

# Caveats around using an existing object as the target

In the example, we passed an empty object as the target because you should generally avoid mutating objects as it introduces confusion.

For example, we could've passed obj1 as the target and obj2 as the source.

index.ts
const obj1 = { name: 'Bobby Hadz' }; const obj2 = { country: 'Chile' }; // ๐Ÿ‘‡๏ธ const result: { name: string; } & { country: string; } const result = Object.assign(obj1, obj2); // ๐Ÿ‘‡๏ธ {name: 'Bobby Hadz', country: 'Chile'} console.log(result); // ๐Ÿ‘‡๏ธ {name: 'Bobby Hadz', country: 'Chile'} console.log(obj1); // โ›”๏ธ Error: Property 'country' does not // exist on type '{ name: string; }'.ts(2339) console.log(obj1.country);
The code for this article is available on GitHub

Note that the target object is changed in place.

This is especially problematic when using TypeScript because obj1 was changed in place but its type is still {name: string}, even though the object contains a country property as well.

This is why you will often see an empty object passed as the first parameter to the Object.assign method.

# The return type of Object.assign in TypeScript

TypeScript uses an intersection type to type the return value of the Object.assign() method.

index.ts
const obj1 = { name: 'Bobby Hadz' }; const obj2 = { country: 'Chile' }; // ๐Ÿ‘‡๏ธ const result: { name: string; } & { country: string; } const result = Object.assign({}, obj1, obj2); // ๐Ÿ‘‡๏ธ { name: 'Bobby Hadz', country: 'Chile' } console.log(result);
The code for this article is available on GitHub

In other words, the return value has a type that has all of the members of all of the objects you passed to the Object.assign() method.

# The latter object wins

When you use the Object.assign() method with objects that have the same properties, the properties are overwritten by objects that come later in the parameter list.

index.ts
const obj1 = { name: 'Bobby Hadz' }; const obj2 = { name: 'James' }; // ๐Ÿ‘‡๏ธ const result: { name: string; } & { name: string; } const result = Object.assign({}, obj1, obj2); console.log(result); // ๐Ÿ‘‰๏ธ {name: 'James'} console.log(result.name); // ๐Ÿ‘‰๏ธ "James"

Both of the objects in the example have a name property, so the object that is passed later in the parameters order wins.

I've also written an article on how to initialize a typed empty object in TypeScript.

# An alternative to using Object.assign

You should also consider using the spread syntax (...) as a replacement for Object.assign().

index.ts
const obj1 = { name: 'Bobby Hadz' }; const obj2 = { country: 'Chile' }; // ๐Ÿ‘‡๏ธ const result: {country: string;name: string;} const result = { ...obj1, ...obj2 }; // ๐Ÿ‘‡๏ธ { name: 'Bobby Hadz', country: 'Chile' } console.log(result);
The code for this article is available on GitHub

The spread syntax (...) unpacks the properties of the objects into a new object.

It is much easier for TypeScript to type the new object correctly than it is when you use the Object.assign() method.

This is generally a better approach because you can't shoot yourself in the foot by forgetting to provide an empty object as the first parameter of the Object.assign() method.

You can unpack as many objects as necessary into a new object and if two objects have the same property, the object that comes later wins.

index.ts
const obj1 = { name: 'Bobby Hadz' }; const obj2 = { name: 'James' }; // ๐Ÿ‘‡๏ธ const result: {name: string;} const result = { ...obj1, ...obj2 }; console.log(result); // ๐Ÿ‘‰๏ธ {name: 'James'}

Both of the objects have a name property, so the latter object wins.

# Additional Resources

You can learn more about the related topics by checking out the following tutorials:

I wrote a book in which I share everything I know about how to become a better, more efficient programmer.
book cover
You can use the search field on my Home Page to filter through all of my articles.

Copyright ยฉ 2024 Borislav Hadzhiev