upm Launches as a Fast, Tiny Package Manager Written in TypeScript

upm uses Node.js to deliver fast npm installs in about 250 KB, with a JavaScript API and security defaults.

  • Sarah Gooding
    Sarah Gooding
4 min read
upm Launches as a Fast, Tiny Package Manager Written in TypeScript

There's a new package manager in town, and this one fits in about 250 KB. Pooya Parsa, a longtime Nuxt contributor and creator of Nitro and UnJS, has introduced upm, a TypeScript package manager for the npm registry that uses Node.js's built-in networking, worker threads, compression, and filesystem tools. It has a JavaScript API, reads supported .npmrc settings, and can install from several other package managers' lockfiles.

With so many package managers already out there, do we really need another one? Parsa addressed the obvious question in upm's docs with a brief rundown of what he perceives to be the drawbacks of existing tools:

  • npm ships with Node.js, but it is simply slow.
  • pnpm is fast and well designed, but it keeps growing in size, features and customization settings.
  • Yarn (berry) has become complex, with its own concepts and unusual requirements.
  • Bun, Deno and Nub are fast, but they fit best when you also use them as runtime and not Node.js.

AI has made it easier to build the tool you wish existed, and upm has something unique going for it: it's small enough to embed in another tool and call directly from JavaScript.

Parsa says the project began as an experiment in how fast and small an npm client could be using the features Node.js already provides. As more and more JavaScript tooling is getting Rustified, upm stands out as an effort to get native-class install speed out of Node.js itself while keeping the package manager tiny and importable.

In the project's size comparison, the measured upm build takes 256 KB on disk and 84.5 KB packed, compared with 48.6 MB for pnpm 12 in the same test. Parsa contends that a native package manager's faster startup can lose its advantage in CI if restoring its much larger binary takes longer. The chart estimates cache restore time, but it does not measure a full CI job, and the Bun and Deno sizes include their runtimes. That makes this comparison a little lopsided.

Fast Cold Installs, Mixed Warm Results

JavaScript developers love to obsess over benchmarks, so yes, there are charts. In its own tests, upm ranks first overall on cold installs across Nitro, Nuxt, and Next.js, with median times of 494 ms to 1.07 s. pnpm 12 is faster on Nitro.

Warm installs are more mixed. With the cache and lockfile kept but node_modules removed, upm ranks fourth at 36–125 ms, behind Bun, aube, and Deno. The benchmark guide says each manager gets a private cache and lifecycle scripts are disabled for all. Estimated CI cache restore times aren't included in the ranking, and results vary by project, machine, and network.

upm Supports Familiar Configuration but Has Limited Compatibility With Other Lockfiles

upm supports install, add, remove, and run, along with a frozen-lockfile mode for CI. Its commands are also exposed as JavaScript functions, so another tool can use its installer or resolver without spawning a separate process. The package store accepts pluggable backends for uses such as shared caches. Parsa also says upm.sh runs an install in the browser tab, letting visitors try it before installing the CLI.

Projects can keep supported .npmrc settings, including scoped registries and credentials. Proxy and certificate settings are not supported. When there is no upm.lock, upm can install versions from package-lock.json, pnpm-lock.yaml, or bun.lock without changing the existing file. That is useful for trying it in an established project, though compatibility with other package manager lockfiles currently has some major limitations: those lockfiles cannot contain workspaces, patches, or Git or file dependencies, and upm cannot add or remove packages while using them.

upm also gives each package access only to dependencies it declares. There is no hoisting to make an undeclared dependency available by accident. Its shared store uses hardlinks to reuse package files across projects, falling back to copies when hardlinks are unavailable.

Security Defaults Skip Install Scripts and Add 24-Hour Release Cooldown

upm skips dependency lifecycle scripts during installation and defaults to a one-day minimum release age when it selects new versions. Both choices address familiar npm attack paths: malicious install scripts that run immediately, and fresh releases pushed into dependency trees before anyone has had a chance to inspect them. The release delay can be configured or disabled.

There are also limits to that protection:

  • If latest points to a version that is too new, upm selects the newest eligible older version.
  • If none satisfies the requested range, installation fails.
  • The age rule does not replace versions already in upm.lock or override an explicitly requested exact version.

Skipping scripts also means packages that build or download files during installation may need extra setup. Developers can still run a downloaded package's command with upx. The default concerns scripts that would run automatically during installation.

upm is a prerelease. Its current limitations include no update command, no Git or local directory dependencies, and limited peer dependency handling. Commands such as audit are not implemented through npm because npm does not understand upm's installed layout or lockfile.

For now, upm shows that a tiny TypeScript npm client can hold its own against native package managers on install speed. It’s early days for the project, and it will be interesting to see how it evolves and whether it gains adoption in a crowded field.

Stay ahead of threats

Subscribe to our newsletter

Get notified when we publish new security blog posts!