Nostics
Guide

Defining diagnostics

Declare stable codes, typed params, docs links, sources, and causes.

Diagnostics work best when you treat them as a catalog. Keep the definitions close together, give each one a permanent code, and make the fix specific.

Shape

import { createConsoleReporter, defineDiagnostics } from 'nostics'

export const diagnostics = defineDiagnostics({
  docsBase: code => `https://nuxt.com/e/${code.replace('NUXT_', '').toLowerCase()}`,
  reporters: [createConsoleReporter()],
  codes: {
    NUXT_B2011: {
      why: (p: { src: string, mode: 'client' | 'server' }) => {
        const expected = p.mode === 'client' ? 'server' : 'client'
        return `Plugin "${p.src}" is ${expected}-only but was registered with mode "${p.mode}".`
      },
      fix: (p: { mode: 'client' | 'server' }) => {
        const expected = p.mode === 'client' ? 'server' : 'client'
        return `Rename the file or register it with mode "${expected}".`
      },
    },
  },
})

Each key becomes a callable handle:

diagnostics.NUXT_B2011({
  src: pluginSrc,
  mode: 'client',
})
throw diagnostics.NUXT_B2011({
  src: pluginSrc,
  mode: 'client',
})

Calling it creates a fresh Diagnostic, runs every reporter, and returns the instance.

Params

why and fix can be strings or functions. Function params are inferred and required at the call site.

export const diagnostics = defineDiagnostics({
  codes: {
    NUXT_B5001: {
      why: (p: { value: string, configPath: string }) =>
        `Invalid compatibilityDate "${p.value}" in ${p.configPath}.`,
      fix: (p: { example: string }) => `Use an ISO date like "${p.example}", or "latest".`,
    },
  },
})

// somewhere in config validation
diagnostics.NUXT_B5001({
  configPath: config.filepath,
  value: config.compatibilityDate,
  example: '2024-04-03',
})

Params from why and fix are combined. In the example above, value, configPath, and example are required.

Call-site fields

Two fields belong at the place where you report the diagnostic:

try {
  await loadConfig()
}
catch (cause) {
  throw diagnostics.NUXT_B2011({
    src: pluginSrc,
    mode: 'client',
    cause,
    sources: [
      pluginSrc + ':' + line + ':' + column, // ./path/to/file:10:5
    ],
  })
}

cause preserves the original error. sources points at user code when the JavaScript stack would point inside your library. The sources is especially important for build and config diagnostics, where the problem is often in user files but the error is thrown from your library, so the stack trace is not helpful. Reporters can use cause and sources to give better output.

Docs URLs

Use docsBase to create the docs URL for each code. A function gives you full control.

defineDiagnostics({
  docsBase: code => `https://nuxt.com/e/${code.replace('NUXT_', '').toLowerCase()}`,
  codes: {
    NUXT_B2011: { why: 'Plugin mode conflicts with file suffix.' },
  },
})

NUXT_B2011 gets https://nuxt.com/e/b2011 printed in the diagnostic.

Use a string when your URL structure is regular and the lowercased code can be appended.

defineDiagnostics({
  docsBase: 'https://nuxt.com/errors',
  codes: {
    NUXT_B2011: { why: 'Plugin mode conflicts with file suffix.' },
  },
})

NUXT_B2011 gets https://nuxt.com/errors/nuxt_b2011.

Set docs on a code to override the URL, or false to omit it.

defineDiagnostics({
  docsBase: 'https://nuxt.com/e',
  codes: {
    NUXT_B2011: {
      why: 'Plugin mode conflicts with file suffix.',
      docs: 'https://nuxt.com/docs/guide/directory-structure/plugins',
    },
    NUXT_I001: {
      why: 'Using default Nuxt configuration.',
      docs: false,
    },
  },
})

Codes

Pick a format and keep it stable:

<PREFIX>_<CATEGORY><NUMBER>

Examples:

NUXT_B2011
NUXT_R1001
NUXT_D0042
NUXT_C0100

Common category letters are C for config, R for runtime, B for build, D for deprecation, W for warning, and E for error. The exact letters matter less than keeping them consistent.

Once a code is published, do not rename it or reuse it for a different problem. This is crucial for users to find the right docs and for the production strip plugin to work correctly.

Split files

For small libraries, one src/diagnostics.ts file is enough. For larger ones, split by area and export the handles directly.

src/diagnostics/
  build.ts
  config.ts
  runtime.ts
src/diagnostics/config.ts
export const configDiagnostics = defineDiagnostics({
  docsBase: code => `https://nuxt.com/e/${code.replace('NUXT_', '').toLowerCase()}`,
  codes: {
    NUXT_C001: { why: 'Could not load Nuxt configuration.' },
  },
})

Direct exports are easier for the production strip plugin to track than factory wrappers or deep barrels.

Write good fixes

Good diagnostics say what happened and what to do next.

NUXT_B2011: {
  why: (p: { src: string, mode: 'client' | 'server' }) => {
    const expected = p.mode === 'client' ? 'server' : 'client'
    return `Plugin "${p.src}" is ${expected}-only but was registered with mode "${p.mode}".`
  },
  fix: (p: { mode: 'client' | 'server' }) => {
    const expected = p.mode === 'client' ? 'server' : 'client'
    return `Rename the file or register it with mode "${expected}".`
  },
}

Avoid fixes that only restate the problem or are too vague to be helpful.

NUXT_B2011: {
  why: 'Plugin mode is wrong.',
  fix: 'Fix the plugin.',
}
Copyright © 2026