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: falseexplicitly.
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:
node -vIf 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 style | Undici 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.rawHeaderscontroller.rawTrailers
If you implemented onBodySent(), note that its signature changed.
Before, handlers received counters:
onBodySent (chunkSize, totalBytesSent) {}In Undici v8, handlers receive the actual chunk:
onBodySent (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:
const client = new Client('https://example.com')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:
const body = new Blob(['hello'])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, orFormData - integrations that depend on HTTP/1.1-only behavior