This TypeScript error occurs when you pass an object literal with extra properties to a function or variable that expects a specific type. TypeScript's excess property checking prevents accidental typos and ensures type safety by flagging properties that don't exist in the target type.
The "Object literal may only specify known properties" error is TypeScript's way of enforcing strict type checking when you create object literals. This is part of TypeScript's excess property checking feature, which helps catch common bugs like typos in property names or accidentally passing extra data. When you pass an object literal directly to a function or assign it to a variable with a specific type, TypeScript performs extra validation beyond normal type compatibility. It checks that every property in the object literal exists in the target type. This prevents subtle bugs where you might think you're passing certain data but actually have a typo in a property name. For example, if you have an interface `User` with properties `name` and `age`, and you try to create `{name: 'John', age: 30, email: '[email protected]'}` where `User` doesn't have an `email` property, TypeScript will flag this as an error. This is different from assigning an existing object variable, which would be allowed due to structural typing.
The simplest fix is to remove properties that don't exist in the target type:
interface User {
name: string;
age: number;
}
// ERROR: 'email' doesn't exist in User
function saveUser(user: User) {}
saveUser({ name: 'John', age: 30, email: '[email protected]' });
// Object literal may only specify known properties...
// FIX: Remove the extra property
saveUser({ name: 'John', age: 30 });
// OR if 'email' should be included, update the interface:
interface User {
name: string;
age: number;
email?: string; // Make it optional
}Check for typos in property names:
// ERROR: Typo in 'colour' (should be 'color')
interface Config {
color: string;
size: number;
}
const config: Config = { colour: 'red', size: 10 };
// Object literal may only specify known properties...
// FIX: Correct the typo
const config: Config = { color: 'red', size: 10 };If you're sure the extra properties are safe, use a type assertion:
interface User {
name: string;
age: number;
}
// ERROR: Extra 'metadata' property
saveUser({ name: 'John', age: 30, metadata: { id: 1 } });
// FIX: Use type assertion
saveUser({ name: 'John', age: 30, metadata: { id: 1 } } as User);
// OR use the newer 'as' syntax
saveUser({ name: 'John', age: 30, metadata: { id: 1 } } as User);
// WARNING: This suppresses the error but the extra property
// will be lost at runtime since TypeScript only affects type checkingFor React props with spread operators:
interface ButtonProps {
label: string;
onClick: () => void;
}
// ERROR: Extra 'className' property
const Button = ({ label, onClick, className }: ButtonProps & { className?: string }) => {
return <button className={className} onClick={onClick}>{label}</button>;
};
// FIX: Update the interface to include className
interface ButtonProps {
label: string;
onClick: () => void;
className?: string;
}TypeScript's structural typing allows extra properties if you assign to a variable first:
interface User {
name: string;
age: number;
}
// ERROR: Direct object literal
saveUser({ name: 'John', age: 30, email: '[email protected]' });
// FIX: Assign to variable first
const userData = { name: 'John', age: 30, email: '[email protected]' };
saveUser(userData); // This works!
// Why this works: TypeScript uses structural typing
// The variable 'userData' has at least the properties of User
// Extra properties don't break the contract
// Common pattern for configuration objects:
interface Config {
apiUrl: string;
timeout: number;
}
function initialize(config: Config) {}
// ERROR: Direct literal with extra debug property
initialize({ apiUrl: '/api', timeout: 5000, debug: true });
// FIX: Use variable
const appConfig = { apiUrl: '/api', timeout: 5000, debug: true };
initialize(appConfig); // WorksThis approach is useful when you need to pass extra data for internal use but the function only requires a subset.
If your type should accept any additional properties, use an index signature:
// BEFORE: Strict interface
interface Config {
apiUrl: string;
timeout: number;
}
// ERROR: Can't pass extra properties
const config: Config = { apiUrl: '/api', timeout: 5000, retries: 3 };
// AFTER: Add index signature for extra properties
interface Config {
apiUrl: string;
timeout: number;
[key: string]: any; // Allows any extra string properties
}
// Now this works:
const config: Config = { apiUrl: '/api', timeout: 5000, retries: 3 };
// More type-safe: Use Record for specific value types
interface Config {
apiUrl: string;
timeout: number;
options: Record<string, string | number | boolean>;
}
const config: Config = {
apiUrl: '/api',
timeout: 5000,
options: { retries: 3, debug: true, mode: 'production' }
};
// For React components with spread props:
interface BaseProps {
children: React.ReactNode;
}
type DivProps = BaseProps & React.HTMLAttributes<HTMLDivElement>;
const Container = (props: DivProps) => {
// Can spread all div attributes including className, style, etc.
return <div {...props}>{props.children}</div>;
};For legacy codebases or specific cases, you can disable excess property checking:
// tsconfig.json
{
"compilerOptions": {
"suppressExcessPropertyErrors": true
}
}WARNING: This disables ALL excess property checking globally. Use only as a last resort.
Better approach: Use a more targeted solution with interface merging or utility types:
// Use Partial for optional configurations
interface RequiredConfig {
apiUrl: string;
timeout: number;
}
type Config = RequiredConfig & Partial<{
retries: number;
debug: boolean;
cache: boolean;
}>;
// Now all are optional extras
const config: Config = {
apiUrl: '/api',
timeout: 5000,
retries: 3, // No error
debug: true // No error
};
// Use Omit to remove strictness from specific functions
interface User {
id: string;
name: string;
age: number;
}
function updateUser(id: string, updates: Partial<Omit<User, 'id'>>) {
// Can accept any subset of User properties except 'id'
}
updateUser('123', { name: 'John', extraField: 'value' }); // Error on extraField
// Make it more permissive:
function updateUser(id: string, updates: Partial<Omit<User, 'id'>> & Record<string, any>) {
// Now accepts extra properties
}TypeScript's utility types can help create more flexible interfaces:
// Use intersection types to combine required and optional properties
interface RequiredFields {
name: string;
age: number;
}
interface OptionalFields {
email?: string;
phone?: string;
address?: string;
}
type User = RequiredFields & OptionalFields & Record<string, any>;
// Now User accepts any extra properties
const user: User = {
name: 'John',
age: 30,
email: '[email protected]',
customField: 'value' // Allowed due to Record<string, any>
};
// For API responses that may include metadata:
type ApiResponse<T> = {
data: T;
success: boolean;
timestamp: string;
} & Record<string, any>;
interface Product {
id: string;
name: string;
price: number;
}
const response: ApiResponse<Product> = {
data: { id: '1', name: 'Widget', price: 9.99 },
success: true,
timestamp: '2024-01-01',
metadata: { page: 1, total: 100 }, // Extra properties allowed
debug: true // Also allowed
};
// Use generics for maximum flexibility
function createConfig<T extends Record<string, any>>(base: T, extras: Record<string, any>) {
return { ...base, ...extras };
}
const config = createConfig(
{ apiUrl: '/api', timeout: 5000 },
{ retries: 3, debug: true } // No type errors
);### Understanding Excess Property Checking
TypeScript's excess property checking is a form of "freshness" checking that applies only to object literals. When you create an object literal, TypeScript considers it "fresh" and checks it more strictly than an object that comes from a variable.
Key concepts:
1. Fresh objects: Object literals get strict checking
2. Stale objects: Variables/parameters get structural checking
3. Type compatibility: TypeScript uses structural typing (duck typing), but fresh objects have extra rules
### When Excess Property Checking Applies
The check triggers when:
1. Assigning an object literal to a variable with a type annotation
2. Passing an object literal as an argument to a function
3. Returning an object literal from a function with a return type annotation
4. Using object literals in JSX/TSX props
### Bypassing Strategies
1. Type assertions: as Type or <Type>{...}
2. Intermediate variables: Assign to variable first
3. Index signatures: [key: string]: any
4. Utility types: Partial<T> & Record<string, any>
5. tsconfig option: "suppressExcessPropertyErrors": true (not recommended)
### React/JSX Specifics
In React, excess property checking helps catch prop typos:
interface ButtonProps {
onClick: () => void;
children: React.ReactNode;
}
// ERROR: 'onclick' (lowercase) doesn't exist
<Button onclick={() => {}}>Click me</Button>
// Common React patterns:
// 1. Spread operator for HTML attributes
interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
label?: string;
}
// 2. Component props with rest parameters
function Card({ title, children, ...rest }: CardProps & React.HTMLAttributes<HTMLDivElement>) {
return <div {...rest}><h2>{title}</h2>{children}</div>;
}### Performance Considerations
Excess property checking happens at compile time and has no runtime impact. However, using index signatures ([key: string]: any) can reduce type safety. Consider more precise alternatives:
// Instead of:
interface LooseConfig {
[key: string]: any;
}
// Use:
interface TypedConfig {
required: string;
optional?: Record<string, string | number | boolean>;
metadata?: {
[K in 'created' | 'modified' | 'version']?: string;
};
}### Testing and Validation
When testing functions that accept object parameters, consider:
1. Creating test data factories that return typed objects
2. Using satisfies operator for inline objects that should match a type
3. Creating helper types for test data that include extra properties
// Using 'satisfies' for test objects
const testUser = {
name: 'Test',
age: 25,
testId: 123 // Extra property for testing
} satisfies User; // Still type-checks against User
// Test data factory
function createTestUser(overrides?: Partial<User> & Record<string, any>): User & { testId: number } {
return {
name: 'Test',
age: 25,
testId: Date.now(),
...overrides,
};
}Function expression requires a return type
Function expression requires a return type
Value of type 'string | undefined' is not iterable
How to fix "Value is not iterable" in TypeScript
Type 'undefined' is not assignable to type 'string'
How to fix "Type undefined is not assignable to type string" in TypeScript
Type narrowing from typeof check produces 'never'
How to fix "Type narrowing produces never" in TypeScript
Type parameter 'T' has conflicting constraints
How to fix "Type parameter has conflicting constraints" in TypeScript