pinojs/pino · error · Error

customLevels is required if useOnlyCustomLevels is set true

Error message

customLevels is required if useOnlyCustomLevels is set true

What it means

useOnlyCustomLevels: true tells Pino to recognize ONLY the levels defined in customLevels, discarding default levels. Without customLevels there would be zero levels, so Pino throws at logger creation.

Source

Thrown at pino.js:164

    [stringifySafeSym]: stringifySafe,
    [formattersSym]: allFormatters
  })

  let chindings = ''
  if (base !== null) {
    if (name === undefined) {
      chindings = coreChindings(base)
    } else {
      chindings = coreChindings(Object.assign({}, base, { name }))
    }
  }

  const time = (timestamp instanceof Function)
    ? timestamp
    : (timestamp ? epochTime : nullTime)
  const timeSliceIndex = time().indexOf(':') + 1

  if (useOnlyCustomLevels && !customLevels) throw Error('customLevels is required if useOnlyCustomLevels is set true')
  if (mixin && typeof mixin !== 'function') throw Error(`Unknown mixin type "${typeof mixin}" - expected "function"`)
  if (msgPrefix && typeof msgPrefix !== 'string') throw Error(`Unknown msgPrefix type "${typeof msgPrefix}" - expected "string"`)

  assertDefaultLevelFound(level, customLevels, useOnlyCustomLevels)
  const levels = mappings(customLevels, useOnlyCustomLevels)

  if (stream && stream[transportUsesMultistreamSym] === true) {
    let sampleLabel = typeof level === 'string' ? level : undefined
    if (!sampleLabel || levels[sampleLabel] === undefined) {
      sampleLabel = Object.keys(levels)[0]
    }
    const sampleNumber = levels[sampleLabel]
    let ok = false
    try {
      const formatted = formatters.level(sampleLabel, sampleNumber)
      ok = formatted && typeof formatted === 'object' && formatted.level === sampleNumber
    } catch {
      ok = false

View on GitHub (pinned to 5aa62305c5)

Solutions

  1. Add a customLevels object, e.g. customLevels: { foo: 10, bar: 20 }.
  2. Or remove useOnlyCustomLevels if you want default levels merged with custom ones.
  3. Verify the option spelling: customLevels (plural), defined as { name: number } with numbers > 0.

Example fix

// before
const logger = pino({ useOnlyCustomLevels: true })
// after
const logger = pino({ useOnlyCustomLevels: true, customLevels: { trace2: 15, debug2: 25 } })
Defensive patterns

Strategy: validation

Validate before calling

function validateCustomLevels(opts) {
  if (opts && opts.useOnlyCustomLevels && !(opts.customLevels && Object.keys(opts.customLevels).length > 0)) {
    throw new TypeError('useOnlyCustomLevels: true requires a non-empty customLevels map')
  }
  return true
}

Type guard

const hasCustomLevels = (opts) => Boolean(opts && opts.customLevels && typeof opts.customLevels === 'object' && Object.keys(opts.customLevels).length > 0)

Try / catch

try {
  const logger = pino(opts)
} catch (err) {
  if (err.message.startsWith('customLevels is required if useOnlyCustomLevels')) {
    logger = pino({ ...opts, customLevels: { info: 30, error: 50 } })
  } else throw err
}

Prevention

When it happens

Trigger: Calling pino({ useOnlyCustomLevels: true }) or pino({ level: 'mylevel', useOnlyCustomLevels: true }) without a customLevels map.

Common situations: Copy-pasting custom-level examples and enabling useOnlyCustomLevels before defining customLevels; enabling the flag in shared config where customLevels is conditionally provided; typos like customLevel (singular).

Understand the failure class

Background: "Must pass :limit option" / "Missing required option" — required option errors explained — this error's family across 41 libraries.

Related errors


AI-assisted analysis of pinojs/pino@5aa62305c5 (2026-09-02). Data as JSON: /api/errors/11840f9f4751fa14. Report an issue: GitHub.