Bun Update Packages: How to Update to the Latest Version
Bun update packages to the latest version: run bun update, bun update --latest, or interactive bun update -i. See how package.json and bun.lock get rewritten.
22 min read

The fastest way to update packages to the latest version with Bun is bun update. Since Bun 1.2 it also rewrites package.json and bun.lock for you, which is the part most guides still get wrong. If you remember one thing from this post, make it this: check git diff after every update, because Bun will touch your manifest.
Bun is at 1.4.x now (the project moved from bun.sh to bun.com), and its package manager covers the whole update workflow without extra tools. You can update everything in range, pick packages interactively with bun update -i, jump majors with bun update --latest, or patch a single transitive dependency. These are the same Node.js packages npm manages. Bun just installs and updates them faster. If you still need a Node runtime alongside Bun, see how to install Node.js with NVM on macOS and Ubuntu.
The workflow below is the one I use: upgrade Bun itself, check what is outdated, update, verify, and roll back if the build breaks. New to Bun? Start with the Bun package manager guide vs NPM, Yarn, and PNPM. Moving an Astro site over? See how to migrate Astro to Bun on Cloudflare Pages.
2026 update
The video below was recorded on Bun 1.0.x. The native bun update -i and bun update --latest flow in this post replaces the npm-check-updates workaround shown in the video. The commands in the written guide are the current ones.
Prerequisites and update workflow at a glance
Before touching packages, make sure you have the basics in place. This is a five-minute insurance policy that saves you an afternoon when a major bump breaks the build.
- Bun installed and reasonably current (
bun --version, target 1.4.x) - A project with
package.jsonand a lockfile (bun.lock, or the oldbun.lockb) - A clean git tree, or at least copies of
package.jsonandbun.lock - Your test/build command known in advance (
bun run build,bun test, whatever CI runs)
Commit first
Every command below rewrites package.json and/or bun.lock. A clean git tree is your undo button: git restore package.json bun.lock && bun install gets you back in seconds. Without that, rollback is manual and miserable.
The full workflow, in order:
| You want to | Run |
|---|---|
| Get the newest Bun itself | bun upgrade |
| See what is outdated | bun outdated |
| Bump everything inside your declared ranges | bun update |
| Choose packages yourself | bun update -i |
| Cross major versions | bun update --latest |
| Update one package | bun update zod |
| Update globally installed tools | bun update -g |
Use that table as the roadmap. Everything below expands one row with verify steps and failure modes.
Upgrade Bun to the latest version first
Outdated Bun, outdated behavior. The update flags and lockfile format have changed across 1.1, 1.2, 1.3, and 1.4, so run this first. How you upgrade depends on how Bun was installed:
curl / install script
bun upgradeThis is the default path from the official install script. It fetches the latest stable build and replaces the binary.
Want the bleeding edge or a pinned channel instead? bun upgrade --canary gets the development build, bun upgrade --stable moves you back to stable.
Homebrew
brew upgrade bunScoop
scoop update bunDo not mix upgrade methods
If Bun was installed with Homebrew or Scoop, do not run bun upgrade on top of it. The package manager owns the binary and will fight you: version drift, checksum errors, or a bun that reappears as the brew version on the next brew upgrade. Use brew upgrade bun / scoop update bun and stay consistent. See the official installation docs for the full matrix.
Verify: bun --version should print 1.4.x. If the number did not move, you upgraded a different install than the one on your PATH. Run which bun (or where bun on Windows) and compare it with where your package manager put its copy.
Check outdated packages with bun outdated
Run this before every update session. bun outdated (since Bun 1.1.26) prints one table with three version columns:
- Current - what is in your lockfile right now
- Update - the highest version inside your declared range
- Latest - the newest published version, majors included
The difference between the last two columns tells you which command you actually need. If everything you care about is in the Update column, plain bun update is enough. If the versions you want sit in Latest, you are looking at a --latest run (or an interactive pick).
bun outdatedOutput is shaped like this (versions will differ in your project):
Package Current Update Latest
ms 2.1.0 2.1.3 2.1.3
zod 3.22.4 3.25.0 4.1.12
typescript 5.4.5 5.9.2 5.9.2Useful filters:
bun outdated '@types/*' # glob: just the @types scope
bun outdated '!@types/*' # negation: everything except @types
bun outdated -r # every workspace in a monorepo
bun outdated --filter web # one workspaceVerify: when everything is current, bun outdated prints just the header and exits 0. That is your “nothing to do” signal. Non-zero or a full table means there is work. Reference: bun outdated docs, v1.1.26 release notes.
Read the columns before you update
The Update vs Latest split is the whole decision. In-range bumps are cheap and boring. Cross-major bumps are a branch, a CI run, and a changelog read. Do not let a single command decide for you.
Update all packages with bun update (in-range)
Plain bun update (alias: bun up) bumps your direct and transitive dependencies within the ranges already declared in package.json. A ^2.1.0 dependency can land on 2.1.3 or 2.9.9, never on 3.0.0.
bun updatePreview first if you want a dry look at what would move:
bun update --dry-runCorrection (2026)
Older versions of this guide said bun update leaves package.json alone. That has not been true since Bun ~1.2. A plain bun update rewrites the range floor for your direct dependencies and updates bun.lock to match. Trust git diff, not memory.
Concretely, this:
"dependencies": {
"ms": "^2.1.0"
}becomes this after bun update picks up 2.1.3:
"dependencies": {
"ms": "^2.1.3"
}The operator is preserved: ^ stays ^, ~ stays ~. Exact pins (2.1.0) and dist-tags (latest) are left as written. Transitive dependencies also move, and the lockfile records every change.
Verify:
git diff package.json bun.lockYou should see only the bumps you expected. If package.json shows a diff on a dependency you thought was pinned, that pin was probably a range after all.
What “broken” looks like: no output at all means nothing was out of range (or nothing is installed yet - run bun install first). A wall of churn in bun.lock with no package.json change is normal after registry metadata moves. The docs: bun update.
Update packages to the latest version with bun update --latest
This is the flag people actually want when they say “update everything to the newest”. --latest (short: -L) ignores your declared ranges for direct dependencies, installs the newest published versions including majors, and rewrites package.json to match.
bun update --latestEverything gets the cross-major treatment:
bun update --latest
# zod 3.22.4 -> 4.1.12 (major)
# typescript 5.4.5 -> 5.9.2 (same major, but outside your ^5.4 range floor rewrite)How the rewrite works: your "zod": "^3.22.4" becomes "zod": "^4.1.12" - same style of range, new floor. Transitive dependencies still respect their dependents’ ranges, and Bun never downgrades a dependency that is already ahead of latest (prerelease installs). Available since Bun 1.2 (release notes).
--force vs --latest: why --force does not update to latest
--force is not --latest
bun update --force means “always request the latest versions from the registry and reinstall all dependencies”. It is a cache and metadata refresh. It does not bypass your semver ranges. If bun update --force seems to “not update” packages, you wanted bun update --latest.
The two commands side by side:
bun update --force # refresh registry metadata, reinstall what your ranges allow
bun update --latest # cross majors, rewrite package.json to the newest versionsUpdate only production or dev dependencies
bun update --prod # or -P: dependencies only
bun update --dev # or -D: devDependencies only
bun update --no-optionalCombine freely: bun update --dev --latest is the usual “take dev tooling to the majors, leave runtime deps alone” move.
Glob patterns and exclusions
bun update '@types/*' --latest # all @types packages to their latest
bun update '!@babel/core' --latest # everything except @babel/core
bun update jquery@3 # move jquery within the 3.x lineOps note: majors break builds. Treat --latest as a branch-plus-CI event, not a Friday 17:00 command. After the majors land and CI is green, redeploy and smoke-test in production. For Astro sites, see how to deploy an Astro blog on Cloudflare.
Pick packages interactively with bun update -i
Since Bun 1.3, the interactive picker is built in. This replaces the bunx npm-check-updates -ui workaround this guide used to recommend.
bun update -iYou get a list of outdated direct dependencies grouped by dependencies, devDependencies, peer, and optional, with Current, Target, and Latest columns. Target is the in-range bump, Latest is the newest published. Color coding: red for major, yellow for minor, green for patch.
Keys worth knowing:
| Key | Action |
|---|---|
Space |
Toggle the highlighted package |
a |
Toggle all |
n |
Select none |
i |
Invert selection |
l |
Flip the highlighted row between target and latest |
Enter |
Apply |
Ctrl+C |
Cancel without changes |
The l key is the one people miss. It lets you take most packages in-range and push a single risky one all the way to latest, in one pass. (Bun 1.3 release notes.)
1.4 behavior change: -i needs a real terminal
Since Bun 1.4.0, bun update -i with a non-TTY stdin exits with code 1 instead of opening the picker. That means it will not work in CI scripts or piped input. Run it interactively, or use bun update --latest / targeted bun update pkg in automation. (Bun 1.4 release notes.)
You no longer need npm-check-updates
bun update -i and bun update --latest cover the old ncu workflow natively. If you want staged upgrades instead - for example, only minors for a while - ncu still does that well:
bunx npm-check-updates -t minor -uThat updates package.json to the newest minor versions only. See npm-check-updates if you need it.
Verify: same as always, git diff package.json bun.lock, then your build.
Targeted updates: single packages, globs, and transitive deps
You rarely need “all or nothing”. Named updates handle the surgical cases:
bun update zod # zod everywhere it appears, including transitively
bun update caniuse-lite # patch a nested/transitive dep
bun update '@types/*' # globTwo details that changed in Bun 1.4 and matter in real projects (release notes):
bun update pkgnow updates that package everywhere it appears, including transitive occurrences, not just the top-level entry. That is what you want for a security fix buried three levels deep.- Catalog dependencies (
catalog:in workspaces) get rewritten too.
A targeted update of a package that is not in your package.json - the caniuse-lite case - fixes the lockfile without adding a dependency to your manifest. That is the right tool for “my transitive dep has an advisory and I cannot wait for the parent to bump”.
Monorepos: --recursive and --filter
bun outdated -r # all workspaces
bun update -r # update every workspace
bun update --filter web # just the web workspace
bun update -r --latest # cross majors everywhere (careful)After a big multi-workspace pass, clean up:
bun dedupe # collapse duplicate versions in the lockfile
bun prune # drop packages not in package.json (new in 1.4)- One dependency needs a fix →
bun update pkg - Everything should move within ranges →
bun update - You want majors →
bun update --latest - You want to choose, row by row →
bun update -i - CI or a script → targeted commands or
--latest, never-i
Update global packages with bun update -g
Global tools are a separate story from project dependencies. Nothing here touches any project’s package.json.
All globals
bun update -gOne global
bun update -g typescriptPin to latest
bun add -g typescript@latestGlobals live in ~/.bun/install/global. A fresh bun add -g pkg is sometimes cleaner than an update when a tool changed its bin layout between majors.
Failure modes: “command not found” after a global update usually means the shim directory is missing from your PATH (~/.bun/bin). And remember the project wins: if a project has a local install of the same tool, bun run uses the local one even when a newer global exists.
How bun update rewrites package.json and bun.lock
This is the section for the “bun update package.json” search. The mechanics are simple once you have seen them once:
- In-range update bumps the range floor and preserves the operator.
^1.1.0becomes^1.2.0,~1.1.0becomes~1.1.5. Nothing crosses a major. --latestrewrites across majors to a same-style range:^1.1.0becomes^2.0.0if 2.0.0 is newest.- Exact pins and dist-tags stay as written unless you pass
--latest. --no-saveupdatesnode_modulesonly.package.jsonand the lockfile stay put. Useful for a quick local experiment you plan to revert.
On the lockfile side: text-based bun.lock replaced the binary bun.lockb in Bun 1.2, and Bun auto-migrates the old format. Since 1.3 it also migrates yarn.lock and pnpm-lock.yaml on install, so moving a project to Bun does not mean hand-writing a lockfile.
Before and after, the whole change can look like this:
- "ms": "^2.1.0"
+ "ms": "^2.1.3"Diff, do not dump
git diff package.json is the honest way to see what changed. Pasting a whole package.json into a chat window to figure out what moved is how people lose track of pins.
If you need staged upgrades - take the minors now, the majors next sprint - that is the one remaining use case for npm-check-updates:
bunx npm-check-updates -t minor -uSecurity-driven updates: bun audit and bun audit fix
Dependency updates are not only about features. bun audit lists known advisories against your tree:
bun auditSince Bun 1.4.0, you can act on the report directly (release notes):
bun audit fix --dry-run # preview first
bun audit fix # upgrade vulnerable packages to safe versions
bun audit fix --latest # allow major jumps when the fix requires oneThat removes the old “audit, open the advisory, hand-edit ranges” loop for most cases.
Two hardening notes worth acting on:
- Hold back brand-new packages. Bun supports a minimum release age, so a hijacked or freshly published malicious version never gets installed on day zero:
# bunfig.toml
[install]
minimumReleaseAge = 604800 # 7 days, in secondsThe CLI equivalent is --minimum-release-age. (Bun 1.3 notes.)
- Patched packages are held back by
bun update. If you usedbun patchand havepatchedDependencies, a normal update will not move that package - it would silently drop your patch.--latestandbun audit fixwill move it, which means you need to re-create the patch afterwards.
Re-patch after moving a patched dependency
bun update --latest or bun audit fix bumps a patched package: your patch no longer applies, and patchedDependencies will fail on the next install until you redo bun patch against the new version. Budget five minutes per patched package.
Ops cadence: none of this costs money. The tooling is Bun plus the public npm registry. What pays off is habit: a weekly bun outdated -r && bun update, or Dependabot/Renovate on the repo with bun.lock in the commit. Small, frequent updates are a one-coffee job. Annual updates are an incident.
Verify the update and roll back if needed
An update that you have not verified is just a modified lockfile. Four steps, every time:
git diff package.json bun.lock- eyeball what actually movedbun install --frozen-lockfile- proves the lockfile is complete and reproducible (the same check CI should run)bun run buildandbun testif you have tests- Smoke-test the app: dev server or preview, click through the critical path
If your build time matters to you - and after a dependency bump you will be running builds more than usual - check our Astro 7 build benchmark. Once the build is green again, Astro build speed optimization is the follow-up that pays off on every subsequent update cycle.
Rollback
Because you committed first:
git restore package.json bun.lock
bun installIf you are mid-branch and want to revert a single dependency instead of the whole pass:
bun update zod@3.22.4 # pin back to the old versionNo git? Keep copies of package.json and bun.lock before you start. That is the entire backup strategy; there is no hidden state elsewhere.
Common failure modes after a big update
- Build breaks on a major. Revert the one dependency (
bun update pkg@1.2.3) or pin it and schedule the migration. Do not pile a second--latestrun on top of a red build. - CI fails on lockfile mismatch.
package.jsonandbun.lockmust be committed together. A CIbun install --frozen-lockfilewill refuse a lockfile that does not match the manifest. - Build dies with OOM on a small VPS. Not a dependency bug: add swap, build elsewhere, or lower parallelism. 1-2 GB boxes run out of heap during Astro/Next builds after heavy devDependency updates.
patchedDependencieserrors after the update. Re-runbun patchagainst the new version of the package, or drop the patch if upstream fixed it.
Cost/ops note: the updates are free. Broken deploys are not. Run this on a branch, let CI build it, then merge. Ten minutes of pipeline beats a rollback in production.
FAQ: updating packages with Bun
Does bun update change package.json?
Yes. A plain bun update rewrites the version ranges of your direct dependencies within their declared bounds (^2.1.0 becomes ^2.1.3) and updates bun.lock. bun update --latest goes further and rewrites ranges across majors. Only --no-save leaves both files untouched.
git diff package.json bun.lock # always check after updatingWhat is the difference between bun update and bun update --latest?
bun update stays inside the ranges already in package.json: no majors, just the newest versions your manifest allows. bun update --latest (or -L) ignores those ranges for direct dependencies, installs the newest published versions including majors, and rewrites package.json to match.
bun update # in-range
bun update --latest # cross-majorHow do I update Bun itself?
Run bun upgrade if you installed Bun with the official script, brew upgrade bun on Homebrew, or scoop update bun on Scoop. Then confirm with bun --version - it should show 1.4.x.
bun upgrade && bun --versionHow do I update global packages with Bun?
bun update -g updates every global package, bun update -g pkg updates one, and bun add -g pkg@latest pins a global install to the newest version. Globals live in ~/.bun/install/global and no project’s package.json is affected.
bun update -gIs bun update --force the same as updating to the latest version?
No. --force means “always request the latest versions from the registry and reinstall all dependencies”. It refreshes cached metadata and reinstalls what your ranges already allow. To cross major versions and get the newest published packages, use bun update --latest.
bun update --latest # the one you wantedConclusions
The 2026 workflow is short: bun upgrade, bun outdated, then bun update / bun update -i / bun update --latest depending on how much churn you want. Verify with git diff and bun install --frozen-lockfile, and roll back with git restore package.json bun.lock && bun install when the build complains.
Two corrections to older advice are worth keeping: bun update does rewrite package.json, and --force is not the “give me the latest versions” flag - --latest is. With those straight, Bun’s built-in tools cover the whole update job without npm-check-updates or any other wrapper.
If you are starting a project from scratch, build a free Astro blog on Cloudflare and run this update loop on it from day one.
Build a free Astro blog on Cloudflare

