bun install installs peer dependencies by default
bun install installs peer dependencies by default, unless they are marked as optional in peerDependenciesMeta. Peer dependencies can be marked optional by setting optional: true in the peerDependenciesMeta object for that package.
bun install default behavior
bun install installs a Node.js-compatible node_modules folder and can be used as a drop-in replacement for npm install in Node.js projects without any code changes.
bun i shorthand for bun install
bun i is the shorthand for bun install.
bun install .npmrc compatibility
bun install reads npm registry configuration from npm's .npmrc file, allowing the same configuration to be used for both npm and Bun.
bun install hardlinks on Windows and Linux
On Windows and Linux, bun install uses hardlinks to save disk space and speed up installs.
Minimum release age in bunfig.toml
In bunfig.toml, set 'minimumReleaseAge = 259200' (in seconds) to configure minimum age for packages. Set 'minimumReleaseAgeExcludes = ["@types/node", "typescript"]' to exclude trusted packages from the age gate.
bun install default behavior and lockfile
Running 'bun install' without arguments installs all dependencies, devDependencies, and optionalDependencies. Bun installs peerDependencies by default. It runs the project's {pre|post}install and {pre|post}prepare scripts at the appropriate time. It writes a bun.lock lockfile to the project root. Bun does not execute lifecycle scripts of installed dependencies unless they are trusted for security reasons.
bun install with specific package and version syntax
The syntax 'bun install react' installs a package with the latest version. The syntax 'bun install react@19.1.1' specifies an exact version. The syntax 'bun install react@latest' specifies a version tag.
--frozen-lockfile flag purpose and behavior
The --frozen-lockfile flag installs the exact versions specified in the lockfile and does not update it. If package.json disagrees with bun.lock, Bun exits with an error. This is used for reproducible installs. If there is no lockfile at all, --frozen-lockfile installs from package.json without writing one. It works on a pruned monorepo checkout; if a workspace listed in bun.lock is missing its package.json on disk, Bun skips it and does not install its exclusive dependencies. Bun does not enable --frozen-lockfile automatically in CI; pass the flag or use 'bun ci'. To validate the lockfile without installing, use 'bun install --frozen-lockfile --dry-run'.
bun install --production flag behavior
--production installs without devDependencies. It implies --frozen-lockfile. It only controls what gets installed; devDependencies already in node_modules from an earlier install stay there. Use 'bun prune --production' to remove them.
bun install logging flags
Use 'bun install --verbose' for debug logging. Use 'bun install --silent' for no logging.
Control concurrent lifecycle scripts with --concurrent-scripts
Use the --concurrent-scripts flag to adjust the maximum number of concurrent scripts during installation. The default is two times the reported cpu count or GOMAXPROCS. Example: 'bun install --concurrent-scripts 5'.
Skip postinstall optimizations with environment variables
Set 'BUN_FEATURE_FLAG_DISABLE_NATIVE_DEPENDENCY_LINKER=1 bun install' to disable optimizations for popular packages like esbuild and sharp. Set 'BUN_FEATURE_FLAG_DISABLE_IGNORE_SCRIPTS=1 bun install' to disable automatic script optimizations.
bun install --global flag for global package installation
Use 'bun install --global' or 'bun install -g' to install a package globally. Example: 'bun install --global cowsay' installs cowsay globally.
bun install --omit flag to exclude dependency types
Use 'bun install --omit dev' to exclude devDependencies from installation. This applies to the root package and workspaces if they exist; transitive dependencies will not have devDependencies. Use 'bun install --omit=dev --omit=peer --omit=optional' to specify multiple exclusions.
bun install --dry-run flag
Use 'bun install --dry-run' to perform a dry run without installing anything.
Installation strategies: hoisted vs isolated
Bun supports two installation strategies controlled with --linker flag. 'hoisted' uses the traditional npm/Yarn approach that flattens dependencies into a shared node_modules directory. 'isolated' uses a pnpm-like approach that creates strict dependency isolation in node_modules/.bun/ with symlinks, preventing phantom dependencies.
Default linker strategy depends on project type and lockfile
For new workspaces/monorepos, the default linker is 'isolated' to prevent phantom dependencies. For new single-package projects, the default is 'hoisted' for traditional npm behavior. For existing projects made pre-v1.3.2, the default is 'hoisted' to preserve backward compatibility. A configVersion field in the lockfile controls the default.
Minimum release age configuration
Use 'bun add @types/bun --minimum-release-age 259200' where 259200 seconds = 3 days to filter out package versions published more recently than the threshold during installation. This protects against supply chain attacks.
Minimum release age filter behavior
The minimum age filter only affects new package resolution; existing packages in bun.lock remain unchanged. Bun filters all dependencies (direct and transitive) to meet the age requirement. When the age gate blocks versions, a stability check detects rapid bugfix patterns by searching up to 7 days past the age gate. If multiple versions were published close together just outside the age gate, Bun extends the filter to skip potentially unstable versions. Exact version requests bypass the stability check but still respect the age gate. Bun treats versions without a time field as passing the age check.
bunfig.toml location search order
Bun looks for bunfig.toml in this order: 1) $XDG_CONFIG_HOME/.bunfig.toml or $HOME/.bunfig.toml (global), 2) ./bunfig.toml (project). If Bun finds both, it loads both. Keys set in the project's bunfig.toml override the same keys in the global file.
bunfig.toml install configuration options
The [install] section in bunfig.toml supports: optional=true (whether to install optionalDependencies), dev=true (whether to install devDependencies), peer=true (whether to install peerDependencies), production=false (equivalent to --production flag), saveTextLockfile=true (equivalent to --save-text-lockfile flag), frozenLockfile=false (equivalent to --frozen-lockfile flag), dryRun=false (equivalent to --dry-run flag), concurrentScripts=16 (equivalent to --concurrent-scripts flag, default is cpu count or GOMAXPROCS x2), linker="hoisted" (installation strategy: "hoisted" or "isolated"), minimumReleaseAge=259200 (in seconds), minimumReleaseAgeExcludes=[list of packages].
Environment variables for bun install configuration
Environment variables take priority over bunfig.toml. BUN_CONFIG_REGISTRY sets an npm registry (default: https://registry.npmjs.org). BUN_CONFIG_TOKEN sets an auth token for the default registry. BUN_CONFIG_YARN_LOCKFILE saves a Yarn v1-style yarn.lock. BUN_CONFIG_SKIP_SAVE_LOCKFILE doesn't save a lockfile. BUN_CONFIG_SKIP_LOAD_LOCKFILE doesn't load a lockfile. BUN_CONFIG_SKIP_INSTALL_PACKAGES doesn't install any packages.
bun install backend options
The --backend flag controls the file installation method. 'hardlink' is the default on Linux and Windows. 'clonefile' is the default on macOS. 'clonefile_each_dir' is similar to clonefile but clones each file individually per directory (macOS only, slower). 'copyfile' is the fallback used when above methods fail (slowest). 'symlink' is typically only used for file: dependencies internally. When unavailable or on error, clonefile and hardlink fall back to platform-specific copying.
Platform-specific CPU and OS flags for bun install
Use 'bun install --cpu=x64 --os=linux' to override the target platform for package selection. Accepted values for --cpu: arm, arm64, ia32, mips, mipsel, ppc, ppc64, s390, s390x, x32, x64. Accepted values for --os: aix, darwin, freebsd, linux, openbsd, sunos, win32, android. These flags install packages for the specified platform instead of the current system.
How bun install checks existing packages
When node_modules folder exists, Bun decides whether to install a package by checking that the 'name' and 'version' in its package.json at the expected node_modules location match the expected name and version. It uses a custom JSON parser which stops parsing as soon as it finds 'name' and 'version'.
bun ci command purpose and usage
Use 'bun ci' for CI/CD environments to enforce reproducible builds. It is equivalent to 'bun install --frozen-lockfile'. It installs exact versions from bun.lock and fails if package.json doesn't match the lockfile. You must commit bun.lock to version control to use bun ci.
Platform-specific dependencies handling
Bun stores normalized cpu and os values from npm in the lockfile along with resolved packages. It skips downloading, extracting, and installing packages disabled for the current target at runtime. The lockfile doesn't change between platforms/architectures even if the packages ultimately installed do change.
Peer dependencies installation
Bun handles peer dependencies like Yarn: 'bun install' installs them automatically. If the dependency is marked optional in peerDependenciesMeta, Bun uses an existing dependency if possible.
bun install examples with specific versions
Example: 'bun install react' installs the latest version of react. Example: 'bun install react@19.1.1' installs version 19.1.1 of react. Example: 'bun install react@latest' installs the version tagged 'latest' of react.
Isolated installs definition and benefits
Isolated installs create a non-hoisted dependency structure where packages can only access their explicitly declared dependencies. Key benefits include: prevents phantom dependencies (packages cannot import undeclared dependencies), provides deterministic resolution (same dependency tree regardless of other installed packages), better for monorepos by preventing cross-contamination between packages, and enables reproducible builds with more predictable resolution behavior.
Default linker behavior by configVersion and workspace status
The default linker strategy depends on the lockfile configVersion and whether the project uses workspaces: configVersion 1 with workspaces uses isolated linker by default; configVersion 1 without workspaces uses hoisted linker; configVersion 0 uses hoisted linker regardless of workspace status. New projects default to configVersion 1. Existing Bun projects made pre-v1.3.2 use configVersion 0 to preserve hoisted linker default. Migrations from pnpm use configVersion 1 (isolated in workspaces), while migrations from npm or yarn use configVersion 0 (hoisted).
--linker flag for specifying installation strategy
The --linker flag specifies whether to use isolated or hoisted installation strategy. Use 'bun install --linker isolated' for strict dependency isolation or 'bun install --linker hoisted' for traditional hoisted installs. The flag can override the default linker strategy determined by configVersion and workspace configuration.
Setting linker strategy in bunfig.toml
Set the default linker strategy in bunfig.toml or globally in $HOME/.bunfig.toml using the [install] section with 'linker = "isolated"' or 'linker = "hoisted"'. This configuration applies to all installs in that project or globally if set in the home directory.
Isolated install directory structure with .bun store
Isolated installs create a two-tier structure: a central .bun/ directory under node_modules contains versioned package installations (e.g., package@1.0.0/) with their own node_modules subdirectories. Scoped packages use + instead of / in directory names (e.g., @scope+package@2.1.0/). Top-level node_modules contains symlinks pointing to the central store entries. The store directory name after @ represents the package's resolution; for registry packages this is the version, for folder/tarball/git/GitHub dependencies it derives from the path or URL and commit. Bun writes at most 80 bytes of resolution: if longer than 63 bytes, it cuts to 63 bytes and appends + with 16 hex digits derived from the full resolution.
Isolated install resolution algorithm
The resolution algorithm works as follows: all packages are installed in node_modules/.bun/package@version/ directories (central store), top-level node_modules contains symlinks pointing to the central store, complex peer dependencies create specialized directory names, packages with identical package IDs and peer dependency sets are shared (deduplication), and on later installs Bun reuses existing store entries and re-points symlinks if a dependency was re-resolved. Store entries no longer referenced remain until 'bun prune' is run.
install.hoist = false for strict resolution
By default, isolated installs create node_modules/.bun/node_modules, a fallback directory with symlinks to every installed package. Setting install.hoist = false (or hoist=false in .npmrc) skips creating this fallback directory, so undeclared imports fail instead of depending on what else happens to be installed. However, packages linked in the root node_modules (direct dependencies, publicHoistPattern matches, and workspace packages) remain resolvable from any store package. Set this in bunfig.toml under [install] section or in .npmrc.
install.hoistPattern and install.publicHoistPattern controls
install.hoistPattern and install.publicHoistPattern offer pattern-based control over the fallback directory (node_modules/.bun/node_modules) and the root node_modules respectively, allowing selective hoisting of packages matching patterns.
Backend strategies for materializing store entries
When the global store is disabled (default) or an entry is not eligible for it, Bun materializes the entry under the project using one of three strategies: Clonefile (macOS, copy-on-write filesystem clones), Hardlink (Linux/Windows, hardlinks to save disk space), or Copyfile (fallback when others are unavailable, full file copies).
Peer dependency handling in isolated installs
Isolated installs encode peer dependencies in the store path. A package with peer dependencies creates specialized directory names that include both the package version and its peer dependency versions (e.g., package@1.0.0_react@18.2.0/), so each unique peer dependency combination gets its own installation.
Verbose logging for debugging isolated installs
Enable verbose logging with 'bun install --linker isolated --verbose' to see what an install is doing. The verbose output shows store entry creation, symlink operations, peer dependency resolution, and deduplication decisions.
Path length considerations for isolated installs on Windows
The path of a package inside the store is at most 34 + 2 * <name length> + 80 characters longer than the project directory (17 more when the package has peer dependencies). On Windows, paths longer than 260 characters work for Bun itself but not as the working directory of the package's lifecycle scripts.
Compatibility issues with isolated installs
Some packages may not work correctly with isolated installs due to hardcoded paths that assume a flat node_modules structure, dynamic imports that don't follow Node.js resolution, or build tools that scan node_modules directly. If compatibility issues occur, switch to hoisted mode with 'bun install --linker hoisted' for specific projects.
When to use isolated installs versus hoisted installs
Use isolated installs when working in monorepos with multiple packages, strict dependency management is required, preventing phantom dependencies is important, or building libraries that need deterministic dependencies. Use hoisted installs when working with legacy code that assumes flat node_modules, compatibility with existing build tools is required, working in environments where symlinks are not well supported, or preferring traditional npm behavior.
Cache reuse during installation
When installing a package, if the cache already contains a version in the range specified by package.json, Bun uses the cached copy instead of downloading it again.
Installation completion check
When the node_modules folder exists, before installing, Bun checks that node_modules contains all expected packages with appropriate versions. If so, bun install completes. Bun uses a custom JSON parser which stops parsing as soon as it finds "name" and "version".
Package retrieval when missing from node_modules
If a package is missing or has a version incompatible with package.json, Bun checks for a compatible module in the cache. If the cache has one, Bun installs it into node_modules. Otherwise, Bun downloads the package from the registry, then installs it.
--backend flag for installation strategies
The --backend flag controls which copying strategy Bun uses and is respected by all of Bun's package management commands. The available options are: hardlink (default on Linux and Windows), clonefile (default on macOS), clonefile_each_dir (clones each file individually per directory, only on macOS, tends to perform slower than clonefile), copyfile (fallback when others fail, slowest option, uses fcopyfile() on macOS and copy_file_range() on Linux), and symlink (symlinks each file instead of copying it, only hoisted installs use it by default, applies to every package when explicitly set, Windows ignores this flag).
Symlink backend behavior and node_modules resolution
When installing with --backend=symlink, Node.js does not resolve node_modules of dependencies unless each dependency has its own node_modules folder or you pass --preserve-symlinks to node. Without the --backend=symlink flag, Bun uses symlinks only for file: dependencies outside the project directory (for example file:../foo) and for transitive file: dependencies.
Example bun install with symlink backend
bun install --backend symlink
node --preserve-symlinks ./foo.js
This example shows using the symlink backend for installation and then running Node.js with the --preserve-symlinks flag to properly resolve symlinked node_modules.
Bun runtime symlink support
Bun's runtime also supports the --preserve-symlinks flag for resolving symlinked dependencies.
Bun does not execute lifecycle scripts by default
Bun is "default-secure" and does not execute arbitrary lifecycle scripts by default, unlike other npm clients. This is a security measure to prevent running untrusted code.
Common package lifecycle scripts
Common lifecycle scripts defined in package.json are: preinstall (runs before the package is installed), postinstall (runs after the package is installed), preuninstall (runs before the package is uninstalled), and prepublishOnly (runs before the package is published). Many other lifecycle scripts exist beyond these.
trustedDependencies field enables lifecycle scripts
To allow lifecycle scripts for specific packages, add the package names to the trustedDependencies array in package.json. After adding a package to trustedDependencies, install or re-install it for Bun to run its lifecycle scripts.
trustedDependencies replaces the default list
When trustedDependencies is defined in package.json, it replaces the default list rather than extending it. If trustedDependencies is omitted, Bun uses its built-in default list for npm sources only. If trustedDependencies is explicitly set to a list like ["pkg-a"], only those packages are allowed to run lifecycle scripts. If trustedDependencies is set to an empty array [], no packages are allowed to run lifecycle scripts, including those on the default list.
Default trusted dependencies only apply to npm sources
The default trusted dependencies list only applies to packages installed from npm. For packages from other sources such as file:, link:, git:, or github: dependencies, you must explicitly add them to trustedDependencies to run their lifecycle scripts, even if the package name matches an entry in the default list. This prevents malicious packages from spoofing trusted package names through local file paths or git repositories.
--ignore-scripts flag disables all lifecycle scripts
The --ignore-scripts flag can be passed to bun install to disable lifecycle scripts for all packages.
Skip lifecycle scripts with ignoreScripts in bunfig.toml
To make --ignore-scripts the default for a project, set install.ignoreScripts = true in bunfig.toml under the [install] section.
Skip lifecycle scripts with ignore-scripts in .npmrc
To make lifecycle script skipping the default for a project, set ignore-scripts=true in .npmrc.
postinstall script use case for native binaries
The postinstall script is widely used to build or install platform-specific binaries for packages implemented as native Node.js add-ons. For example, node-sass uses postinstall to build a native binary for Sass.