How to fix Index signature parameter type cannot be generic in TypeScript
This TypeScript error occurs when you try to use a generic type parameter as an index signature key, which is not allowed. Index signatures only support specific key types like string, number, symbol, or template literal types. The fix involves using mapped types, keyof constraints, or redesigning your type structure.
Quick fix
Fixes TS1337
// ❌ WRONG - Causes error
interface GenericIndex<T> {
[key: T]: string; // Error: Index signature parameter type cannot be generic
}
// ✅ CORRECT - Use mapped type
type GenericMapped<T extends string | number | symbol> = {
[K in T]: string;
};
// Example usage
type StringKeys = GenericMapped<"a" | "b" | "c">;
// Result: { a: string; b: string; c: string; }
// ✅ Also works with keyof constraints
type PickProperties<T, K extends keyof T> = {
[P in K]: T[P];
};
// Example: Pick specific properties from an object type
interface User {
id: number;
name: string;
email: string;
age: number;
}
type UserNameAndEmail = PickProperties<User, "name" | "email">;
// Result: { name: string; email: string; }- Use mapped types instead of index signatures with generics
- Use Record utility type for dictionary-like structures
- Constrain generic parameters to allowed index signature types
- Use conditional types and type inference for complex scenarios
// ❌ WRONG - Causes errorinterface GenericIndex<T> {[key: T]: string; // Error: Index signature parameter type cannot be generic}// ✅ CORRECT - Use mapped typetype GenericMapped<T extends string | number | symbol> = {[K in T]: string;};// Example usagetype StringKeys = GenericMapped<"a" | "b" | "c">;// Result: { a: string; b: string; c: string; }// ✅ Also works with keyof constraintstype PickProperties<T, K extends keyof T> = {[P in K]: T[P];};// Example: Pick specific properties from an object typeinterface User {id: number;name: string;email: string;age: number;}type UserNameAndEmail = PickProperties<User, "name" | "email">;// Result: { name: string; email: string; }Index signature parameter type cannot be generic
What this error means
The "Index signature parameter type cannot be generic" error appears when you attempt to use a generic type parameter as the key type in an index signature. Index signatures in TypeScript define the shape of objects when accessing properties with dynamic keys, and they have strict requirements for what types can be used as keys. TypeScript only allows specific types for index signature keys: - Primitive types: `string`, `number`, `symbol` - Template literal types (e.g., ``x-${string}``) - Union types consisting only of these allowed types Generic type parameters like `<T>` cannot be used directly because TypeScript needs to know the concrete key type at compile time for type safety and performance reasons. This restriction ensures that TypeScript can properly check property access and assignment operations. The error typically occurs when you're trying to create flexible object types with dynamic keys based on generic parameters, but you need to use alternative TypeScript features like mapped types or `keyof` constraints instead.
Typical symptoms
- Compilation fails with 'Index signature parameter type cannot be generic' error
- IDE shows red underlines on index signature definitions with generic parameters
- Error appears when defining interfaces or types with index signatures using generic type parameters
- Code that works with concrete types fails when converted to use generics
- Attempting to create flexible object types with dynamic keys based on generic parameters
- Trying to use template types or conditional types as index signature keys
Common causes
- Using a generic type parameter directly as an index signature key (e.g., `[key: T]: string`)
- Attempting to create a completely dynamic object type where keys are determined by a generic parameter
- Confusing index signatures with mapped types, which serve similar but distinct purposes
- Trying to use conditional types or template literal types with generics in index signatures
- Missing proper constraints on generic type parameters when working with object keys
- Attempting to create dictionary-like types where the key type is generic rather than specific
- Using complex type transformations that result in generic types being used as index signature keys
How to fix "Index signature parameter type cannot be generic"
Instead of trying to use generic type parameters in index signatures, use mapped types which are designed for this purpose:
// ❌ WRONG - Causes error
interface GenericIndex<T> {
[key: T]: string; // Error: Index signature parameter type cannot be generic
}
// ✅ CORRECT - Use mapped type
type GenericMapped<T extends string | number | symbol> = {
[K in T]: string;
};
// Example usage
type StringKeys = GenericMapped<"a" | "b" | "c">;
// Result: { a: string; b: string; c: string; }
// ✅ Also works with keyof constraints
type PickProperties<T, K extends keyof T> = {
[P in K]: T[P];
};
// Example: Pick specific properties from an object type
interface User {
id: number;
name: string;
email: string;
age: number;
}
type UserNameAndEmail = PickProperties<User, "name" | "email">;
// Result: { name: string; email: string; }Mapped types iterate over union types and create object types with those keys.
For dictionary objects where keys come from a generic type, use TypeScript's built-in Record utility type:
// ❌ WRONG
interface Dictionary<T extends string> {
[key: T]: any; // Error
}
// ✅ CORRECT - Use Record
type Dictionary<T extends string | number | symbol> = Record<T, any>;
// Example usage
type StatusCodes = Dictionary<200 | 404 | 500>;
// Equivalent to: { 200: any; 404: any; 500: any; }
type StringDictionary = Dictionary<string>;
// Equivalent to: { [key: string]: any; }
// ✅ You can also create your own Record-like type
type MyRecord<K extends keyof any, T> = {
[P in K]: T;
};
// Usage with constraints
type Config<T extends string> = MyRecord<T, boolean>;
const config: Config<"darkMode" | "notifications"> = {
darkMode: true,
notifications: false,
};The Record<K, T> type constructs an object type with keys of type K and values of type T.
If you need an index signature, constrain the generic parameter to one of the allowed types:
// ❌ WRONG - Generic without constraint
interface FlexibleObject<T> {
[key: T]: string; // Error
}
// ✅ CORRECT - Constrain to allowed types
interface FlexibleObject<T extends string | number | symbol> {
[key: T]: string; // Now works because T is constrained
}
// ✅ More specific constraints
interface StringKeyObject<T extends string> {
[key: T]: string;
}
interface NumberKeyObject<T extends number> {
[key: T]: string;
}
// ✅ Using template literal types
interface PrefixedKeys<T extends string> {
[key: `prefix-${T}`]: string;
}
// Example usage
type MyPrefixed = PrefixedKeys<"a" | "b">;
// Result: { "prefix-a": string; "prefix-b": string; }
// ✅ Union type constraints
interface MixedKeys<T extends string | number> {
[key: T]: string;
}By constraining T to string | number | symbol, TypeScript knows it's a valid index signature type.
For advanced use cases, combine conditional types with inference:
// ❌ WRONG - Complex generic in index signature
interface ComplexIndex<T> {
[key: T extends string ? `key-${T}` : never]: string; // Error
}
// ✅ CORRECT - Use conditional mapped type
type ComplexMapped<T> = T extends string ? {
[K in `key-${T}`]: string;
} : never;
// Example usage
type Result = ComplexMapped<"a" | "b">;
// Result: { "key-a": string; "key-b": string; }
// ✅ Another example with inference
type ExtractKeys<T> = T extends { [key: infer K]: any } ? K : never;
// ✅ Create type-safe dictionaries with validation
type ValidatedDictionary<Keys extends string, Value> = {
[K in Keys]: Value;
} & {
// Additional methods or properties
getKeyCount(): number;
};
// Implementation
function createDictionary<Keys extends string, Value>(
keys: Keys[],
defaultValue: Value
): ValidatedDictionary<Keys, Value> {
const dict = {} as any;
for (const key of keys) {
dict[key] = defaultValue;
}
dict.getKeyCount = () => keys.length;
return dict;
}
const myDict = createDictionary(["a", "b", "c"], 0);
// Type: ValidatedDictionary<"a" | "b" | "c", number>Conditional types allow you to create complex type transformations while staying within TypeScript's type system constraints.
Sometimes the best solution is to rethink your type design:
// ❌ Problematic design requiring generic index signatures
interface Problematic<T> {
data: { [key: T]: any }; // Would cause error if T is generic
}
// ✅ Alternative 1: Use tuple or array for ordered data
interface Alternative1<T> {
data: T[]; // Array of values
keys: string[]; // Separate array for keys
}
// ✅ Alternative 2: Use Map or Set for dynamic collections
interface Alternative2<T> {
data: Map<string, T>; // Map preserves key-value pairs
}
// ✅ Alternative 3: Union type of possible states
type Alternative3 =
| { type: "stringKeys"; data: Record<string, any> }
| { type: "numberKeys"; data: Record<number, any> }
| { type: "specificKeys"; data: { a: any; b: any; c: any } };
// ✅ Alternative 4: Factory function pattern
function createDataStore<T extends Record<string, any>>(initialData: T) {
return {
data: initialData,
get<K extends keyof T>(key: K): T[K] {
return this.data[key];
},
set<K extends keyof T>(key: K, value: T[K]) {
this.data[key] = value;
}
};
}
// Usage
const store = createDataStore({
name: "John",
age: 30,
active: true
});
store.get("name"); // string
store.set("age", 31); // OKConsider whether you truly need dynamic keys or if a more structured approach would be better.
As a last resort, you can use type assertions, but use them carefully:
// ⚠️ Use with caution - type assertion bypasses type checking
interface WithAssertion<T> {
data: { [key: string]: any } as { [key in T & (string | number | symbol)]: any };
}
// Better approach: type guard function
function isRecordWithKeys<T extends string>(
obj: any,
keys: T[]
): obj is Record<T, any> {
if (typeof obj !== "object" || obj === null) return false;
return keys.every(key => key in obj);
}
// Usage with type guard
function processData<T extends string>(data: any, expectedKeys: T[]) {
if (isRecordWithKeys(data, expectedKeys)) {
// TypeScript now knows data is Record<T, any>
console.log(data);
} else {
throw new Error("Invalid data structure");
}
}
// Another approach: indexed access types
type ValueOf<T> = T[keyof T];
interface Container<T> {
values: T;
getValue<K extends keyof T>(key: K): T[K];
setValue<K extends keyof T>(key: K, value: T[K]): void;
}
// Usage
const container: Container<{ x: number; y: string }> = {
values: { x: 1, y: "hello" },
getValue(key) { return this.values[key]; },
setValue(key, value) { this.values[key] = value; }
};Type assertions should be used sparingly and only when you have external guarantees about the type structure.
Advanced notes
### Understanding TypeScript's Index Signature Restrictions
TypeScript restricts index signature key types to string, number, symbol, and template literal types for several important reasons:
1. Type Safety: Index signatures define the shape for *all* possible property accesses with a certain key type. If generic types were allowed, TypeScript couldn't guarantee type safety for arbitrary property accesses.
2. Performance: Concrete key types allow TypeScript to optimize type checking and compilation. Generic types would require runtime type information or complex compile-time analysis.
3. JavaScript Semantics: JavaScript objects only support string and symbol keys (numbers are converted to strings). TypeScript's restrictions align with JavaScript's actual behavior.
### Mapped Types vs Index Signatures
While they look similar, mapped types and index signatures serve different purposes:
Index Signatures:
- Define shape for *all* properties with keys of a certain type
- Example: { [key: string]: number } means *any* string key returns a number
- Cannot use generic type parameters as keys
Mapped Types:
- Transform existing types by iterating over their keys
- Example: { [K in keyof T]: T[K] } preserves the exact keys of T
- Can use generic type parameters with constraints
- Create new types based on existing ones
### Common Patterns and Solutions
#### Pattern 1: Dictionary with Generic Value Type
// ✅ Use Record
type Dictionary<T> = Record<string, T>;
// ✅ Or explicit mapped type
type Dictionary2<T> = {
[key: string]: T;
};#### Pattern 2: Type-Safe Configuration Objects
type ConfigOptions = "darkMode" | "notifications" | "analytics";
type Config = Record<ConfigOptions, boolean>;
// Or with default values
type ConfigWithDefaults = {
[K in ConfigOptions]: boolean;
} & {
version: string;
};#### Pattern 3: Dynamic Key Generation
type DynamicKeys<Prefix extends string, Keys extends string> = {
[K in Keys as `${Prefix}-${K}`]: boolean;
};
type Prefixed = DynamicKeys<"app", "theme" | "language">;
// Result: { "app-theme": boolean; "app-language": boolean; }### Template Literal Types in Index Signatures
TypeScript 4.1+ allows template literal types in index signatures, but with limitations:
// ✅ Valid - template literal type
interface Headers {
[header: `x-${string}`]: string;
}
// ❌ Invalid - generic in template literal
interface Invalid<T extends string> {
[key: `prefix-${T}`]: string; // Still not allowed
}
// ✅ Workaround - mapped type with template literals
type Valid<T extends string> = {
[K in T as `prefix-${K}`]: string;
};### When to Consider Alternative Approaches
If you frequently encounter this error, consider:
1. Using Maps instead of objects for truly dynamic key-value stores
2. Designing with union types instead of trying to make everything generic
3. Using factory functions that return properly typed objects
4. Leveraging existing utility types like Record, Partial, Required, etc.
### Debugging Tips
1. Use tsc --noEmit --strict to catch these errors early
2. Check if your generic parameter needs extends string | number | symbol constraint
3. Consider whether you're trying to use an index signature when a mapped type would be more appropriate
4. Use TypeScript playground to experiment with different approaches
Remember: TypeScript's type system is designed to catch potential runtime errors at compile time. While generic index signatures might seem convenient, their restriction prevents entire categories of type-unsafe code.
Frequently asked questions
- What does "TS1337" mean?
- The "Index signature parameter type cannot be generic" error appears when you attempt to use a generic type parameter as the key type in an index signature. Index signatures in TypeScript define the shape of objects when accessing properties with dynamic keys, and they have strict requirements for what types can be used as keys. , `x-${string}) - Union types consisting only of these allowed types Generic type parameters like <T> cannot be used directly because TypeScript needs to know the concrete key type at compile time for type safety and performance reasons.
- How do I fix "TS1337"?
- This TypeScript error occurs when you try to use a generic type parameter as an index signature key, which is not allowed. Index signatures only support specific key types like string, number, symbol, or template literal types. The fix involves using mapped types, keyof constraints, or redesigning your type structure.
- What causes "TS1337"?
- Using a generic type parameter directly as an index signature key (e.g., [key: T]: string) Attempting to create a completely dynamic object type where keys are determined by a generic parameter Confusing index signatures with mapped types, which serve similar but distinct purposes
- Is the "TS1337" error serious?
- It is a medium-severity error — more of a blocker than a risk. The steps above resolve it.
Official resources & further reading
- 📚TypeScript Official Documentation
- 📖TypeScript Handbook - Object Types(Official Docs)
- 📖TypeScript Handbook - Generics(Official Docs)
- 📖TypeScript Handbook - Mapped Types(Official Docs)
- 📖TypeScript Handbook - Indexed Access Types(Official Docs)
- 🐙