@tanstack/devtools-utils provides factory functions that simplify creating devtools plugins for each framework. Instead of manually wiring up render functions and no-op variants, these helpers produce correctly-typed plugin objects (and their production-safe no-op counterparts) from your components. Each framework has its own subpath export with an API tailored to that framework's conventions.
npm install @tanstack/devtools-utils
npm install @tanstack/devtools-utils
Every panel component receives a theme prop so the panel can match the devtools shell appearance. The interface is defined per-framework in each subpath:
interface DevtoolsPanelProps {
theme?: 'light' | 'dark'
}
interface DevtoolsPanelProps {
theme?: 'light' | 'dark'
}
The Vue variant additionally accepts 'system' as a theme value.
Import it from the framework-specific subpath:
// React
import type { DevtoolsPanelProps } from '@tanstack/devtools-utils/react'
// Solid
import type { DevtoolsPanelProps } from '@tanstack/devtools-utils/solid'
// Preact
import type { DevtoolsPanelProps } from '@tanstack/devtools-utils/preact'
// Vue
import type { DevtoolsPanelProps } from '@tanstack/devtools-utils/vue'
// React
import type { DevtoolsPanelProps } from '@tanstack/devtools-utils/react'
// Solid
import type { DevtoolsPanelProps } from '@tanstack/devtools-utils/solid'
// Preact
import type { DevtoolsPanelProps } from '@tanstack/devtools-utils/preact'
// Vue
import type { DevtoolsPanelProps } from '@tanstack/devtools-utils/vue'
Creates a [Plugin, NoOpPlugin] tuple from a React component and plugin metadata.
Signature:
function createReactPlugin(options: {
name: string
id?: string
defaultOpen?: boolean
Component: (props: DevtoolsPanelProps) => JSX.Element
}): readonly [Plugin, NoOpPlugin]
function createReactPlugin(options: {
name: string
id?: string
defaultOpen?: boolean
Component: (props: DevtoolsPanelProps) => JSX.Element
}): readonly [Plugin, NoOpPlugin]
Usage:
import { createReactPlugin } from '@tanstack/devtools-utils/react'
const [MyPlugin, NoOpPlugin] = createReactPlugin({
name: 'My Store',
id: 'my-store',
defaultOpen: false,
Component: ({ theme }) => <MyStorePanel theme={theme} />,
})
import { createReactPlugin } from '@tanstack/devtools-utils/react'
const [MyPlugin, NoOpPlugin] = createReactPlugin({
name: 'My Store',
id: 'my-store',
defaultOpen: false,
Component: ({ theme }) => <MyStorePanel theme={theme} />,
})
The returned tuple contains two factory functions:
A common pattern for tree-shaking:
const [MyPlugin, NoOpPlugin] = createReactPlugin({ /* ... */ })
const ActivePlugin = process.env.NODE_ENV === 'development' ? MyPlugin : NoOpPlugin
const [MyPlugin, NoOpPlugin] = createReactPlugin({ /* ... */ })
const ActivePlugin = process.env.NODE_ENV === 'development' ? MyPlugin : NoOpPlugin
For library authors shipping a class-based devtools core that exposes mount(el, theme) and unmount() methods. This factory wraps that class in a React component that handles mounting into a div, passing the theme, and cleaning up on unmount.
Signature:
function createReactPanel<
TComponentProps extends DevtoolsPanelProps | undefined,
TCoreDevtoolsClass extends {
mount: (el: HTMLElement, theme: 'light' | 'dark') => void
unmount: () => void
},
>(CoreClass: new () => TCoreDevtoolsClass): readonly [Panel, NoOpPanel]
function createReactPanel<
TComponentProps extends DevtoolsPanelProps | undefined,
TCoreDevtoolsClass extends {
mount: (el: HTMLElement, theme: 'light' | 'dark') => void
unmount: () => void
},
>(CoreClass: new () => TCoreDevtoolsClass): readonly [Panel, NoOpPanel]
Usage:
import { createReactPanel } from '@tanstack/devtools-utils/react'
class MyDevtoolsCore {
mount(el: HTMLElement, theme: 'light' | 'dark') {
// render your devtools UI into el
}
unmount() {
// cleanup
}
}
const [MyPanel, NoOpPanel] = createReactPanel(MyDevtoolsCore)
// Then use the panel component inside createReactPlugin:
const [MyPlugin, NoOpPlugin] = createReactPlugin({
name: 'My Store',
Component: MyPanel,
})
import { createReactPanel } from '@tanstack/devtools-utils/react'
class MyDevtoolsCore {
mount(el: HTMLElement, theme: 'light' | 'dark') {
// render your devtools UI into el
}
unmount() {
// cleanup
}
}
const [MyPanel, NoOpPanel] = createReactPanel(MyDevtoolsCore)
// Then use the panel component inside createReactPlugin:
const [MyPlugin, NoOpPlugin] = createReactPlugin({
name: 'My Store',
Component: MyPanel,
})
The returned Panel component:
NoOpPanel renders an empty fragment and does nothing.
Identical API to createReactPlugin, using Preact's JSX types. Import from @tanstack/devtools-utils/preact.
Signature:
function createPreactPlugin(options: {
name: string
id?: string
defaultOpen?: boolean
Component: (props: DevtoolsPanelProps) => JSX.Element
}): readonly [Plugin, NoOpPlugin]
function createPreactPlugin(options: {
name: string
id?: string
defaultOpen?: boolean
Component: (props: DevtoolsPanelProps) => JSX.Element
}): readonly [Plugin, NoOpPlugin]
Usage:
import { createPreactPlugin } from '@tanstack/devtools-utils/preact'
const [MyPlugin, NoOpPlugin] = createPreactPlugin({
name: 'My Store',
id: 'my-store',
Component: ({ theme }) => <MyStorePanel theme={theme} />,
})
import { createPreactPlugin } from '@tanstack/devtools-utils/preact'
const [MyPlugin, NoOpPlugin] = createPreactPlugin({
name: 'My Store',
id: 'my-store',
Component: ({ theme }) => <MyStorePanel theme={theme} />,
})
The return value and behavior are the same as createReactPlugin -- a [Plugin, NoOpPlugin] tuple where Plugin renders your component and NoOpPlugin renders nothing.
Also available for Preact with the same class-based API as createReactPanel:
import { createPreactPanel } from '@tanstack/devtools-utils/preact'
const [MyPanel, NoOpPanel] = createPreactPanel(MyDevtoolsCore)
import { createPreactPanel } from '@tanstack/devtools-utils/preact'
const [MyPanel, NoOpPanel] = createPreactPanel(MyDevtoolsCore)
Same option-object API as React and Preact, using Solid's JSX types. Import from @tanstack/devtools-utils/solid.
Signature:
function createSolidPlugin(options: {
name: string
id?: string
defaultOpen?: boolean
Component: (props: DevtoolsPanelProps) => JSX.Element
}): readonly [Plugin, NoOpPlugin]
function createSolidPlugin(options: {
name: string
id?: string
defaultOpen?: boolean
Component: (props: DevtoolsPanelProps) => JSX.Element
}): readonly [Plugin, NoOpPlugin]
Usage:
import { createSolidPlugin } from '@tanstack/devtools-utils/solid'
const [MyPlugin, NoOpPlugin] = createSolidPlugin({
name: 'My Store',
id: 'my-store',
Component: (props) => <MyStorePanel theme={props.theme} />,
})
import { createSolidPlugin } from '@tanstack/devtools-utils/solid'
const [MyPlugin, NoOpPlugin] = createSolidPlugin({
name: 'My Store',
id: 'my-store',
Component: (props) => <MyStorePanel theme={props.theme} />,
})
Solid also provides a class-based panel factory. It uses createSignal and onMount/onCleanup instead of React hooks:
import { createSolidPanel } from '@tanstack/devtools-utils/solid'
const [MyPanel, NoOpPanel] = createSolidPanel(MyDevtoolsCore)
import { createSolidPanel } from '@tanstack/devtools-utils/solid'
const [MyPanel, NoOpPanel] = createSolidPanel(MyDevtoolsCore)
The Vue factory has a different API from the JSX-based frameworks. It takes a name string and a Vue DefineComponent as separate arguments rather than an options object.
Signature:
function createVuePlugin<TComponentProps extends Record<string, any>>(
name: string,
component: DefineComponent<TComponentProps, {}, unknown>,
): readonly [Plugin, NoOpPlugin]
function createVuePlugin<TComponentProps extends Record<string, any>>(
name: string,
component: DefineComponent<TComponentProps, {}, unknown>,
): readonly [Plugin, NoOpPlugin]
Usage:
import { createVuePlugin } from '@tanstack/devtools-utils/vue'
import MyStorePanel from './MyStorePanel.vue'
const [MyPlugin, NoOpPlugin] = createVuePlugin('My Store', MyStorePanel)
import { createVuePlugin } from '@tanstack/devtools-utils/vue'
import MyStorePanel from './MyStorePanel.vue'
const [MyPlugin, NoOpPlugin] = createVuePlugin('My Store', MyStorePanel)
The returned functions differ from the JSX-based variants:
Both accept props that get forwarded to the component.
For class-based devtools cores, Vue provides createVuePanel. It creates a Vue defineComponent that handles mounting and unmounting the core class:
import { createVuePanel } from '@tanstack/devtools-utils/vue'
const [MyPanel, NoOpPanel] = createVuePanel(MyDevtoolsCore)
import { createVuePanel } from '@tanstack/devtools-utils/vue'
const [MyPanel, NoOpPanel] = createVuePanel(MyDevtoolsCore)
The panel component accepts theme and devtoolsProps as props. It mounts the core instance into a div element on onMounted and calls unmount() on onUnmounted.
Use the factories when you are building a reusable library plugin that will be published as a package. The factories ensure:
Use manual plugin objects when you are building a one-off internal devtools panel for your application. In that case, passing name and render directly to the devtools configuration is simpler and avoids the extra abstraction:
// Manual approach -- fine for one-off panels
{
name: 'App State',
render: (el, theme) => <MyPanel theme={theme} />,
}
// Manual approach -- fine for one-off panels
{
name: 'App State',
render: (el, theme) => <MyPanel theme={theme} />,
}
The factory approach becomes more valuable as you add id, defaultOpen, and need both a development and production variant of the same plugin.
Your weekly dose of JavaScript news. Delivered every Monday to over 100,000 devs, for free.
Your weekly dose of JavaScript news. Delivered every Monday to over 100,000 devs, for free.
