- npm provides the core dependency management, versioning and scripting needed to structure projects that target Cloudflare’s workerd runtime.
- workerd differs from Node.js by focusing on secure, web‑standard APIs, requiring compatibility layers for Node‑specific modules.
- The new nodejs_compat_v2 mode mixes native C++ implementations, unenv polyfills and mocks to greatly improve npm package support.
- Module aliasing and selective polyfills let you customize behavior for incompatible dependencies and unlock more of the npm ecosystem on Workers.
Combining the npm ecosystem with Cloudflare’s workerd runtime can sound a bit mysterious, but under the hood it’s all about making Node.js-style code and packages run smoothly on a web‑centric platform. Cloudflare Workers and Pages now offer an improved Node.js compatibility layer that lets you pull in many more npm packages without wrestling with low‑level differences between runtimes.
This article explains how npm packages intersect with workerd and the new compatibility flags, showing why some packages used to fail and what changes with the nodejs_compat_v2 mode. You’ll also see how npm behaviors (installation, updates, scripts and dependency types) fit into projects that target workerd so you can structure apps confidently and avoid surprises.
What is npm and why it matters for workerd
npm remains the de facto package manager for Node.js, powering both server‑side code and a massive portion of today’s frontend tooling. It started as a simple dependency manager but quickly evolved into a universal registry and CLI that practically every JavaScript developer touches daily.
The npm registry contains millions of packages, which means there’s likely a library for almost any concern: HTTP clients, authentication, database drivers, build tools, testing frameworks and more. For workerd and Cloudflare Workers, this ecosystem is both a blessing and a challenge: you gain access to many tools, but many were built assuming a Node.js runtime rather than a web‑standard environment.
npm is equally central to frontend workflows, where bundlers, transpilers and linters are installed as dev‑time dependencies. Whether building a React SPA or a Worker script that runs on workerd, you’ll likely use npm (or Yarn/pnpm) to manage dependencies and scripts.
At its core, npm automates installation, updates and dependency tracking, keeping modules in node_modules and recording requirements in package.json. For workerd‑based Workers your npm setup looks familiar, but the runtime executing your code is the workerd engine rather than Node.js itself.
Alternatives like Yarn and pnpm offer different CLIs and performance traits, but when targeting workerd the concept is the same: a package manager resolves modules while Cloudflare’s build tooling and compatibility flags decide how those modules execute in the Worker runtime.
How dependency installation works with npm

