---
title: "bun pm"
description: "Package manager utilities"
---

The `bun pm` command group is a set of utilities for working with Bun's package manager.

## pack

To create a tarball of the current workspace:

```bash terminal icon="terminal"
bun pm pack
```

`bun pm pack` creates a `.tgz` file containing all files that would be published to npm, following the same rules as `npm pack`.

## Examples

Basic usage:

```bash terminal icon="terminal"
bun pm pack
# Creates my-package-1.0.0.tgz in current directory
```

Quiet mode for scripting:

```bash terminal icon="terminal"
TARBALL=$(bun pm pack --quiet)
echo "Created: $TARBALL"
```

```txt
Created: my-package-1.0.0.tgz
```

Custom destination:

```bash terminal icon="terminal"
bun pm pack --destination ./dist
# Saves tarball in ./dist/ directory
```

## Options

- `--dry-run`: Perform all tasks except writing the tarball to disk. Shows what would be included.
- `--destination <dir>`: The directory to save the tarball in.
- `--filename <name>`: An exact file name for the tarball.
- `--ignore-scripts`: Skip running pre/postpack and prepare scripts.
- `--gzip-level <0-9>`: Set the gzip compression level, from 0 to 9 (default 9).
- `--quiet`: Print only the tarball filename, suppressing the rest of the output. Useful in scripts.

> **Note:** `--filename` and `--destination` cannot be used at the same time.

## Output Modes

**Default output:**

```bash terminal icon="terminal"
bun pm pack
```

```txt
bun pack v1.2.19

packed 131B package.json
packed 40B index.js

my-package-1.0.0.tgz

Total files: 2
Shasum: f2451d6eb1e818f500a791d9aace80b394258a90
Unpacked size: 171B
Packed size: 249B
```

**Quiet output:**

```bash terminal icon="terminal"
bun pm pack --quiet
```

```txt
my-package-1.0.0.tgz
```

## bin

To print the path to the `bin` directory for the local project:

```bash terminal icon="terminal"
bun pm bin
```

```txt
/path/to/current/project/node_modules/.bin
```

To print the path to the global `bin` directory:

```bash terminal icon="terminal"
bun pm bin -g
```

```txt
<$HOME>/.bun/bin
```

## ls

To print a list of installed dependencies in the current project and their resolved versions, excluding their dependencies:

```bash terminal icon="terminal"
bun pm ls
# or
bun list
```

```txt
/path/to/project node_modules (135 installed)
├── eslint@8.38.0
├── react@18.2.0
├── react-dom@18.2.0
├── typescript@5.0.4
└── zod@3.21.4
```

To print all installed dependencies, including nth-order dependencies:

```bash terminal icon="terminal"
bun pm ls --all
# or
bun list --all
```

```txt
/path/to/project node_modules (135 installed)
├── @eslint-community/eslint-utils@4.4.0
├── @eslint-community/regexpp@4.5.0
├── @eslint/eslintrc@2.0.2
├── @eslint/js@8.38.0
├── @nodelib/fs.scandir@2.1.5
├── @nodelib/fs.stat@2.0.5
├── @nodelib/fs.walk@1.2.8
├── acorn@8.8.2
├── acorn-jsx@5.3.2
├── ajv@6.12.6
├── ansi-regex@5.0.1
├── ...
```

