
TypeScript 5.5 Satisfies Operator: Why You Should Use It Everywhere
The satisfies operator is TypeScript's most underused feature. Learn how it gives you the best of both worlds: type checking AND type inference, with practical examples you can use today.
If you've been writing TypeScript for a while, you've probably faced this dilemma: do you annotate your variable with a type (and lose inference), or skip the annotation (and lose validation)? The satisfies operator, stabilized in TypeScript 5.5, solves this elegantly — and most developers still aren't using it enough.
The Problem: Type Annotations Kill Inference
Consider a common pattern — defining a configuration object:
// With type annotation: you get validation but LOSE narrowed types
type Routes = Record<string, { path: string; auth: boolean }>;
const routes: Routes = {
home: { path: "/", auth: false },
dashboard: { path: "/dashboard", auth: true },
profile: { path: "/profile", auth: true },
};
// routes.home exists? TypeScript doesn't know — it's just Record<string, ...>
// routes.home is typed as { path: string; auth: boolean } | undefined
routes.nonExistent; // No error! Any string key is valid
You validated the shape, but TypeScript forgot which keys actually exist. Now try accessing routes.home.path — you'll need a null check even though you know it's there.
Enter satisfies: Validate Without Losing Information
The satisfies operator checks that an expression matches a type without widening it:
const routes = {
home: { path: "/", auth: false },
dashboard: { path: "/dashboard", auth: true },
profile: { path: "/profile", auth: true },
} satisfies Record<string, { path: string; auth: boolean }>;
// Now TypeScript knows EXACTLY which keys exist
routes.home.path; // ✅ "/" (literal type!)
routes.dashboard.auth; // ✅ true (literal type!)
routes.nonExistent; // ❌ Error: Property does not exist
// You get autocomplete for "home" | "dashboard" | "profile"
type RouteKey = keyof typeof routes; // "home" | "dashboard" | "profile"
This is the magic: you get full type validation and precise inference. The compiler checks your object matches Record<string, { path: string; auth: boolean }>, but the inferred type retains every literal key and value.
Real-World Patterns in Next.js
Here's where satisfies shines in production code. When building a Next.js app, theme configuration becomes type-safe without losing specificity:
// Theme tokens with full validation AND autocomplete
const theme = {
colors: {
primary: "#3b82f6",
secondary: "#64748b",
danger: "#ef4444",
},
spacing: {
sm: "0.5rem",
md: "1rem",
lg: "2rem",
},
} satisfies Record<string, Record<string, string>>;
// In your component:
function Alert({ variant }: { variant: keyof typeof theme.colors }) {
// variant is "primary" | "secondary" | "danger" — not just string
return <div style={{ color: theme.colors[variant] }} />;
}
Another powerful use case: API route handlers with validated response shapes:
type ApiHandler = {
GET?: () => Promise<Response>;
POST?: () => Promise<Response>;
};
export const { GET, POST } = {
GET: async () => {
const data = await fetchData();
return Response.json(data);
},
POST: async () => {
return Response.json({ created: true });
},
} satisfies ApiHandler;
When NOT to Use satisfies
Don't reach for satisfies when you genuinely want widening. If a function parameter should accept any string, annotating with : string is correct. satisfies is for when you want the compiler to validate structure while preserving the specific shape you wrote.
Key Takeaways
- Use
satisfiesinstead of type annotations when you need both validation and inference - It's ideal for config objects, route maps, theme tokens, and lookup tables
- Combine with
as const satisfies Tfor fully immutable, literal-typed configurations - Stop choosing between safety and developer experience —
satisfiesgives you both
Start replacing your Record annotations with satisfies today. Your future self (and your team's autocomplete) will thank you.
Không spam, hủy đăng ký bất kỳ lúc nào.


