On this page

Node.js has shipped a built-in fetch() implementation powered by undici since Node.js v18. This guide explains the relationship between the undici npm package and the built-in fetch, and when you should install one versus relying on the other.

The fetch(), Request, Response, Headers, and FormData globals in Node.js v18+ are provided by a version of undici that is bundled into Node.js itself. You can check which version is bundled with:

When you install undici from npm, you get the full library with all of its additional APIs, and potentially a newer release than what your Node.js version bundles.

When you send a FormData body, keep fetch and FormData together from the same implementation.

Use one of these patterns:

const body = new FormData()
body.set('name', 'some')
body.set('someOtherProperty', '8000')

await fetch('https://example.com', {
  method: 'POST',
  body
})
import { fetch, FormData } from 'undici'

const body = new FormData()
body.set('name', 'some')
body.set('someOtherProperty', '8000')

await fetch('https://example.com', {
  method: 'POST',
  body
})

If you want the installed undici package to provide the globals, call install():

import { install } from 'undici'

install()

const body = new FormData()
body.set('name', 'some')
body.set('someOtherProperty', '8000')

await fetch('https://example.com', {
  method: 'POST',
  body
})

install() replaces the global fetch, Headers, Response, Request, and FormData implementations with undici's versions, and also installs undici's WebSocket, CloseEvent, ErrorEvent, MessageEvent, and EventSource globals.

Avoid mixing implementations in the same request, for example:

import { fetch } from 'undici'

const body = new FormData()

await fetch('https://example.com', {
  method: 'POST',
  body
})

Those combinations may behave differently across Node.js and undici versions. Using matching pairs keeps multipart handling predictable.

If all of the following are true, you can rely on the built-in globals and skip adding undici to your dependencies:

  • You only need the standard Fetch API (fetch, Request, Response, Headers, FormData).
  • You are running Node.js v18 or later.
  • You do not depend on features or bug fixes introduced in a version of undici newer than the one bundled with your Node.js release.
  • You want zero additional runtime dependencies.
  • You want cross-platform interoperability with browsers and other runtimes (Deno, Bun, Cloudflare Workers, etc.) using the same Fetch API surface.

This is common in applications that make straightforward HTTP requests or in libraries that target multiple JavaScript runtimes.

Install undici from npm when you need capabilities beyond the standard Fetch API:

undici exposes request, stream, pipeline, and connect methods that provide lower-level control and significantly better performance than fetch:

import { request } from 'undici';

const { statusCode, headers, body } = await request('https://example.com');
const data = await body.json();

Client, Pool, BalancedPool, Agent, and their configuration options let you manage connection lifecycle, keep-alive behavior, pipelining depth, and concurrency limits:

import { Pool } from 'undici';

const pool = new Pool('https://example.com', { connections: 10 });
const { body } = await pool.request({ path: '/', method: 'GET' });

ProxyAgent and EnvHttpProxyAgent handle HTTP(S) proxying. Note that Node.js v22.21.0+ and v24.0.0+ support environment-variable-based proxy configuration for the built-in fetch via the --use-env-proxy flag (or NODE_USE_ENV_PROXY=1). However, undici's ProxyAgent still provides programmatic control through the dispatcher API:

import { ProxyAgent, fetch } from 'undici';

const proxyAgent = new ProxyAgent('https://my-proxy.example.com:8080');
const response = await fetch('https://example.com', { dispatcher: proxyAgent });

MockAgent, MockClient, and MockPool let you intercept and mock HTTP requests without patching globals or depending on external libraries:

import { MockAgent, setGlobalDispatcher, fetch } from 'undici';

const mockAgent = new MockAgent();
setGlobalDispatcher(mockAgent);

const pool = mockAgent.get('https://example.com');
pool.intercept({ path: '/api' }).reply(200, { message: 'mocked' });

Custom dispatchers and interceptors (retry, redirect, cache, DNS) give you fine-grained control over how requests are processed.

The npm package often includes features, performance improvements, and bug fixes that have not yet landed in a Node.js release. If you need a specific fix or feature, you can install a newer version directly.

Node.js versionBundled undici versionNotes
v18.x~5.xfetch is experimental (behind --experimental-fetch in early v18)
v20.x~6.xfetch is stable
v22.x~6.x / ~7.xfetch is stable
v24.x~7.xfetch is stable; env-proxy support via --use-env-proxy

You can always check the exact bundled version at runtime with process.versions.undici.

Installing undici from npm does not replace the built-in globals. If you want your installed version to replace the global fetch and related classes, use install(). Otherwise, import fetch directly from 'undici':