# How Redux Handles Type Inference with Generic Store Enhancers

> Discover how Redux achieves type inference with generic store enhancers. Learn how type parameters propagate through the StoreEnhancer and createStore for seamless integration.

- Repository: [Redux/redux](https://github.com/reduxjs/redux)
- Tags: internals
- Published: 2026-03-05

---

**Redux handles type inference with generic store enhancers by propagating generic type parameters through the `StoreEnhancer` higher-order type and `createStore` function overloads, automatically merging extension types into the final store signature.**

Redux achieves end-to-end type safety with generic store enhancers by leveraging TypeScript's inference engine to propagate extension types through the store creation pipeline. In the `reduxjs/redux` repository, the type system uses higher-order generic parameters to capture additional properties and state fields introduced by enhancers, ensuring the returned store reflects both the base API and enhancer-specific extensions.

## The StoreEnhancer Type Architecture

The foundation of Redux's type inference mechanism lies in the **`StoreEnhancer`** type defined in [`src/types/store.ts`](https://github.com/reduxjs/redux/blob/main/src/types/store.ts). This higher-order function type receives a store creator and returns a new creator with merged generic extensions:

```typescript
export type StoreEnhancer<Ext extends {} = {}, StateExt extends {} = {}> = <
  NextExt extends {},
  NextStateExt extends {}
>(
  next: StoreEnhancerStoreCreator<NextExt, NextStateExt>
) => StoreEnhancerStoreCreator<NextExt & Ext, NextStateExt & StateExt>

```

The type parameters serve distinct purposes:

- **`Ext`** represents additional properties (such as a custom `dispatch` method or extra store methods) that the enhancer adds to the store object.
- **`StateExt`** represents extra fields injected into the store's state shape.

When an enhancer wraps another creator, TypeScript intersects the incoming `NextExt` and `NextStateExt` with the enhancer's own extensions using the `&` operator. This intersection guarantees that composed enhancers accumulate their type contributions rather than overwriting them.

## How createStore Captures Enhancer Generics

The `createStore` function in [`src/createStore.ts`](https://github.com/reduxjs/redux/blob/main/src/createStore.ts) provides overloads specifically designed to extract and propagate the generic parameters from any passed enhancer. The relevant overload captures the `Ext` and `StateExt` generics from the enhancer argument:

```typescript
export function createStore<
  S,
  A extends Action,
  Ext extends {} = {},
  StateExt extends {} = {}
>(
  reducer: Reducer<S, A>,
  enhancer?: StoreEnhancer<Ext, StateExt>
): Store<S, A, UnknownIfNonSpecific<StateExt>> & NoInfer<Ext>

```

When you supply a typed enhancer to `createStore`, TypeScript's inference engine matches the enhancer's concrete generic arguments against the `Ext` and `StateExt` slots in this overload. The compiler then substitutes these inferred types into the return type, which intersects the base `Store` type with the enhancer's extensions.

### Utility Types That Preserve Type Safety

Redux employs two critical utility types to prevent type leakage and widening:

- **`UnknownIfNonSpecific<T>`** collapses an unconstrained generic (`{}`) to `unknown`, preventing accidental `any` types from propagating through the system when no state extensions are provided.
- **`NoInfer<Ext>`** blocks TypeScript from widening the `Ext` type when the store is used in different contexts, preserving the exact shape inferred from the enhancer definition.

These utilities ensure that the type inference remains strict and that the returned store type accurately reflects only the extensions explicitly declared by the enhancer.

## Practical Examples of Generic Store Enhancers

### Extending Dispatch with Promise Support

You can augment the `dispatch` method to handle thunks or promises by defining the extension type in the `StoreEnhancer` generic parameter:

```typescript
import { createStore, StoreEnhancer, Action } from 'redux';

type PromiseDispatch = <T extends Action>(promise: Promise<T>) => Promise<T>;

const promiseEnhancer: StoreEnhancer<{
  dispatch: PromiseDispatch;
}> = createStore => (reducer, preloaded) => {
  const store = createStore(reducer, preloaded);
  return {
    ...store,
    dispatch: (arg: any) => {
      if (arg && typeof arg.type === 'string') {
        return store.dispatch(arg);
      }
      if (arg && typeof arg.then === 'function') {
        return arg.then(store.dispatch);
      }
      return arg;
    },
  };
};

interface CounterState { value: number }

const counter = (state: CounterState = { value: 0 }, action: Action) =>
  action.type === 'inc' ? { value: state.value + 1 } : state;

const store = createStore(counter, promiseEnhancer);

// TypeScript recognizes both signatures
store.dispatch({ type: 'inc' });
store.dispatch(Promise.resolve({ type: 'inc' }));

```

The generic `StoreEnhancer<{ dispatch: PromiseDispatch }>` drives the inference, causing `createStore` to return a store whose `dispatch` method accepts both plain actions and promises.

### Augmenting Store State

To inject additional fields into the state tree, specify the `StateExt` generic parameter:

```typescript
import { createStore, StoreEnhancer, Action } from 'redux';

interface ExtraState { extra: string }

const stateEnhancer: StoreEnhancer<{}, ExtraState> = createStore => (
  reducer,
  preloaded
) => {
  const wrapReducer = <S, A extends Action>(r: (s: S, a: A) => S) =>
    (s: S & Partial<ExtraState>, a: A) => ({
      ...r(s as S, a),
      extra: 'added',
    });

  const store = createStore(wrapReducer(reducer), preloaded);
  return {
    ...store,
    replaceReducer(next) {
      store.replaceReducer(wrapReducer(next));
    },
  };
};

const baseReducer = (state = { value: 0 }, action: Action) => state;
const store = createStore(baseReducer, stateEnhancer);

// TypeScript knows getState() includes the extra field
store.getState().extra; // Type: string

```

Here `StoreEnhancer<{}, ExtraState>` instructs Redux to merge `ExtraState` into the base state type, making the additional property available on `getState()` with full type checking.

### Adding Custom Store Methods

Arbitrary methods can be attached to the store instance by declaring them in the `Ext` parameter:

```typescript
import { createStore, StoreEnhancer } from 'redux';

const methodEnhancer: StoreEnhancer<{ foo(): string }> = createStore => (
  ...args
) => ({
  ...createStore(...args),
  foo: () => 'bar',
});

const store = createStore(() => ({}), methodEnhancer);
store.foo(); // TypeScript validates: () => string

```

The generic `{ foo(): string }` is intersected with the base `Store` type, ensuring `store.foo` is type-checked as a valid method call.

## Summary

- **Higher-order typing**: The `StoreEnhancer` type in [`src/types/store.ts`](https://github.com/reduxjs/redux/blob/main/src/types/store.ts) uses intersection types (`NextExt & Ext`) to merge extensions from multiple enhancers.
- **Overload resolution**: `createStore` in [`src/createStore.ts`](https://github.com/reduxjs/redux/blob/main/src/createStore.ts) captures `Ext` and `StateExt` generics from the enhancer argument and propagates them to the return type.
- **Type safety utilities**: `UnknownIfNonSpecific` prevents `any` leakage, while `NoInfer` blocks unwanted type widening.
- **End-to-end inference**: TypeScript's engine automatically reflects enhancer additions—whether custom dispatch signatures, state fields, or methods—in the final store type without manual type annotations.

## Frequently Asked Questions

### What is a StoreEnhancer in Redux?

A `StoreEnhancer` is a higher-order function that accepts a store creator and returns a new, enhanced store creator capable of extending the store's functionality. According to the Redux source code in [`src/types/store.ts`](https://github.com/reduxjs/redux/blob/main/src/types/store.ts), it is typed as a generic function that can add properties to the store object (`Ext`) and fields to the state (`StateExt`) while preserving type safety through TypeScript's inference system.

### How does TypeScript infer the Ext generic parameter?

TypeScript infers the `Ext` parameter by analyzing the concrete type argument provided to `StoreEnhancer` when you define your enhancer. When this enhancer is passed to `createStore`, the function overload in [`src/createStore.ts`](https://github.com/reduxjs/redux/blob/main/src/createStore.ts) captures that specific type and intersects it with the base `Store` return type using the `&` operator, ensuring the resulting store instance knows about your custom extensions.

### What happens if I don't provide generic parameters to my enhancer?

If you define an enhancer without generic parameters (e.g., `StoreEnhancer` instead of `StoreEnhancer<MyExt>`), TypeScript defaults both `Ext` and `StateExt` to the empty object type `{}`. The utility type `UnknownIfNonSpecific` then collapses unconstrained state extensions to `unknown`, and `NoInfer` prevents the empty object from widening, resulting in a store that maintains the base Redux API without additional typed extensions.

### Can I combine multiple generic store enhancers?

Yes. The `StoreEnhancer` type architecture supports composition through its higher-order function signature. When you pass multiple enhancers to `createStore` (or compose them with `compose`), TypeScript intersects the `Ext` and `StateExt` generics from each enhancer. For example, combining `StoreEnhancer<{ dispatch: PromiseDispatch }>` with `StoreEnhancer<{ foo(): string }>` results in a store type that includes both the custom `dispatch` signature and the `foo` method.