Haliviq
TypeScript

satisfies keeps literal types AND checks the shape

Annotating a variable with a type widens it, so you lose the exact literal values. The satisfies operator validates the object against the type but keeps the narrow inference, which is handy for config objects and route maps where you want both autocomplete on the real keys and a safety net on the shape.

What happened

We had a route map written as an object, with the variable annotated as Record<string, Route>. The shape was checked, but autocomplete did not show the real keys like home or login, and a mistyped key did not error because the type allowed any string.

We wanted both: the compiler checking that every entry has the right shape, and the editor knowing which keys actually exist.

Why it happens

With a type annotation, the variable gets the type you wrote, not the type of the value you assigned. All that remains is the broad information in Record<string, Route>, and the specifics of the object you wrote are gone.

satisfies (available since TypeScript 4.9) checks the expression against a type without changing the variable's type. The inferred type still comes from the actual value, so the keys are exactly the ones you wrote, and properties that the target type declares as literal unions, such as 'GET' | 'POST', stay literal. If you want every value to be a literal, combine it with as const.

What we do now

Our config objects and route maps now use satisfies instead of an annotation. A mistyped key is flagged immediately, autocomplete lists only the real keys, and an entry with the wrong shape errors right at that entry.

When you deliberately want a wide variable, for example one that accepts many kinds of values, a normal annotation is still the right choice. satisfies fits objects whose contents you already know.

Check the shape but keep the real keysts
type Route = { path: string; method: 'GET' | 'POST' }

const routes = {
  home: { path: '/', method: 'GET' },
  login: { path: '/login', method: 'POST' },
} satisfies Record<string, Route>

routes.login.method // 'POST'
routes.logn // error: Property 'logn' does not exist

Quick checklist

  1. 1Remove the type annotation from the variable.
  2. 2Add satisfies followed by the type to check against at the end of the object.
  3. 3Try a mistyped key to confirm the compiler complains.
  4. 4If you need fully literal values, add as const before satisfies.

Rule of thumb

Use satisfies when you want the compiler to check a shape without widening the type of the object.

We'd love to hear what you're building.

Talk to us