Introduction
Introduced in TypeScript 4.9, the satisfies operator lets you validate that a value matches a type without widening it. Unlike type annotations (which can lose specificity) and type assertions (which override the compiler), satisfies checks the type while preserving the most specific type TypeScript can infer.
Key Concepts
satisfiesOperator: Validates a value against a type at the declaration site without changing the inferred type.- Type Widening: When a type annotation causes TypeScript to forget specific literal types (e.g.,
"red"becomesstring). - Validation Without Widening:
satisfiesensures the value conforms to a type while keeping the narrowest inferred type.
Real World Context
Configuration objects are the canonical use case. You want to ensure a config object matches a schema (has the right keys and value types) but also want to keep the literal types for autocompletion. With satisfies, your IDE shows the exact values while the compiler still validates the shape.
Deep Dive
The Problem: Annotation Widens, Assertion Lies
Consider a color palette:
typescripttype ColorConfig = Record<string, string | number[]>; // With annotation — loses specificity const palette: ColorConfig = { primary: "#3178c6", secondary: "#6b7280", accent: [49, 120, 198], }; palette.primary.toUpperCase(); // Error: Property 'toUpperCase' does not exist on 'string | number[]'
The annotation ColorConfig widens every value to string | number[], so TypeScript does not know that primary is specifically a string.
The Solution: satisfies
typescriptconst palette = { primary: "#3178c6", secondary: "#6b7280", accent: [49, 120, 198], } satisfies ColorConfig; palette.primary.toUpperCase(); // OK — TypeScript knows primary is "#3178c6" palette.accent.map(v => v * 2); // OK — TypeScript knows accent is number[]
The satisfies operator validates that the object matches ColorConfig (correct keys, correct value types) but does not widen the inferred types. Each property retains its specific type.
Catching Errors While Keeping Specificity
satisfies catches structural errors just like an annotation would:
typescriptconst palette = { primary: "#3178c6", secondary: "#6b7280", accent: true, // Error: Type 'boolean' is not assignable to 'string | number[]' } satisfies ColorConfig;
The boolean true does not match string | number[], so the compiler flags it immediately.
Combining with as const
For maximum specificity, combine satisfies with as const:
typescripttype Route = { path: string; method: "GET" | "POST" }; const routes = { home: { path: "/", method: "GET" }, login: { path: "/login", method: "POST" }, } as const satisfies Record<string, Route>; // routes.home.path is "/" (literal), not string // routes.login.method is "POST" (literal), not "GET" | "POST" // Still validated against Record<string, Route>
The as const makes all values literal and readonly, while satisfies ensures the object matches the expected shape.
Common Pitfalls
- Using
satisfieswhen an annotation is fine — If you do not need to preserve literal types, a simple annotation works.satisfiesadds value when you need both validation and specificity. - Confusing
satisfieswithas—satisfiesvalidates;asoverrides. They serve different purposes. Usesatisfieswhen you want the compiler to check the type. Useaswhen you want to override the compiler.
Best Practices
- Use
satisfiesfor configuration objects and constants — Any object that should match a schema but needs to retain literal types for autocompletion. - Combine
as const satisfies Typefor maximum safety — This pattern gives you readonly literal types with full schema validation.
Summary
satisfiesvalidates a value against a type without widening the inferred type.- It catches structural errors while preserving literal types for autocompletion.
- Combine with
as constfor readonly, literal, and validated types. - Use it for configuration objects, theme definitions, and route tables.
Code Examples
// satisfies validates the shape without losing literal types
type Theme = {
colors: Record<string, string>;
spacing: Record<string, number>;
};
const theme = {
colors: {
primary: "#3178c6",
background: "#ffffff",
},
spacing: {
sm: 4,
md: 8,
lg: 16,
},
} satisfies Theme;
// theme.colors.primary is "#3178c6" (literal), not just string
// theme.spacing.md is 8 (literal), not just number