ZetCode

TypeScript satisfies Operator

last modified October 15, 2026

The satisfies operator, added in TypeScript 4.9, checks that an expression matches a type without changing the type of the expression. A type annotation also checks the value, but replaces its inferred type with the annotated one, which often loses useful information. The satisfies operator gives both: validation and the precise inferred type.

The problem with annotations

We define a palette of colors. Some colors are strings, some are RGB tuples.

annotation.ts
type Color = string | [number, number, number];

const palette: Record<string, Color> = {
    red: [255, 0, 0],
    green: "#00ff00",
    blue: [0, 0, 255],
};

console.log(palette.green.toUpperCase());

The annotation makes sure that every value is a valid Color. But it also makes the type of each property Color, so the compiler no longer knows that green is a string.

$ npx tsc --noEmit
annotation.ts(9,27): error TS2339: Property 'toUpperCase' does not exist on type 'Color'.
  Property 'toUpperCase' does not exist on type '[number, number, number]'.

Using satisfies

With satisfies, the object is still checked against the type, but the variable keeps the inferred type.

satisfies.ts
type Color = string | [number, number, number];

const palette = {
    red: [255, 0, 0],
    green: "#00ff00",
    blue: [0, 0, 255],
} satisfies Record<string, Color>;

console.log(palette.green.toUpperCase());
console.log(palette.red.map(c => c / 255));

The type of palette.green is string and the type of palette.red is [number, number, number], so string and array methods are available.

$ npx tsx satisfies.ts
#00FF00
[ 1, 0, 0 ]

Catching errors

The value is still validated. A wrong value is reported at the place where it is defined.

invalid.ts
type Color = string | [number, number, number];

const palette = {
    red: [255, 0, 0],
    green: "#00ff00",
    blue: [0, 0],
} satisfies Record<string, Color>;

The blue tuple has only two elements, so it is not a valid Color.

$ npx tsc --noEmit
invalid.ts(6,5): error TS2322: Type '[number, number]' is not assignable to type 'Color'.
  Type '[number, number]' is not assignable to type '[number, number, number]'.
    Source has 2 element(s) but target requires 3.

Checking keys

When the keys are given as a union type, satisfies reports both missing keys and misspelled ones.

keys.ts
type Env = "development" | "test" | "production";

const ports = {
    development: 3000,
    test: 3001,
    prodution: 80,
} satisfies Record<Env, number>;

The compiler points out the unknown prodution key. Once it is corrected, a missing environment would be reported as well.

$ npx tsc --noEmit
keys.ts(6,5): error TS2561: Object literal may only specify known properties, but 'prodution' does not exist in type 'Record<Env, number>'. Did you mean to write 'production'?

Keeping literal types

Combined with as const, satisfies validates an object and keeps the exact literal values of its properties.

routes.ts
type Route = {
    path: string;
    method: "GET" | "POST";
};

const routes = {
    users: { path: "/users", method: "GET" },
    login: { path: "/login", method: "POST" },
} as const satisfies Record<string, Route>;

type RouteName = keyof typeof routes;

function describe(name: RouteName): string {
    const route = routes[name];
    return `${route.method} ${route.path}`;
}

console.log(describe("users"));
console.log(describe("login"));

The RouteName type is "users" | "login", derived from the object itself. With a Record<string, Route> annotation, it would be just string, and any name would be accepted.

$ npx tsx routes.ts
GET /users
POST /login

satisfies versus type assertions

A type assertion with as also keeps the expression in place, but it does not check the value: it only tells the compiler to treat it as the given type.

versus_as.ts
type User = {
    name: string;
    age: number;
};

const asserted = { name: "Alice" } as User;
console.log(asserted.age);

The object has no age property, yet the assertion compiles and asserted.age is undefined at runtime.

$ npx tsx versus_as.ts
undefined
versus_satisfies.ts
type User = {
    name: string;
    age: number;
};

const checked = { name: "Alice" } satisfies User;

With satisfies, the missing property is reported.

$ npx tsc --noEmit
versus_satisfies.ts(6,35): error TS1360: Type '{ name: string; }' does not satisfy the expected type 'User'.
  Property 'age' is missing in type '{ name: string; }' but required in type 'User'.

Function return values

The operator can be used on any expression, for example on the object returned from a function.

return_value.ts
type ApiResponse = {
    status: number;
    data: unknown;
};

function okResponse(items: string[]) {
    return {
        status: 200,
        data: items,
    } satisfies ApiResponse;
}

const response = okResponse(["a", "b"]);
console.log(response.status, response.data.length);

The returned object must match ApiResponse, but the inferred return type keeps data as string[] instead of unknown, so length can be used. Like an annotation, satisfies also reports unknown properties in object literals, so a misspelled stauts key would be an error.

$ npx tsx return_value.ts
200 2

When to use what

Best Practices

Source

TypeScript 4.9 release notes: The satisfies operator

This tutorial covered the TypeScript satisfies operator. Use it when a value must match a type, but its own, more precise type is still needed.

Author

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all TypeScript tutorials.