| | |

Bun 05 ⚡ Managing Packages: Add, Remove, and Update Operations

⏱️ Estimated read: 8 min | |

Package management in Bun follows the familiar add, remove, and update pattern, but the execution engine underneath is fundamentally different. Because Bun is a native binary with a global install cache and a highly optimized resolver, the same commands that take seconds in npm or minutes in Yarn complete in milliseconds. This chapter covers the commands, the flags that control dependency placement and behavior, and the unique --trust mechanism that governs lifecycle scripts.

Key point: bun add saves to dependencies by default. bun add -d saves to devDependencies. bun remove strips a package from the manifest and node_modules. bun update resolves the latest compatible versions. All three commands write to the text-based bun.lock immediately, without a separate lockfile-only step.


Why Bun’s commands are faster

The binary advantage. Bun’s package manager is written in Zig and compiled to a native executable. It does not boot a JavaScript runtime to perform resolution. When you run bun add express, Bun resolves the package, checks the global cache for the tarball, extracts it via hard links into node_modules, and updates package.json and bun.lock. The entire sequence typically completes in 20–100ms, compared to 1–3 seconds for npm install express .

The lifecycle script problem. npm and Yarn run preinstall and postinstall scripts automatically. This is a security risk: any package you install can execute arbitrary code during installation. Bun blocks these scripts by default. A package that requires a lifecycle script must be explicitly trusted, either with the --trust flag on the command line or by adding it to trustedDependencies in package.json .

The lockfile format. Bun writes its lockfile during the same operation, not as a separate step. There is no --package-lock-only equivalent needed. Every bun add, bun remove, and bun update immediately produces an updated bun.lock (or bun.lockb in legacy projects) .

The update semantics problem. bun update without arguments updates every package to the latest version that satisfies the range in package.json. The --latest flag ignores the range and installs the latest version, potentially crossing major versions. The --interactive flag prompts for each package. These semantics match npm’s behavior, but the speed makes interactive updates practical for projects with hundreds of dependencies .

The trade-off. Bun’s compatibility with native modules is not yet complete. Packages that require node-gyp compilation may fail during installation. The --trust flag and trustedDependencies field exist to manage this, but they require the developer to know which packages need build scripts .


a. Adding dependencies

bun add installs a package and saves it to dependencies. The -d (or --dev) flag saves to devDependencies. The --optional flag saves to optionalDependencies. An exact version can be pinned with the @ syntax or the --exact flag.

# Add a production dependency
bun add express

# Add a dev dependency
bun add -d typescript

# Add an optional dependency
bun add --optional sharp

# Add an exact version
bun add --exact lodash@4.17.21

The --trust flag permits a package to run its lifecycle scripts. This is required for native modules like esbuild, sharp, and node-sass that compile code during installation.

# Install and allow postinstall script
bun add esbuild --trust

Without --trust, the script is skipped. The package may still install, but its native binaries will be missing, and the package will fail at runtime. The trustedDependencies field in package.json records the trust decision for future installs.

Bun supports the standard protocols for non-registry packages: git:, github:, file:, link:, and workspace:. A package from a Git repository is installed with bun add github:user/repo. A local package is installed with bun add file:../my-package .


b. Removing dependencies

bun remove removes a package from package.json and from node_modules. The command handles every dependency field: dependencies, devDependencies, optionalDependencies, and peerDependencies.

# Remove a package
bun remove lodash

# Remove from all workspaces
bun remove lodash --recursive

In a monorepo, the --recursive flag (or -r) applies the removal to every workspace. Without it, the command operates only on the current workspace’s manifest. The lockfile and node_modules are updated in the same operation .


c. Updating dependencies

bun update updates packages within the ranges declared in package.json. The --latest flag ignores the ranges and installs the newest available version, which may be a major bump.

# Update within ranges
bun update

# Update to latest (may cross major)
bun update --latest

# Interactive update
bun update --interactive

# Update a specific package
bun update express

The --interactive flag presents a list of packages with their current and target versions. This is useful for reviewing major version changes before applying them. The --dry-run flag shows what would change without modifying files.

For updating a specific package to a specific version, bun add is used rather than bun update. The bun add express@5.0.0 command changes the version in package.json and installs it.


Complete Example Session

# ============================================
# PART 1: ADD A PRODUCTION DEPENDENCY
# ============================================

bun add express
# package.json: "express": "^5.1.0"
# node_modules populated
# bun.lock updated
# ============================================
# PART 2: ADD A DEV DEPENDENCY
# ============================================

bun add -d typescript
# package.json: "typescript": "^5.8.0" (devDependencies)
# ============================================
# PART 3: ADD WITH TRUST
# ============================================

bun add esbuild --trust
# esbuild postinstall script runs
# trustedDependencies updated in package.json
# ============================================
# PART 4: ADD AN EXACT VERSION
# ============================================

bun add --exact lodash@4.17.21
# package.json: "lodash": "4.17.21" (no caret)
# ============================================
# PART 5: UPDATE WITHIN RANGES
# ============================================