Running the standard npm install command populates your node_modules by reading dependency lists and pulling in every listed package along with its transitive dependencies, so you don’t manually chase nested requirements.
To add a new library you typically run a single install command, and since npm 5 it’s automatically added to the dependencies section of package.json unless you override that behavior.
npm supports flags that classify how a package is used in your project, which is useful when targeting runtimes like workerd where you may want different bundles or build processes:
--save-devadds the package todevDependencies, marking it as needed only during development or build steps such as test runners or bundlers.--no-saveinstalls without modifyingpackage.json, handy for quick experiments or one‑off commands.--save-optionalplaces the package intooptionalDependencies, so install failures for it don’t abort the whole process.--no-optionalprevents optional dependencies from being installed, reducing footprint or avoiding problematic optional packages on some platforms.
The difference between dependencies and devDependencies matters when building for Workers, because only runtime dependencies typically need bundling and shipping; dev dependencies are stripped during the build, keeping deployments smaller.
Optional dependencies give you flexible failure handling, but your code must check availability before relying on them. This helps when a package should use different implementations in Node.js vs. workerd or fall back when a native module isn’t supported.
Managing updates and versions in npm projects
npm’s update command upgrades packages according to the semver ranges you declare, scanning installed modules and updating them to the newest permitted versions for both direct and nested dependencies.
You can also update a single package when needed, useful if a library releases a bugfix or an improvement that affects your Worker or its compatibility with non‑Node runtimes like workerd.
npm follows semantic versioning, letting you control upgrade boundaries precisely, which is critical when your Worker depends on a library tied to a specific major version or when breaking changes are introduced upstream.
Locking versions and using lockfiles keeps builds reproducible, so teams and CI environments produce the same dependency graph across local development, staging and production Workers.
npm scripts and automation in workerd‑based workflows
The scripts field in package.json serves as your script runner, letting you map short names to longer CLI commands and execute them with npm run <script-name>.
Modern projects use npm scripts to wrap build tools, tests and bundlers, and Worker projects targeting workerd commonly expose bundling, type checking and deployment commands this way.
A common pattern is to wire up a bundler or task runner via a script entry, turning a complex CLI invocation into a simple command accessible to the whole team.
Scripting becomes powerful when combined with Node.js compatibility flags for workerd, since scripts can control which compatibility options are active and what polyfills or aliases are applied before bundling the final Worker.
workerd vs Node.js: understanding the runtime gap
workerd is an open‑source JavaScript and WebAssembly engine optimized for edge execution, built on V8—the same low‑level engine used by Node.js and Chromium—but designed with different operating conditions and trust models.
Node.js was created to run JavaScript on a host OS and exposes powerful system APIs such as process, fs and low‑level crypto utilities, making it ideal for servers, CLIs and backend infrastructure with direct machine access.
workerd is tuned for running untrusted code in multi‑tenant edge processes, emphasizing isolation and web‑centric APIs like fetch, streams and Cloudflare‑specific bindings (KV, Durable Objects, internal RPC) rather than filesystem or process access.
To improve interoperability Cloudflare helped establish WinterCG, aiming to align server‑side JavaScript runtimes and the web platform around a common API set so apps behave similarly across environments.
However, many npm packages assume a Node.js environment and import built‑in modules like events, fs, net, crypto or buffer. Without a compatibility layer these imports can fail because workerd doesn’t automatically provide Node‑specific modules.
From polyfills to built‑in Node.js APIs in Workers
Cloudflare initially relied on polyfills to bridge Node.js and workerd, using JavaScript implementations to mimic Node APIs. In 2021 Workers gained a polyfill‑based compatibility mode, and Wrangler started injecting those polyfills when node_compat = true was set in wrangler.toml.
With node_compat = true, Wrangler bundled JS implementations for several core Node modules, leveraging plugins like @esbuild-plugins/node-globals-polyfill and rollup-plugin-node-polyfills so imports such as import EventEmitter from 'events' could work in a Worker.
Polyfills enabled many npm packages to run on Workers but had limits, especially for modules that do heavy binary or crypto work where native implementations are far faster and more accurate than pure JS shims.
Buffer is a clear example of a feature hard to emulate efficiently in user‑land, since operations like copying and encoding conversions benefit from optimized native implementations. The same applies to APIs like Crypto and AsyncLocalStorage.
To improve performance and completeness, Cloudflare began embedding some Node APIs into the runtime in 2023 via the nodejs_compat flag; these core modules are implemented in C++ and surfaced to Workers for better fidelity than JS polyfills.
When using built‑in Node modules in Workers you should import them with the node: prefix, for example import { Buffer } from 'node:buffer', signaling dependency on a runtime‑provided module rather than a registry package.
Why many npm packages still failed with early nodejs_compat
Early nodejs_compat still caused failures because many libraries used unprefixed imports, e.g., import { EventEmitter } from 'events', which the bundler treated as filesystem modules and failed to resolve when they weren’t present.
A common error occurred when importing drivers like pg that depend on core modules unprefixed, causing build steps to complain the module wasn’t found even though Node regards it as built‑in.
Developers faced a trade‑off between small native API support and a slower, incomplete polyfill set, plus missing globals like process that many libraries assumed would exist on the global object.
That friction made it hard to reliably use complex npm packages on workerd, especially when indirect dependencies expected specific Node modules or globals, leading to build‑time failures before the Worker could run.
The new nodejs_compat_v2: better npm support on workerd
nodejs_compat_v2 blends native implementations with on‑demand polyfills, making far more of the npm ecosystem usable on Workers by deciding when to use C++‑backed modules, JS polyfills or lightweight stubs that let imports succeed.
Enable this mode by adding compatibility_flags = ["nodejs_compat_v2"] to wrangler.toml, which changes both how the runtime exposes Node APIs and how Wrangler bundles Node‑style imports and dependencies.
Many packages that previously failed to import now load correctly under v2, including libraries such as body-parser, jsonwebtoken, got, passport, knex and others—reducing build‑time errors in favor of localized runtime feedback for unsupported operations.
In v2 you can write imports like import { Buffer } from 'buffer' and the runtime routes them efficiently to C++‑backed implementations; at the same time, modules like net can be polyfilled by Wrangler using unenv, letting native and polyfilled APIs coexist without conflict.
Wrangler now injects polyfills only for Node modules your Worker actually uses, keeping bundle sizes lean by analyzing code and dependencies instead of shipping a full suite of polyfills by default.
unenv polyfills and mocked Node.js APIs
When a native implementation or mature polyfill is unavailable, unenv provides mocked modules that expose the same interfaces but either perform no‑ops or throw descriptive runtime errors when unsupported methods are invoked.
Errors such as [unenv] <method name> is not implemented yet! are more explicit and localized, letting a Worker start and fail only at the call site that triggers the incompatibility instead of aborting at build time.
Mocked modules let packages that partially depend on Node features still be imported and used, as long as you avoid the unsupported pieces; safe parts can run while file‑dependent operations trigger errors only if executed.
Previously, any import of fs could make a package unusable in Workers, but with nodejs_compat_v2 and unenv mocks the dependency can be included and invoked selectively.
This shift from build‑time to runtime feedback simplifies debugging, because you can identify exactly which method and call stack trigger incompatibility and then restructure code or provide targeted polyfills or aliases as a workaround.
Module aliasing: customizing behavior for problematic dependencies
Module aliasing lets you redirect imports to your own implementations, configured in wrangler.toml, so a problematic module path resolves to a custom file instead of the default behavior.
If a library depends on fs.readFile but you don’t need filesystem access, alias "fs" to ./fs-polyfill and expose a custom readFile that logs, calls another API or reads from KV.
After aliasing, imports like import { readFile } from 'fs' resolve to your module and bypass unenv’s defaults, preventing “not implemented yet” errors while keeping the consuming package unchanged.
Aliasing also helps when a dependency pulls in Node‑specific packages such as node-fetch, which may rely on unsupported Node modules; you can map "node-fetch" to a module that re‑exports the global fetch.
Tools like nolyfill make re‑export patterns straightforward, enabling you to short‑circuit incompatible implementations and keep dependent packages functioning on workerd.
Module aliasing acts as a flexible compatibility layer on top of nodejs_compat_v2, letting you adapt specific packages without rewriting or forking them.
Performance, ecosystem collaboration and rollout
Critical Node.js APIs implemented natively in C++ inside workerd deliver better performance and correctness, and modules like Buffer, AsyncLocalStorage and Crypto benefit from these native implementations wrapped in JS shims that mirror Node behavior.
Cloudflare contributes to unenv, which provides smart, on‑demand polyfills and mocks, because it targets multiple runtimes and has been adopted by projects like Nuxt and Nitro; adding only necessary polyfills keeps apps lightweight and encourages ecosystem convergence.
The broader aim is portability of Node‑style code across different runtimes, so developers can write once and run on Node.js, workerd or other environments with minimal friction by automatically choosing polyfills and native features based on usage.
The improved behavior in nodejs_compat_v2 is expected to become the default over time when your Worker’s compatibility date is recent enough, so more Workers will transparently benefit from stronger npm compatibility without extra configuration.
Developers are encouraged to try the improved Node.js compatibility and update their wrangler.toml, reporting remaining incompatibilities or bugs through feedback channels so gaps can be closed.
The combination of npm’s mature dependency management, workerd’s secure web‑centric runtime and the evolving Node.js compatibility layer gives you a practical path to reuse a huge amount of existing JavaScript code while taking advantage of edge execution, isolation and Cloudflare’s platform features. With smart polyfills, native implementations, mocking and module aliasing at your disposal, it becomes far more realistic to bring sophisticated npm packages into your Workers projects without constantly fighting the runtime.