To print only trusted dependencies (those allowed to run [lifecycle scripts](/pm/lifecycle)). When `trustedDependencies` is set in `package.json`, Bun shows the packages from that list; otherwise it shows packages from its [default trusted dependencies](https://github.com/aphrody-labs/bun/blob/main/src/install/default-trusted-dependencies.txt) list.

```bash terminal icon="terminal"
bun pm ls --trusted
# or
bun list --trusted
```

```txt
/path/to/project node_modules (135 installed)
└── esbuild@0.21.5
```

To leave out `devDependencies` and the packages only they need, pass `--production` (or `--omit=dev`; `--omit` also takes `optional` and `peer`):

```bash terminal icon="terminal"
bun pm ls --production
bun pm ls --all --omit=dev
```

As with `npm ls`, `--depth=0` lists the direct dependencies, which is the default. Bun accepts no other depth.

To print every installed version of a package, direct dependency or not, pass its name. The exit code is 1 when no package has that name. [`bun why`](/pm/cli/why) shows what depends on it.

```bash terminal icon="terminal"
bun pm ls ansi-regex
```

```txt
/path/to/project node_modules
├── ansi-regex@5.0.1
└── ansi-regex@6.0.1
```

To print the packages installed globally with `bun add -g`:

```bash terminal icon="terminal"
bun pm ls -g
# or
bun list --global
```

```txt
<$HOME>/.bun/install/global node_modules (1 installed)
└── typescript@5.0.4
```

## licenses

List every installed package grouped by license, as read from each package's `package.json` in `node_modules`. Bun lists packages without a `license` field under `Unknown`. Bun marks packages only reachable through `devDependencies` with `(dev)`.

```bash terminal icon="terminal"
bun pm licenses
# or
bun pm licenses ls
```

```txt
bun pm licenses v1.3.0 (a4b2f86f)

MIT (2)
├── path-parse@1.0.6
└── resolve@1.9.0

Unknown (4)
├── a-dep@1.0.1 (dev)
├── no-deps@1.0.0
├── no-deps@1.0.1
└── one-dep@1.0.0

6 packages across 2 licenses (checked 6 packages in bun.lock) [4.00ms]
```

| Flag                           | Description                                                                     |
| ------------------------------ | ------------------------------------------------------------------------------- |
| `--json`                       | Print a JSON object keyed by license                                            |
| `--long`                       | Also print each package's `author`, `description`, and `homepage`               |
| `--prod`, `-p`                 | Skip `devDependencies` (same as `--omit=dev`)                                   |
| `--dev`, `-D`                  | Only list what `devDependencies` pull in                                        |
| `--omit=<dev\|optional\|peer>` | Skip a dependency type, as with `bun install`                                   |
| `--filter <pattern>`, `-F`     | Only list dependencies of the matching workspaces ([filter syntax](/pm/filter)) |

```bash terminal icon="terminal"
bun pm licenses --json --prod
```

```json
{
  "MIT": [
    {
      "name": "path-parse",
      "versions": ["1.0.6"],
      "paths": ["/home/me/app/node_modules/path-parse"],
      "license": "MIT",
      "homepage": "https://github.com/jbgutierrez/path-parse#readme",
      "author": "Javier Blanco <http://jbgutierrez.info>",
      "description": "Node.js path.parse() ponyfill"
    }
  ]
}
```

From a workspace root, Bun lists every workspace's dependencies. From inside a workspace package, Bun lists only that package's dependencies. Use [`bun why`](/pm/cli/why) to find out what pulls in an unexpected package.

Requires both `bun.lock` and `node_modules`. Bun skips packages that are in the lockfile but missing from `node_modules` (e.g. after `bun install --production`) and prints a warning.

## diff

Show what changed between two versions of a package: a summary of the files and lines touched, the `package.json` changes worth a second look (new install scripts, dependencies, entry points, binaries), then a unified diff of every file. Each side can be a registry spec, a local folder, or a `.tgz`.

```bash terminal icon="terminal"
bun pm diff react                     # the version in bun.lock → latest
bun pm diff react@18.2.0 19.0.0       # two published versions
bun pm diff react@18.2.0..19.0.0      # same thing
bun pm diff ./vendored-pkg pkg@2.1.0  # a folder against a published version
bun pm diff                           # in a package folder: what is published → this folder
```

```txt
is-number@6.0.0 → is-number@7.0.0
4 files changed, 0 added, 0 removed  (+73 -44 lines)

  ! engines changed: { "node": ">=0.10.0" } → { "node": ">=0.12.0" }

diff --git a/index.js b/index.js
--- a/index.js
+++ b/index.js
@@ -9,10 +9,10 @@
 module.exports = function(num) {
   if (typeof num === 'number') {
-    return num - num === 0;
+    return num - num === 0;
   }
...
```

On a terminal, code is compared by meaning and shown as written. Both sides of every `.js`/`.mjs`/`.cjs`/`.jsx`/`.ts`/`.tsx` file are parsed and re-printed in one canonical form (equivalent syntax folded, unreachable code dropped); that canonical form decides _what changed_, and the hunks show the _original_ lines — comments, types, JSX and names intact:

- `1 + 1` vs `2`, `'a'` vs `"a"`, `!0` vs `true`, a re-wrapped call, a whole-file reformat, or a local renamed consistently everywhere (a bundler's `utils` → `utils$1`) are not changes — locals on both sides are matched by how they are used, never by what they are called. Lines folded away this way are counted in the file header (`3 folded`), and a file with nothing left reads `formatting only`. Nothing is folded on the strength of one line: the old file with the folded lines as the new file has them must still print in the same canonical form as the old file, otherwise the file is shown as a plain diff. Exported names and the `#!` line are compared as written.
- A comment edit, a changed type annotation, or any change in control flow is a change, shown in the author's words.
- Unchanged text whose meaning moved — code that became unreachable, or dead code that came alive because a condition elsewhere flipped — is marked `~`.
- Minified bundles have no readable original, so an un-minified re-print stands in for it (one declaration, call and property per line, each numbered by its `line:col` in the original) and the same folded comparison decides what changed — with the short names two builds' minifiers handed out differently renamed in lockstep; `dist/*.min.js` diffs down to the lines that actually changed rather than one enormous `-`/`+` pair. The re-print is used only when it parses back to the same program as the file; otherwise the file's own lines are shown. `.json` and `.css` files are compared and shown as their canonical print (`normalized`), so a reformat around one edit is one edit; identical prints read `formatting only`.
- The summary at the end calls out what a reviewer looks for first: new `install`/`postinstall` scripts, dependency and entry-point changes, a file that newly imports `child_process`/`fs`/`net`/`http`/`vm`/…, new package imports, growth in `eval()` / `new Function()` / `process.env` / `fetch()` use, newly executable files, a text file that became binary, and terminal escape sequences or bidirectional-text controls appearing in a release. For a published version, `bun install` takes dependencies, `bin`, `os` and `cpu` from the registry's entry for it: where that entry and the `package.json` in the tarball disagree, the summary shows both and goes by the registry. `*.map` files are summarised as regenerated rather than shown. Files over 64 MB are diffed as text (`too large to normalize`).
- Line tints follow the terminal: light or dark is taken from `COLORFGBG` or by asking the terminal for its background colour (OSC 11), and exact colours are used when `COLORTERM=truecolor`.
- Nothing in a package can drive your terminal: control characters, escape sequences and bidirectional-text overrides in file contents and names are drawn as `␛`, `^M`, `‹U+202E›`, with `NO_COLOR` too. Permission changes show as `mode 644 → 755` / `new file mode 100755`.

To look at part of a package, name the files: as a `:path` suffix on a spec, as a bare `:path` argument, or as extra arguments after the two sides. A path matches a file exactly, a directory, a file name anywhere in the package, or a glob:

```bash
$ bun pm diff axios@1.6.0:lib/adapters 1.6.1          # one directory
$ bun pm diff axios@1.6.0 axios@1.6.1 dist/node/axios.cjs README.md
$ bun pm diff lodash :trimEnd.js                       # installed → latest, one file
$ bun pm diff react-dom@18.2.0 18.3.1 '*.min.js'
$ bun pm diff react-dom@18.2.0 18.3.1 'cjs/**/*.production.min.js'
```

A local folder that has a `package.json` is read the way `bun pm pack` would publish it — the `files` field, `.npmignore` / `.gitignore`, `bin` — so diffing a checkout against the registry compares what would ship, not `node_modules/`, `vendor/` or build output. Against a tarball or a published version, the `workspace:` and `catalog:` versions of a package of the current project are compared as the versions `bun pm pack` writes in their place. Two folders are compared as written.

Piped or with `NO_COLOR`, the output is a plain unified patch of the real file contents (so `bun pm diff a b > changes.patch` applies with `git apply` or `patch -p1`, empty files and permission changes included), and `--raw` turns the re-print off on a terminal too. Binary files and source maps are named under a `diff --bun` header that both tools skip: their contents are not in the patch, only their sizes and, for binary files, the start of each side's SHA-256 (`--json` has all of it). A file counts as binary when it has a NUL byte near its start and nothing would read it as text: a `.js`, `.ts`, `.json`, `.css`, `.html`, `.md`, `.yaml`, `.sh`, … file, a `#!` script, and any file that parses as JavaScript are always shown.

| Flag                     | Description                                                                                          |
| ------------------------ | ---------------------------------------------------------------------------------------------------- |
| `--raw`, `--unformatted` | Compare bytes as-is; skip the JS/CSS/JSON re-print                                                   |
| `--minify`               | Also fold equivalent syntax (`!0`/`true`, quote style, redundant parens) so only meaning is compared |
| `--unminify`             | Rename short locals in lockstep in every JS file, not just ones that look minified                   |
| `-w`, `--ignore-space`   | On a terminal, show files that differ only in whitespace as `whitespace only` instead of hunks       |
| `--stat`                 | One line per file with a change count and bar instead of the hunks                                   |
| `--name-only`            | Only list the files that differ, marked `A`dded / `M`odified / `D`eleted                             |
| `-U <n>`, `--unified`    | Lines of context around each change (default 3)                                                      |
| `--diff <spec>`          | npm-compatible way to name a side; may be given twice                                                |
| `--json`                 | One JSON document: `from`/`to`, per-file `status`/counts/`patch`, plain-text `notes`, and `totals`   |

With one name and no version, the left side is the version this project's `bun.lock` resolved and the right side is `latest`, so `bun pm diff <pkg>` answers "what would updating this pull in?". That is the version `<pkg>` means in the folder you run it from: a workspace package's own dependency comes before the copy at the project's root. An alias (`"legacy": "npm:pkg@1"`) names the package behind it. `bun pm diff <pkg> 2.0.0` compares the same version with `2.0.0`, and `latest` can stand where a second version can. Registry, scope and auth settings come from `bunfig.toml` / `.npmrc` as for `bun install`, and a download is checked against its integrity hash in the same way; outside a project only registry specs and absolute or `./` paths are accepted.

## whoami

Print your npm username. Requires you to be logged in (`bunx npm login`) with credentials in either `bunfig.toml` or `.npmrc`:

```bash terminal icon="terminal"
bun pm whoami
```

## hash

To generate and print the hash of the current lockfile:

```bash terminal icon="terminal"
bun pm hash
```

To print the string used to hash the lockfile:

```bash terminal icon="terminal"
bun pm hash-string
```

To print the hash stored in the current lockfile:

```bash terminal icon="terminal"
bun pm hash-print
```

## cache

To print the path to Bun's global module cache:

```bash terminal icon="terminal"
bun pm cache
```

To clear Bun's global module cache:

```bash terminal icon="terminal"
bun pm cache rm
bun pm cache rm --dry-run   # print the directories that would be deleted
```

This deletes everything in the cache directory, including the [global store](/pm/global-store) at `<cache>/links/`. The directory itself stays, so it can be a symlink or a mount. There is no way to remove one package: `bun pm cache rm <package>` is an error. Every project with `globalStore = true` symlinks into that directory, so run `bun install` again in each of them afterwards. Bun refuses to clear a directory that is or contains your project, your home directory or your Bun install.

To refresh stale registry metadata without deleting the cache, use `bun add <pkg> --force` or `bun update <pkg>`. See [Cache](/pm/cli/install#cache).

To remove only the packages that are older than 30 days:

```bash terminal icon="terminal"
bun pm cache prune
bun pm cache prune --max-age 7   # older than 7 days
bun pm cache prune --dry-run     # list what would be removed
```

- The age of a package is the time since Bun downloaded it. A package that you still install from the cache every day counts as old too.
- Where the file system records access times (Linux with the default `relatime`), the age is the time since an install last linked the package from the cache. macOS, Windows and `noatime` mounts normally do not record that.
- The next `bun install` that needs a removed package downloads it again. Keep this in mind before `bun install --offline`.
- The [global store](/pm/global-store) in `<cache>/links/`, cached git clones and package manifests stay.
- The size in the summary is the size of the removed files. A file that a `node_modules` tree hardlinks stays on disk until that tree is removed too.

`rm` and `prune` take the cache directory from `$BUN_INSTALL_CACHE_DIR`, `--cache-dir` or `$BUN_INSTALL`. They ignore a directory named in `bunfig.toml`, `.npmrc` or `.env`, so the files of a project cannot choose what gets deleted. To clear or prune such a directory, pass it with `--cache-dir`.

To move the cache entries a lockfile needs between machines as one file, for example through a CI cache:

```bash terminal icon="terminal"
# after an install
bun pm cache pack .ci/bun-cache.pack

# on another runner, before the install
bun pm cache unpack .ci/bun-cache.pack
bun install --frozen-lockfile
```

- `pack` writes every package of `bun.lock` that is in the local cache. It warns about packages it had to leave out, except optional packages for another platform.
- `unpack` adds the entries the cache does not have and never changes an entry that exists. A damaged or incomplete pack is refused as a whole.
- With `--dry-run`, `pack` reads the cache and writes no file, and `unpack` checks the whole pack and leaves the cache as it is. Both print what they would have done.
- The file is not compressed; CI caches compress on their own.
- Make one pack per operating system, and a new one after upgrading Bun.

<Warning>
  `bun install` trusts its cache, so a pack is as trusted as the cache directory itself. Its checksums detect damage,
  not tampering. Only unpack a file that you packed.
</Warning>

## fetch

To download every dependency into Bun's global module cache without installing anything:

```bash terminal icon="terminal"
bun pm fetch
```

```txt
bun pm fetch v1.5.0 (ca7428e9)

Fetched 42 packages into cache [1.20s]
Cache: /home/user/.bun/install/cache
```

- Dependencies resolve the way `bun install` resolves them, but `node_modules` is not created, lifecycle scripts do not run, and `package.json` and the lockfile are left untouched.
- With a lockfile, only what is missing from the cache is downloaded: the tarball URLs and git commits the lockfile records.
- A later `bun install` (or `bun install --offline`) installs from the cache. Without a lockfile, `--offline` cannot tell which commit a `github:` ref or which file a tarball URL stands for, so commit the lockfile if you depend on those.
- Packages for another operating system or CPU are skipped; pass `--os` / `--cpu` to fetch them.
- `-g` fetches the dependencies of globally installed packages.
- `--dry-run` prints how many packages are missing from the cache and downloads none of them.
- A [security scanner](/pm/security-scanner-api) does not run: nothing is linked or executed. It runs on the `bun install` that follows.

## migrate

To migrate another package manager's lockfile without installing anything:

```bash terminal icon="terminal"
bun pm migrate
```

Pass `--dry-run` to run the migration without writing `bun.lock` or `package.json`.

## untrusted

To print current untrusted dependencies with scripts:

```bash terminal icon="terminal"
bun pm untrusted
```

```txt
./node_modules/@biomejs/biome @1.8.3
 » [postinstall]: node scripts/postinstall.js

These dependencies had their lifecycle scripts blocked during install.
```

## trust

To run scripts for untrusted dependencies and add to `trustedDependencies`:

```bash terminal icon="terminal"
bun pm trust <names>
```

Options for the `trust` command:

- `--all`: Trust all untrusted dependencies.
- `--dry-run`: Print the scripts that would run and the packages that would be trusted, without running scripts or writing `package.json` and `bun.lock`.

A package that does not come from the npm registry (a tarball, `git`, `file:` or `link:` dependency) also has to be declared in the `package.json` of the project or of a workspace. `bun install`, `bun pm trust` and `bun add --trust` do not run the scripts of the others, and `bun pm untrusted` marks them with `cannot be trusted by name`.

## default-trusted

To print the default trusted dependencies list:

```bash terminal icon="terminal"
bun pm default-trusted
```

See the [current list on GitHub](https://github.com/aphrody-labs/bun/blob/main/src/install/default-trusted-dependencies.txt).

## version

To display current package version and help:

```bash terminal icon="terminal"
bun pm version
```

```txt
bun pm version v1.3.3 (ca7428e9)
Current package version: v1.0.0

Increment:
  patch      1.0.0 → 1.0.1
  minor      1.0.0 → 1.1.0
  major      1.0.0 → 2.0.0
  prerelease 1.0.0 → 1.0.1-0
  prepatch   1.0.0 → 1.0.1-0
  preminor   1.0.0 → 1.1.0-0
  premajor   1.0.0 → 2.0.0-0
  from-git   Use version from latest git tag
  1.2.3      Set specific version

Options:
  --no-git-tag-version Skip git operations
  --allow-same-version Prevents throwing error if version is the same
  --message=<val>, -m  Custom commit message, use %s for version substitution
  --preid=<val>        Prerelease identifier (i.e beta → 1.0.1-beta.0)
  --force, -f          Bypass dirty git history check
  --dry-run            Print the new version without writing, running scripts, or tagging

Examples:
  bun pm version patch
  bun pm version 1.2.3 --no-git-tag-version
  bun pm version prerelease --preid beta --message "Release beta: %s"
```

To bump the version in `package.json`:

```bash terminal icon="terminal"
bun pm version patch
```

```txt
v1.0.1
```

Supports `patch`, `minor`, `major`, `premajor`, `preminor`, `prepatch`, `prerelease`, `from-git`, or specific versions like `1.2.3`. By default it creates a git commit and tag, also inside a git worktree or submodule; pass `--no-git-tag-version` to skip them. Pass `--dry-run` to print the new version without writing `package.json`, running lifecycle scripts, or touching git.

Increments give the same version as `npm version`: on `1.0.0-rc.1`, `major` gives `1.0.0`, the release that the prerelease was made for.

The `preversion`, `version` and `postversion` scripts see `$npm_old_version` and `$npm_new_version`. `$npm_package_version` is the old version in `preversion` and the new one after it.

## pkg

Manage `package.json` data with get, set, delete, and fix operations.

All commands support dot and bracket notation:

```bash terminal icon="terminal"
scripts.build              # dot notation
contributors[0]            # array access
workspaces.0               # dot with numeric index
scripts[test:watch]        # bracket for special chars
exports[./package.json]    # a bracketed name is literal, dots included
contributors.name          # get: the name of every element
```

Examples:

```bash terminal icon="terminal"
# get
bun pm pkg get name                               # single property
bun pm pkg get name version                       # multiple properties
bun pm pkg get                                    # entire package.json
bun pm pkg get scripts.build                      # nested property

# set
bun pm pkg set name="my-package"                  # simple property
bun pm pkg set scripts.test="jest" version=2.0.0  # multiple properties
bun pm pkg set private=true --json                # JSON values with --json flag
bun pm pkg set 'keywords[]=cli'                   # append to an array (created if missing)
bun pm pkg set 'contributors[0].name=Jane'        # update an array element

# delete
bun pm pkg delete description                     # single property
bun pm pkg delete scripts.test contributors[0]    # multiple/nested

# fix
bun pm pkg fix                                    # auto-fix common issues
```

With `--json`, a value that is not valid JSON is an error. `set`, `delete`, and `fix` accept `--dry-run`, which prints the resulting `package.json` instead of writing it.
