--- title: devtools description: How to time-travel debug your store nav: 205 --- # devtools `devtools` middleware lets you use [Redux DevTools Extension](https://github.com/reduxjs/redux-devtools) without Redux. Read more about the benefits of using [Redux DevTools for debugging](https://redux.js.org/style-guide/#use-the-redux-devtools-extension-for-debugging). > [!IMPORTANT] > In order to use `devtools` from `zustand/middleware` you need to install > `@redux-devtools/extension` library. ```js const nextStateCreatorFn = devtools(stateCreatorFn, devtoolsOptions) ``` - [Types](#types) - [Signature](#signature) - [Mutator](#mutator) - [Reference](#reference) - [Usage](#usage) - [Debugging a store](#debugging-a-store) - [Debugging a Slices pattern based store](#debugging-a-slices-pattern-based-store) - [Filtering actions with actionsDenylist](#filtering-actions-with-actionsdenylist) - [Cleanup](#cleanup) - [Troubleshooting](#troubleshooting) - [Only one store is displayed](#only-one-store-is-displayed) - [Action names are labeled as 'anonymous'](#all-action-names-are-labeled-as-anonymous) ## Types ### Signature ```ts devtools(stateCreatorFn: StateCreator, devtoolsOptions?: DevtoolsOptions): StateCreator ``` ### Mutator ```ts ['zustand/devtools', never] ``` ## Reference ### `devtools(stateCreatorFn, devtoolsOptions)` #### Parameters - `stateCreatorFn`: A function that takes `set` function, `get` function and `store` as arguments. Usually, you will return an object with the methods you want to expose. - **optional** `devtoolsOptions`: An object to define `Redux Devtools` options. - **optional** `name`: A custom identifier for the connection in the Redux DevTools. - **optional** `enabled`: Defaults to `true` when is on development mode, and defaults to `false` when is on production mode. Enables or disables the Redux DevTools integration for this store. - **optional** `anonymousActionType`: Defaults to the inferred action type or `anonymous` if unavailable. A string to use as the action type for anonymous mutations in the Redux DevTools. - **optional** `store`: A custom identifier for the store in the Redux DevTools. - **optional** `actionsDenylist`: A string or array of strings (regex patterns) that specify which actions should be filtered out from Redux DevTools. This option is passed directly to Redux DevTools for filtering. For example, `['secret.*']` will filter out all actions starting with "secret". #### Returns `devtools` returns a state creator function. ## Usage ### Debugging a store This example shows you how you can use `Redux Devtools` to debug a store ```ts import { create, StateCreator } from 'zustand' import { devtools } from 'zustand/middleware' type JungleStore = { bears: number addBear: () => void fishes: number addFish: () => void } const useJungleStore = create()( devtools((set) => ({ bears: 0, addBear: () => set((state) => ({ bears: state.bears + 1 }), undefined, 'jungle/addBear'), fishes: 0, addFish: () => set( (state) => ({ fishes: state.fishes + 1 }), undefined, 'jungle/addFish', ), })), ) ``` ### Debugging a Slices pattern based store This example shows you how you can use `Redux Devtools` to debug a Slices pattern based store ```ts import { create, StateCreator } from 'zustand' import { devtools } from 'zustand/middleware' type BearSlice = { bears: number addBear: () => void } type FishSlice = { fishes: number addFish: () => void } type JungleStore = BearSlice & FishSlice const createBearSlice: StateCreator< JungleStore, [['zustand/devtools', never]], [], BearSlice > = (set) => ({ bears: 0, addBear: () => set( (state) => ({ bears: state.bears + 1 }), undefined, 'jungle:bear/addBear', ), }) const createFishSlice: StateCreator< JungleStore, [['zustand/devtools', never]], [], FishSlice > = (set) => ({ fishes: 0, addFish: () => set( (state) => ({ fishes: state.fishes + 1 }), undefined, 'jungle:fish/addFish', ), }) const useJungleStore = create()( devtools((...args) => ({ ...createBearSlice(...args), ...createFishSlice(...args), })), ) ``` ### Filtering actions with actionsDenylist You can filter out specific actions from Redux DevTools using the `actionsDenylist` option. This is useful for hiding internal or sensitive actions from the DevTools timeline. ```ts import { create } from 'zustand' import { devtools } from 'zustand/middleware' type Store = { user: string | null token: string | null login: (user: string, token: string) => void logout: () => void updateData: () => void } const useStore = create()( devtools( (set) => ({ user: null, token: null, login: (user, token) => set({ user, token }, undefined, 'auth/login'), logout: () => set({ user: null, token: null }, undefined, 'auth/logout'), updateData: () => set({ user: 'updated' }, undefined, 'internal/updateData'), }), { name: 'AuthStore', // Filter out actions matching these regex patterns actionsDenylist: ['internal/.*'], // Hides all 'internal/*' actions }, ), ) ``` You can also use a single regex string: ```ts const useStore = create()( devtools( (set) => ({ // ... state and actions }), { name: 'MyStore', actionsDenylist: 'secret.*', // Hides all actions starting with 'secret' }, ), ) ``` > [!NOTE] > The `actionsDenylist` option uses regex pattern matching and is handled directly by Redux DevTools Extension. > All actions are still sent to DevTools, but matching actions are filtered from the display. ### Cleanup When a store is no longer needed, you can clean up the Redux DevTools connection by calling the `cleanup` method on the store: ```ts import { create } from 'zustand' import { devtools } from 'zustand/middleware' const useStore = create( devtools((set) => ({ count: 0, increment: () => set((state) => ({ count: state.count + 1 })), })), ) // When you're done with the store, clean it up useStore.devtools.cleanup() ``` This is particularly useful in applications that wrap store in context or create multiple stores dynamically. ## Troubleshooting ### Only one store is displayed By default, `Redux Devtools` only show one store at a time, so in order to see other stores you need to use store selector and choose a different store. ### All action names are labeled as 'anonymous' If an action type name is not provided, it is defaulted to "anonymous". You can customize this default value by providing a `anonymousActionType` parameter: For instance the next example doesn't have action type name: ```ts import { create, StateCreator } from 'zustand' import { devtools } from 'zustand/middleware' type BearSlice = { bears: number addBear: () => void } type FishSlice = { fishes: number addFish: () => void } type JungleStore = BearSlice & FishSlice const createBearSlice: StateCreator< JungleStore, [['zustand/devtools', never]], [], BearSlice > = (set) => ({ bears: 0, addBear: () => set((state) => ({ bears: state.bears + 1 })), eatFish: () => set((state) => ({ fishes: state.fishes - 1 })), }) const createFishSlice: StateCreator< JungleStore, [['zustand/devtools', never]], [], FishSlice > = (set) => ({ fishes: 0, addFish: () => set((state) => ({ fishes: state.fishes + 1 })), }) const useJungleStore = create()( devtools((...args) => ({ ...createBearSlice(...args), ...createFishSlice(...args), })), ) ``` In order to fix the previous example, we need to provide an action type name as the third parameter. Additionally, to preserve the default behavior of the replacement logic, the second parameter should be set to `undefined`. Here's the fixed previous example ```ts import { create, StateCreator } from 'zustand' type BearSlice = { bears: number addBear: () => void } type FishSlice = { fishes: number addFish: () => void } type JungleStore = BearSlice & FishSlice const createBearSlice: StateCreator< JungleStore, [['zustand/devtools', never]], [], BearSlice > = (set) => ({ bears: 0, addBear: () => set((state) => ({ bears: state.bears + 1 }), undefined, 'bear/addBear'), }) const createFishSlice: StateCreator< JungleStore, [['zustand/devtools', never]], [], FishSlice > = (set) => ({ fishes: 0, addFish: () => set((state) => ({ fishes: state.fishes + 1 }), undefined, 'fish/addFish'), }) const useJungleStore = create()( devtools((...args) => ({ ...createBearSlice(...args), ...createFishSlice(...args), })), ) ``` > [!IMPORTANT] > Do not set the second parameter to `true` or `false` unless you want to override the default > replacement logic