---
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.