writing-vite-devtools-integrations

Build custom developer tools that integrate with Vite DevTools using @vitejs/devtools-kit .

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "writing-vite-devtools-integrations" with this command: npx skills add vitejs/devtools/vitejs-devtools-writing-vite-devtools-integrations

Vite DevTools Kit

Build custom developer tools that integrate with Vite DevTools using @vitejs/devtools-kit .

Core Concepts

A DevTools plugin extends a Vite plugin with a devtools.setup(ctx) hook. The context provides:

Property Purpose

ctx.docks

Register dock entries (iframe, action, custom-render, launcher)

ctx.views

Host static files for UI

ctx.rpc

Register RPC functions, broadcast to clients

ctx.rpc.sharedState

Synchronized server-client state

ctx.logs

Emit structured log entries and toast notifications

ctx.viteConfig

Resolved Vite configuration

ctx.viteServer

Dev server instance (dev mode only)

ctx.mode

'dev' or 'build'

Quick Start: Minimal Plugin

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

/// <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, and dock IDs with your plugin name:

// Good - namespaced 'my-plugin:get-modules' 'my-plugin:state'

// Bad - may conflict 'get-modules' 'state'

Dock Entry Types

Type Use Case

iframe

Full UI panels, dashboards (most common)

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

ctx.docks.register({ id: 'my-plugin', title: 'My Plugin', icon: 'ph:house-duotone', type: 'iframe', url: '/.my-plugin/', })

Action Entry

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

ctx.docks.register({ id: 'my-custom', title: 'Custom View', icon: 'ph:code-duotone', type: 'custom-render', renderer: { importFrom: 'my-plugin/devtools-renderer', importName: 'default', }, })

Launcher Entry

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 }, }, })

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

// No await needed context.logs.add({ message: 'Plugin initialized', level: 'info', })

With Handle

const handle = await context.logs.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' )

labels

string[]

Tags for filtering

autoDismiss

number

Toast auto-dismiss time in ms (default: 5000)

autoDelete

number

Auto-delete time in ms

The from field is automatically set to 'server' or 'browser' .

Deduplication

Re-adding with the same id updates the existing entry instead of creating a duplicate:

context.logs.add({ id: 'my-scan', message: 'Scanning...', level: 'info', status: 'loading' }) context.logs.add({ id: 'my-scan', message: 'Scan complete', level: 'success', status: 'idle' })

RPC Functions

Server-Side Definition

import { defineRpcFunction } from '@vitejs/devtools-kit'

const getModules = defineRpcFunction({ name: 'my-plugin:get-modules', type: 'query', // 'query' | 'action' | 'static' setup: ctx => ({ handler: async (filter?: string) => { // ctx has full DevToolsNodeContext return modules.filter(m => !filter || m.includes(filter)) }, }), })

// Register in setup ctx.rpc.register(getModules)

Client-Side Call (iframe)

import { getDevToolsRpcClient } from '@vitejs/devtools-kit/client'

const rpc = await getDevToolsRpcClient() const modules = await rpc.call('my-plugin:get-modules', 'src/')

Client-Side Call (action/renderer script)

import type { DevToolsClientScriptContext } from '@vitejs/devtools-kit/client'

export default function setup(ctx: DevToolsClientScriptContext) { ctx.current.events.on('entry:activated', async () => { const data = await ctx.current.rpc.call('my-plugin:get-data') }) }

Broadcasting to Clients

// Server broadcasts to all clients ctx.rpc.broadcast({ method: 'my-plugin:on-update', args: [{ changedFile: '/src/main.ts' }], })

Type Safety

Extend the DevTools Kit interfaces for full type checking:

// src/types.ts import '@vitejs/devtools-kit'

declare module '@vitejs/devtools-kit' { interface DevToolsRpcServerFunctions { 'my-plugin:get-modules': (filter?: string) => Promise<Module[]> }

interface DevToolsRpcClientFunctions { 'my-plugin:on-update': (data: { changedFile: string }) => void }

interface DevToolsRpcSharedStates { 'my-plugin:state': MyPluginState } }

Shared State

Server-Side

const state = await ctx.rpc.sharedState.get('my-plugin:state', { initialValue: { count: 0, items: [] }, })

// Read console.log(state.value())

// Mutate (auto-syncs to clients) state.mutate((draft) => { draft.count += 1 draft.items.push('new item') })

Client-Side

const client = await getDevToolsRpcClient() const state = await client.rpc.sharedState.get('my-plugin:state')

// Read console.log(state.value())

// Subscribe to changes state.on('updated', (newState) => { console.log('State updated:', newState) })

Client Scripts

For action buttons and custom renderers:

// src/devtools-action.ts import type { DevToolsClientScriptContext } from '@vitejs/devtools-kit/client'

export default function setup(ctx: DevToolsClientScriptContext) { ctx.current.events.on('entry:activated', () => { console.log('Action activated') // Your inspector/tool logic here })

ctx.current.events.on('entry:deactivated', () => { console.log('Action deactivated') // Cleanup }) }

Export from package.json:

{ "exports": { ".": "./dist/index.mjs", "./devtools-action": "./dist/devtools-action.mjs" } }

Debugging with Self-Inspect

Use @vitejs/devtools-self-inspect to debug your DevTools plugin. It shows registered RPC functions, dock entries, client scripts, and plugins in a meta-introspection UI at /.devtools-self-inspect/ .

import DevTools from '@vitejs/devtools' import DevToolsSelfInspect from '@vitejs/devtools-self-inspect'

export default defineConfig({ plugins: [ DevTools(), DevToolsSelfInspect(), ], })

Best Practices

  • Always namespace - Prefix all identifiers with your plugin name

  • Use type augmentation - Extend DevToolsRpcServerFunctions for type-safe RPC

  • Keep state serializable - No functions or circular references in shared state

  • Batch mutations - Use single mutate() call for multiple changes

  • Host static files - Use ctx.views.hostStatic() for your UI assets

  • Use Iconify icons - Prefer ph:* (Phosphor) icons: icon: 'ph:chart-bar-duotone'

  • Deduplicate logs - Use explicit id for logs representing ongoing operations

  • Use Self-Inspect - Add @vitejs/devtools-self-inspect during development to debug your plugin

Example Plugins

Real-world example plugins in the repo — reference their code structure and patterns when building new integrations:

  • A11y Checker (examples/plugin-a11y-checker ) — Action dock entry, client-side axe-core audits, logs with severity levels and element positions, log handle updates

  • File Explorer (examples/plugin-file-explorer ) — Iframe dock entry, RPC functions (static/query/action), hosted UI panel, RPC dump for static builds, backend mode detection

Further Reading

  • RPC Patterns - Advanced RPC patterns and type utilities

  • Dock Entry Types - Detailed dock configuration options

  • Shared State Patterns - Framework integration examples

  • Project Structure - Recommended file organization

  • Logs Patterns - Log entries, toast notifications, and handle patterns

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

devtools

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

commit

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

base

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

unit-testing

No summary provided by upstream source.

Repository SourceNeeds Review