Contributing
Contributing to Bun
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.
Using Nix (Alternative)#
The repository includes a Nix flake as an alternative to installing dependencies manually:
nix develop
bun bdnix develop provides all dependencies in an isolated, reproducible environment without requiring sudo.
Install Dependencies (Manual)#
Using your system's package manager, install Bun's dependencies:
brew install automake ccache cmake coreutils gnu-sed go icu4c libiconv libtool ninja pkg-config rustup-init ruby zstdsudo apt install curl wget lsb-release software-properties-common cmake git golang libtool ninja-build pkg-config ruby-full python3 perl zstd xz-utilssudo pacman -S base-devel cmake git go libiconv libtool make ninja pkg-config python rustup sed unzip ruby perl zstdsudo dnf install clang21 llvm21 lld21 cmake git golang libtool ninja-build pkg-config ruby python3 perl zstd libatomic-static libstdc++-static sed unzip which libicu-devel 'perl(Math::BigInt)'sudo zypper install go cmake ninja automake git icu rustup ruby perl python3 zstdBun 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 | shBefore 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.
curl -fsSL https://bun.com/install | bashnpm install -g bunbrew tap oven-sh/bun
brew install bunOptional: Install ccache#
ccache caches compilation artifacts, which speeds up rebuilds:
# For macOS
brew install ccache
# For Ubuntu/Debian
sudo apt install ccache
# For Arch
sudo pacman -S ccache
# For Fedora
sudo dnf install ccache
# For openSUSE
sudo zypper install ccacheThe build scripts detect and use ccache automatically if it's available. Check cache statistics with ccache --show-stats.
Install LLVM#
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# LLVM has an automatic installation script that is compatible with all versions of Ubuntu
wget https://apt.llvm.org/llvm.sh -O - | sudo bash -s -- 21 allsudo pacman -S llvm clang lldsudo dnf install llvm clang lld-develsudo zypper install clang21 lld21 llvm21If none of these work, install it manually.
Make sure Clang/LLVM 21 is in your path:
which clang-21If not, add it manually:
# use fish_add_path if you're using fish
# use path+="$(brew --prefix llvm@21)/bin" if you are using zsh
export PATH="$(brew --prefix llvm@21)/bin:$PATH"# use fish_add_path if you're using fish
export PATH="$PATH:/usr/lib/llvm21/bin"⚠️ On Ubuntu <= 20.04, you may need to install the C++ standard library separately. See the troubleshooting section.
Building Bun#
After cloning the repository, run the following command to build. This can take a while: it downloads and builds dependencies.
bun run buildThe binary is at ./build/debug/bun-debug. It is recommended to add this to your $PATH. To verify the build worked, print its version:
build/debug/bun-debug --version
x.y.z_debugVSCode#
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:
bun-debugRunning debug builds#
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.ts
bun bd ./foo.tsA 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>(orbun run rust:checkfor the whole workspace) to type-check Rust changes without linking.bun run watchrunscargo checkon 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>=1enables debug logging for the correspondingdeclare_scope!(<scope>, ...)/scoped_log!(<scope>, ...)logs. SetBUN_DEBUG_QUIET_LOGS=1to disable all debug logging that isn't explicitly enabled. To dump debug logs into a file, setBUN_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.
Code generation scripts#
Bun's build process runs several code generation scripts automatically when certain files change:
./src/codegen/generate-jssink.ts-- Generatesbuild/debug/codegen/JSSink.cpp,build/debug/codegen/JSSink.hwhich implement various classes for interfacing withReadableStream. This is internally howFileSink,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.tsfiles 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 likenode:fs,bun:ffiinto files included in the final binary. In development, these can be reloaded without rebuilding native code (you still need to runbun 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 likeReadableStreamandWritableStream. 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.
Modifying ESM modules#
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.
Release build#
To compile a release build of Bun, run:
bun run build:releaseThe binaries are at ./build/release/bun and ./build/release/bun-profile.
Download release build from pull requests#
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 onlybun-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 --versionYou may need the gh CLI installed to authenticate with GitHub.
Viewing CI failures from the terminal#
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 build
bun 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 finishes
bun 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#
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:asanCI runs the test suite with at least one target built with AddressSanitizer.
Building WebKit locally + Debug mode of JSC#
JavaScriptCore (with WTF, bmalloc and, off macOS, ICU) is compiled from source as part of the normal build, like every other dependency: the build fetches just Source/{bmalloc,WTF,JavaScriptCore} of the pinned WEBKIT_VERSION (a sparse fetch of ~35 MB, seconds — not a 12 GB clone) into vendor/WebKit and compiles it in its own ninja graph. It needs ruby, python3 and perl for JSC's code generators, zstd for packing the ICU data (targets other than macOS), and, when cross-compiling a macOS target from Linux, flex and bison for the MIG host tool (on a Mac, Xcode's mig is used). --webkit=prebuilt opts into downloading oven-sh/WebKit's release tarball for that commit instead of compiling it:
bun run build --webkit=prebuiltTo work on JSC itself you want a full clone with history instead. Clone it anywhere outside vendor/ (that directory is the build's own fetch of the pinned commit), point $BUN_WEBKIT_PATH at it, and build with bun run build:local — the same build with --local-deps=WebKit=$BUN_WEBKIT_PATH, compiling whatever is checked out in your clone instead of the pinned commit, into build/debug-local (any profile takes --local-deps=WebKit=<path> directly, like every other dep):
# Clone WebKit next to bun (anywhere outside vendor/) and tell the build where it is
git clone https://github.com/oven-sh/WebKit ../WebKit
export BUN_WEBKIT_PATH=$PWD/../WebKit
# Check out the version pinned in WEBKIT_VERSION (scripts/build/deps/webkit.ts)
git -C ../WebKit checkout <WEBKIT_VERSION>
# Build bun with JSC compiled from that clone
bun run build:localbun run build:local builds JSC and Bun in one ninja graph; on subsequent runs only the WebKit files you changed (and what includes them) recompile. ninja -Cbuild/debug-local also works after the first build. bun run build:local --target=jsc (or --target=jsc on any build) additionally links JSC's jsc shell into the build directory; it is not built by default.
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
CompilationDatabaseline in.clangdconfig should beCompilationDatabase: build/debug-local - In
.vscode/launch.json, many configurations use./build/debug/, change them as you see fit
A full WebKit clone is ~12 GB (its build artifacts go into build/*-local, not the clone).
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 or release tag.
Troubleshooting#
'span' file not found on Ubuntu#
⚠️ 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 bun run build may fail with the following error:
fatal error: 'span' file not found
#include <span>
^~~~~~The issue may also surface when first running bun run build, 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 update
sudo apt install gcc-11 g++-11
# If the above command fails with `Unable to locate package gcc-11` we need
# to add the APT repository
sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test
# Now run `apt install` again
sudo apt install gcc-11 g++-11Then set GCC 11 as the default compiler:
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-11 100
sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-11 100libarchive#
If you see an error on macOS when compiling libarchive, run:
brew install pkg-configmacOS library not found for -lSystem#
If you see this error when compiling, run:
xcode-select --installCannot find libatomic.a#
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 --static-libatomic=offThe built version of Bun may not work on other systems if compiled this way.
Using bun-debug#
- 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 enablescoped_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.tsis in/tmp/bun-debug-src/home/bun/index.ts
Contributing to the docs#
The docs are the MDX files in docs/ in the Bun repository. bun.com/docs is built from them.
Voice#
A docs page describes what Bun does and what you can do with it. Write it so that a developer who is new to Bun can read the page once and act on it. These guidelines are adapted from the Next.js docs contribution guide:
- Write short sentences that each make one point. If a sentence needs several commas or a parenthetical, split it up or turn it into a list.
- Use plain words: "use" rather than "utilize", "to" rather than "in order to". Cut filler such as "Note that" and "Please".
- Use the active voice and name the actor: "Bun reads
bunfig.toml", rather than "bunfig.tomlis read". A sentence built around "is" and "by" is usually passive. - Describe current behavior in the present tense: "Bun installs the package", rather than "Bun will install the package".
- Name the subject when "this" or "it" could refer to more than one thing: "
--isolateis how Jest behaves by default", rather than "This is how Jest behaves by default". - Address the reader as "you", and make Bun (or the specific tool) the other actor: "Bun caches the tarball", rather than "we cache the tarball" or "let's cache the tarball".
- Leave out "easy", "simple", "just", and "quick". They add nothing when a task is easy and discourage readers when it is not. State the concrete property instead: "one command", "no configuration".
- Say what to do rather than what to avoid: "use
port: 0so the operating system picks a free port", rather than "don't hardcode ports". State limitations plainly; a limitation is a fact, not a warning to the reader. - Use gender-neutral language: "developers", "users", "they".
- Make link text name its destination: "see
bun install", rather than "see here". - Run every code example before you publish it, and check option names and defaults against the implementation on
main.