Configuring a development environment for Bun can take 10-30 minutes depending on your internet connection and computer speed. You will need ~10GB of free disk space for the repository and build artifacts.If you are using Windows, see Building Windows.
Bun is written in Rust and requires a specific nightly toolchain (pinned in rust-toolchain.toml). Install Rust with rustup rather than your distro’s rust/cargo packages — the build scripts use rustup to automatically install and update the pinned nightly:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
Before starting, install a release build of Bun: the build uses Bun’s bundler to transpile and minify code, and to run the code generation scripts.
ccache caches compilation artifacts, which speeds up rebuilds:
# For macOSbrew install ccache# For Ubuntu/Debiansudo apt install ccache# For Archsudo pacman -S ccache# For Fedorasudo dnf install ccache# For openSUSEsudo zypper install ccache
The build scripts detect and use ccache automatically if it’s available. Check cache statistics with ccache --show-stats.
Bun requires LLVM 21.1.8 (clang is part of LLVM). The build system enforces this version: a mismatched version causes memory allocation failures at runtime. In most cases, you can install LLVM through your system package manager:
brew install llvm@21
If none of these work, install it manually.Make sure Clang/LLVM 21 is in your path:
which clang-21
If not, add it manually:
# use fish_add_path if you're using fish# use path+="$(brew --prefix llvm@21)/bin" if you are using zshexport PATH="$(brew --prefix llvm@21)/bin:$PATH"
⚠️ On Ubuntu <= 20.04, you may need to install the C++ standard library separately. See the troubleshooting section.
VSCode is the recommended IDE for working on Bun; the repository includes configuration for it. After opening the repository, run Extensions: Show Recommended Extensions to install the recommended extensions for Rust and C++. rust-analyzer picks up the workspace Cargo.toml automatically and uses the pinned toolchain in rust-toolchain.toml for analysis, so diagnostics match the build.If you use a different editor, point rust-analyzer (or your editor’s Rust plugin) at the repo root — the Cargo workspace and rust-toolchain.toml are discovered automatically.We recommend adding ./build/debug to your $PATH so that you can run bun-debug in your terminal:
The bd package.json script compiles and runs a debug build of Bun, only printing the output of the build process if it fails.
bun bd <args>bun bd test foo.test.tsbun bd ./foo.ts
A full debug build can take a few minutes when Rust or C++ has changed; cargo’s incremental compilation makes subsequent Rust-only rebuilds much faster. If your development workflow is “change one line, save, rebuild”, you will still spend too much time waiting for the link step. Instead:
Batch up your changes
Use cargo check -p <crate> (or bun run rust:check for the whole workspace) to type-check Rust changes without linking. bun run watch runs cargo check on every save.
Ensure rust-analyzer is running for inline diagnostics (the recommended VSCode extensions set this up)
Prefer using the debugger (“CodeLLDB” in VSCode) to step through the code.
Use debug logs. BUN_DEBUG_<scope>=1 enables debug logging for the corresponding declare_scope!(<scope>, ...) / scoped_log!(<scope>, ...) logs. Set BUN_DEBUG_QUIET_LOGS=1 to disable all debug logging that isn’t explicitly enabled. To dump debug logs into a file, set BUN_DEBUG=<path-to-file>.log. Debug logs are removed in release builds.
src/js/**.ts changes rebuild almost instantly. Single-crate Rust changes and C++ changes are incremental; only the final link is unavoidable.
Bun’s build process runs several code generation scripts automatically when certain files change:
./src/codegen/generate-jssink.ts — Generates build/debug/codegen/JSSink.cpp, build/debug/codegen/JSSink.h which implement various classes for interfacing with ReadableStream. This is internally how FileSink, ArrayBufferSink, "type": "direct" streams and other code related to streams work.
./src/codegen/generate-classes.ts — Generates Rust & C++ bindings for JavaScriptCore classes implemented in Rust. **/*.classes.ts files define the interfaces for classes, methods, prototypes, and getters/setters; the code generator reads them to generate the boilerplate that implements the JavaScript objects in C++ and wires them up to Rust.
./src/codegen/cppbind.ts — Scans the C++ bindings for functions marked with an export attribute and generates automatic Rust FFI wrappers (cpp.rs) for them.
./src/codegen/bundle-modules.ts — Bundles built-in modules like node:fs, bun:ffi into files included in the final binary. In development, these can be reloaded without rebuilding native code (you still need to run bun run build, but it re-reads the transpiled files from disk afterwards). In release builds, these are embedded into the binary.
./src/codegen/bundle-functions.ts — Bundles globally-accessible functions implemented in JavaScript/TypeScript like ReadableStream and WritableStream. These are used similarly to the builtin modules, but the output more closely aligns with what WebKit/Safari does for Safari’s built-in functions, so implementations can be copy-pasted from WebKit as a starting point.
Certain modules like node:fs, node:stream, bun:sqlite, and ws are implemented in JavaScript. These live in src/js/{node,bun,thirdparty} files and are pre-bundled using Bun.
You can run the release build from a pull request without building it locally, which is useful for manually testing changes before they are merged.Use the bun-pr npm package:
bunx bun-pr <pr-number>bunx bun-pr <branch-name>bunx bun-pr "https://github.com/oven-sh/bun/pull/1234566"bunx bun-pr --asan <pr-number> # Linux x64 only
bun-pr downloads the release build from the pull request’s GitHub Actions artifacts and adds it to $PATH as bun-${pr-number}, so you can run it directly:
bun-1234566 --version
You may need the gh CLI installed to authenticate with GitHub.
Bun’s CI runs on BuildKite. Install the BuildKite CLI (brew install buildkite/buildkite/bk) and set BUILDKITE_API_TOKEN to a read-scoped API token. The repo includes a .bk.yaml so bk commands default to the bun pipeline.
bun run ci:status # progress summary for the current branch's latest buildbun run ci:errors # rendered test-failure output, tagged [new] vs [also on main]bun run ci:logs # save full logs for each failed job to ./tmp/ci-<build>/bun run ci:watch # watch until the build finishesbun run ci:find # print the build number (compose with raw `bk`)
All of these accept a target: #1234 (PR number), a PR URL, a branch name, or a build number. Without one they use the current git branch.
AddressSanitizer helps find memory issues, and is enabled by default in debug builds of Bun on Linux and macOS. This covers the Rust code, the C++ bindings, and all dependencies. It makes the build take about 2x longer; if that’s stopping you from being productive you can disable it with bun run build:debug:noasan (or pass --asan=off to scripts/build.ts), but generally we recommend batching your changes up between builds.To build a release build with AddressSanitizer, run:
bun run build:asan
CI runs the test suite with at least one target built with AddressSanitizer.
WebKit is not cloned by default (to save time and disk space). To clone and build WebKit locally, run:
# Clone WebKit into ./vendor/WebKitgit clone https://github.com/oven-sh/WebKit vendor/WebKit# Check out the commit hash specified in WEBKIT_VERSION in scripts/build/deps/webkit.tsgit -C vendor/WebKit checkout <commit_hash># Build bun with the local JSC build — this automatically configures and builds JSCbun run build:local
bun run build:local handles everything: configuring JSC, building JSC, and building Bun. On subsequent runs, JSC rebuilds incrementally if any WebKit sources changed. ninja -Cbuild/debug-local also works after the first build, and builds both Bun and JSC.The build output goes to ./build/debug-local (instead of ./build/debug), so you’ll need to update a couple of places:
The first line in src/js/builtins.d.ts
The CompilationDatabase line in .clangd config should be CompilationDatabase: build/debug-local
In .vscode/launch.json, many configurations use ./build/debug/, change them as you see fit
The WebKit folder, including build artifacts, is 8GB+ in size.If you are using a JSC debug build with VSCode, run the C/C++: Select a Configuration command so IntelliSense finds the debug headers.If you make changes to Bun’s WebKit fork, you also have to change WEBKIT_VERSION in scripts/build/deps/webkit.ts to point to your commit hash.
⚠️ These instructions are specific to Ubuntu. The same issues are unlikely on other Linux distributions.
Clang uses libstdc++, the C++ standard library implementation provided by the GNU Compiler Collection (GCC), by default. Clang can link against libc++ instead, but that requires explicitly passing the -stdlib flag.Bun relies on C++20 features like std::span, which are not available in GCC versions lower than 11. As a result, running make setup may fail with the following error:
fatal error: 'span' file not found#include <span> ^~~~~~
The issue may also surface when first running bun setup, with Clang unable to compile a simple program:
The C++ compiler "/usr/bin/clang++-21"is not able to compile a simple test program.
To fix the error, update GCC to version 11. It may be available in your distribution’s official repositories; otherwise, add a third-party repository that provides GCC 11 packages:
sudo apt updatesudo apt install gcc-11 g++-11# If the above command fails with `Unable to locate package gcc-11` we need# to add the APT repositorysudo add-apt-repository -y ppa:ubuntu-toolchain-r/test# Now run `apt install` againsudo apt install gcc-11 g++-11
Bun defaults to linking libatomic statically, as not all systems have it. If you are building on a distro that does not have a static libatomic available, enable dynamic linking with:
bun run build -DUSE_STATIC_LIBATOMIC=OFF
The built version of Bun may not work on other systems if compiled this way.
Disable logging: BUN_DEBUG_QUIET_LOGS=1 bun-debug ... (to disable all debug logging)
Enable logging for a specific scope: BUN_DEBUG_EventLoop=1 bun-debug ... (to enable scoped_log!(EventLoop, ...) output)
Bun transpiles every file it runs. To see the actual executed source in a debug build, find it in /tmp/bun-debug-src/...path/to/file. For example, the transpiled version of /home/bun/index.ts is in /tmp/bun-debug-src/home/bun/index.ts