This TypeScript configuration error occurs when you enable exactOptionalPropertyTypes without enabling strictNullChecks or the strict flag. The exactOptionalPropertyTypes compiler option depends on strict type checking to function properly.
The "'exactOptionalPropertyTypes' requires strict mode" error appears when you try to enable the exactOptionalPropertyTypes compiler option in tsconfig.json without having strictNullChecks enabled. This is a configuration dependency error, not a code error. The exactOptionalPropertyTypes flag was introduced in TypeScript 4.4 to make optional properties behave more strictly. It distinguishes between a property being omitted versus being explicitly set to undefined. However, this distinction only makes sense when strictNullChecks is enabled, as that's what makes undefined a distinct type. Unlike most strict-family options, exactOptionalPropertyTypes is NOT automatically included when you set "strict": true. You must enable it explicitly, but when you do, TypeScript requires that strictNullChecks (or the umbrella strict flag) is also enabled. Without strict null checking, the compiler cannot enforce the exact optional property semantics.
The simplest fix is to enable the strict flag, which includes strictNullChecks and all other strict-family options:
{
"compilerOptions": {
"strict": true,
"exactOptionalPropertyTypes": true,
"target": "ES2020",
"module": "commonjs"
}
}The strict flag enables:
- strictNullChecks (required for exactOptionalPropertyTypes)
- strictFunctionTypes
- strictBindCallApply
- strictPropertyInitialization
- noImplicitThis
- alwaysStrict
- noImplicitAny
After saving the file, run your TypeScript compiler:
npx tsc
# or
npm run buildThe configuration error should be resolved, though you may now see new type errors in your code from the stricter checking.
If you don't want to enable all strict mode options yet, you can enable just strictNullChecks:
{
"compilerOptions": {
"strictNullChecks": true,
"exactOptionalPropertyTypes": true,
"target": "ES2020",
"module": "commonjs"
}
}This satisfies the requirement for exactOptionalPropertyTypes while avoiding other strict checks.
However, note that strictNullChecks alone may still introduce many type errors in existing code:
// Without strictNullChecks, this is allowed:
let name: string = null; // OK (bad!)
// With strictNullChecks, you'll get errors:
let name: string = null; // Error: Type 'null' is not assignable to type 'string'
// Fix by allowing null explicitly:
let name: string | null = null; // OKIf enabling strict mode would introduce too many errors in your codebase, temporarily remove exactOptionalPropertyTypes:
{
"compilerOptions": {
// "exactOptionalPropertyTypes": true, // Commented out
"target": "ES2020",
"module": "commonjs"
}
}This allows your project to compile without the configuration error. You can re-enable it later when you're ready to adopt stricter type checking.
Plan your migration:
1. First enable strict: true or strictNullChecks: true
2. Fix the resulting type errors incrementally
3. Then add exactOptionalPropertyTypes: true
4. Fix any new errors from exact optional semantics
If you're using extends in your tsconfig.json, a parent config might be disabling strict mode:
// tsconfig.json
{
"extends": "./tsconfig.base.json",
"compilerOptions": {
"exactOptionalPropertyTypes": true
}
}Check the base config:
cat tsconfig.base.jsonIf the base config has strict or strictNullChecks set to false, override it:
// tsconfig.json
{
"extends": "./tsconfig.base.json",
"compilerOptions": {
"strict": true, // Override the base config
"exactOptionalPropertyTypes": true
}
}Options in your config override inherited ones, so this will enable strict mode for your project.
After making changes, verify the final effective configuration:
npx tsc --showConfigThis shows the merged configuration after all extends and overrides:
{
"compilerOptions": {
"strict": true,
"strictNullChecks": true,
"exactOptionalPropertyTypes": true,
// ... other options
}
}Look for both strictNullChecks and exactOptionalPropertyTypes showing as true.
If strictNullChecks shows as false, trace through your extends chain to find where it's being disabled.
### What exactOptionalPropertyTypes Actually Does
With exactOptionalPropertyTypes enabled, optional properties are treated more precisely:
Without exactOptionalPropertyTypes:
interface User {
email?: string;
}
// All of these are valid:
const user1: User = {}; // email omitted
const user2: User = { email: "[email protected]" }; // email present
const user3: User = { email: undefined }; // email explicitly undefined
// The property exists with value undefined:
console.log("email" in user3); // trueWith exactOptionalPropertyTypes:
interface User {
email?: string;
}
const user1: User = {}; // OK - email omitted
const user2: User = { email: "[email protected]" }; // OK
const user3: User = { email: undefined };
// Error: Type 'undefined' is not assignable to type 'string'
// with 'exactOptionalPropertyTypes: true'
// To allow explicit undefined, add it to the type:
interface User {
email?: string | undefined; // Now explicit undefined is allowed
}### Why It Requires strictNullChecks
Without strictNullChecks, undefined is assignable to all types:
// With strictNullChecks: false
let name: string = undefined; // Allowed (unsafe!)In this mode, the distinction between email?: string and email?: string | undefined is meaningless because undefined is already part of string. The exactOptionalPropertyTypes feature cannot function without strict null checking.
### Migration Strategy for Large Codebases
If you have a large codebase and want to adopt exactOptionalPropertyTypes:
Step 1: Enable strict mode first
{
"compilerOptions": {
"strict": true
}
}Fix all the resulting errors. This may take time, but you can use @ts-expect-error or @ts-ignore temporarily:
// Temporary suppression during migration
// @ts-expect-error - TODO: Fix null handling
const value: string = potentiallyNull;Step 2: Enable exactOptionalPropertyTypes
Once strict mode is stable, add:
{
"compilerOptions": {
"strict": true,
"exactOptionalPropertyTypes": true
}
}Step 3: Fix new errors
Look for patterns like:
// Before
interface Config {
timeout?: number;
}
function reset(config: Config) {
config.timeout = undefined; // Error with exactOptionalPropertyTypes
}
// Fix option 1: Delete the property
function reset(config: Config) {
delete config.timeout;
}
// Fix option 2: Allow undefined explicitly
interface Config {
timeout?: number | undefined;
}### Recommended tsconfig.json for New Projects
For maximum type safety:
{
"compilerOptions": {
"target": "ES2022",
"module": "node16",
"moduleResolution": "node16",
"lib": ["ES2022"],
// Strict type checking
"strict": true,
"exactOptionalPropertyTypes": true,
"noUncheckedIndexedAccess": true,
"noPropertyAccessFromIndexSignature": true,
// Additional strictness
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"allowUnusedLabels": false,
// Emit
"declaration": true,
"sourceMap": true,
"outDir": "./dist",
// Other
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
}
}### Compatibility with Libraries
Some third-party libraries may have type definitions that are incompatible with exactOptionalPropertyTypes. If you encounter errors in node_modules/@types:
// Error in @types/some-libraryYou have several options:
1. Skip library checks (quickest):
{
"compilerOptions": {
"skipLibCheck": true // Ignore errors in .d.ts files
}
}2. Report the issue to DefinitelyTyped or the library maintainer
3. Create a local type override in your project:
// types/library-fixes.d.ts
declare module "problematic-library" {
export interface Config {
option?: string | undefined; // Add explicit undefined
}
}Function expression requires a return type
Function expression requires a return type
Value of type 'string | undefined' is not iterable
How to fix "Value is not iterable" in TypeScript
Type 'undefined' is not assignable to type 'string'
How to fix "Type undefined is not assignable to type string" in TypeScript
Type narrowing from typeof check produces 'never'
How to fix "Type narrowing produces never" in TypeScript
Type parameter 'T' has conflicting constraints
How to fix "Type parameter has conflicting constraints" in TypeScript