` and `{ theme, devtoolsOpen }`.
5. **Rendering** - The container is a regular `
` element. Your plugin can render anything into it - DOM nodes, a framework component tree via portals, a canvas, an iframe, etc.
6. **Plugin prop changes** - When the theme or devtools open state changes, `render` is called again with the new props. Your plugin should update accordingly.
```mermaid
flowchart TD
A["User opens devtools"] --> B["TanStackDevtoolsCore created
with plugins array"]
B --> C["Solid UI renders sidebar
tabs and containers"]
C --> D["<h3 id='plugin-title-container-{id}'>
created per plugin"]
C --> E["<div id='plugin-container-{id}'>
created per active plugin"]
D --> F["plugin.name
string set or function called"]
E --> G["plugin.render(div, props)
called with container + plugin props"]
```
## Framework Adapter Pattern
You rarely interact with the raw `TanStackDevtoolsPlugin` interface directly. Instead, each framework adapter converts your familiar component model into the DOM-based plugin contract.
### React / Preact
The React adapter takes your JSX element and uses `createPortal` to render it into the plugin's container element:
```tsx
// What you write:
,
}]}
/>
// What happens internally:
// The adapter's render function calls:
// createPortal(
, containerElement)
```
Your React component runs in its normal React tree with full access to hooks, context, state management, etc. It just renders into a different DOM location via the portal.
### Solid
The Solid adapter uses Solid's `
` component to mount your JSX into the container:
```tsx
// What you write:
,
}]}
/>
// What happens internally:
// The adapter wraps your component in:
// {yourComponent}
```
Since the devtools core is itself built in Solid, this is the most native integration. Your component runs inside the same Solid reactive system as the devtools shell.
### Vue
The Vue adapter uses `` to render your Vue component into the container:
```vue
```
Your Vue component receives the `theme` as a prop along with any other props you pass. It runs within the Vue app's reactivity system with full access to composition API, inject/provide, etc.
### Svelte
The Svelte adapter mounts the plugin component directly into the container and forwards the shared plugin props together with any plugin-specific props:
```svelte
```
Internally, the adapter mounts a Svelte host component into the plugin container with `{ theme, devtoolsOpen, ...plugin.props }`. Repeated renders update that host's component and props, preserving the mounted plugin's state when its component identity is unchanged. The host is unmounted when the plugin is destroyed or the adapter shuts down, which runs the component's Svelte cleanup lifecycle.
### The Key Insight
```mermaid
graph LR
subgraph fw["Your Framework Runtime"]
comp["Your Plugin Component
Full reactivity, hooks, signals"]
end
subgraph core["Devtools Core (Solid.js)"]
container["Plugin Container
<div id='plugin-container-{id}'>"]
end
comp -- "Framework rendering API" --> container
```
Regardless of framework, your plugin component runs in its **normal framework context** with full reactivity, hooks, signals, lifecycle methods, and dependency injection. The adapter renders it into a DOM location owned by the devtools core. This means:
- React plugins can use `useState`, `useEffect`, `useContext`, and any React library.
- Solid plugins can use signals, stores, `createEffect`, and the full Solid API.
- Vue plugins can use `ref`, `computed`, `watch`, `inject`, and any Vue composable.
- Svelte plugins can use runes, context, lifecycle hooks, and any Svelte library.
The framework adapter handles all the wiring between your component and the devtools container.
## Plugin State
The devtools persist the active/visible plugin selection in `localStorage` under the key `tanstack_devtools_state`. This means that when a user opens specific plugin tabs, their selection survives page reloads.
Key behaviors:
- **Maximum 3 visible plugins** - At most 3 plugin panels can be displayed simultaneously. Activating a 4th plugin deactivates the earliest one.
- **`defaultOpen` vs. saved state** - If `defaultOpen: true` is set on a plugin and no saved state exists in localStorage, the plugin opens on first load. Once the user changes the selection, their preference takes precedence.
- **Single-plugin auto-open** - If only one plugin is registered, it opens automatically regardless of `defaultOpen` or saved state.
- **Stale plugin cleanup** - When the devtools load, any plugin IDs in the saved state that no longer match a registered plugin are automatically removed.
## Cleanup
When `TanStackDevtoolsCore.unmount()` is called - either explicitly or because the framework component unmounts - the following happens:
1. The Solid reactive system disposes of the entire devtools component tree.
2. For each active plugin that provides a `destroy` function, `destroy(pluginId)` is called.
3. All DOM containers created by the core are removed.
Framework adapters handle their own cleanup automatically:
- **React** unmounts the portal, which triggers the normal React cleanup cycle (`useEffect` cleanup functions, etc.).
- **Vue** destroys the Teleport, which runs `onUnmounted` hooks in your component.
- **Solid** disposes of the Portal's reactive scope, running any `onCleanup` callbacks.
- **Svelte** calls `unmount()` for the mounted component, running its cleanup lifecycle.
You typically do **not** need to implement `destroy` unless your plugin has manual subscriptions, timers, WebSocket connections, or other resources that aren't tied to your framework's lifecycle. If all your cleanup is handled by framework hooks (like `useEffect` cleanup in React or `onCleanup` in Solid), the adapter takes care of it for you.