devtools-plugin-panel

Build devtools panel components that display emitted event data. Listen via EventClient.on(), handle theme (light/dark), use @tanstack/devtools-ui components. Plugin registration (name, render, id, defaultOpen), lifecycle (mount, activate, destroy), max 3 active plugins. Two paths: Solid.js core with devtools-ui for multi-framework support, or framework-specific panels.

443 stars

Best use case

devtools-plugin-panel is best used when you need a repeatable AI agent workflow instead of a one-off prompt.

Build devtools panel components that display emitted event data. Listen via EventClient.on(), handle theme (light/dark), use @tanstack/devtools-ui components. Plugin registration (name, render, id, defaultOpen), lifecycle (mount, activate, destroy), max 3 active plugins. Two paths: Solid.js core with devtools-ui for multi-framework support, or framework-specific panels.

Teams using devtools-plugin-panel should expect a more consistent output, faster repeated execution, less prompt rewriting.

When to use this skill

  • You want a reusable workflow that can be run more than once with consistent structure.

When not to use this skill

  • You only need a quick one-off answer and do not need a reusable workflow.
  • You cannot install or maintain the underlying files, dependencies, or repository context.

Installation

Claude Code / Cursor / Codex

$curl -o ~/.claude/skills/devtools-plugin-panel/SKILL.md --create-dirs "https://raw.githubusercontent.com/TanStack/devtools/main/packages/devtools/skills/devtools-plugin-panel/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/devtools-plugin-panel/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How devtools-plugin-panel Compares

Feature / Agentdevtools-plugin-panelStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

Build devtools panel components that display emitted event data. Listen via EventClient.on(), handle theme (light/dark), use @tanstack/devtools-ui components. Plugin registration (name, render, id, defaultOpen), lifecycle (mount, activate, destroy), max 3 active plugins. Two paths: Solid.js core with devtools-ui for multi-framework support, or framework-specific panels.

Where can I find the source code?

You can find the source code on GitHub using the link provided at the top of the page.

Related Guides

SKILL.md Source

## TanStackDevtoolsPlugin Interface

The low-level contract every plugin implements. Framework adapters wrap this automatically.

```ts
// Source: packages/devtools/src/context/devtools-context.tsx
interface TanStackDevtoolsPlugin {
  id?: string
  name: string | ((el: HTMLHeadingElement, theme: 'dark' | 'light') => void)
  render: (el: HTMLDivElement, theme: 'dark' | 'light') => void
  destroy?: (pluginId: string) => void
  defaultOpen?: boolean
}
```

- **`name`** (required) -- String tab title, or function receiving `(el, theme)` for custom rendering.
- **`render`** (required) -- Called on activation with a `<div>` container and theme. Called again on theme change.
- **`id`** (optional) -- Stable identifier. If omitted: `name.toLowerCase().replace(' ', '-')-{index}`. Explicit ids persist selection across reloads.
- **`defaultOpen`** (optional) -- Opens panel on first load when no saved state. Max 3 open. Does not override saved preferences.
- **`destroy`** (optional) -- Called on deactivation or unmount. Framework adapters handle cleanup automatically.

---

## Two Development Paths

### Path 1: Solid.js Core + Framework Adapters (Multi-Framework)

Build the panel in Solid.js using `@tanstack/devtools-ui` components. Use `constructCoreClass` for lazy loading, then `createReactPanel`/`createSolidPanel` to wrap for each framework. The devtools core is Solid, so Solid panels run natively.

### Path 2: Framework-Specific Panel (Single Framework)

Build directly in your framework and use `createReactPlugin`/`createVuePlugin`/`createSolidPlugin`/`createPreactPlugin` from `@tanstack/devtools-utils`.

---

## Path 1: Solid.js Core Panel

### Step 1: Define Event Map and Create EventClient

```ts
// src/event-client.ts
import { EventClient } from '@tanstack/devtools-event-client'

type StoreEvents = {
  'state-changed': { storeName: string; state: unknown; timestamp: number }
  'action-dispatched': { storeName: string; action: string; payload: unknown }
  reset: void
}

class StoreInspectorClient extends EventClient<StoreEvents> {
  constructor() {
    super({ pluginId: 'store-inspector' })
  }
}

export const storeInspector = new StoreInspectorClient()
```

