Robust Configuration Objects In TypeScript: The Power of As Const and Satisfies
Updated on · 4 min read|
Struggling with TypeScript config objects that silently accept typos or destroy useful autocomplete? If you've defined settings for React, Node, CLIs, or any JavaScript application, you know the pain. This article explores two TypeScript features - as const
assertions and the newer satisfies
operator - that enable developers to enforce configuration integrity at compile-time without incurring runtime costs, making your code safer and easier to maintain.
Why plain objects can betray you
When you write a simple object literal in TypeScript, the compiler often tries to be helpful, but sometimes too helpful:
typescript// Initial object literal const themeConfig = { mode: "dark", }; // Later... interface Theme { mode: "light" | "dark"; spacingUnit?: number; } // Let's assign it (or pass it to a function expecting Theme) const currentTheme: Theme = themeConfig; // This works! // But what if we had a typo initially? const badConfig = { mod: "dark", // Typo! 'mod' instead of 'mode' }; const oopsTheme: Theme = badConfig; // Still no error here!
typescript// Initial object literal const themeConfig = { mode: "dark", }; // Later... interface Theme { mode: "light" | "dark"; spacingUnit?: number; } // Let's assign it (or pass it to a function expecting Theme) const currentTheme: Theme = themeConfig; // This works! // But what if we had a typo initially? const badConfig = { mod: "dark", // Typo! 'mod' instead of 'mode' }; const oopsTheme: Theme = badConfig; // Still no error here!
When you write const themeConfig = { mode: 'dark' }
, TypeScript infers the type of mode
as the general string
, not the specific literal 'dark'
. This is called "widening". Because string
isn't assignable to 'light' | 'dark'
, the assignment const currentTheme: Theme = themeConfig
would actually fail if Theme
was used immediately. But often, the check happens later, and crucial type inference information is already lost. Furthermore, excess properties like mod
aren't caught reliably once the type is widened or assigned indirectly.
First aid: freezing values with as const
A common first step to retain specific types is using as const
:
typescriptconst theme = { mode: "dark", rtl: false, } as const; // Now, theme.mode is type 'dark', not string // theme.rtl is type false, not boolean // It's also deeply readonly theme.mode = "light"; // Error! Cannot assign to 'mode' because it is a read-only property.
typescriptconst theme = { mode: "dark", rtl: false, } as const; // Now, theme.mode is type 'dark', not string // theme.rtl is type false, not boolean // It's also deeply readonly theme.mode = "light"; // Error! Cannot assign to 'mode' because it is a read-only property.
Adding as const
does two key things:
- Deep
readonly
: prevents accidental mutations anywhere inside the object. - Literal types: preserves the most specific type for each value (
'dark'
instead ofstring
,false
instead ofboolean
).
This approach enhances IntelliSense for property access and can prevent certain classes of errors.
The power of template literal types can further enhance this pattern when working with string manipulations and complex type constraints.
However, as const
has limitations. While it helps in inferring precise types for existing properties, it doesn't prevent the inclusion of extraneous properties or ensure all required properties are present according to a specific shape.

