Skip to main content

writing-vite-devtools-integrations

Creates devtools integrations that mount inside the Vite DevTools hub via @vitejs/devtools-kit. Use when building Vite plugins with devtools panels, RPC functions, dock entries, shared state, messages/notifications, terminals, command palette entries, or any hub-level integration. Applies to files importing from @vitejs/devtools-kit or containing devtools.setup hooks in Vite plugins. For building one portable devtool integration without a hub (CLI, static deploy, MCP), see the `devframe` skill instead.

Source facts

Repository
vitejs/devtools
Last source activity
October 7, 2026 at 06:12
Detected SKILL.md language
English
Stars
1,203
Forks
92

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

File Explorer
13 files

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
writing-vite-devtools-integrations
description
Creates devtools integrations that mount inside the Vite DevTools hub via @vitejs/devtools-kit. Use when building Vite plugins with devtools panels, RPC functions, dock entries, shared state, messages/notifications, terminals, command palette entries, or any hub-level integration. Applies to files importing from @vitejs/devtools-kit or containing devtools.setup hooks in Vite plugins. For building one portable devtool integration without a hub (CLI, static deploy, MCP), see the `devframe` skill instead.
# Vite DevTools Kit **`@vitejs/devtools-kit` is the hub that unites many devtools integrations.** It owns the cross-tool surface — docking, the command palette, terminal aggregation, cross-tool toasts — and wraps the framework-neutral [Devframe](https://devfra.me/) container with the Vite-specific glue (`Plugin.devtools.setup`). If you have a portable Devframe app already, drop it in via `createPluginFromDevframe(d)` from `@vitejs/devtools-kit/node` and the kit auto-derives the iframe dock entry. If you're authoring a Vite-specific integration that needs hub features directly, reach for `Plugin.devtools.setup`. ## Core Concepts A DevTools plugin extends a Vite plugin with a `devtools.setup(ctx)` hook. The context is the **kit-augmented context** (`KitNodeContext` extended with Vite-specific fields) — it carries Devframe's portable surface plus the hub-only subsystems the kit owns: | Property | Layer | Purpose | |----------|-------|---------| | `ctx.docks` | **kit** | Register dock entries (iframe, action, custom-render, launcher, json-render) | | `ctx.terminals` | **kit** | Spawn and manage child processes with streaming terminal output | | `ctx.messages` | **kit** | Emit structured message entries and toast notifications | | `ctx.commands` | **kit** | Register executable commands with keyboard shortcuts and palette visibility | | `ctx.rpc` | devframe | Register RPC functions, broadcast to clients | | `ctx.rpc.sharedState` | devframe | Synchronized server-client state | | `ctx.rpc.streaming` | devframe | Streaming channels — chunk-style server↔client data with cancellation, replay, Web Streams interop | | `ctx.views` | devframe | Host static files for UI (`hostStatic(base, distDir)`) | | `ctx.diagnostics` | devframe | Structured diagnostics host (nostics) — register custom error codes | | `ctx.createJsonRenderer` | core | Create server-side JSON render specs for zero-client-code UIs | | `ctx.viteConfig` | core | Resolved Vite configuration | | `ctx.viteServer` | core | Dev server instance (dev mode only) | | `ctx.mode` | devframe | `'dev'` or `'build'` | ## Quick Start: Bridge a Devframe App If you already have a portable Devframe definition, this is the one-liner. The kit synthesises the iframe dock entry from the definition's `id` / `name` / `icon` / `basePath`, mounts the SPA via `views.hostStatic`, runs the devtool's own `setup`, then runs the optional kit-only `options.setup`. ```ts // vite.config.ts import { createPluginFromDevframe } from '@vitejs/devtools-kit/node' import devtool from './my-devtool' export default { plugins: [ createPluginFromDevframe(devtool, { // Optional kit-only setup for hub features: setup(ctx) { ctx.commands.register({ id: 'my-devtool:clear-cache', title: 'Clear Cache', handler: () => {/* ... */}, }) }, }), ], } ``` ## Quick Start: Minimal Hub-Native Plugin When the integration is intrinsically tied to Vite (it inspects the resolved config, augments middleware, etc.), reach for `Plugin.devtools.setup` directly: ```ts /// <reference types="@vitejs/devtools-kit" /> import type { Plugin } from 'vite' export default function myPlugin(): Plugin { return { name: 'my-plugin', devtools: { setup(ctx) { ctx.docks.register({ id: 'my-plugin', title: 'My Plugin', icon: 'ph:puzzle-piece-duotone', type: 'iframe', url: 'https://example.com/devtools', }) }, }, } } ``` ## Quick Start: Full Integration ```ts /// <reference types="@vitejs/devtools-kit" /> import type { Plugin } from 'vite' import { fileURLToPath } from 'node:url' import { defineRpcFunction } from '@vitejs/devtools-kit' export default function myAnalyzer(): Plugin { const data = new Map<string, { size: number }>() return { name: 'my-analyzer', // Collect data in Vite hooks transform(code, id) { data.set(id, { size: code.length }) }, devtools: { setup(ctx) { // 1. Host static UI const clientPath = fileURLToPath( new URL('../dist/client', import.meta.url) ) ctx.views.hostStatic('/__my-analyzer/', clientPath) // 2. Register dock entry ctx.docks.register({ id: 'my-analyzer', title: 'Analyzer', icon: 'ph:chart-bar-duotone', type: 'iframe', url: '/__my-analyzer/', }) // 3. Register RPC function ctx.rpc.register( defineRpcFunction({ name: 'my-analyzer:get-data', type: 'query', setup: () => ({ handler: async () => Array.from(data.entries()), }), }) ) }, }, } } ``` ## Namespacing Convention **CRITICAL**: Always prefix RPC functions, shared state keys, dock IDs, and command IDs with your plugin name: ```ts // Good - namespaced 'my-plugin:get-modules' 'my-plugin:state' 'my-plugin:clear-cache' // command ID // Bad - may conflict 'get-modules' 'state' ``` ## Dock Entry Types | Type | Use Case | |------|----------| | `iframe` | Full UI panels, dashboards (most common) | | `json-render` | Server-side JSON specs — zero client code needed | | `action` | Buttons that trigger client-side scripts (inspectors, toggles) | | `custom-render` | Direct DOM access in panel (framework mounting) | | `launcher` | Actionable setup cards for initialization tasks | ### Iframe Entry ```ts ctx.docks.register({ id: 'my-plugin', title: 'My Plugin', icon: 'ph:house-duotone', type: 'iframe', url: '/__my-plugin/', }) ``` Iframes can also point at a **remote-hosted URL** that connects back via WebSocket, so you don't have to ship a SPA dist with your plugin: ```ts ctx.docks.register({ id: 'my-remote-tool', title: 'My Tool', icon: 'ph:cloud-duotone', type: 'iframe', url: 'https://example.com/devtools', remote: true, // or { transport: 'query', originLock: false } }) ``` On the hosted page, call `connectRemoteDevTools()` from `@vitejs/devtools-kit/client` to get a fully connected `DevToolsRpcClient`. Dev-mode only — auto-hidden in build mode. See [Remote Client Patterns](./references/remote-client-patterns.md). ### Action Entry ```ts ctx.docks.register({ id: 'my-inspector', title: 'Inspector', icon: 'ph:cursor-duotone', type: 'action', action: { importFrom: 'my-plugin/devtools-action', importName: 'default', }, }) ``` ### Custom Render Entry ```ts ctx.docks.register({ id: 'my-custom', title: 'Custom View', icon: 'ph:code-duotone', type: 'custom-render', renderer: { importFrom: 'my-plugin/devtools-renderer', importName: 'default', }, }) ``` ### JSON Render Entry Build UIs entirely from server-side TypeScript — no client code needed: ```ts const ui = ctx.createJsonRenderer({ root: 'root', elements: { root: { type: 'Stack', props: { direction: 'vertical', gap: 12 }, children: ['heading', 'info'], }, heading: { type: 'Text', props: { content: 'Hello from JSON!', variant: 'heading' }, }, info: { type: 'KeyValueTable', props: { entries: [ { key: 'Version', value: '1.0.0' }, { key: 'Status', value: 'Running' }, ], }, }, }, }) ctx.docks.register({ id: 'my-panel', title: 'My Panel', icon: 'ph:chart-bar-duotone', type: 'json-render', ui, }) ``` ### Launcher Entry ```ts const entry = ctx.docks.register({ id: 'my-setup', title: 'My Setup', icon: 'ph:rocket-launch-duotone', type: 'launcher', launcher: { title: 'Initialize My Plugin', description: 'Run initial setup before using the plugin', buttonStart: 'Start Setup', buttonLoading: 'Setting up...', onLaunch: async () => { // Run initialization logic }, }, }) ``` ## Terminals & Subprocesses Spawn and manage child processes with streaming terminal output: ```ts const session = await ctx.terminals.startChildProcess( { command: 'vite', args: ['build', '--watch'], cwd: process.cwd(), }, { id: 'my-plugin:build-watcher', title: 'Build Watcher', icon: 'ph:terminal-duotone', }, ) // Lifecycle controls await session.terminate() await session.restart() ``` A common pattern is combining with launcher docks — see [Terminals Patterns](./references/terminals-patterns.md). ## Commands & Command Palette Register executable commands discoverable via `Mod+K` palette: ```ts import { defineCommand } from '@vitejs/devtools-kit' ctx.commands.register(defineCommand({ id: 'my-plugin:clear-cache', title: 'Clear Build Cache', icon: 'ph:trash-duotone', keybindings: [{ key: 'Mod+Shift+C' }], when: 'clientType == embedded', handler: async () => { /* ... */ }, })) ``` Commands support sub-commands (two-level hierarchy), conditional visibility via `when` clauses, and user-customizable keyboard shortcuts. See [Commands Patterns](./references/commands-patterns.md) and [When Clauses](./references/when-clauses.md) for full details. ## Logs & Notifications Plugins can emit structured log entries from both server and client contexts. Logs appear in the built-in **Logs** panel and can optionally show as toast notifications. ### Fire-and-Forget ```ts // No await needed context.messages.add({ message: 'Plugin initialized', level: 'info', }) ``` ### With Handle ```ts const handle = await context.messages.add({ id: 'my-build', message: 'Building...', level: 'info', status: 'loading', }) // Update later await handle.update({ message: 'Build complete', level: 'success', status: 'idle', }) // Or dismiss await handle.dismiss() ``` ### Key Fields | Field | Type | Description | |-------|------|-------------| | `message` | `string` | Short title (required) | | `level` | `'info' \| 'warn' \| 'error' \| 'success' \| 'debug'` | Severity (required) | | `description` | `string` | Detailed description | | `notify` | `boolean` | Show as toast notification | | `filePosition` | `{ file, line?, column? }` | Source file location (clickable) | | `elementPosition` | `{ selector?, boundingBox?, description? }` | DOM element position | | `id` | `string` | Explicit id for deduplication | | `status` | `'loading' \| 'idle'` | Shows spinner when loading | | `category` | `string` | Grouping (e.g., `'a11y'`, `'lint'`) |
View on GitHub
This SKILL.md is very large, so SkillsMP previews the first section here. View on GitHub