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.
Solutions
- Add a customLevels object, e.g. customLevels: { foo: 10, bar: 20 }.
- Or remove useOnlyCustomLevels if you want default levels merged with custom ones.
- 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
- Never set useOnlyCustomLevels without also defining customLevels
- Keep customLevels and useOnlyCustomLevels adjacent in the options object to avoid conditional omission
- Type the options object so useOnlyCustomLevels: true requires customLevels (discriminated union)
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
- Levels comparison should be one of "ASC", "DESC" or…
- unknown level
- unknown level value
- only one of option.transport or stream can be specified
- option.transport do not allow stream, please pass to option…
AI-assisted analysis of pinojs/pino@5aa62305c5 (2026-09-02).
Data as JSON: /api/errors/11840f9f4751fa14.
Report an issue: GitHub.
Appendix: 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 = falseView on GitHub (pinned to 5aa62305c5)