Using TypeScript’s satisfies Operator for Safer Configuration Objects
Why literal types matter
When I work with configuration objects — think feature flags, UI themes, or API payloads — I want two things at once: accurate autocomplete for the exact values I intend, and a safety net that warns me if I accidentally add a typo or an extra field. Plain TypeScript interfaces give you the latter but erase literal values; as const gives you the former but disables excess‑property checks. The satisfies operator, introduced in TypeScript 4.9, lets you have both.
The problem with plain objects
Consider a simple feature‑flag object that drives a toggle in the UI:
const featureFlags = {
newDashboard: true,
enableChat: false,
betaAnalytics: true,
};
If I later write if (featureFlags.newDasboard) (note the typo), TypeScript will happily treat newDasboard as a possible property because the inferred type of featureFlags is { newDashboard: boolean; enableChat: boolean; betaAnalytics: boolean; }. The typo is not caught because the object type is widened to accept any property with a boolean value — excess properties are allowed.
On the flip side, if I annotate the variable with an explicit type, I lose the exact boolean literals:
const featureFlags: { newDashboard: boolean; enableChat: boolean; betaAnalytics: boolean } = {
newDashboard: true,
enableChat: false,
betaAnalytics: true,
};
Now featureFlags.newDashboard is just boolean, not the specific true or false literal, so autocomplete shows only boolean and I lose the benefit of knowing the exact value at compile time.
Enter satisfies
The satisfies operator lets you validate that a value conforms to a type without changing the inferred type of the value itself. The syntax is straightforward:
const featureFlags = {
newDashboard: true,
enableChat: false,
betaAnalytics: true,
} satisfies { newDashboard: boolean; enableChat: boolean; betaAnalytics: boolean };
Here’s what happens:
- TypeScript first infers the type of the object literal as
{ newDashboard: true; enableChat: false; betaAnalytics: true }(thanks to literal inference). - It then checks that this inferred type satisfies the annotation on the right‑hand side. If the object had a typo or an extra field, the check would fail.
- Finally, the value
featureFlagsretains the richer literal type, giving you exact‑value autocomplete while still guarding against misspellings.
Now the typo example is caught:
if (featureFlags.newDasboard) { // ❌ Error: Property 'newDasboard' does not exist on type ... }
Putting it together: a feature‑flag example
In a real project I keep feature flags in a separate file so they can be imported anywhere. Using satisfies gives me confidence that the shape matches what the rest of the code expects, while preserving the exact boolean values for conditional rendering.
// featureFlags.ts export const featureFlags = { // UI toggles newDashboard: true, enableChat: false, // Experimental features betaAnalytics: true, // A flag that will be removed soon legacyReport: false, } satisfies { newDashboard: boolean; enableChat: boolean; betaAnalytics: boolean; legacyReport: boolean; }; // Somewhere else in the codebase import { featureFlags } from './featureFlags'; if (featureFlags.newDashboard) { // ✅ Autocomplete shows the exact literal `true` / `false` renderNewDashboard(); } // Adding a new flag? The shape must match. export const updatedFlags = { ...featureFlags, newFlag: 'yes' } satisfies { // This will error because `newFlag` is not in the type and its value is not a boolean newDashboard: boolean; enableChat: boolean; betaAnalytics: boolean; legacyReport: boolean; };Why not just use
as const?You could write
const featureFlags = { ... } as constto keep the literals, but then TypeScript treats the object as readonly and prevents excess‑property checking entirely. If you later add a field that shouldn’t be there, you won’t get an error until you try to use it where a stricter type is expected.satisfiesgives you the best of both worlds: literal inference and structural validation.When to reach for this pattern
Use
satisfieswhenever you:
- Want literal types for better autocomplete (e.g., boolean flags, string union keys, discriminant values).
- Need to ensure the value conforms to a specific shape, especially when the object is shared across modules.
- Are defining configuration objects, theme objects, or API response mocks that should stay typo‑free.
It’s lightweight, requires no extra dependencies, and works with any TypeScript version 4.9+. Give it a try in your next config file — you’ll likely find fewer runtime surprises and a smoother developer experience.