# How to Integrate Zustand with Redux DevTools for Debugging: A Complete Guide

> Easily integrate Zustand with Redux DevTools for powerful debugging. Learn how to use the devtools middleware to track state changes and enable time-travel debugging for your Zustand stores.

- Repository: [Poimandres/zustand](https://github.com/pmndrs/zustand)
- Tags: how-to-guide
- Published: 2026-03-06

---

**Zustand integrates with Redux DevTools through the `devtools` middleware, which intercepts state changes in [`src/middleware/devtools.ts`](https://github.com/pmndrs/zustand/blob/main/src/middleware/devtools.ts) and forwards them to the browser extension while supporting time-travel debugging and multiple store tracking.**

The `pmndrs/zustand` repository provides a lightweight yet powerful **devtools middleware** that connects Zustand stores directly to the Redux DevTools Extension without requiring Redux itself. This integration allows you to inspect state changes, travel back in time, and dispatch actions from the browser extension while maintaining Zustand's minimal API surface.

## How the Devtools Middleware Works

The middleware implementation in [`src/middleware/devtools.ts`](https://github.com/pmndrs/zustand/blob/main/src/middleware/devtools.ts) wraps your store's `setState` method to create a bridge between Zustand and the Redux DevTools Extension. Understanding this mechanism helps you debug effectively and configure advanced options.

### Extension Detection and Initialization

At store creation, the middleware checks for the presence of `window.__REDUX_DEVTOOLS_EXTENSION__`. If the extension is missing or running in production (without explicit override), the middleware returns the original `StateCreator` unchanged, ensuring zero overhead when debugging is disabled.

```ts
const extensionConnector = (enabled ?? import.meta.env?.MODE !== 'production')
  && window.__REDUX_DEVTOOLS_EXTENSION__;

```

This detection occurs at lines 98-104 of [`devtools.ts`](https://github.com/pmndrs/zustand/blob/main/devtools.ts), providing automatic environment-aware behavior.

### Connection Handling and Store Grouping

The middleware manages connections through two patterns:

- **Single-store mode**: When the `store` option is undefined, the middleware creates a dedicated DevTools connection for that store.
- **Multi-store mode**: When you provide a `store` identifier, the middleware maintains a **global map** called `trackedConnections` that groups multiple stores under one DevTools connection. This allows you to debug several stores simultaneously using the extension's selector.

The logic resides in `extractConnectionInformation` and the `trackedConnections` management system (lines 38-61).

### State Mutation Interception

The core functionality replaces the original `set` function with a wrapper that:

1. Executes the state update
2. Builds an **action descriptor** (`{type: ...}`) using the explicit action name (third argument to `set`), the `anonymousActionType` option, or a heuristic stack trace analysis via `findCallerName`
3. Sends the action and new state to the extension via `connection?.send`
4. For store-scoped connections, prefixes the action type with `${store}/${action.type}` and sends a snapshot of all tracked stores

This interception logic spans lines 155-191 of [`devtools.ts`](https://github.com/pmndrs/zustand/blob/main/devtools.ts).

### Inbound Message Processing

The middleware subscribes to DevTools messages and handles various dispatch types through a switch block starting at line 300:

- **`ACTION`**: Parses payload data and applies `__setState` actions to replace store state
- **`DISPATCH`**: Processes `RESET`, `COMMIT`, `ROLLBACK`, `JUMP_TO_STATE`, and `IMPORT_STATE` operations using the `setStateFromDevtools` helper to restore previous states

## Setting Up Redux DevTools in Zustand

### Installation Requirements

Install the DevTools extension for your browser, then ensure your environment supports the connection. No additional npm packages are strictly required for the basic integration, though TypeScript users may reference `@redux-devtools/extension` for types.

### Basic Store Configuration

Wrap your store creator with the `devtools` middleware imported from `zustand/middleware`:

```ts
import { create } from 'zustand';
import { devtools } from 'zustand/middleware';

type Counter = {
  count: number;
  inc: () => void;
};

export const useCounter = create<Counter>()(
  devtools(
    (set) => ({
      count: 0,
      inc: () => set((s) => ({ count: s.count + 1 }), undefined, 'counter/inc'),
    }),
    {
      name: 'CounterStore',
      enabled: true,
      anonymousActionType: 'unknown_action',
    },
  ),
);

```

The third argument to `set` (`'counter/inc'` in the example) defines the action name visible in the DevTools timeline.

### Naming Actions for Clear Debugging

Always provide explicit action names as the third parameter to `setState` to avoid anonymous actions in your DevTools history:

```ts
// Good: Clear action name
set({ user: newUser }, undefined, 'auth/setUser');

// Avoid: Relies on anonymousActionType fallback or stack heuristics
set({ user: newUser });

```

### Multiple Stores and Scoped Connections

When managing several stores, give each a unique `store` identifier to group them under a single DevTools connection:

```ts
export const useAuth = create<Auth>()(
  devtools(authCreator, { store: 'auth' })
);

export const useProfile = create<Profile>()(
  devtools(profileCreator, { store: 'profile' })
);

```

This configuration enables switching between stores via the DevTools instance selector while maintaining isolated state management.

### Cleanup on Unmount

For dynamically created stores (such as those inside React contexts), unsubscribe from DevTools when the component unmounts to prevent memory leaks and orphaned connections:

```ts
useEffect(() => {
  return () => {
    useMyStore.devtools.cleanup();
  };
}, []);

```

The `cleanup` method (lines 246-256 in [`devtools.ts`](https://github.com/pmndrs/zustand/blob/main/devtools.ts)) removes the store from `trackedConnections` and closes the DevTools subscription.

## Advanced Configuration Options

The `devtools` middleware accepts an options object that forwards settings to the Redux DevTools Extension:

| Option | Description |
|--------|-------------|
| **name** | Display name for the connection in the DevTools UI |
| **enabled** | Boolean override; defaults to `true` in development, `false` in production |
| **anonymousActionType** | Fallback label when actions omit explicit names |
| **store** | Unique key for scoping state in multi-store setups |
| **actionsDenylist** | Regex or array of regexes to hide internal actions from the timeline |

Example with action filtering:

```ts
devtools(storeCreator, {
  name: 'AuthStore',
  actionsDenylist: ['internal/.*', 'secret/.*'],
})

```

## TypeScript Implementation with Slices

When using the slices pattern, type each slice with the `[['zustand/devtools', never]]` mutator to preserve type safety through the middleware chain:

```ts
import { create, StateCreator } from 'zustand';
import { devtools } from 'zustand/middleware';

type BearSlice = { bears: number; addBear: () => void };
type FishSlice = { fish: number; addFish: () => void };
type JungleStore = BearSlice & FishSlice;

const bearSlice: StateCreator<JungleStore, [['zustand/devtools', never]], [], BearSlice> = (set) => ({
  bears: 0,
  addBear: () => set(s => ({ bears: s.bears + 1 }), undefined, 'bear/add'),
});

const fishSlice: StateCreator<JungleStore, [['zustand/devtools', never]], [], FishSlice> = (set) => ({
  fish: 0,
  addFish: () => set(s => ({ fish: s.fish + 1 }), undefined, 'fish/add'),
});

export const useJungle = create<JungleStore>()(
  devtools((...args) => ({
    ...bearSlice(...args),
    ...fishSlice(...args),
  }), { name: 'Jungle' }),
);

```

According to the `pmndrs/zustand` documentation, **devtools should always be the outermost middleware** in the composition chain to properly intercept all state changes (see [`docs/learn/guides/advanced-typescript.md`](https://github.com/pmndrs/zustand/blob/main/docs/learn/guides/advanced-typescript.md)).

## Summary

- The **devtools middleware** in [`src/middleware/devtools.ts`](https://github.com/pmndrs/zustand/blob/main/src/middleware/devtools.ts) provides Redux DevTools integration by wrapping `setState` and forwarding changes to the browser extension.
- It automatically detects the **Redux DevTools Extension** via `window.__REDUX_DEVTOOLS_EXTENSION__` and gracefully degrades when unavailable.
- Use the **third argument of `set`** to provide descriptive action names for the DevTools timeline.
- Configure the **`store`** option to group multiple Zustand stores under a single DevTools connection using the internal `trackedConnections` map.
- Call **`store.devtools.cleanup()`** when unmounting dynamically created stores to prevent memory leaks.
- Apply the **`[['zustand/devtools', never]]`** mutator type to slices when using TypeScript to maintain type safety through the middleware chain.

## Frequently Asked Questions

### How do I hide internal actions from Redux DevTools?

Use the `actionsDenylist` option in the devtools configuration, which accepts regex patterns that match action types you want to exclude from the timeline. For example, `actionsDenylist: ['internal/.*']` hides all actions prefixed with "internal/" while keeping public actions visible.

### Can I use Redux DevTools with Zustand without installing Redux?

Yes. Zustand's devtools middleware communicates directly with the Redux DevTools Extension browser plugin without requiring the Redux library as a dependency. The middleware implements the extension's communication protocol independently, as defined in [`src/middleware/devtools.ts`](https://github.com/pmndrs/zustand/blob/main/src/middleware/devtools.ts).

### Why are my state changes not appearing in Redux DevTools?

First, verify that `enabled` is not explicitly set to `false` and that you are not in a production build (unless `enabled` is explicitly `true`). Ensure you are passing an action name as the third argument to `set`, or check that the extension is installed and `window.__REDUX_DEVTOOLS_EXTENSION__` is available in your browser console. For TypeScript users, ensure devtools is the outermost middleware wrapper.

### How do I debug multiple Zustand stores simultaneously?

Assign each store a unique `store` identifier in the devtools options. The middleware maintains a global `trackedConnections` map that groups these stores under a single DevTools connection, allowing you to switch between store states using the extension's dropdown selector while preserving independent state management logic.