Event names are suffixes only. The `pluginId` is prepended automatically: `'store-inspector:state-changed'`.

### Step 2: Build the Solid.js Panel Component

```tsx
/** @jsxImportSource solid-js */
import { createSignal, onCleanup, For } from 'solid-js'
import {
  MainPanel,
  Header,
  HeaderLogo,
  Section,
  SectionTitle,
  JsonTree,
  Button,
  Tag,
  useTheme,
} from '@tanstack/devtools-ui'
import { storeInspector } from './event-client'

export default function StoreInspectorPanel() {
  const { theme } = useTheme()
  const [state, setState] = createSignal<Record<string, unknown>>({})
  const [actions, setActions] = createSignal<
    Array<{ action: string; payload: unknown }>
  >([])

  const cleanupState = storeInspector.on('state-changed', (e) => {
    setState((prev) => ({ ...prev, [e.payload.storeName]: e.payload.state }))
  })
  const cleanupActions = storeInspector.on('action-dispatched', (e) => {
    setActions((prev) => [
      ...prev,
      { action: e.payload.action, payload: e.payload.payload },
    ])
  })

  onCleanup(() => {
    cleanupState()
    cleanupActions()
  })

  return (
    <MainPanel>
      <Header>
        <HeaderLogo flavor={{ light: '#1a1a2e', dark: '#e0e0e0' }}>
          Store Inspector
        </HeaderLogo>
      </Header>
      <Section>
        <SectionTitle>Current State</SectionTitle>
        <JsonTree value={state()} copyable defaultExpansionDepth={2} />
      </Section>
      <Section>
        <SectionTitle>
          Action Log
          <Tag color="purple" label="Actions" count={actions().length} />
        </SectionTitle>
        <For each={actions()}>
          {(a) => (
            <div>
              <strong>{a.action}</strong>
              <JsonTree value={a.payload} copyable defaultExpansionDepth={1} />
            </div>
          )}
        </For>
        <Button variant="danger" onClick={() => setActions([])}>
          Clear Log
        </Button>
      </Section>
    </MainPanel>
  )
}
```

### Step 3: Create Core Class and Framework Adapters

```ts
// src/core.ts
import { constructCoreClass } from '@tanstack/devtools-utils/solid/class'

export const [StoreInspectorCore, NoOpStoreInspectorCore] = constructCoreClass(
  () => import('./panel'),
)
```

```tsx
// src/react.tsx
import { createReactPanel } from '@tanstack/devtools-utils/react'
import { StoreInspectorCore } from './core'

export const [StoreInspectorPanel, NoOpStoreInspectorPanel] =
  createReactPanel(StoreInspectorCore)
```

```tsx
// src/react-plugin.tsx
import { createReactPlugin } from '@tanstack/devtools-utils/react'
import { StoreInspectorPanel } from './react'

export const [StoreInspectorPlugin, NoOpStoreInspectorPlugin] =
  createReactPlugin({
    name: 'Store Inspector',
    id: 'store-inspector',
    defaultOpen: true,
    Component: StoreInspectorPanel,
  })
```

### Step 4: Register

```tsx
import { TanStackDevtools } from '@tanstack/react-devtools'
import { StoreInspectorPlugin } from 'your-package/react-plugin'

function App() {
  return (
    <>
      <YourApp />
      <TanStackDevtools plugins={[StoreInspectorPlugin()]} />
    </>
  )
}
```

---

## Path 2: Framework-Specific Panel (React Example)

```tsx
import { useState, useEffect } from 'react'
import { EventClient } from '@tanstack/devtools-event-client'
import { createReactPlugin } from '@tanstack/devtools-utils/react'

type MyEvents = {
  'data-update': { items: Array<{ id: string; value: number }> }
}

class MyPluginClient extends EventClient<MyEvents> {
  constructor() {
    super({ pluginId: 'my-plugin' })
  }
}

export const myPlugin = new MyPluginClient()

function MyPluginPanel({ theme }: { theme?: 'light' | 'dark' }) {
  const [items, setItems] = useState<Array<{ id: string; value: number }>>([])

  useEffect(() => {
    const cleanup = myPlugin.on('data-update', (e) => {
      setItems(e.payload.items)
    })
    return cleanup
  }, [])

  return (
    <div style={{ color: theme === 'dark' ? '#fff' : '#000' }}>
      <h3>My Plugin</h3>
      <ul>
        {items.map((item) => (
          <li key={item.id}>
            {item.id}: {item.value}
          </li>
        ))}
      </ul>
    </div>
  )
}

export const [MyPlugin, NoOpMyPlugin] = createReactPlugin({
  name: 'My Plugin',
  id: 'my-plugin',
  defaultOpen: false,
  Component: MyPluginPanel,
})
```

