The event system is how plugins communicate with the devtools UI and with the application. It is built on EventClient, a type-safe event emitter/listener from @tanstack/devtools-event-client. It is completely framework-agnostic.
Create a typed EventClient by extending the base class with your event map:
import { EventClient } from '@tanstack/devtools-event-client'
type MyEvents = {
'state-update': { count: number }
'action': { type: string }
}
class MyEventClient extends EventClient<MyEvents> {
constructor() {
super({ pluginId: 'my-plugin' })
}
}
export const myEventClient = new MyEventClient()
The constructor accepts the following options:
| Option | Type | Default | Description |
|---|---|---|---|
| pluginId | string | — | Required. Identifies this plugin in the event system. |
| debug | boolean | false | Enable debug logging to the console. |
| enabled | boolean | true | Whether the client connects to the bus at all. |
| reconnectEveryMs | number | 300 | Interval (ms) between connection retry attempts. |
The generic EventMap type maps event names to payload types. Keys are event suffixes only — the pluginId is prepended automatically by EventClient when emitting and listening:
type MyEvents = {
'state-update': { count: number }
'action': { type: string }
}
TypeScript enforces correct event names and payload shapes at compile time. You get autocomplete on event names and type errors if the payload does not match the declared shape.
Call eventClient.emit(eventSuffix, payload) to dispatch an event. You pass only the suffix (the part after the colon). The pluginId is prepended automatically:
// If pluginId is 'my-plugin' and event map has 'state-update'
myEventClient.emit('state-update', { count: 42 })
// Dispatches event named 'my-plugin:state-update'
If the client is not yet connected to the bus, the event is queued and flushed once the connection succeeds (see Connection Lifecycle below).
There are three methods for subscribing to events. Each returns a cleanup function you call to unsubscribe.
Listen to a specific event from this plugin. Like emit, you pass only the suffix:
const cleanup = myEventClient.on('state-update', (event) => {
console.log(event.payload.count) // typed as { count: number }
})
// Later: stop listening
cleanup()
The callback receives the full event object:
{
type: 'my-plugin:state-update', // fully qualified event name
payload: { count: number }, // typed payload
pluginId: 'my-plugin' // originating plugin
}
Listen to all events from all plugins. Useful for logging, debugging, or building cross-plugin features:
const cleanup = myEventClient.onAll((event) => {
console.log(event.type, event.payload)
})
Listen to all events from this plugin only (filtered by pluginId):
const cleanup = myEventClient.onAllPluginEvents((event) => {
// Only fires for events where event.pluginId === 'my-plugin'
console.log(event.type, event.payload)
})
The EventClient manages its connection to the event bus automatically:
When enabled is set to false, the EventClient is effectively inert — emit() is a no-op and on() returns a no-op cleanup function. This is useful for conditionally disabling devtools instrumentation (e.g., in production).
When connectToServerBus: true is set in the component's eventBusConfig prop, the ClientEventBus connects to the ServerEventBus started by the Vite plugin (default port 4206). This enables server-side features like console piping and the plugin marketplace.
<TanStackDevtools
eventBusConfig={{
connectToServerBus: true,
debug: false,
port: 4206, // default
}}
/>
Without the Vite plugin running, the EventClient still works for same-page communication between your application code and the devtools panel. The server bus is only needed for features that bridge the browser and the dev server.
Set debug: true in the EventClient constructor or in eventBusConfig to enable verbose console logging. Debug logs are prefixed with [tanstack-devtools:{pluginId}] for plugin events and [tanstack-devtools:client-bus] for the client bus.
const myEventClient = new MyEventClient()
// In the constructor: super({ pluginId: 'my-plugin', debug: true })
Example output:
🌴 [tanstack-devtools:client-bus] Initializing client event bus
🌴 [tanstack-devtools:my-plugin] Registered event to bus my-plugin:state-update
🌴 [tanstack-devtools:my-plugin] Emitting event my-plugin:state-update
This is helpful when diagnosing issues with event delivery, connection timing, or verifying that your event map is wired up correctly.