Bun 05 ⚡ Managing Packages: Add, Remove, and Update Operations
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
| Command | Destination |
|---|---|
bun add <pkg> | dependencies |
bun add -d <pkg> | devDependencies |
bun add --optional <pkg> | optionalDependencies |
bun add --exact <pkg>@<ver> | Exact version |
bun add <pkg> --trust | Allow lifecycle scripts |
bun add github:user/repo | Git source |
Remove Commands
| Command | Effect |
|---|---|
bun remove <pkg> | Remove from all fields |
bun remove <pkg> -r | Remove from all workspaces |
Update Commands
| Command | Effect |
|---|---|
bun update | Update within ranges |
bun update --latest | Update to latest |
bun update --interactive | Choose 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
| Pitfall | Why It Happens | Fix |
|---|---|---|
| Native module fails | Postinstall script blocked | bun add <pkg> --trust |
| Lockfile out of sync | Manual package.json edit | bun install to reconcile |
| Update crosses major version | --latest used | Use bun update without --latest |
| Remove fails in monorepo | Package in different workspace | bun remove <pkg> -r |
bun add uses wrong version | Registry tag is not latest | Specify 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
| Item | Value |
|---|---|
| Add default | dependencies |
| Add dev flag | -d |
| Trust flag | --trust |
| Exact version | --exact |
| Remove recursive | -r |
| Update within range | bun update |
| Update latest | bun update --latest |
| Interactive | bun update -i |
Key takeaways:
- Lifecycle scripts are blocked by default. Native packages require
--trustor atrustedDependenciesentry. This is a deliberate security measure . bun addsaves todependencies. The-dflag saves todevDependencies. The--exactflag pins the version without a caret.bun updaterespects ranges. The--latestflag ignores them and may cross major versions. Use--interactiveto review.bun removecleans every field. The-rflag 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.”
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!