---

## Plugin Lifecycle Sequence

1. **Initialization** -- `TanStackDevtoolsCore` receives `plugins` array. Each plugin gets an `id` (explicit or generated).
2. **DOM containers created** -- Core creates `<div id="plugin-container-{id}">` and `<h3 id="plugin-title-container-{id}">` per plugin.
3. **Activation** -- On tab click or `defaultOpen`, `plugin.render(container, theme)` called.
4. **Framework portaling** -- React uses `createPortal`, Solid uses `<Portal>`, Vue uses `<Teleport>`.
5. **Theme change** -- `render` called again with new theme value.
6. **Deactivation/Unmount** -- `destroy(pluginId)` called if provided. Framework adapters handle cleanup.

Active plugin selection persisted in `localStorage` under key `tanstack_devtools_state`.

---

## Common Mistakes

### CRITICAL: Not Cleaning Up Event Listeners

Each `on()` returns a cleanup function. Forgetting it causes memory leaks and duplicate handlers.

Wrong:

```ts
useEffect(() => {
  client.on('state', cb)
}, [])
```

Correct:

```ts
useEffect(() => {
  const cleanup = client.on('state', cb)
  return cleanup
}, [])
```

In Solid, use `onCleanup()`:

```ts
const cleanup = storeInspector.on('state-changed', handler)
onCleanup(cleanup)
```

Source: docs/building-custom-plugins.md

### HIGH: Oversubscribing to Events in Multiple Components

Do not call `on()` in multiple components for the same event. Subscribe once in a shared store/hook.

Wrong:

```ts
function ComponentA() {
  useEffect(() => {
    const c = client.on('state', cb1)
    return c
  }, [])
}
function ComponentB() {
  useEffect(() => {
    const c = client.on('state', cb2)
    return c
  }, [])
}
```

Correct:

```ts
function useStoreState() {
  const [state, setState] = useState(null)
  useEffect(() => {
    const cleanup = client.on('state', (e) => setState(e.payload))
    return cleanup
  }, [])
  return state
}
```

Source: maintainer interview

### MEDIUM: Hardcoding Repeated Event Payload Fields

When emitting events that share common fields, create a shared base object.

Wrong:

```ts
client.emit('state-changed', { storeName: 'main', version: '1.0', state })
client.emit('action-dispatched', { storeName: 'main', version: '1.0', action })
```

Correct:

```ts
const base = { storeName: 'main', version: '1.0' }
client.emit('state-changed', { ...base, state })
client.emit('action-dispatched', { ...base, action })
```

Source: maintainer interview

### MEDIUM: Ignoring Theme Prop in Panel Component

Panels must adapt styling to theme. Factory-created plugins receive `props.theme`.

Wrong:

```tsx
function MyPanel() {
  return <div style={{ color: 'white' }}>Always white text</div>
}
```

Correct:

```tsx
function MyPanel({ theme }: { theme?: 'light' | 'dark' }) {
  return (
    <div style={{ color: theme === 'dark' ? '#e0e0e0' : '#1a1a1a' }}>
      Theme-aware text
    </div>
  )
}
```

In Solid panels using devtools-ui, use `useTheme()` instead of prop drilling.

Source: docs/plugin-lifecycle.md

### MEDIUM: Not Knowing Max 3 Active Plugins Limit

`MAX_ACTIVE_PLUGINS = 3` (in `packages/devtools/src/utils/constants.ts`). If more than 3 set `defaultOpen: true`, only the first 3 open. Activating a 4th deactivates the earliest. Single-plugin exception: if only 1 plugin is registered, it opens automatically.

Source: packages/devtools/src/utils/get-default-active-plugins.ts

### MEDIUM: Using Raw DOM Manipulation Instead of Framework Portals

Framework adapters handle portaling. Do not manually manipulate DOM.

Wrong:

```ts
render: (el) => {
  const div = document.createElement('div')
  div.textContent = 'Hello'
  el.appendChild(div)
}
```

