📄 tanstack/devtools/latest/docs/framework/react/guides/custom-plugins

File: custom-plugins.md | Updated: 11/15/2025

Source: https://tanstack.com/devtools/latest/docs/framework/react/guides/custom-plugins



TanStack

Devtools v0v0

Search...

+ K

Auto

Log In

TanStack StartRC

Docs Examples GitHub Contributors

TanStack Router

Docs Examples GitHub Contributors

TanStack Query

Docs Examples GitHub Contributors

TanStack Table

Docs Examples Github Contributors

TanStack Formnew

Docs Examples Github Contributors

TanStack DBbeta

Docs Github Contributors

TanStack Virtual

Docs Examples Github Contributors

TanStack Paceralpha

Docs Examples Github Contributors

TanStack Storealpha

Docs Examples Github Contributors

TanStack Devtoolsalpha

Docs Github Contributors

More Libraries

Maintainers Partners Support Learn StatsBETA Discord Merch Blog GitHub Ethos Brand Guide

Documentation

Framework

React logo

React

Version

Latest

Search...

+ K

Menu

Getting Started

Guides

API Reference

Examples

Framework

React logo

React

Version

Latest

Menu

Getting Started

Guides

API Reference

Examples

On this page

Custom plugins

Copy Markdown

TanStack devtools allows you to create your own custom plugins by emitting and listening to our event bus.

Prerequisite
------------

This guide will walk you through a simple example where our library is a counter with a count history. A working example can be found in our custom-plugin example .

This is our library code:

counter.ts

tsx

export function createCounter() {
  let count = 0
  const history = []

  return {
    getCount: () => count,
    increment: () => {
      count++
      history.push(count)
    },
    decrement: () => {
      count--
      history.push(count)
    },
  };
}


export function createCounter() {
  let count = 0
  const history = []

  return {
    getCount: () => count,
    increment: () => {
      count++
      history.push(count)
    },
    decrement: () => {
      count--
      history.push(count)
    },
  };
}

Event Client Setup
------------------

Install the TanStack Devtools Event Client utils.

bash

npm i @tanstack/devtools-event-client


npm i @tanstack/devtools-event-client

First you will need to setup the EventClient.

eventClient.ts

tsx

import { EventClient } from '@tanstack/devtools-event-client'


type EventMap = {
  // The key of the event map is a combination of {pluginId}:{eventSuffix}
  // The value is the expected type of the event payload
  'custom-devtools:counter-state': { count: number, history: number[] }
}

class CustomEventClient extends EventClient<EventMap> {
  constructor() {
    super({
      // The pluginId must match that of the event map key
      pluginId: 'custom-devtools',
    })
  }
}

// This is where the magic happens, it'll be used throughout your application.
export const DevtoolsEventClient = new CustomEventClient()


import { EventClient } from '@tanstack/devtools-event-client'


type EventMap = {
  // The key of the event map is a combination of {pluginId}:{eventSuffix}
  // The value is the expected type of the event payload
  'custom-devtools:counter-state': { count: number, history: number[] }
}

class CustomEventClient extends EventClient<EventMap> {
  constructor() {
    super({
      // The pluginId must match that of the event map key
      pluginId: 'custom-devtools',
    })
  }
}

// This is where the magic happens, it'll be used throughout your application.
export const DevtoolsEventClient = new CustomEventClient()

Event Client Integration
------------------------

Now we need to hook our EventClient into the application code. This can be done in many way's, a useEffect that emits the current state, or a subscription to an observer, all that matters is that when you want to emit the current state you do the following.

Our new library code will looks as follows:

counter.ts

tsx

import { DevtoolsEventClient } from './eventClient.ts'

export function createCounter() {
  let count = 0
  const history: Array<number> = []

  return {
    getCount: () => count,
    increment: () => {
      count++
      history.push(count)

      // The emit eventSuffix must match that of the EventMap defined in eventClient.ts
      DevtoolsEventClient.emit('counter-state', {
        count,
        history,
      })
    },
    decrement: () => {
      count--
      history.push(count)

      DevtoolsEventClient.emit('counter-state', {
        count,
        history,
      })
    },
  }
}


import { DevtoolsEventClient } from './eventClient.ts'

export function createCounter() {
  let count = 0
  const history: Array<number> = []

  return {
    getCount: () => count,
    increment: () => {
      count++
      history.push(count)

      // The emit eventSuffix must match that of the EventMap defined in eventClient.ts
      DevtoolsEventClient.emit('counter-state', {
        count,
        history,
      })
    },
    decrement: () => {
      count--
      history.push(count)

      DevtoolsEventClient.emit('counter-state', {
        count,
        history,
      })
    },
  }
}

