bun add: install a package with Bun (dev, exact, global)
- tool
- Bun
- versions
- Bun 1.2+
- os
- linux, macos, windows
bun add express adds a package to the dependencies of package.json, installs it into node_modules and updates the bun.lock lockfile, the Bun equivalent of npm install express. Add -d (--dev) for devDependencies, --exact to save the version without a caret, and -g to install a command-line tool globally.
Add a dependency and a dev dependency
export NO_COLOR=1
echo '{ "name": "app", "private": true }' > package.json
npx --yes bun@1 add is-odd@3.0.1 2>&1 | grep -E "^installed"
npx --yes bun@1 add -d --exact is-number@7.0.0 2>&1 | grep -E "^installed"
cat package.json
ls bun.lock node_modules/is-odd/package.jsonOutput
installed is-odd@3.0.1
installed is-number@7.0.0
{
"name": "app",
"private": true,
"dependencies": {
"is-odd": "3.0.1"
},
"devDependencies": {
"is-number": "7.0.0"
}
}
bun.lock
node_modules/is-odd/package.jsonBun prints one installed line per package you asked for, writes bun.lock (a text lockfile; Bun 1.1 and older wrote the binary bun.lockb) and creates node_modules. The example starts Bun through npx bun@1 so it needs only Node.js; with Bun installed the commands are bun add is-odd@3.0.1 and bun add -d --exact is-number@7.0.0.
Variants
| Command | What it does |
|---|---|
| bun add react | Latest version, saved as a caret range (^x.y.z) |
| bun add react@18.3.1 | A specific version |
| bun add react@next | A dist-tag |
| bun add -d typescript | devDependencies (long form --dev) |
| bun add --exact zod | Save the exact version instead of a range (-E) |
| bun add --optional sharp | optionalDependencies |
| bun add --peer react | peerDependencies |
| bun add --trust esbuild | Add the package and allow its lifecycle scripts (trustedDependencies) |
| bun add -g serve | Install a CLI globally (into ~/.bun/bin by default) |
| bun add github:user/repo | A Git dependency; git+https and tarball URLs also work |
| bun remove is-odd | Remove the package from package.json, node_modules and bun.lock |
Defaults and pitfalls
- Ranges. Without a version, Bun saves a caret range like npm does; an exact version you type (
is-odd@3.0.1) is saved as written, as the example shows. Setexact = trueunder[install]inbunfig.tomlto save exact versions by default. - Lifecycle scripts. Dependencies'
postinstallscripts are not run unless the package is listed intrustedDependencies; packages that build native code may look installed but not work until you trust them. - Version not found. A version or tag that does not exist fails with No version matching found for specifier; check the published versions with
bun info pkgornpm view pkg versions. - CI. Use
bun install --frozen-lockfilein CI, notbun add. It fails with lockfile had changes, but lockfile is frozen whenpackage.jsonandbun.lockdisagree. - Workspaces. In a monorepo, run
bun addinside the package folder, or use--filter, to add the dependency to that package rather than to the root.
How to confirm it worked
cat package.json shows the new entry in the right section, bun pm ls lists the installed top-level packages with their versions, and ls node_modules/PACKAGE shows that it is on disk. Running bun install again without changes should report that nothing needs to be installed.
FAQ
What is the difference between bun add and bun install?
bun add pkg adds a new dependency to package.json and installs it. bun install with no package name installs everything already listed in package.json and the lockfile, for example after a fresh clone.
Does bun add work with an existing package-lock.json?
Yes. When a project has no bun.lock yet, Bun can read package-lock.json or yarn.lock to keep the versions you already use, then writes its own bun.lock. Commit bun.lock once the project uses Bun.
Why does bun add not run the postinstall script of a package?
Bun does not run lifecycle scripts of dependencies by default, for security. List packages you trust in trustedDependencies in package.json, or run bun pm trust package, to allow their scripts.