bun update
# All packages updated to latest within ranges
# bun.lock updated
# ============================================
# PART 6: UPDATE TO LATEST
# ============================================

bun update --latest
# Major version bumps may occur
# ============================================
# PART 7: INTERACTIVE UPDATE
# ============================================

bun update --interactive
# Prompts for each package with available updates
# ============================================
# PART 8: REMOVE A DEPENDENCY
# ============================================

bun remove lodash
# package.json: lodash removed
# node_modules: lodash removed
# ============================================
# PART 9: REMOVE ACROSS WORKSPACES
# ============================================

bun remove lodash --recursive
# lodash removed from all workspace manifests

The nine parts trace the lifecycle: adding production and dev dependencies, trusting a native package, pinning an exact version, updating within and beyond ranges, interactive updating, and removing from a single package or all workspaces.


Quick Reference

Add Commands

CommandDestination
bun add <pkg>dependencies
bun add -d <pkg>devDependencies
bun add --optional <pkg>optionalDependencies
bun add --exact <pkg>@<ver>Exact version
bun add <pkg> --trustAllow lifecycle scripts
bun add github:user/repoGit source

Remove Commands

CommandEffect
bun remove <pkg>Remove from all fields
bun remove <pkg> -rRemove from all workspaces

Update Commands

CommandEffect
bun updateUpdate within ranges
bun update --latestUpdate to latest
bun update --interactiveChoose updates interactively
bun update <pkg>Update a specific package

Best Practices

✅ Do This:

bun add -d typescript
<!-- Build tools in devDependencies -->               # ✅
bun add esbuild --trust
<!-- Explicitly trust native packages -->             # ✅
bun update --interactive
<!-- Review major version changes -->                # ✅

❌ Don’t Do This:

# Assume postinstall scripts run
<!-- Bun blocks them by default -->                  # ❌
# Run bun update --latest without reviewing
<!-- Major bumps can break APIs -->                  # ❌
# Use --trust on untrusted packages
<!-- Allows arbitrary code execution -->             # ❌

Common Pitfalls

PitfallWhy It HappensFix
Native module failsPostinstall script blockedbun add <pkg> --trust
Lockfile out of syncManual package.json editbun install to reconcile
Update crosses major version--latest usedUse bun update without --latest
Remove fails in monorepoPackage in different workspacebun remove <pkg> -r
bun add uses wrong versionRegistry tag is not latestSpecify version explicitly

Real-World Examples

1. Add Production

bun add express

2. Add Dev

bun add -d vitest

3. Add with Trust

bun add sharp --trust

4. Add Exact

bun add --exact lodash@4.17.21

5. Update

bun update

6. Update Latest

bun update --latest

7. Interactive

bun update -i

8. Remove

bun remove lodash

9. Remove Recursive

bun remove lodash -r

10. Add from Git

bun add github:user/repo

Visual

Add Operation Flow

┌──────────────────────────────────────────────┐
│  bun add express                             │
└──────────────────┬───────────────────────────┘
                   │
                   ▼
┌──────────────────────────────────────────────┐
│  1. Resolve version (^5.1.0)                 │
│  2. Check global cache for tarball           │
│  3. Extract to node_modules via hard links   │
│  4. Check trustedDependencies for scripts    │
│  5. Run postinstall if trusted               │
│  6. Write package.json and bun.lock          │
└──────────────────┬───────────────────────────┘
                   │
                   ▼
┌──────────────────────────────────────────────┐
│  Dependency available                        │
└──────────────────────────────────────────────┘

Update Decision

┌──────────────────────────────────────────────┐
│  bun update                                  │
│  (without --latest)                          │
└──────────────────┬───────────────────────────┘
                   │
                   ▼
┌──────────────────────────────────────────────┐
│  Respect ranges in package.json              │
│  Update to latest matching version           │
└──────────────────┬───────────────────────────┘
                   │
                   ▼
┌──────────────────────────────────────────────┐
│  bun update --latest                         │
│  → Ignore ranges, use latest tag             │
│  → May cross major version                   │
└──────────────────────────────────────────────┘

Summary

ItemValue
Add defaultdependencies
Add dev flag-d
Trust flag--trust
Exact version--exact
Remove recursive-r
Update within rangebun update
Update latestbun update --latest
Interactivebun update -i

Key takeaways:

  • Lifecycle scripts are blocked by default. Native packages require --trust or a trustedDependencies entry. This is a deliberate security measure .
  • bun add saves to dependencies. The -d flag saves to devDependencies. The --exact flag pins the version without a caret.
  • bun update respects ranges. The --latest flag ignores them and may cross major versions. Use --interactive to review.
  • bun remove cleans every field. The -r flag applies the removal to all workspaces in a monorepo.
  • The lockfile is written immediately. No separate step is needed to update bun.lock.

Remember: The three commands are the daily cycle. add grows the tree, update refreshes it, and remove prunes it. The --trust flag is the gatekeeper for native code. Bun’s speed makes the cycle fast enough to run interactively, which changes the workflow from “run and wait” to “run and observe.”

Was this helpful?


Stop using slow, ad-bloated tool sites! 🤮

🔎 Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)

⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free

🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!