Menu

TypeScript satisfies Operator: vs Type Annotations and as

The satisfies operator checks that a value matches a type without changing the value's inferred type. Learn what it does, how it compares with a type annotation and with as (the same object written three ways), how it combines with as const, and why it fits config objects.

This page includes runnable editors - edit, run, and see output instantly.

value satisfies Type checks that value matches Type at compile time, and then leaves the value's own, more precise type alone. An annotation would replace that precise type with Type; satisfies validates without widening.

satisfies still does the checking: a missing color, a misspelled key such as bleu, or a value like true is a compile error on that line. It exists since TypeScript 4.9 and, like every type annotation, it is removed from the emitted JavaScript.

The Problem satisfies Solves

With a type annotation, the variable's type is the annotation. The compiler forgets what it saw in the literal. Here the same palette is annotated instead, and now TypeScript no longer knows that green is a string:

The compiler reports:

index.ts(11,27): error TS2339: Property 'toUpperCase' does not exist on type 'Color'.
  Property 'toUpperCase' does not exist on type '[number, number, number]'.

Before TypeScript 4.9 the choices were: annotate and narrow by hand everywhere (typeof palette.green === "string"), or skip the annotation and lose the check. satisfies gives both. Change : Record<ColorName, Color> to satisfies Record<ColorName, Color> after the closing brace and it runs.

satisfies vs Type Annotation vs as

The same settings object, written three ways:

as let the missing lang through, and asserted.lang is undefined at run time while its type says string. Remove lang from the other two lines and both fail with TS2741, Property 'lang' is missing in type ....

Annotation const x: T = vAssertion v as Tv satisfies T
Missing propertieserrorallowederror
Extra properties (object literal)errorallowederror
Wrong property typeerroronly if the types do not overlaperror
Type of x afterwardsTTthe inferred type of v
Literal types ("dark", 8080)widened to Twidened to Tkept where T allows them
Keys of a Record<string, ...>any string (typos compile)any stringexactly the keys written
Runtime effectnonenonenone

Rule of thumb: annotate when you want the variable to have the declared type (a value you will reassign, a public API), and use satisfies when you want a check but the value's own type is more useful.

Catching Mistakes in Object Literals

satisfies runs the full assignability check, including excess property checks, so typos in keys are errors:

type Route = { path: string; method: "GET" | "POST" };

const home = { path: "/", metod: "GET" } satisfies Route;
// error TS2561: Object literal may only specify known properties, but 'metod' does not exist in type 'Route'. Did you mean to write 'method'?

The check also gives the literal a contextual type, just as an annotation does. That matters in two ways. String literals are kept as literal types when the target type expects them: { path: "/", method: "GET" } satisfies Route has method: "GET", while the same object with no annotation would infer method: string. And callback parameters are inferred from the target type:

Record Keys Stay Known

A common use is a lookup table. Annotated as Record<string, T>, every string is a valid key and a typo compiles, returning undefined at run time. With satisfies, the values are still checked against T, but the variable's type lists exactly the keys you wrote:

keyof typeof endpoints is only useful because the keys survived. With the annotation it would be plain string.

To require a fixed set of keys, satisfy a Record over a union: satisfies Record<"dev" | "prod", string> reports a missing prod with TS2741 and an unknown staging with TS2353.

as const satisfies

as const and satisfies combine. Write as const first: it makes the value deeply readonly with literal types, then satisfies checks that exact value.

Each route is checked against Route (a method: "PUT" would be an error), and the tuple of literal types stays available, so Path is a union of the real paths. Use readonly Route[] (or ReadonlyArray<Route>) as the target, since an as const array is readonly.

Config Objects

Configuration is where satisfies earns its place: the shape must be right, and code elsewhere wants the precise values.

Forget the production entry, misspell logLevel, or write logLevel: "verbose", and the compiler points at the exact line. The same pattern suits *.config.ts files: export default { ... } satisfies SomeConfig checks the whole file while the exported object keeps its literal values.

When Not to Use satisfies

  • The variable will be reassigned. let cfg = { port: 3000 } satisfies { port: number | string } gives cfg the type { port: number }, so a later cfg = { port: "80" } fails (TS2322). Annotate variables you mean to change.
  • You want the declared type on purpose. For a function return value or an exported constant that is part of an API, the annotation's type is the contract, and leaking the exact literal type can make later changes breaking.
  • The value is not a literal. satisfies shines on object and array literals. On a variable or a call result it is a plain assignability check, which an annotation already gives you.

Frequently Asked Questions

What does satisfies do in TypeScript?

expression satisfies Type checks at compile time that the expression is assignable to Type, reporting missing properties, extra properties and wrong value types, and then leaves the expression's own inferred type unchanged. You get the safety of an annotation and the precision of inference. It is erased from the JavaScript output.

What is the difference between satisfies and a type annotation?

Both check the value. An annotation (const x: T = ...) then gives the variable the type T, forgetting what the compiler knew about the value (literal types, which union member each property is, which keys exist). satisfies T keeps the inferred type, so x.someKey is known to exist and a string | number property that holds a string is typed string.

What is the difference between satisfies and as in TypeScript?

as is an assertion: it overrides the type and checks almost nothing, so missing properties go unnoticed. satisfies is a check: the value must really match the type, and its own inferred type is kept. When both would compile, satisfies is the safer choice.

What does as const satisfies mean?

It applies both: as const makes the value deeply readonly with literal types, then satisfies checks that result against a type. Write as const first: const routes = [...] as const satisfies readonly Route[];. The variable keeps the exact literal types for later use, and a wrong entry is still a compile error.

Which TypeScript version added satisfies?

TypeScript 4.9, released in November 2022. It is plain erasable syntax, so it also runs under Node's built-in type stripping, and every current TypeScript version (including 7) supports it.

Coddy programming languages illustration

Learn to code with Coddy

GET STARTED