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 bd

nix 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 zstd

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.

curl -fsSL https://bun.com/install | bash

Optional: 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 ccache

The 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

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 zsh
export 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.

Building Bun#

After cloning the repository, run the following command to build. This can take a while: it downloads and builds dependencies.

bun run build

The 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_debug

VSCode#

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-debug

Running 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.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.

Code generation scripts#

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.

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:release

The 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 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.

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:asan

CI 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=prebuilt

To 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:local

bun 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 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

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++-11

Then 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 100

libarchive#

If you see an error on macOS when compiling libarchive, run:

brew install pkg-config

macOS library not found for -lSystem#

If you see this error when compiling, run:

xcode-select --install

Cannot 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=off

The 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 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

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.toml is 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: "--isolate is 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: 0 so 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.