---
title: "Utility Kit"
description: "Utility kit for easier DevTools integrations."
canonical_url: "https://nuxt.com/modules/devtools/utils-kit"
---
# Utility Kit

> Utility kit for easier DevTools integrations.

`@nuxt/devtools-kit` is the utility kit for DevTools integrations, similar to `@nuxt/kit`.

```bash
npm i @nuxt/devtools-kit
```

```ts
import { NUXT_DEVTOOLS_GROUP_ID, onDevtoolsReady } from '@nuxt/devtools-kit'
```

We recommend module authors to install `@nuxt/devtools-kit` as a dependency and `@nuxt/devtools` as a dev dependency.

## `@nuxt/devtools-kit`

### Joining the `Nuxt` Dock Group

Nuxt DevTools registers a public `Nuxt` dock group (`NUXT_DEVTOOLS_GROUP_ID`, id `nuxt`) on the
framework category, with `nuxt:devtools` as its default member. Module
authors join the group natively by pointing their own dock entries at it —
no special Nuxt API required:

```ts
import { NUXT_DEVTOOLS_GROUP_ID, onDevtoolsReady } from '@nuxt/devtools-kit'

onDevtoolsReady((ctx) => {
  ctx.docks.register({
    id: 'my-module',
    type: 'iframe',
    title: 'My Module',
    icon: 'i-ph-puzzle-piece',
    url: '/my-module/',
    groupId: NUXT_DEVTOOLS_GROUP_ID,
  })
})
```

### `addCustomTab()`

