# Typed store patterns

This page adds the type patterns for reusing a creator, extracting a store's complete shape, and carrying custom middleware changes through composition. For the roles of [`StateCreator`](https://bench-zustand-56.atloria.app/p/bench-zustand-56-D8Xr4XerWj/developer/zustand#statecreator), [`StoreApi`](https://bench-zustand-56.atloria.app/p/bench-zustand-56-D8Xr4XerWj/developer/zustand#storeapi), [`UseBoundStore`](https://bench-zustand-56.atloria.app/p/bench-zustand-56-D8Xr4XerWj/developer/zustand#useboundstore), [`ExtractState`](https://bench-zustand-56.atloria.app/p/bench-zustand-56-D8Xr4XerWj/developer/zustand#extractstate), [`create`](https://bench-zustand-56.atloria.app/p/bench-zustand-56-D8Xr4XerWj/developer/zustand#create), and [`createStore`](https://bench-zustand-56.atloria.app/p/bench-zustand-56-D8Xr4XerWj/developer/zustand#createstore), see [How Zustand works](https://bench-zustand-56.atloria.app/p/bench-zustand-56-D8Xr4XerWj/developer/how-zustand-works).

## The model

A typed store has three related layers:

```mermaid
flowchart LR
  T["State and actions: T"] --> C["StateCreator<T>"]
  C --> R["create() or createStore()"]
  R --> A["UseBoundStore or StoreApi<T>"]
  A --> X["ExtractState<typeof store>"]
  C --> M["Middleware mutator tuples"]
  M --> R
```

