The satisfies operator in TypeScript is a powerful tool for ensuring type safety without sacrificing flexibility, particularly when dealing with object literals that need to conform to a specific shape but might have additional properties.

Let’s see it in action. Imagine you have a configuration object for a hypothetical service. You want to ensure it has specific keys like port and host, but you also want to allow for other, undefined configuration options.

// Define the expected shape of our configuration
interface ServiceConfig {
  host: string;
  port: number;
}

// A configuration object with only the required properties
const minimalConfig = {
  host: "localhost",
  port: 8080,
};

// A configuration object with required properties and an extra one
const extendedConfig = {
  host: "example.com",
  port: 3000,
  timeout: 5000, // Extra property
};

// Without 'satisfies'
// If we tried to assign extendedConfig directly to ServiceConfig,
// TypeScript would complain about the 'timeout' property.
// const invalidAssignment: ServiceConfig = extendedConfig; // Error: Object literal may only specify known properties...

// With 'satisfies'
const validExtendedConfig = {
  host: "example.com",
  port: 3000,
  timeout: 5000,
} satisfies ServiceConfig;

// Now, validExtendedConfig is *typed* as ServiceConfig,
// but it still *retains* its original, more specific type internally.
// This means you get compile-time checks for ServiceConfig properties,
// but you don't lose the extra properties when you need them.

console.log(validExtendedConfig.host); // OK
console.log(validExtendedConfig.port); // OK
// console.log(validExtendedConfig.timeout); // Error: Property 'timeout' does not exist on type 'ServiceConfig'.
// But if you need the timeout:
const timeoutValue = (validExtendedConfig as any).timeout; // This is where 'satisfies' helps avoid explicit casts in many cases.

The core problem satisfies solves is the tension between statically defining a known shape and allowing for dynamic or unknown additional properties in object literals. Typically, if you declare a variable with a specific interface type, and then assign an object literal to it, TypeScript performs a strict check. Any property in the object literal not present in the interface will result in a compile-time error. This is great for ensuring you’ve provided all required properties, but it prevents you from including any optional or future properties without widening the type of your variable to any or a more permissive type, losing valuable type information.

satisfies allows you to assert that an object literal conforms to a specific type (like ServiceConfig) without making that specific type the declared type of the variable itself. Instead, the variable’s type is inferred from the object literal, and then satisfies is used as a check against the provided type. The crucial distinction is that the inferred type of the variable remains the original, more specific type of the object literal, while its runtime value is guaranteed to meet the criteria of the satisfies type.

Consider the extendedConfig example again. Without satisfies, if you wanted to use extendedConfig where a ServiceConfig was expected, you’d have to:

  1. Widen the type: const myConfig: ServiceConfig = { ... }; would fail. You might then do const myConfig: any = { ... }; which loses all type safety.
  2. Use a type assertion: const myConfig = { ... } as ServiceConfig; This tells TypeScript "trust me, this object is a ServiceConfig," but it doesn’t verify it. If you later added a property like port: "8080" (string instead of number), the assertion would still pass, but you’d get a runtime error.

satisfies acts as a compile-time validator. When you write const validExtendedConfig = { ... } satisfies ServiceConfig;, TypeScript checks:

  • Does the object literal have all the properties required by ServiceConfig (i.e., host and port)?
  • Are the types of those properties compatible with ServiceConfig (i.e., host is a string, port is a number)?

If these checks pass, validExtendedConfig is inferred as its original, more specific type (an object with host, port, and timeout). However, when you access properties through the lens of ServiceConfig (e.g., validExtendedConfig.host), TypeScript enforces the ServiceConfig type. But if you need to access properties not in ServiceConfig (like timeout), you can do so because the original, inferred type is preserved.

The magic of satisfies is that it enables a form of "type-safe widening." You can add extra properties to your object literal, and as long as the core required properties are present and correctly typed according to your interface, satisfies will allow it. The variable itself still holds the original, more specific type, so you retain access to those extra properties. This is particularly useful for configuration objects, internationalization dictionaries, or any scenario where you have a base contract but want to allow for extensions.

The way satisfies works under the hood is by performing a structural type compatibility check. It takes the inferred type of the expression on the left (your object literal) and compares it against the type provided on the right (ServiceConfig). If the left-hand type is assignable to the right-hand type (meaning it structurally matches or is more specific), the check passes. The resulting type of the variable is the inferred type of the left-hand expression, not the type on the right. This preserves the original properties and their specific types.

One subtle but powerful aspect is how satisfies handles primitive types. If you have an enum or a union of literal types for a property, satisfies will ensure that the object literal’s property value is a member of that enum or union. For example, if ServiceConfig had mode: "read" | "write", an object literal with mode: "execute" would fail the satisfies ServiceConfig check, even though "execute" is a string.

The next logical step after mastering satisfies is understanding how to use it in conjunction with mapped types and conditional types to create even more dynamic and robust type checking for complex object structures.

Want structured learning?

Take the full Typescript course →