pinojs/pino · error · Error
stream object needs to implement either StreamEntry or Desti
Error message
stream object needs to implement either StreamEntry or DestinationStream interface
What it means
multistream's add() requires each destination to be either a DestinationStream (has a write function) or a StreamEntry (has a .stream property). The check exists so the failure is reported here with a clear message instead of an obscure crash later inside write().
Source
Thrown at lib/multistream.js:116
function flushSync () {
for (const { stream } of this.streams) {
if (typeof stream.flushSync === 'function') {
stream.flushSync()
}
}
}
function add (dest) {
if (!dest) {
return this
}
// Check that dest implements either StreamEntry or DestinationStream
const isStream = typeof dest.write === 'function' || dest.stream
const stream_ = dest.write ? dest : dest.stream
// This is necessary to provide a meaningful error message, otherwise it throws somewhere inside write()
if (!isStream) {
throw Error('stream object needs to implement either StreamEntry or DestinationStream interface')
}
const { streams, streamLevels } = this
let level
if (typeof dest.levelVal === 'number') {
level = dest.levelVal
} else if (typeof dest.level === 'string') {
level = streamLevels[dest.level]
} else if (typeof dest.level === 'number') {
level = dest.level
} else {
level = DEFAULT_INFO_LEVEL
}
const dest_ = {
stream: stream_,
level,View on GitHub (pinned to 5aa62305c5)
Solutions
- Pass an actual stream (e.g. fs.createWriteStream(path), process.stdout) either directly or as { stream, level }.
- Check that the object you pass has .write (function) or .stream before adding.
- If wrapping, set the entry's stream property: { stream: myStream, level: 'info' }.
- Ensure you are not passing a string path — open the stream first with fs.createWriteStream or pino.destination.
Example fix
// before
pino(pino.multistream([{ level: 'info' }])) // missing stream
// after
const dest = pino.destination({ dest: './app.log', sync: false })
pino(pino.multistream([{ level: 'info', stream: dest }])) Defensive patterns
Strategy: type-guard
Validate before calling
function isUsableStream(s) {
return !!s && (typeof s.write === 'function' || (typeof s.stream === 'object' && s.stream !== null))
}
const entries = candidates.map(c => isUsableStream(c) ? c : { stream: c, level: 'info' }) Type guard
function isStreamEntryOrDestination(dest) {
return dest != null && (
typeof dest.write === 'function' ||
(typeof dest === 'object' && dest.stream != null && typeof dest.stream.write === 'function')
)
} Try / catch
try {
multi.add(dest)
} catch (e) {
if (e.message.includes('StreamEntry or DestinationStream')) {
throw new TypeError('multistream destination must have .write or .stream: got ' + typeof dest, { cause: e })
}
throw e
} Prevention
- Always build destinations with pino.destination or fs.createWriteStream, never raw path strings.
- For leveled entries use the { stream, level } shape with stream set to a real stream.
- Validate each multistream entry with a type guard at config load time.
- Prefer passing process.stdout directly rather than wrapping it in bare config objects.
When it happens
Trigger: pino.multistream([{ level: 'info' }]) passing an object that lacks both write and stream; add({ level: 'debug' }) with a plain config object; passing null/undefined-ish wrappers or a stream created incorrectly.
Common situations: Wrapping streams in an options-like object but forgetting the stream key; passing a filename string instead of a created stream; upgrading pino and switching from a bare stream to { level, stream } entries incorrectly.
Related errors
- Levels comparison should be one of "ASC", "DESC" or "functio
- unknown level ${level}
- default level:${defaultLevel} must be included in custom lev
- levels cannot be overridden
- pre-existing level values cannot be used for new levels
AI-assisted analysis of pinojs/pino@5aa62305c5 (2026-09-02).
Data as JSON: /api/errors/1ccac2530683d7a8.
Report an issue: GitHub.