The satisfies Operator
Validate a value against a type without widening or throwing away its precise inferred type.
satisfies (TypeScript 4.9+) settles an old tension. An annotation like const x: T = ... checks your value but also widens it to T, losing the specific literals. Leaving the annotation off keeps the precise type but skips the check. satisfies gives you both: it verifies the value conforms to a type, then hands you back the narrow inferred type.
type Config = Record<string, string | number>;
const settings = {
port: 3000,
host: "localhost",
} satisfies Config;
settings.port.toFixed(0); // OK β port is known to be number, not string | numberHad you written const settings: Config, then settings.port would be string | number and .toFixed would error. satisfies keeps the check while preserving the truth.
Example
When to use it
- A palette object uses satisfies to be validated against a colour map type while retaining tuple types for each colour.
- A route configuration uses satisfies Record<string, RouteConfig> to catch missing fields without widening the type.
- A feature flags object uses satisfies to ensure all required flags are present while keeping their literal boolean types.
More examples
satisfies preserves literal types
satisfies validates palette against ColorMap but keeps each value's precise type, so destructuring works without casting.
type ColorMap = Record<string, string | [number, number, number]>;
const palette = {
red: [255, 0, 0],
green: [0, 255, 0],
blue: 'blue',
} satisfies ColorMap;
// palette.red is [number, number, number], not string | [number, number, number]
const [r] = palette.red;Catching missing required keys
satisfies checks that all required keys are present at the declaration site while keeping the narrowest inferred types.
type Routes = { home: string; about: string; contact: string };
const routes = {
home: '/',
about: '/about',
// contact: '/contact', // Error if uncommented line is removed
} satisfies Routes; // Error: Property 'contact' is missingsatisfies vs type annotation
Contrasts a type annotation (which widens to string) with satisfies (which keeps the literal type 'verbose').
type Config = { debug: boolean; level: string };
// With annotation: level is string
const a: Config = { debug: true, level: 'verbose' };
// With satisfies: level is 'verbose' (literal)
const b = { debug: true, level: 'verbose' } satisfies Config;
// b.level autocompletes as 'verbose', a.level as string
Discussion