Question
I want to store a mapping of string -> string in a TypeScript object and make sure every assigned value is a string.
For example:
const stuff = {};
stuff["a"] = "foo"; // okay
stuff["b"] = "bar"; // okay
stuff["c"] = false; // should be an error because boolean is not a string
Is there a way to define the object so TypeScript enforces that all indexed values must be strings, or another chosen type?
Short Answer
By the end of this page, you will understand how to type objects with dynamic keys in TypeScript so that every value must match a specific type. You will learn index signatures, the Record utility type, how assignment checking works, and how to avoid common mistakes when working with key-value maps.
Concept
In TypeScript, a plain empty object like this:
const stuff = {};
does not automatically mean “an object whose keys are strings and whose values are strings.” It simply means an object with no known properties yet.
If you want an object to behave like a dictionary or map—where keys are looked up dynamically—you should give it an explicit type.
The core feature for this is an index signature.
const stuff: { [key: string]: string } = {};
This tells TypeScript:
- keys are strings
- values must be strings
So now this is valid:
stuff["a"] = "foo";
stuff["b"] = "bar";
And this becomes a type error:
stuff["c"] = false;
This matters because many real programs use objects as lookup tables:
- configuration maps
- HTTP header collections
- error message dictionaries
- language translation tables
- cache objects
Without typing these objects, wrong values can slip in and cause bugs later. TypeScript helps catch those mistakes at development time.
Mental Model
Think of an indexed object as a set of labeled boxes.
- Each key is the label on a box
- Each value is what you put inside the box
If you declare the object as:
{ [key: string]: string }
then you are saying:
“You can create as many boxes as you want, with any string label, but every box must contain a string.”
So:
stuff["a"] = "hello"is okay because the box contains a stringstuff["c"] = falseis not okay because that box contains a boolean
The index signature is the rule for every box in the collection.
Syntax and Examples
Basic index signature
const stuff: { [key: string]: string } = {};
stuff["a"] = "foo";
stuff["b"] = "bar";
// stuff["c"] = false; // Error
This is the most direct way to say:
- any string key is allowed
- every value must be a string
Using Record
TypeScript also provides a built-in utility type:
const stuff: Record<string, string> = {};
stuff["a"] = "foo";
stuff["b"] = "bar";
// stuff["c"] = false; // Error
Record<string, string> means the same thing as:
{ [key: string]: string }
Many developers prefer Record because it is shorter and easier to read.
Step by Step Execution
Consider this example:
const stuff: Record<string, string> = {};
stuff["a"] = "foo";
stuff["b"] = "bar";
// stuff["c"] = false;
Step 1: Create the object
const stuff: Record<string, string> = {};
TypeScript reads this as:
stuffis an object- keys are strings
- values are strings
Step 2: Assign a string value
stuff["a"] = "foo";
- key:
"a"→ valid because it is a string - value:
"foo"→ valid because it is a string
So the assignment is allowed.
Step 3: Assign another string value
stuff[] = ;
Real World Use Cases
Configuration values
const envLabels: Record<string, string> = {
dev: "Development",
prod: "Production"
};
Useful when every entry should be plain text.
Translation dictionaries
const en: Record<string, string> = {
welcome: "Welcome",
logout: "Log out"
};
Translation keys are dynamic, but values should stay strings.
API error message maps
const errorMessages: Record<string, string> = {
NOT_FOUND: "Resource not found",
UNAUTHORIZED: "Please sign in"
};
Header or metadata storage
const : <, > = {
: ,
:
};
Real Codebase Usage
In real projects, indexed object types are often used for flexible data structures where the exact keys are not known in advance.
Common patterns
Validation output
const errors: Record<string, string> = {};
if (!email) {
errors.email = "Email is required";
}
A form can have many field names, but every error message should be a string.
Configuration maps
const featureFlags: Record<string, boolean> = {
darkMode: true,
betaSearch: false
};
Same idea, different value type.
Guarding missing values
const messages: Record<string, string> = {
hello: "Hi"
};
function getMessage(key: ): {
messages[key] ?? ;
}
Common Mistakes
1. Leaving the object untyped
const stuff = {};
stuff["a"] = "foo";
This does not clearly describe the intended shape of the object. Always add a type when you want dictionary-like behavior.
Correct:
const stuff: Record<string, string> = {};
2. Confusing key type with value type
const stuff: Record<string, string> = {};
This means:
- keys are strings
- values are strings
It does not mean only certain string keys are allowed. Any string key is allowed.
3. Expecting lookup safety for missing keys
const stuff: Record<string, string> = {};
console.log(stuff["missing"]);
At runtime, this may be if the key does not exist.
Comparisons
| Approach | Example | Best for | Notes |
|---|---|---|---|
| Index signature | { [key: string]: string } | Direct object typing | Very explicit |
Record | Record<string, string> | Readable dictionary types | Common and concise |
| Fixed object type | { a: string; b: string } | Known property names | Does not allow arbitrary keys easily |
Map | Map<string, string> | Advanced key-value storage | Different API from plain objects |
Index signature vs fixed properties
Cheat Sheet
// Index signature
const data: { [key: string]: string } = {};
// Record utility type
const data2: Record<string, string> = {};
Rules
stringkey type means any string key is allowed- value type controls what can be assigned
- wrong value types cause compile-time errors
Record<string, T>is equivalent to{ [key: string]: T }
Examples
const names: Record<string, string> = {};
names.first = "Alice";
names["last"] = "Smith";
// names.age = 30; // Error
Common alternatives
Record<string, number>
Record<, >
<, []>
FAQ
How do I type an object with string keys and string values in TypeScript?
Use either of these:
const data: { [key: string]: string } = {};
or:
const data: Record<string, string> = {};
What is the difference between Record<string, string> and { [key: string]: string }?
They mean the same thing. Record is just a built-in utility type that is often shorter and cleaner.
Why does const stuff = {} not enforce string values?
Because {} by itself does not describe a dictionary of string values. TypeScript needs an explicit type annotation.
Can I use types other than string for the values?
Yes. For example:
const flags: Record<, > = {};
: <, > = {};
Mini Project
Description
Build a small label dictionary for an application. The object should store message keys such as save, cancel, and delete, and every value must be a string. This demonstrates how to safely use dynamic keys while keeping values type-checked.
Goal
Create a typed dictionary object in TypeScript that only accepts string values for every string key.
Requirements
- Create an object that allows dynamic string keys.
- Ensure every value assigned to the object must be a string.
- Add at least three valid key-value pairs.
- Show one invalid assignment that TypeScript should reject.
- Read a value from the object and print it.
Keep learning
Related questions
Angular formGroup Error Explained: Fixing 'Can't bind to formGroup' in Reactive Forms
Learn why Angular shows 'Can't bind to formGroup' and how to fix it by importing ReactiveFormsModule correctly.
Fix "Element implicitly has an 'any' type" in TypeScript Object Indexing
Learn why TypeScript rejects string object indexing and how to fix it with keyof, unions, and typed object keys in React.
Fix "Property has no initializer" in Angular TypeScript Components
Learn why Angular TypeScript shows "Property has no initializer" and how to fix it using defaults, optional properties, or definite assignment.