How to dynamically assign properties to an object in TypeScript? A clear guide for beginners
\n\nHello, friend! If you've ever written in JavaScript and then switched to TypeScript, you've probably encountered a situation where you need to add a new property to an object on the fly. In JS, this is done in no time: obj.newProp = 'value'. But as soon as you try the same thing in TypeScript, the compiler starts complaining: "Property 'newProp' does not exist on type ...". Sound familiar?
Don't worry, this is normal. TypeScript protects us from accidental errors, but sometimes its strictness seems excessive. In this article, we'll look at how to correctly and safely assign properties to objects dynamically. You'll learn several proven methods — from simple to more elegant ones. Let's go!
\n\nWhy does TypeScript complain?
\n\nBefore solving the problem, let's understand its root. TypeScript is a superset of JavaScript that adds static typing. When you declare an object:
\n\nconst user = { name: 'Anna', age: 25 };\n\n\nTypeScript automatically infers its type: { name: string; age: number }. This means the user object can only contain these two properties. Trying to add email will cause a compilation error:
user.email = 'anna@example.com'; // ❌ Error!\n\n\nTypeScript doesn't know that such a property might appear and blocks the code. But in real life, you often need to add properties dynamically — for example, when working with data from an API or forms. Let's see how to do this correctly.
\n\nMethod 1: The simplest — as any
\n\nA quick but dirty method is to use as any. It literally tells TypeScript: "Turn off type checking for this object."
const user: any = { name: 'Anna', age: 25 };\nuser.email = 'anna@example.com'; // ✅ Works\nuser.isAdmin = true; // ✅ Works\n\n\nWhy is this bad? Because you lose all the benefits of TypeScript: autocompletion, type checking, protection against typos. If you accidentally write user.emial, there will be no error — you'll only find out about it at runtime. Use this method only for quick prototyping or when you know exactly what you're doing.
Method 2: Index Signature
\n\nA more correct approach is to declare in advance that the object can contain dynamic properties. This is done using an index signature.
\n\ninterface User {\n name: string;\n age: number;\n [key: string]: any; // 👈 Allow any additional properties\n}\n\nconst user: User = { name: 'Anna', age: 25 };\nuser.email = 'anna@example.com'; // ✅ Works\nuser.phone = '+7-999-123-45-67'; // ✅ Works\n\n\nWhat's happening here? The notation [key: string]: any means: "the object can have any string keys, and their values can be of any type." Now you can add properties dynamically, and TypeScript won't complain.
Important nuance: if you want to restrict the types of values, replace any with a specific type:
interface User {\n name: string;\n age: number;\n [key: string]: string | number; // Only strings and numbers\n}\n\n\nAn index signature is an excellent choice when you know in advance that the object will be extended. But be careful: if the interface has other properties with different types, they must also match the signature.
\n\nMethod 3: The utility type Record
\n\nIf you need to create an object where all keys and values are of the same type, use the built-in type Record<Keys, Type>. It's perfect for dictionaries or mappings.
type UserRoles = Record<string, 'admin' | 'user' | 'guest'>;\n\nconst roles: UserRoles = {\n anna: 'admin',\n ivan: 'user',\n};\