Nostics
Guide

Reporters and formatters

Send diagnostics to the console, files, HTTP, or your own reporter.

A reporter runs every time a diagnostic is created. A formatter turns a diagnostic into text.

Reporters

import { createConsoleReporter, defineDiagnostics } from 'nostics'

export const diagnostics = defineDiagnostics({
  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}".`
      },
    },
  },
})

Reporters run in order. They receive the Diagnostic and optional reporter options.

import type { DiagnosticReporter } from 'nostics'

const sentryReporter: DiagnosticReporter = (diagnostic) => {
  Sentry.captureMessage(diagnostic.message, {
    tags: { code: diagnostic.name },
    extra: {
      fix: diagnostic.fix,
      docs: diagnostic.docs,
      sources: diagnostic.sources,
    },
  })
}

Built-ins:

ReporterImportUse
createConsoleReporter(options?)nosticsConsole reporter; defaults to console.warn + formatDiagnostic
createFileReporter()nostics/reporters/nodeAppend NDJSON to a local file
createFetchReporter(url)nostics/reporters/fetchPOST JSON to a URL
createDevReporter()nostics/reporters/devSend browser diagnostics to Vite dev server

Console reporter

createConsoleReporter() uses console.warn by default. Pass a method when you call the diagnostic if you need a different console method.

const plugin = resolvePlugin()

diagnostics.NUXT_B2011({
  src: plugin.src,
  mode: plugin.mode,
}, { method: 'error' })

If you always want the same method, bake it into the reporter:

const reporterAlwaysError = createConsoleReporter({ method: 'error' })

File reporter

Use the Node reporter when your code runs in Node and you want a local log.

import { createFileReporter } from 'nostics/reporters/node'

export const diagnostics = defineDiagnostics({
  reporters: [
    createFileReporter({
      logFile: '.nuxt-diagnostics.log',
      excludeStackFrames: [/\/node_modules\//, /\(node:/],
    }),
  ],
  codes: {
    NUXT_B2011: { why: 'Plugin mode conflicts with file suffix.' },
  },
})

Each diagnostic is written as one JSON line.

tail -f .nuxt-diagnostics.log | jq .

Fetch reporter

Use the fetch reporter for fire-and-forget HTTP logging.

import { createFetchReporter } from 'nostics/reporters/fetch'

export const diagnostics = defineDiagnostics({
  reporters: [createFetchReporter('https://telemetry.example.com/diagnostics')],
  codes: {
    NUXT_B2011: { why: 'Plugin mode conflicts with file suffix.' },
  },
})

Fetch failures are swallowed so reporting does not break user code.

Reporter options

Reporter options are typed and merged into the diagnostic call signature.

const audited: DiagnosticReporter<{ priority: number }> = (diagnostic, options) => {
  audit.record({
    code: diagnostic.name,
    priority: options.priority,
  })
}

const diagnostics = defineDiagnostics({
  reporters: [audited],
  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}".`
      },
    },
  },
})

const plugin = resolvePlugin()

diagnostics.NUXT_B2011(
  {
    src: plugin.src,
    mode: plugin.mode,
  },
    // the second argument is required now
  { priority: 1 },
)

Use required reporter options sparingly. They make every call site pass the second argument.

Formatters

The default formatter is formatDiagnostic.

import { formatDiagnostic } from 'nostics'

console.log(formatDiagnostic(diagnostic))
[NUXT_B2011] Plugin `./runtime/analytics.server.ts` is server-only but was registered with mode `client`.
├▶ fix: Rename the file or register it with mode `server`.
├▶ sources: modules/analytics.ts:18:5
╰▶ see: https://nuxt.com/e/b2011

Detail lines are rendered in this order: fix, sources, see.

Other formatters:

FormatterImportOutput
formatDiagnosticnosticsPlain multiline text
ansiFormatter(colors)nostics/formatters/ansiColored multiline text
jsonFormatternostics/formatters/jsonOne JSON string

ansiFormatter accepts any color helper with this shape:

interface Colors {
  red: (s: string) => string
  yellow: (s: string) => string
  cyan: (s: string) => string
  gray: (s: string) => string
  bold: (s: string) => string
  dim: (s: string) => string
}

You can also write a formatter as a plain function:

import type { Diagnostic } from 'nostics'

function compact(diagnostic: Diagnostic): string {
  return `${diagnostic.name}: ${diagnostic.message}`
}
Copyright © 2026