Satisfies Operator in TypeScript
The satisfies operator validates that a value matches a type without changing the value's inferred type. Use it to catch errors while keeping precise type information.
The TypeScript satisfies operator checks that a value matches a given type, but unlike a type annotation, it does not change how TypeScript sees that value. The original, precise type is preserved. Only the validation runs.
Think of the difference between these two lines:
const palette: Record<string, string | number[]> = { red: [255, 0, 0], green: "#00ff00", blue: [0, 0, 255] };
const palette2 = { red: [255, 0, 0], green: "#00ff00", blue: [0, 0, 255] } satisfies Record<string, string | number[]>;With the type annotation on the first line, TypeScript treats palette.green as string | number[]. You cannot call .toUpperCase() on it without narrowing, even though you know it is a string.
With satisfies on the second line, TypeScript validates that the object matches the expected record type, but it still knows that palette2.green is specifically a string, not string | number[]. You can call .toUpperCase() directly. The validation ran, but the precise type survived.
Catching errors that type annotations miss
Without satisfies, you face a trade-off. If you skip the type annotation entirely, you get precise types but no validation of the structure:
const palette = {
red: [255, 0, 0],
green: "#00ff00",
bleu: [0, 0, 255],
// No error -- TypeScript doesn't know this should have been "blue"
};If you add a type annotation, you catch the typo (the key bleu is not in the Colors union), but you lose precision. The type system now sees palette.green as string | RGB instead of the specific string it actually is. Calling .toUpperCase() on it produces an error because the union includes the array type.
Satisfies gives you both: the typo is caught AND the precise type is preserved:
type Colors = "red" | "green" | "blue";
type RGB = [number, number, number];
const palette = {
red: [255, 0, 0],
green: "#00ff00",
bleu: [0, 0, 255],
// Error: 'bleu' does not exist in type 'Record<Colors, string | RGB>'
} satisfies Record<Colors, string | RGB>;Fix the typo and TypeScript still knows each property's exact type. palette.green is string, not string | RGB.
Ensuring exact keys
Satisfies can validate that an object has exactly the keys you expect, no more and no fewer:
type Colors = "red" | "green" | "blue";
const favorites = {
red: "yes",
green: false,
blue: "kinda",
platypus: false,
// Error: 'platypus' does not exist in type 'Record<Colors, unknown>'
} satisfies Record<Colors, unknown>;After removing the extra key, TypeScript preserves the specific type of each property. favorites.green is boolean, not unknown.
Ensuring value types without caring about keys
You can also validate values while ignoring the keys entirely. This is useful when the property names are dynamic or user-defined:
type RGB = [number, number, number];
const palette = {
red: [255, 0, 0],
green: "#00ff00",
blue: [0, 0],
// Error: Type '[number, number]' is not assignable to type 'string | RGB'
} satisfies Record<string, string | RGB>;The third entry (blue) has only two elements instead of three, which violates the RGB constraint. The error surfaces at compile time.
Satisfies with arrays
Satisfies works with arrays too. Validate that an array contains only certain types while keeping the exact tuple structure:
const coords = [
{ x: 10, y: 20 },
{ x: 5, y: 15 },
] satisfies { x: number; y: number }[];
// TypeScript still knows each element's exact shape
const first = coords[0];
// first.x and first.y are number, with no union or wideningSatisfies vs as const
Both satisfies and as const preserve precise types, but they serve different purposes and are often used together.
| Aspect | satisfies | as const |
|---|---|---|
| Validates against a type | Yes | No |
| Makes properties readonly | No | Yes |
Narrows literals (e.g. 3000 to its literal type) | No | Yes |
| Changes runtime value | No | No |
You can combine them: validate the shape with satisfies, then lock the values with as const where needed. For more on as const, see Use as const in TypeScript.
When not to use satisfies
Satisfies is not a replacement for type annotations everywhere. Use a type annotation when you intentionally want to widen a type, such as when declaring a function parameter or a variable that will be reassigned with different values of the same type.
Satisfies is best for values that are defined once and used as-is, like configuration objects, lookup tables, route maps, and test fixtures. For more on choosing between type annotations and other approaches, see When to Add Type Annotations in TypeScript.
Rune AI
Key Insights
satisfiesvalidates a value against a type without widening the inferred type.- Use it instead of type annotations when you need to keep precise property types.
- It catches typos, missing properties, and type mismatches at compile time.
- Introduced in TypeScript 4.9.
- Works with objects, arrays, and any expression.
Frequently Asked Questions
How is satisfies different from a type annotation?
When was satisfies added to TypeScript?
Conclusion
The satisfies operator solves a long-standing tension in TypeScript: you want to validate that code matches a type, but you also want TypeScript to remember the exact, specific types. Use satisfies when a type annotation would lose information you need later.
More in this topic
TypeScript Compiler Explained
The TypeScript compiler (tsc) turns .ts files into .js files while checking types. Learn what it does, how to use it, and how it fits your workflow.
Set Up ESLint for TypeScript
Install and configure ESLint with typescript-eslint to catch bugs and enforce consistent code style in your TypeScript project.
Pick Utility Type in TypeScript
Pick creates a new type by selecting only the properties you name from an existing type. Use it to build focused types for specific views, API responses, or function parameters.