TypeScript satisfies Explained: Check a Type Without Losing Precision
The satisfies operator checks that a value matches a type while keeping the value's own, more specific type. How it differs from a type annotation and from as, the patterns where it shines (config objects, route maps, as const), and the mistakes to avoid.
TypeScript's satisfies operator answers a common frustration: you want TypeScript to check that an object has the right shape, but you don't want it to forget the exact values you wrote.
const config = { ... } satisfies Config
The problem with a type annotation
type Colors = Record<string, string | [number, number, number]>
const palette: Colors = {
red: [255, 0, 0],
green: '#00ff00',
}
palette.green.toUpperCase()
// ❌ Error: Property 'toUpperCase' does not exist on type 'string | [number, number, number]'
The annotation : Colors makes palette be a Colors. TypeScript now only knows each value is "a string or a tuple", even though you can see green is a string. It also forgets which keys exist — palette.blue type-checks fine and is undefined at runtime.
The fix: satisfies
const palette = {
red: [255, 0, 0],
green: '#00ff00',
} satisfies Colors
palette.green.toUpperCase() // ✅ green is known to be a string
palette.red[0] // ✅ red is known to be a tuple
palette.blue // ✅ Error: 'blue' doesn't exist
satisfies checks the value against Colors — a typo like gren: 123 is still an error — but the variable keeps its own precise type.
satisfies vs annotation vs as
| Checks the value? | Resulting type | |
|---|---|---|
const x: T = value |
Yes | T (widened) |
const x = value satisfies T |
Yes | The value's own type (precise) |
const x = value as T |
No — you're telling TypeScript to trust you | T |
as is an assertion: it silences errors instead of catching them. AI-generated code reaches for as a lot; satisfies is usually what was meant. (TypeScript for AI-Generated Code)
Patterns where it shines
Config objects — checked, with literal values kept:
type Env = 'development' | 'production'
type AppConfig = { env: Env; port: number; features: Record<string, boolean> }
export const config = {
env: 'production',
port: 8080,
features: { newCheckout: true, betaSearch: false },
} satisfies AppConfig
config.features.newCheckout // ✅ known key
Lookup tables that must cover every case:
type Status = 'draft' | 'published' | 'archived'
const statusLabel = {
draft: 'Draft',
published: 'Live',
archived: 'Archived',
} satisfies Record<Status, string>
// Add a new Status and forget a label → compile error
With as const for fully literal, readonly data:
const routes = {
home: '/',
pricing: '/pricing',
post: '/blog/:slug',
} as const satisfies Record<string, `/${string}`>
// routes.pricing has type '/pricing', and every value must start with '/'
Mistakes to avoid
- Using it for function parameters or return types. Those already have annotations;
satisfiesis for values. - Expecting it to change the value. It's compile-time only — no runtime effect.
- Thinking it validates external data. Data from an API or form is unchecked at runtime; use a validator like Zod. (Validate Input With Zod)
The summary
value satisfies Typechecks the shape but keeps the precise inferred type.- Prefer it to
as(which skips checking) and to annotations when you need the specifics. - Great for configs, lookup tables and
as constdata.
EasySpawn gives Claude Code a persistent workspace where tsc runs after every change, so type errors are caught and fixed before code reaches you. See how it works or join the waitlist.
Related: type vs interface · TypeScript Generics Explained · What Is TypeScript? · Type Is Not Assignable
Keep reading
TypeScript Enums vs Union Types: Which Should You Use?
TypeScript enums generate runtime code and have surprising behaviour; string literal unions and as const objects give the same safety with plain JavaScript. How each works, why erasableSyntaxOnly and Node's built-in type stripping reject enums, and a practical rule for choosing.
Upgrading to Tailwind CSS v4: What Changed and How to Migrate
Tailwind CSS v4 moved configuration into CSS, replaced the @tailwind directives with one import, detects your files automatically and renamed several utilities. How to run the official upgrade tool, the breaking changes that actually bite (shadows, rings, borders, opacity utilities), browser requirements, and what to check after.