::warning
**Deprecated.** `addCustomTab()` is soft-deprecated (`NDT_DEP_0005`) in favour of
registering a dock entry on the Vite DevTools docks host
(`onDevtoolsReady((ctx) => ctx.docks.register(...))`). It still works as a shim. Note the docks
host does not cover `vnode` views. See the
[migration guide](https://nuxt.com/modules/devtools/module-migration#ndt_dep_0005).
::

A shorthand for calling the hook `devtools:customTabs`.

```ts
import { addCustomTab } from '@nuxt/devtools-kit'

addCustomTab(() => ({
  // unique identifier
  name: 'my-module',
  // title to display in the tab
  title: 'My Module',
  // any icon from Iconify, or a URL to an image
  icon: 'carbon:apps',
  // iframe view
  view: {
    type: 'iframe',
    src: '/url-to-your-module-view',
  },
}))
```

The iframe view supports the following options:

- `src`: URL of the iframe
- `persistent`: Whether to persist the iframe instance when switching tabs (default: `true`)
- `permissions`: Additional permissions to allow in the iframe (merged with default `clipboard-write` and `clipboard-read`)

```ts
const view: ModuleIframeView = {
  type: 'iframe',
  src: '/url-to-your-module-view',
  persistent: true,
  permissions: ['camera', 'microphone'],
}
```

### `refreshCustomTabs()`

::warning
**Deprecated.** `refreshCustomTabs()` is soft-deprecated (`NDT_DEP_0006`). Update
dock entries directly via the handle returned by
`ctx.docks.register(...)`. It still works as a shim. See the
[migration guide](https://nuxt.com/modules/devtools/module-migration#ndt_dep_0006).
::

A shorthand for call hook `devtools:customTabs:refresh`. It will refresh all custom tabs.

### Spawning terminals

**Use Vite DevTools' own terminals host** (`ctx.terminals`), reached through the
connected context from [`onDevtoolsReady()`](#ondevtoolsready). It spawns and
owns the process and surfaces it in the built-in **Terminals** dock — no
Nuxt-specific wrapper needed:

```ts
import { onDevtoolsReady } from '@nuxt/devtools-kit'

onDevtoolsReady((ctx) => {
  // Output-only child process
  ctx.terminals.startChildProcess(
    {
      command: 'npm',
      args: ['run', 'dev']
    },
    { id: 'my-module:dev', title: 'Dev Server', icon: 'logos-npm-icon' },
  )

  // Fully interactive PTY (powered by `zigpty`, with a graceful pipe fallback)
  ctx.terminals.startPtySession(
    { command: 'bash' },
    { id: 'my-module:shell', title: 'Shell', interactive: true },
  )
})
```

If your module needs to keep owning the process itself and merely stream output,
register a read-only session with `ctx.terminals.register({ id, title, status, stream })`.
See the [Vite DevTools Kit](https://github.com/vitejs/devtools) docs for the full
`ctx.terminals` API.

### `startSubprocess()`

::warning
**Deprecated — do not use in new code.** `startSubprocess()` is soft-deprecated
(`NDT_DEP_0004`) in favour of the Vite DevTools terminals host, used from the
[`onDevtoolsReady`](#ondevtoolsready) hook
(`onDevtoolsReady((ctx) => ctx.terminals.startChildProcess(...))` — see
[Spawning terminals](#spawning-terminals) above). It still works as a shim
(bridged onto `ctx.terminals`), so existing modules keep working, but it emits
the `NDT_DEP_0004` deprecation diagnostic and will be removed in a future major.
See the [migration guide](https://nuxt.com/modules/devtools/module-migration#ndt_dep_0004).
::

Legacy usage (module owns the process; output is surfaced as a read-only session
in the built-in **Terminals** dock):

```ts
import { startSubprocess } from '@nuxt/devtools-kit'

const subprocess = startSubprocess(
  {
    command: 'vite',
    args: ['build', '--watch']
  },
  { id: 'my-module:build', name: 'Build', icon: 'ph:terminal-duotone' },
)

subprocess.restart()
subprocess.terminate()
```

### `extendServerRpc()`

::warning
**Deprecated.** `extendServerRpc()` is soft-deprecated (`NDT_DEP_0003`) in favour
of the Vite DevTools RPC registration. It still works as a shim. See the
[migration guide](https://nuxt.com/modules/devtools/module-migration#ndt_dep_0003).
::

Extend the server RPC with your own methods.

```ts
import { extendServerRpc } from '@nuxt/devtools-kit'

const rpc = extendServerRpc('my-module', {
  async myMethod() {
    return 'hello'
  },
})
```

The forward path registers functions on the Vite DevTools RPC host from the
[`onDevtoolsReady()`](#ondevtoolsready) hook, scoped to your module's namespace:

```ts
import { onDevtoolsReady } from '@nuxt/devtools-kit'

export default defineNuxtModule({
  setup(_options, nuxt) {
    onDevtoolsReady((ctx) => {
      ctx.scope('my-module').rpc.register({
        name: 'my-method', // registered as `my-module:my-method`
        type: 'query',
        handler: async () => 'hello',
      })
    })
  },
})
```

Learn more about [Custom RPC functions](https://nuxt.com/modules/devtools/module-authors#custom-rpc-functions).

### `onDevtoolsReady()`

**The recommended entry point for DevTools integration.** The callback runs once
the Vite DevTools kit has connected and receives the connected
`ViteDevToolsNodeContext`, so the full devframe surface is guaranteed to be
available — no connect-safe buffering needed. Do all your DevTools integration here:

```ts
import { NUXT_DEVTOOLS_GROUP_ID, onDevtoolsReady } from '@nuxt/devtools-kit'

export default defineNuxtModule({
  setup(_options, nuxt) {
    onDevtoolsReady((ctx) => {
      // `ctx` is the connected ViteDevToolsNodeContext
      ctx.docks.register({
        id: 'my-module',
        title: 'My Module',
        icon: 'carbon:apps',
        type: 'iframe',
        url: '/__my-module__/',
        groupId: NUXT_DEVTOOLS_GROUP_ID,
      })

      ctx.scope('my-module').rpc.register({
        name: 'my-method', // registered as `my-module:my-method`
        type: 'query',
        handler: async () => 'hello',
      })

      ctx.messages.add({ message: 'My module is ready', level: 'info' })
    })
  },
})
```

It is backed by the `devtools:ready` Nuxt hook:

```ts
nuxt.hook('devtools:ready', (ctx) => {
  // ...
})
```

Inside `onDevtoolsReady()`, the `ctx` is the connected `ViteDevToolsNodeContext`,
which exposes the full devframe-native surface:

- `ctx.docks` — register dock entries (iframe / action / json-render / launcher / custom-render / group)
- `ctx.terminals` — spawn and manage child processes / PTY sessions
- `ctx.messages` — structured messages and toast notifications
- `ctx.commands` — command-palette entries
- `ctx.diagnostics` — structured, coded diagnostics (powered by [nostics](https://github.com/vercel-labs/nostics))
- `ctx.rpc` — the devframe RPC host (`register`/`invokeLocal`/`broadcast`/`sharedState`/`streaming`/…)
- `ctx.scope(namespace)` — the same RPC host with every name prefixed by `namespace:`

If you need the raw context outside the hook, `nuxt.devtools.devtoolsKit` is
available as an escape hatch (it is `undefined` until the kit connects).

See the [Vite DevTools Kit](https://github.com/vitejs/devtools) docs for the
full host API.

### Deprecation diagnostics

Nuxt DevTools emits coded deprecation diagnostics (`NDT_DEP_xxxx`) through
`nostics`. They print to the terminal and, once the Vite DevTools kit connects,
also surface in the DevTools diagnostics UI, each with a code, a fix, and a link
to the [migration guide](https://nuxt.com/modules/devtools/module-migration). See
[`extendServerRpc()`](https://nuxt.com/modules/devtools/module-migration#ndt_dep_0003) and
[`startSubprocess()`](https://nuxt.com/modules/devtools/module-migration#ndt_dep_0004).

### `devtools:notify` hook

Push a notification into the unified devframe **Messages** system — the same
system that powers the Vite DevTools **Messages** dock and its toast overlay.
The notification is forwarded to `ctx.messages`, so no direct kit access is
needed, and calls made before the kit connects are buffered and replayed once
it does:

```ts
// server-side (module setup)
nuxt.callHook('devtools:notify', {
  message: 'Something happened',
  level: 'error', // 'info' | 'warn' | 'error' | 'success' | 'debug' (default 'info')
  description: 'More details about what happened.',
  notify: true, // also show a transient toast
})
```

Omit `autoDelete` for a **persistent**, leveled entry that builds history in the
Messages dock; set `notify` + `autoDismiss` + `autoDelete` for an **ephemeral**,
toast-only notification. From the client, the same input is available on the
injected client as `client.devtools.notify(...)`.

## `@nuxt/devtools-kit/iframe-client`

To provide complex interactions for your module integrations, we recommend to host your own view and display it in devtools via iframe.

::warning
The helpers below depend on the `__NUXT_DEVTOOLS__` object that the Nuxt client injects into **legacy** custom tabs (`addCustomTab()`). A dock entry registered with `ctx.docks.register()` is rendered by Vite DevTools itself and does not receive it. In a native dock, connect to Vite DevTools directly and read the host client from the parent window:

```ts
import type { NuxtDevtoolsHostClient } from '@nuxt/devtools-kit/types'
import { getDevToolsRpcClient } from '@vitejs/devtools-kit/client'

const kit = await getDevToolsRpcClient()
const { rpc } = kit.scope('my-module')

// the user's app is the parent window of every dock iframe (same origin only)
const host = (window.parent as Window & { __NUXT_DEVTOOLS_HOST__?: NuxtDevtoolsHostClient }).__NUXT_DEVTOOLS_HOST__
```

See [Custom RPC Functions](https://nuxt.com/modules/devtools/module-authors#custom-rpc-functions) for the full flow.
::

To get the information from the devtools and the client app in a legacy custom tab, you can do this in your client app:

```ts
import { useDevtoolsClient } from '@nuxt/devtools-kit/iframe-client'

export const devtoolsClient = useDevtoolsClient()
```

When the iframe been served with the same origin (CORS limitation), devtools will automatically inject `__NUXT_DEVTOOLS__` to the iframe's window object. You can access it as a ref using `useDevtoolsClient()` utility.

### `onDevtoolsReady()`

The client-side mirror of the server's [`onDevtoolsReady`](#ondevtoolsready). The
callback runs once the Vite DevTools client is connected and receives the
`DevToolsRpcClient` (the same object as `client.devtools.devtoolsKit`), giving
full devframe-native access. This is the recommended way to register client RPC
functions:

```ts
import { onDevtoolsReady } from '@nuxt/devtools-kit/iframe-client'

onDevtoolsReady((kit) => {
  kit.client.register({
    name: 'my-module:on-update',
    type: 'event',
    handler: (payload) => {
      // ...
    },
  })
})
```

### `useDevtoolsClient()`

It will return a ref of `NuxtDevtoolsIframeClient` object that is initially `undefined` and will be updated when the connection is ready.

`NuxtDevtoolsIframeClient` contains two properties:

- `host`: APIs to communicate with the main app in browser
- `devtools`: APIs to communicate with the devtools

`host` can be undefined when devtools are accessed standalone or from a different origin.

For example, you can get the router instance from the client app:

```ts
const router = computed(() => devtoolsClient.value?.host?.nuxt.vueApp.config.globalProperties?.$router)
```

The `devtools` object also exposes `devtoolsKit` — the connected Vite DevTools
RPC client (mirroring `nuxt.devtools.devtoolsKit` on the server) — for full
devframe-native access. Use it to register client RPC functions, call server
functions, and more:

```ts
const kit = devtoolsClient.value?.devtools.devtoolsKit
kit?.client.register({
  name: 'my-module:on-update',
  type: 'event',
  handler: (payload) => {
    // ...
  },
})
```

This replaces the deprecated `devtools.extendClientRpc()`.

### `onDevtoolsClientConnected()`

Similiar to `useDevtoolsClient()` but as a callback style:

```ts
import { onDevtoolsClientConnected } from '@nuxt/devtools-kit/iframe-client'

onDevtoolsClientConnected(async (client) => {
  // client is NuxtDevtoolsIframeClient

  const config = client.devtools.rpc.getServerConfig()
  // ...
})
```

## `@nuxt/devtools-kit/host-client`

When you have iframe for your devtools view, sometimes you need to communicate with the devtools host (the main app in browser) with a runtime plugin. You can use `@nuxt/devtools-kit/host-client` to do that.

### `useDevtoolsHostClient()`

It will return a ref of `NuxtDevtoolsHostClient` object that is initially `undefined` and will be updated when the host is initialized by NuxtDevtools.

```ts
import { useDevtoolsHostClient } from '@nuxt/devtools-kit/host-client'

export default defineNuxtPlugin({
  name: 'my-module:devtools',
  setup(nuxtApp) {
    const devtoolsHost = useDevtoolsHostClient()

    // ...
  }
})
```

### `onDevtoolsHostClientConnected()`

Similiar to `useDevtoolsHostClient()` but as a callback style:

```ts
import { onDevtoolsHostClientConnected } from '@nuxt/devtools-kit/host-client'

onDevtoolsHostClientConnected(async (host) => {

})
```


## Sitemap

See the full [sitemap](https://nuxt.com/sitemap.md) for all pages.