The addition here is the type flow: one creator can feed either binding, and `ExtractState<typeof store>` can recover the complete state-and-actions shape after the store is created. For the initializer arguments, bindings, and vanilla API, see [How Zustand works](https://bench-zustand-56.atloria.app/p/bench-zustand-56-D8Xr4XerWj/developer/how-zustand-works).

The type parameter is important because the state type is used both as an input to `get` and as the initializer's result. TypeScript cannot reliably infer that invariant type from the initializer alone, so use the curried form when you provide the state type: `create<T>()((set) => ...)` or `createStore<T>()((set) => ...)`.

## Define the creator once

Give the state and actions a single type, then export a creator that can be used by either a React-bound or vanilla store:

```ts title="counter-store.ts"
import { type StateCreator } from 'zustand'

export type CounterStore = {
  count: number
  increment: () => void
}

export const counterStoreCreator: StateCreator<CounterStore> = (set) => ({
  count: 0,
  increment: () => set((state) => ({ count: state.count + 1 })),
})
```

The creator returns both data and actions. `set` performs a shallow merge by default, so the action returns only the changed field. Treat nested objects and arrays as immutable: create a new nested value before passing it to `set`.

## Bind the creator in React

Pass the creator to the curried form of [`create`](https://bench-zustand-56.atloria.app/p/bench-zustand-56-D8Xr4XerWj/developer/zustand#create). The result is a callable store whose selector is typed as `CounterStore`:

```tsx title="Counter.tsx"
import { create } from 'zustand'
import { type CounterStore, counterStoreCreator } from './counter-store'

const useCounterStore = create<CounterStore>()(counterStoreCreator)

export function Counter() {
  const count = useCounterStore((state) => state.count)
  const increment = useCounterStore((state) => state.increment)

  return (
    <button type="button" onClick={increment}>
      Count: {count}
    </button>
  )
}
```

The button reads one atomic value and one action. Clicking it updates the store and the component re-renders because its selected `count` changes.

When a component needs several values, select them with a stable result or use `useShallow` from `zustand/react/shallow`; avoid subscribing to the whole store when only part of it is needed.

## Infer the store state

Use [`ExtractState`](https://bench-zustand-56.atloria.app/p/bench-zustand-56-D8Xr4XerWj/developer/zustand#extractstate) when a utility, test, or component prop needs the complete state-and-actions shape of an existing store. It reads the return type of `getState()` rather than making you repeat the type:

```ts title="counter-state.ts"
import { create, type ExtractState } from 'zustand'

type CounterStateShape = {
  count: number
  increment: () => void
}

const useCounterStore = create<CounterStateShape>()((set) => ({
  count: 0,
  increment: () => set((state) => ({ count: state.count + 1 })),
}))

export type CounterState = ExtractState<typeof useCounterStore>

export function formatCounter(state: CounterState): string {
  return `Count: ${state.count}`
}

export const currentCounter = formatCounter(useCounterStore.getState())
```

`CounterState` includes `count` and `increment`. The store itself remains a `UseBoundStore`, so `getState()` returns that same inferred shape.

## Use a vanilla store with a React binding

Use [`createStore`](https://bench-zustand-56.atloria.app/p/bench-zustand-56-D8Xr4XerWj/developer/zustand#createstore) when the store must exist independently of React—for example, when you create a scoped store for a particular owner. Bind that store in a component with [`useStore`](https://bench-zustand-56.atloria.app/p/bench-zustand-56-D8Xr4XerWj/developer/zustand#usestore):

```tsx title="ScopedCounter.tsx"
import { useState } from 'react'
import { createStore, type StoreApi } from 'zustand'
import { useStore } from 'zustand'
import { type CounterStore, counterStoreCreator } from './counter-store'

function createCounterStore(): StoreApi<CounterStore> {
  return createStore<CounterStore>()(counterStoreCreator)
}

export function ScopedCounter() {
  const [counterStore] = useState(createCounterStore)
  const count = useStore(counterStore, (state) => state.count)
  const increment = useStore(counterStore, (state) => state.increment)

  return (
    <button type="button" onClick={increment}>
      Scoped count: {count}
    </button>
  )
}
```

The store is created once for this component instance. [`useStore`](https://bench-zustand-56.atloria.app/p/bench-zustand-56-D8Xr4XerWj/developer/zustand#usestore) subscribes to the vanilla API and returns the selected value, while the `StoreApi<CounterStore>` annotation keeps direct store access typed outside the component.

## Compose middleware without losing the state type

For middleware that wraps a creator, compose the shipped middleware directly inside `create`. This keeps the initializer contextually typed:

```ts title="persisted-counter.ts"
import { create } from 'zustand'
import { devtools, persist } from 'zustand/middleware'

interface CounterState {
  count: number
  increment: () => void
}

export const usePersistedCounter = create<CounterState>()(
  devtools(
    persist(
      (set) => ({
        count: 0,
        increment: () => set((state) => ({ count: state.count + 1 })),
      }),
      { name: 'counter-store' },
    ),
  ),
)
```

The store keeps `CounterState` while `persist` receives its storage name and `devtools` wraps the resulting creator. Keep this nesting immediately inside `create`; moving it into an untyped helper loses the contextual inference that supplies the creator's `set` type.

For the general roles of [`Mutate`](https://bench-zustand-56.atloria.app/p/bench-zustand-56-D8Xr4XerWj/developer/zustand#mutate), [`StateCreator`](https://bench-zustand-56.atloria.app/p/bench-zustand-56-D8Xr4XerWj/developer/zustand#statecreator), [`StoreMutators`](https://bench-zustand-56.atloria.app/p/bench-zustand-56-D8Xr4XerWj/developer/zustand#storemutators), and [`StoreMutatorIdentifier`](https://bench-zustand-56.atloria.app/p/bench-zustand-56-D8Xr4XerWj/developer/zustand#storemutatoridentifier), see [How Zustand works](https://bench-zustand-56.atloria.app/p/bench-zustand-56-D8Xr4XerWj/developer/how-zustand-works). This page adds the custom-middleware pattern: use `Mis` for mutators already present on the creator's input and `Mos` for mutators it produces, then make the declaration merge and runtime implementation agree:

```ts
import type {
  Mutate,
  StateCreator,
  StoreApi,
  StoreMutatorIdentifier,
  StoreMutators,
} from 'zustand'

declare module 'zustand/vanilla' {
  interface StoreMutators<S, A> {
    counterLabel: S & { counterLabel: A }
  }
}

type CounterLabel = <
  T,
  A,
  Mps extends [StoreMutatorIdentifier, unknown][] = [],
  Mos extends [StoreMutatorIdentifier, unknown][] = [],
>(
  creator: StateCreator<T, [...Mps, ['counterLabel', A]], Mos>,
  label: A,
) => StateCreator<T, Mps, [['counterLabel', A], ...Mos]>

type LabeledCounterApi = Mutate<
  StoreApi<CounterStore>,
  [['counterLabel', string]]
>

type CounterStore = {
  count: number
}

```

The declaration describes the type-level contract; a working middleware also has to implement the corresponding runtime change. Do not use this extension pattern when ordinary shipped middleware already provides the behavior.

## Choose the pattern

- Use `create<T>()` when a React component can consume one global bound store.
- Use `ExtractState<typeof store>` when another type needs the store's complete inferred shape.
- Use `createStore<T>()` and `useStore(store, selector)` when store creation must be separate from React or scoped to an owner.
- Nest shipped middleware directly inside `create` so contextual typing reaches every creator.
- Use mutator tuples and declaration merging only when you are implementing middleware that changes the store API.

For immutable update details, see [Immutable state updates](https://bench-zustand-56.atloria.app/p/bench-zustand-56-D8Xr4XerWj/developer/immutable-state-updates). For vanilla-store lifecycles, see [Vanilla and scoped stores](https://bench-zustand-56.atloria.app/p/bench-zustand-56-D8Xr4XerWj/developer/vanilla-and-scoped-stores). For middleware ordering and composition, see [Compose store middleware](https://bench-zustand-56.atloria.app/p/bench-zustand-56-D8Xr4XerWj/developer/compose-store-middleware).