The missing piece: satisfies
for shape validation (TypeScript 4.9+)
While as const
freezes the literal values, satisfies
validates the overall shape without throwing away those specific types. It's the perfect complement.
Let's define the shape we want our theme to have:
typescriptinterface ThemeShape { mode: "light" | "dark"; rtl?: boolean; // Optional property }
typescriptinterface ThemeShape { mode: "light" | "dark"; rtl?: boolean; // Optional property }
Now, let's use satisfies
to check our object against this shape:
typescriptconst theme = { mode: "dark", rtl: false, // lightness: 0.8, // ← Uncommenting this line results in a TypeScript error. } satisfies ThemeShape;
typescriptconst theme = { mode: "dark", rtl: false, // lightness: 0.8, // ← Uncommenting this line results in a TypeScript error. } satisfies ThemeShape;
The satisfies
operator provides the following benefits:
- Clear shape errors: extraneous properties (like
lightness
) or properties with incorrect types are flagged directly on the object literal with helpful error messages referencingThemeShape
. - Preserved inference: crucially,
theme.mode
is still inferred as the literal type'dark'
, not widened tostring
. Autocomplete remains precise!
These validation techniques can be combined with type guards for comprehensive type safety in your applications.

The winning combo: as const satisfies Shape
For immutable configurations (like theme files, feature flags, i18n messages), you often want both shape validation and the benefits of literal types with immutability. You can combine both keywords:
typescriptinterface ThemeShape { mode: "light" | "dark"; rtl?: boolean; } // The Gold Standard for immutable configs: export const theme = { mode: "dark", rtl: false, // typo: 'drak' // ← Uncomment for immediate 'mode' type error // extraProp: 123 // ← Uncomment for immediate excess property error } as const satisfies ThemeShape; // Lock types/make readonly FIRST, THEN validate shape // theme.mode is 'dark' (literal type) // theme is readonly // theme conforms to ThemeShape
typescriptinterface ThemeShape { mode: "light" | "dark"; rtl?: boolean; } // The Gold Standard for immutable configs: export const theme = { mode: "dark", rtl: false, // typo: 'drak' // ← Uncomment for immediate 'mode' type error // extraProp: 123 // ← Uncomment for immediate excess property error } as const satisfies ThemeShape; // Lock types/make readonly FIRST, THEN validate shape // theme.mode is 'dark' (literal type) // theme is readonly // theme conforms to ThemeShape
The recommended approach is as follows:
- Declare an interface or type alias describing the desired object structure.
- Write your object literal with the intended values.
- Append
as const
to lock in the literal types and make it deeply readonly. - Append
satisfies YourShapeInterface
to validate that this specific, readonly structure and its types conform to your intended shape.
(Order matters: as const satisfies Shape
works. satisfies Shape as const
does not.)
Working with more complex scenarios might require conditional types or intersection types to build sophisticated type definitions.
This combination provides compile-time armor with zero runtime overhead.
Real-world example: feature flags
Imagine managing feature flags:
typescript// featureFlags.ts interface FeatureFlags { newNavbar: boolean; betaSignup: boolean; darkModeV2?: boolean; // Optional flag } export const flags = { newNavbar: true, betaSignup: false, // darkModV2: true // ← Typo caught instantly by 'satisfies' after 'as const'! } as const satisfies FeatureFlags; // In your React component: // flags.newNavbar has type 'true', flags.betaSignup has type 'false' if (flags.newNavbar) { // ... render new navbar } // If you rename 'newNavbar' to 'navBarV2' in featureFlags.ts, // every usage of 'flags.newNavbar' across your codebase will // immediately show a compile-time error. Ship with confidence!
typescript// featureFlags.ts interface FeatureFlags { newNavbar: boolean; betaSignup: boolean; darkModeV2?: boolean; // Optional flag } export const flags = { newNavbar: true, betaSignup: false, // darkModV2: true // ← Typo caught instantly by 'satisfies' after 'as const'! } as const satisfies FeatureFlags; // In your React component: // flags.newNavbar has type 'true', flags.betaSignup has type 'false' if (flags.newNavbar) { // ... render new navbar } // If you rename 'newNavbar' to 'navBarV2' in featureFlags.ts, // every usage of 'flags.newNavbar' across your codebase will // immediately show a compile-time error. Ship with confidence!
When working with React components, proper type safety extends to contexts and refs to ensure your entire application remains type-safe.
Broader applications and quick tips
This satisfies
(and as const satisfies ...
) pattern is powerful for more than just simple configs:
- i18n translation files: ensure every language file has the required keys
- tooling configs (ESLint, Tailwind): catch misspelled rule names or config keys
- API response mocking / snapshots: validate mock data against API interfaces at compile time
- Form validation: when building forms with React, type-safe validation schemas enhance reliability
For JavaScript developers transitioning to TypeScript, leveraging higher-order functions and tagged templates can become even more powerful when combined with TypeScript's type system.
Closing thoughts
With satisfies
and as const
, you move from potentially error-prone, loosely-typed configurations to compiler-enforced truth, catching typos and structural issues before they cause runtime bugs. It's a small change in syntax for a big gain in safety and maintainability.
Adopting these patterns for existing and new configuration objects is recommended, as it can often reveal subtle, pre-existing type-related issues. Complement these techniques with defensive programming practices for robust error handling.
As you advance your TypeScript skills, explore mapped types, generics, and the powerful infer
keyword to round out your TypeScript expertise.

References and resources
- Advanced Conditional Types in TypeScript
- Intersection Types in TypeScript
- JavaScript Advanced String Manipulation with Tagged Templates
- JavaScript Higher-Order Functions Explained
- Managing Forms with React Hook Form
- TypeScript Error Handling and Defensive Programming
- TypeScript Generics Guide
- TypeScript Infer Keyword: Unlocking Type Information
- TypeScript Mapped Types Guide
- TypeScript Template Literal Types: Practical Use-Cases
- TypeScript Type Inference
- TypeScript Union Types Guide
- TypeScript: Typing React Context
- TypeScript: Typing the React useRef Hook
- Understanding and Implementing Type Guards in TypeScript
- TypeScript Official Documentation on
as const
- TypeScript Official Documentation on
satisfies
Operator