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.

6 min read

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:

typescripttypescript
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:

typescripttypescript
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:

typescripttypescript
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:

typescripttypescript
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:

typescripttypescript
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:

typescripttypescript
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 widening

Satisfies vs as const

Both satisfies and as const preserve precise types, but they serve different purposes and are often used together.

Aspectsatisfiesas const
Validates against a typeYesNo
Makes properties readonlyNoYes
Narrows literals (e.g. 3000 to its literal type)NoYes
Changes runtime valueNoNo

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

Rune AI

Key Insights

  • satisfies validates 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.
RunePowered by Rune AI

Frequently Asked Questions

How is satisfies different from a type annotation?

A type annotation like `const x: Type = value` widens the value to `Type`, losing specific information. `const x = value satisfies Type` checks the value against `Type` but keeps the original precise type.

When was satisfies added to TypeScript?

The satisfies operator was introduced in TypeScript 4.9, released in November 2022.

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.