On this page

This guide covers the changes you are most likely to hit when upgrading an application or library from Undici v7 to v8.

  • Make sure your runtime is Node.js >= 22.19.0.
  • If you have custom dispatchers, interceptors, or handlers, review the handler API changes before updating.
  • If you rely on HTTP/1.1-only behavior, plan to set allowH2: false explicitly.

Undici v8 requires Node.js >= 22.19.0.

If you are still on Node.js 20 or an older Node.js 22 release, upgrade Node.js first:

If that command prints a version lower than v22.19.0, upgrade Node.js before installing Undici v8.

Undici v8 uses the newer dispatcher handler API consistently.

If you implemented custom dispatchers, interceptors, or wrappers around dispatch(), update legacy callbacks such as onConnect, onHeaders, and onComplete to the newer callback names.

Undici 7 styleUndici 8 style
onConnect(abort, context)onRequestStart(controller, context)
onHeaders(statusCode, rawHeaders, resume, statusText)onResponseStart(controller, statusCode, headers, statusText)
onData(chunk)onResponseData(controller, chunk)
onComplete(trailers)onResponseEnd(controller, trailers)
onError(err)onResponseError(controller, err)
onUpgrade(statusCode, rawHeaders, socket)onRequestUpgrade(controller, statusCode, headers, socket)

Before:

client.dispatch(options, {
  onConnect (abort) {
    this.abort = abort
  },
  onHeaders (statusCode, headers, resume) {
    this.resume = resume
    return true
  },
  onData (chunk) {
    chunks.push(chunk)
    return true
  },
  onComplete (trailers) {
    console.log(trailers)
  },
  onError (err) {
    console.error(err)
  }
})

After:

client.dispatch(options, {
  onRequestStart (controller) {
    this.controller = controller
  },
  onResponseStart (controller, statusCode, headers, statusText) {
    console.log(statusCode, statusText, headers)
  },
  onResponseData (controller, chunk) {
    chunks.push(chunk)
  },
  onResponseEnd (controller, trailers) {
    console.log(trailers)
  },
  onResponseError (controller, err) {
    console.error(err)
  }
})

In Undici v7, legacy handlers could return false or keep references to abort() and resume() callbacks. In Undici v8, use the controller instead:

onRequestStart (controller) {
  this.controller = controller
}

onResponseData (controller, chunk) {
  controller.pause()
  setImmediate(() => controller.resume())
}

onResponseError (controller, err) {
  controller.abort(err)
}

If you need the raw header arrays, read them from the controller:

  • controller.rawHeaders
  • controller.rawTrailers

If you implemented onBodySent(), note that its signature changed.

Before, handlers received counters:

In Undici v8, handlers receive the actual chunk:

If you need a notification that the whole body has been sent, use onRequestSent():

onRequestSent () {
  console.log('request body fully sent')
}

Undici v8 enables HTTP/2 by default when a TLS server negotiates it via ALPN.

If your application depends on HTTP/1.1-specific behavior, set allowH2: false explicitly.

Before:

After, to keep HTTP/1.1 only:

const client = new Client('https://example.com', {
  allowH2: false
})

The same applies when you configure an Agent:

const agent = new Agent({
  allowH2: false
})

Undici v8 no longer accepts fake Blob-like values that only imitate Blob or File via properties such as Symbol.toStringTag.

If you were passing custom objects that looked like Blobs, replace them with actual Blob or File instances:

setGlobalDispatcher() and getGlobalDispatcher() remain the public APIs and should continue to be used.

Internally, Undici v8 stores its dispatcher under Symbol.for('undici.globalDispatcher.2') and mirrors a v1-compatible wrapper for legacy consumers such as Node.js built-in fetch.

If your code was reading or writing Symbol.for('undici.globalDispatcher.1') directly, migrate to the public APIs instead:

import { setGlobalDispatcher, getGlobalDispatcher, Agent } from 'undici'

setGlobalDispatcher(new Agent())
const dispatcher = getGlobalDispatcher()

If you must expose a dispatcher to legacy v1 handler consumers, wrap it with Dispatcher1Wrapper:

import { Agent, Dispatcher1Wrapper } from 'undici'

const legacyCompatibleDispatcher = new Dispatcher1Wrapper(new Agent())

After moving to Undici v8, it is worth checking these paths in your test suite:

  • requests that use a custom dispatcher
  • setGlobalDispatcher() behavior
  • any custom interceptor or retry handler
  • uploads that use Blob, File, or FormData
  • integrations that depend on HTTP/1.1-only behavior