Correct:

```tsx
import { createReactPlugin } from '@tanstack/devtools-utils/react'
const [Plugin, NoOpPlugin] = createReactPlugin({
  name: 'My Plugin',
  Component: ({ theme }) => <div>Hello</div>,
})
```

Source: docs/plugin-lifecycle.md

### MEDIUM: Not Keeping Devtools Packages at Latest Versions

All `@tanstack/devtools-*` packages should be on compatible versions. For external plugins, pin to compatible ranges.

Source: maintainer interview

## References

- [devtools-ui components and API](references/panel-api.md)

Related Skills

devtools-instrumentation

443
from TanStack/devtools

Analyze library codebase for critical architecture and debugging points, add strategic event emissions. Identify middleware boundaries, state transitions, lifecycle hooks. Consolidate events (1 not 15), debounce high-frequency updates, DRY shared payload fields, guard emit() for production. Transparent server/client event bridging.

devtools-event-client

443
from TanStack/devtools

Create typed EventClient for a library. Define event maps with typed payloads, pluginId auto-prepend namespacing, emit()/on()/onAll()/onAllPluginEvents() API. Connection lifecycle (5 retries, 300ms), event queuing, enabled/disabled state, SSR fallbacks, singleton pattern. Unique pluginId requirement to avoid event collisions.

devtools-bidirectional

443
from TanStack/devtools

Two-way event patterns between devtools panel and application. App-to-devtools observation, devtools-to-app commands, time-travel debugging with snapshots and revert. structuredClone for snapshot safety, distinct event suffixes for observation vs commands, serializable payloads only.

devtools-production

443
from TanStack/devtools

Handle devtools in production vs development. removeDevtoolsOnBuild, devDependency vs regular dependency, conditional imports, NoOp plugin variants for tree-shaking, non-Vite production exclusion patterns.

devtools-marketplace

443
from TanStack/devtools

Publish plugin to npm and submit to TanStack Devtools Marketplace. PluginMetadata registry format, plugin-registry.ts, pluginImport (importName, type), requires (packageName, minVersion), framework tagging, multi-framework submissions, featured plugins.

devtools-app-setup

443
from TanStack/devtools

Install TanStack Devtools, pick framework adapter (React/Vue/Solid/Preact), register plugins via plugins prop, configure shell (position, hotkeys, theme, hideUntilHover, requireUrlFlag, eventBusConfig). TanStackDevtools component, defaultOpen, localStorage persistence.

devtools-vite-plugin

443
from TanStack/devtools

Configure @tanstack/devtools-vite for source inspection (data-tsd-source, inspectHotkey, ignore patterns), console piping (client-to-server, server-to-client, levels), enhanced logging, server event bus (port, host, HTTPS), production stripping (removeDevtoolsOnBuild), editor integration (launch-editor, custom editor.open). Must be FIRST plugin in Vite config. Vite ^6 || ^7 only.

devtools-framework-adapters

443
from TanStack/devtools

Use devtools-utils factory functions to create per-framework plugin adapters. createReactPlugin/createSolidPlugin/createVuePlugin/createPreactPlugin, createReactPanel/createSolidPanel/createVuePanel/createPreactPanel. [Plugin, NoOpPlugin] tuple for tree-shaking. DevtoolsPanelProps (theme). Vue uses (name, component) not options object. Solid render must be function.

laravel-plugin-discovery

144923
from affaan-m/everything-claude-code

Discover and evaluate Laravel packages via LaraPlugins.io MCP. Use when the user wants to find plugins, check package health, or assess Laravel/PHP compatibility.

DevelopmentClaude

mixpanel-automation

31392
from sickn33/antigravity-awesome-skills

Automate Mixpanel tasks via Rube MCP (Composio): events, segmentation, funnels, cohorts, user profiles, JQL queries. Always search tools first for current schemas.

Data AutomationClaude

Teleton Plugin Builder

13
from TONresistor/teleton-plugins

This skill guides an AI agent through the process of building custom plugins for Teleton, a Telegram AI agent operating on the TON blockchain.

Coding & Development

claude-code-plugin-release

46120
from thedotmack/claude-mem

Automated semantic versioning and release workflow for Claude Code plugins. Handles version increments across package.json, marketplace.json, and plugin.json, build verification, git tagging, GitHub releases, and changelog generation.