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

  1. Pass an actual stream (e.g. fs.createWriteStream(path), process.stdout) either directly or as { stream, level }.
  2. Check that the object you pass has .write (function) or .stream before adding.
  3. If wrapping, set the entry's stream property: { stream: myStream, level: 'info' }.
  4. 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

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


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