satisfies Fixed How I Type Config Objects
Every project ends up with a config object mapping names to settings, and typing it used to mean choosing between two bad options.
Annotate it and you get checking, but the keys widen:
const routes: Record<string, Route> = { home: { path: '/', auth: false }, admin: { path: '/admin', auth: true },};
type RouteName = keyof typeof routes; // stringroutes.hme; // no error, it's a Record<string, Route>Leave the annotation off and the keys survive, but nothing validates the values. A typo in auth sails through, and it’s boolean | string now because inference took the union of what it saw.
satisfies gives you both. It checks the value against a type without replacing the inferred one:
const routes = { home: { path: '/', auth: false }, admin: { path: '/admin', auth: true },} satisfies Record<string, Route>;
type RouteName = keyof typeof routes; // 'home' | 'admin'routes.hme; // errorSame checking, and RouteName is now a union you can use in a function signature. Getting a wrong route name rejected at the call site is the actual payoff.
Where it earns its keep
Narrowing survives, which is the part I didn’t expect. Give a property the type string | string[] in your constraint and pass a plain string, and the inferred type stays string. No cast needed to call .toUpperCase() on it later.
It also composes with as const when you want the whole thing readonly:
const routes = { … } as const satisfies Record<string, Route>;Order matters. as const first, then satisfies.
Where it doesn’t help
satisfies is compile-time only. It disappears at runtime, so it’s the wrong tool for anything crossing a boundary: request bodies, environment variables, API responses. Those still need a parser like Zod.
The rule I settled on is simple enough to apply without thinking. Data I wrote gets satisfies. Data someone else wrote gets parsed.