Important

EventClient is framework agnostic so this process will be the same regardless of framework or even in vanilla JavaScript.

Consuming The Event Client
--------------------------

Now we need to create our devtools panel, for a simple approach write the devtools in the framework that the adapter is, be aware that this will make the plugin framework specific.

Because TanStack is framework agnostic we have taken a more complicated approach that will be explained in coming docs (if framework agnosticism is not a concern to you, you can ignore this).

DevtoolsPanel.ts

tsx

import { DevtoolsEventClient } from './eventClient.ts'

export function DevtoolPanel() {
  const [state, setState] = useState()

  useEffect(() => {
    // subscribe to the emitted event
    const cleanup = DevtoolsEventClient.on("counter-state", e => setState(e.payload))
    return cleanup
  }, [])

  return (
    <div>
      <div>{state.count}</div>
      <div>{JSON.stringify(state.history)}</div>
    </div>
  )
}


import { DevtoolsEventClient } from './eventClient.ts'

export function DevtoolPanel() {
  const [state, setState] = useState()

  useEffect(() => {
    // subscribe to the emitted event
    const cleanup = DevtoolsEventClient.on("counter-state", e => setState(e.payload))
    return cleanup
  }, [])

  return (
    <div>
      <div>{state.count}</div>
      <div>{JSON.stringify(state.history)}</div>
    </div>
  )
}

Application Integration
-----------------------

This step follows what's shown in basic-setup for a more documented guide go check it out. As well as the complete custom-devtools example in our examples section.

Main.tsx

tsx

import { DevtoolPanel } from './DevtoolPanel'

createRoot(document.getElementById('root')!).render(
  <StrictMode>
    <App />

    <TanStackDevtools
      plugins={[\
        {\
          // Name it what you like, this is how it will appear in the Menu\
          name: 'Custom devtools',\
          render: <DevtoolPanel />,\
        },\
      ]}
    />
  </StrictMode>,
)


import { DevtoolPanel } from './DevtoolPanel'

createRoot(document.getElementById('root')!).render(
  <StrictMode>
    <App />

    <TanStackDevtools
      plugins={[\
        {\
          // Name it what you like, this is how it will appear in the Menu\
          name: 'Custom devtools',\
          render: <DevtoolPanel />,\
        },\
      ]}
    />
  </StrictMode>,
)

Debugging
---------

Both the TanStackDevtools component and the TanStack EventClient come with built in debug mode which will log to the console the emitted event as well as the EventClient status.

TanStackDevtool's debugging mode can be activated like so:

tsx

<TanStackDevtools
  eventBusConfig={{ debug: true }}
  plugins={[\
    {\
      // call it what you like, this is how it will appear in the Menu\
      name: 'Custom devtools',\
      render: <DevtoolPanel />,\
    },\
  ]}
/>


<TanStackDevtools
  eventBusConfig={{ debug: true }}
  plugins={[\
    {\
      // call it what you like, this is how it will appear in the Menu\
      name: 'Custom devtools',\
      render: <DevtoolPanel />,\
    },\
  ]}
/>

Where as the EventClient's debug mode can be activated by:

tsx

class CustomEventClient extends EventClient<EventMap> {
  constructor() {
    super({
      pluginId: 'custom-devtools',
      debug: true,
    })
  }
}


class CustomEventClient extends EventClient<EventMap> {
  constructor() {
    super({
      pluginId: 'custom-devtools',
      debug: true,
    })
  }
}

Activating the debug mode will log to the console the current events that emitter has emitted or listened to. The EventClient will have appended [tanstack-devtools:${pluginId}] and the client will have appended [tanstack-devtools:client-bus].

Heres an example of both:

🌴 [tanstack-devtools:client-bus] Initializing client event bus

🌴 [tanstack-devtools:custom-devtools-plugin] Registered event to bus custom-devtools:counter-state


🌴 [tanstack-devtools:client-bus] Initializing client event bus

🌴 [tanstack-devtools:custom-devtools-plugin] Registered event to bus custom-devtools:counter-state

Edit on GitHub

React adapter

Core API Reference

Partners Become a Partner

Code RabbitCode Rabbit CloudflareCloudflare AG GridAG Grid NetlifyNetlify NeonNeon WorkOSWorkOS ClerkClerk ConvexConvex ElectricElectric SentrySentry PrismaPrisma StrapiStrapi UnkeyUnkey