--- title: Using devtools-utils id: devtools-utils --- `@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. ## Installation ```bash npm install @tanstack/devtools-utils ``` ## DevtoolsPanelProps 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: ```ts interface DevtoolsPanelProps { theme?: 'light' | 'dark' } ``` > The Vue and Svelte variants additionally accept `'system'` as a theme value. Import it from the framework-specific subpath: ```ts // 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' // Svelte import type { DevtoolsPanelProps } from '@tanstack/devtools-utils/svelte' // Angular import type { DevtoolsPanelProps } from '@tanstack/devtools-utils/angular' ``` ## React ### createReactPlugin Creates a `[Plugin, NoOpPlugin]` tuple from a React component and plugin metadata. **Signature:** ```ts function createReactPlugin(options: { name: string id?: string defaultOpen?: boolean Component: (props: DevtoolsPanelProps) => JSX.Element }): readonly [Plugin, NoOpPlugin] ``` **Usage:** ```tsx import { createReactPlugin } from '@tanstack/devtools-utils/react' const [MyPlugin, NoOpPlugin] = createReactPlugin({ name: 'My Store', id: 'my-store', defaultOpen: false, Component: ({ theme }) => , }) ``` The returned tuple contains two factory functions: - **`Plugin()`** -- returns a plugin object with `name`, `id`, `defaultOpen`, and a `render` function that renders your `Component` with the current plugin props. - **`NoOpPlugin()`** -- returns a plugin object with the same metadata but a `render` function that renders an empty fragment. Use this for production builds where you want to strip devtools out. A common pattern for tree-shaking: ```tsx const [MyPlugin, NoOpPlugin] = createReactPlugin({ /* ... */ }) const ActivePlugin = process.env.NODE_ENV === 'development' ? MyPlugin : NoOpPlugin ``` ### createReactPanel For library authors shipping a class-based devtools core that exposes `mount(el, props)` and `unmount()` methods. This factory wraps that class in a React component that handles mounting into a `div`, passing the complete plugin props, and cleaning up on unmount. **Signature:** ```ts function createReactPanel< TComponentProps extends DevtoolsPanelProps, TCoreDevtoolsClass extends { mount: (el: HTMLElement, props: DevtoolsPanelProps) => void unmount: () => void }, >(CoreClass: new () => TCoreDevtoolsClass): readonly [Panel, NoOpPanel] ``` **Usage:** ```tsx import { createReactPanel, type DevtoolsPanelProps, } from '@tanstack/devtools-utils/react' class MyDevtoolsCore { mount(el: HTMLElement, props: DevtoolsPanelProps) { // 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: - Creates a `div` with `height: 100%` and stores a ref to it. - Instantiates `CoreClass` on mount and calls `core.mount(el, props)`. - Calls `core.unmount()` on cleanup. - Re-mounts when the plugin props change. `NoOpPanel` renders an empty fragment and does nothing. ## Preact ### createPreactPlugin Identical API to `createReactPlugin`, using Preact's JSX types. Import from `@tanstack/devtools-utils/preact`. **Signature:** ```ts function createPreactPlugin(options: { name: string id?: string defaultOpen?: boolean Component: (props: DevtoolsPanelProps) => JSX.Element }): readonly [Plugin, NoOpPlugin] ``` **Usage:** ```tsx import { createPreactPlugin } from '@tanstack/devtools-utils/preact' const [MyPlugin, NoOpPlugin] = createPreactPlugin({ name: 'My Store', id: 'my-store', Component: ({ theme }) => , }) ``` The return value and behavior are the same as `createReactPlugin` -- a `[Plugin, NoOpPlugin]` tuple where `Plugin` renders your component and `NoOpPlugin` renders nothing. ### createPreactPanel Also available for Preact with the same class-based API as `createReactPanel`: ```ts import { createPreactPanel } from '@tanstack/devtools-utils/preact' const [MyPanel, NoOpPanel] = createPreactPanel(MyDevtoolsCore) ``` ## Solid ### createSolidPlugin Same option-object API as React and Preact, using Solid's JSX types. Import from `@tanstack/devtools-utils/solid`. **Signature:** ```ts function createSolidPlugin(options: { name: string id?: string defaultOpen?: boolean Component: (props: DevtoolsPanelProps) => JSX.Element }): readonly [Plugin, NoOpPlugin] ``` **Usage:** ```tsx import { createSolidPlugin } from '@tanstack/devtools-utils/solid' const [MyPlugin, NoOpPlugin] = createSolidPlugin({ name: 'My Store', id: 'my-store', Component: (props) => , }) ``` ### createSolidPanel Solid also provides a class-based panel factory. It uses `createSignal` and `onMount`/`onCleanup` instead of React hooks: ```ts import { createSolidPanel } from '@tanstack/devtools-utils/solid' const [MyPanel, NoOpPanel] = createSolidPanel(MyDevtoolsCore) ``` ## Vue ### createVuePlugin 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:** ```ts function createVuePlugin>( name: string, component: DefineComponent, ): readonly [Plugin, NoOpPlugin] ``` **Usage:** ```ts 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: - **`Plugin(props)`** -- returns `{ name, component, props }` where `component` is your Vue component. - **`NoOpPlugin(props)`** -- returns `{ name, component: Fragment, props }` where the component is Vue's built-in `Fragment` (renders nothing visible). Both accept props that get forwarded to the component. ### createVuePanel For class-based devtools cores, Vue provides `createVuePanel`. It creates a Vue `defineComponent` that handles mounting and unmounting the core class: ```ts 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`. ## Svelte ### createSveltePlugin The Svelte factory takes the plugin metadata and Svelte component in a configuration object. **Signature:** ```ts function createSveltePlugin>( config: { name: string id?: string defaultOpen?: boolean Component: Component }, ): readonly [Plugin, NoOpPlugin] ``` **Usage:** ```ts import { createSveltePlugin } from '@tanstack/devtools-utils/svelte' import MyStorePanel from './MyStorePanel.svelte' const [MyPlugin, NoOpPlugin] = createSveltePlugin({ Component: MyStorePanel, name: 'My Store', id: 'my-store', defaultOpen: true, }) ``` The returned functions: - **`Plugin(props?)`** -- returns the plugin metadata, component, and forwarded props. - **`NoOpPlugin(props?)`** -- preserves the plugin metadata and props but replaces the component with one that renders nothing visible. Both accept an optional `props` object that gets forwarded to the component on mount. ### createSveltePanel For class-based devtools cores, Svelte provides `createSveltePanel`. It creates a wrapper that handles mounting and unmounting the core class: ```ts import { createSveltePanel } from '@tanstack/devtools-utils/svelte' const [MyPanel, NoOpPanel] = createSveltePanel(MyDevtoolsCore) ``` The panel constructs the core without arguments. It creates a `div` element, passes the complete `{ theme, devtoolsOpen }` plugin props to `mount()`, and registers `unmount()` with Svelte's component cleanup lifecycle. ## Angular ### createAngularPlugin The Angular factory takes a `name` string and an Angular component class as separate arguments, similar to the Vue API. **Signature:** ```ts function createAngularPlugin( name: string, component: Type, ): readonly [Plugin, NoOpPlugin] ``` **Usage:** ```ts import { createAngularPlugin } from '@tanstack/devtools-utils/angular' import { MyStorePanelComponent } from './my-store-panel.component' const [MyPlugin, NoOpPlugin] = createAngularPlugin('My Store', MyStorePanelComponent) ``` The returned functions: - **`Plugin(inputs?)`** -- returns `{ name, component, inputs }` where `component` is your Angular component class. - **`NoOpPlugin(inputs?)`** -- returns `{ name, component: NoOpComponent, inputs }` where the component is an empty standalone component (renders nothing visible). Both accept an optional `inputs` object that gets forwarded to the component via `setInput()`. ### createAngularPanel For class-based devtools cores, Angular provides `createAngularPanel`. It creates a standalone Angular component that handles mounting and unmounting the core class: ```ts import { createAngularPanel } from '@tanstack/devtools-utils/angular' const [MyPanel, NoOpPanel] = createAngularPanel(MyDevtoolsCore) ``` The panel component accepts `theme` and `devtoolsProps` as signal inputs. It mounts the core instance into a `div` element using `afterNextRender` and calls `unmount()` via `DestroyRef.onDestroy`. ## When to Use Factories vs Manual Plugin Objects **Use the factories** when you are building a reusable library plugin that will be published as a package. The factories ensure: - Consistent plugin object shape across frameworks. - A matching `NoOpPlugin` variant for production tree-shaking. - Correct typing without manual type annotations. **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: ```tsx // Manual approach -- fine for one-off panels { name: 'App State', render: (el, theme) => , } ``` The factory approach becomes more valuable as you add `id`, `defaultOpen`, and need both a development and production variant of the same plugin.