# Bytecode Caching
Source: https://bun.com/docs/bundler/bytecode

Speed up JavaScript execution with bytecode caching in Bun's bundler

Bytecode caching is a build-time optimization that improves startup time by pre-compiling your JavaScript to bytecode. For example, when compiling TypeScript's `tsc` with bytecode enabled, startup time improves by **2x**.

## Usage

### Basic usage (CommonJS)

Enable bytecode caching with the `--bytecode` flag. Without `--format`, the output format defaults to CommonJS:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun build ./index.ts --target=bun --bytecode --outdir=./dist
```

The build writes two files:

* `dist/index.js` - Your bundled JavaScript (CommonJS)
* `dist/index.js.jsc` - The bytecode cache file

At runtime, Bun automatically detects and uses the `.jsc` file:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun ./dist/index.js  # Automatically uses index.js.jsc
```

### With standalone executables

When you create an executable with `--compile`, Bun embeds the bytecode in the binary. Both ESM and CommonJS work:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
# ESM (requires --compile)
bun build ./cli.ts --compile --bytecode --format=esm --outfile=mycli

# CommonJS (works with or without --compile)
bun build ./cli.ts --compile --bytecode --outfile=mycli
```

The resulting executable contains both the code and the bytecode.

### ESM bytecode

ESM bytecode requires `--compile` because Bun embeds module metadata (import/export information) in the compiled binary. With this metadata, the JavaScript engine skips parsing entirely at runtime.

Without `--compile`, ESM bytecode would still require parsing the source to analyze module dependencies, which defeats the purpose of bytecode caching.

### Combining with other optimizations

Combine bytecode with minification and source maps:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun build --compile --bytecode --minify --sourcemap ./cli.ts --outfile=mycli
```

* `--minify` reduces code size before generating bytecode (less code -> less bytecode)
* `--sourcemap` preserves error reporting (errors still point to original source)
* `--bytecode` eliminates parsing overhead

## Performance impact

The performance improvement scales with your codebase size:

| Application size          | Typical startup improvement |
| ------------------------- | --------------------------- |
| Small CLI (\< 100 KB)     | 1.5-2x faster               |
| Medium-large app (> 5 MB) | 2-4x faster                 |

Larger applications benefit more because they have more code to parse.

## When to use bytecode

### Great for:

#### CLI tools

* Invoked frequently (linters, formatters, git hooks)
* Startup time is the entire user experience
* Users notice the difference between 90ms and 45ms startup
* Example: TypeScript compiler, Prettier, ESLint

#### Build tools and task runners

* Run hundreds or thousands of times during development
* Milliseconds saved per run compound quickly
* Developer experience improvement
* Example: Build scripts, test runners, code generators

#### Standalone executables

* Distributed to users who care about snappy performance
* Single-file distribution is convenient
* File size less important than startup time
* Example: CLIs distributed via npm or as binaries

### Skip it for:

* ❌ **Small scripts**
* ❌ **Code that runs once**
* ❌ **Development builds**
* ❌ **Size-constrained environments**

## Limitations

### Version compatibility

Bytecode is **not portable across Bun versions**. The bytecode format is tied to JavaScriptCore's internal representation, which changes between versions.

When you update Bun, you must regenerate bytecode:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
# After updating Bun
bun build --bytecode ./index.ts --outdir=./dist
```

If bytecode doesn't match the current Bun version, Bun ignores it and falls back to parsing the JavaScript source.

**Best practice**: Generate bytecode as part of your CI/CD build process. Don't commit `.jsc` files to git. Regenerate them whenever you update Bun.

### Source code still required

Bytecode doesn't replace your JavaScript. You must deploy both files:

* The `.js` file (your bundled source code)
* The `.jsc` file (the bytecode cache)

At runtime:

1. Bun loads the `.js` file, sees a `@bytecode` pragma, and checks the `.jsc` file
2. Bun loads the `.jsc` file
3. Bun validates the bytecode hash matches the source
4. If valid, Bun uses the bytecode
5. If invalid, Bun falls back to parsing the source

### Bytecode is not obfuscation

Bytecode **does not obscure your source code**. It's an optimization, not a security measure.

## Production deployment

### Docker

Include bytecode generation in your Dockerfile:

```dockerfile Dockerfile icon="docker" theme={"theme":{"light":"github-light","dark":"dracula"}}
FROM oven/bun:1 AS builder
WORKDIR /app
COPY package.json bun.lock ./
RUN bun install --frozen-lockfile

COPY . .
RUN bun build --bytecode --minify --sourcemap \
  --target=bun \
  --compile \
  ./src/server.ts --outfile=./dist/server

FROM oven/bun:1 AS runner
WORKDIR /app
COPY --from=builder /app/dist/server /app/server
CMD ["./server"]
```

The bytecode is architecture-independent.

### CI/CD

Generate bytecode during your build pipeline:

```yaml workflow.yml icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
# GitHub Actions
- name: Build with bytecode
  run: |
    bun install
    bun build --bytecode --minify \
      --outdir=./dist \
      --target=bun \
      ./src/index.ts
```

## Debugging

### Verify bytecode is being used

Check that the `.jsc` file exists:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
ls -lh dist/
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
-rw-r--r--  1 user  staff   245K  index.js
-rw-r--r--  1 user  staff   1.1M  index.js.jsc
```

The `.jsc` file should be 2-8x larger than the `.js` file.

To log whether Bun uses the bytecode, set `BUN_JSC_verboseDiskCache=1` in your environment.

On a cache hit, Bun logs:

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
[Disk Cache] Cache hit for sourceCode
```

On a cache miss:

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
[Disk Cache] Cache miss for sourceCode
```

Several cache-miss lines are normal: Bun doesn't bytecode-cache the JavaScript in its builtin modules.

### Common issues

**Bytecode silently ignored**: Usually caused by a Bun version update. The cache version doesn't match, so Bun rejects the bytecode. Regenerate to fix.

**File size too large**: This is expected. Consider:

* Using `--minify` to reduce code size before bytecode generation
* Compressing `.jsc` files for network transfer (gzip/brotli)
* Evaluating whether the startup gain is worth the size increase

## What is bytecode?

When you run JavaScript, the JavaScript engine doesn't execute your source code directly. Instead, it goes through several steps:

1. **Parsing**: The engine reads your JavaScript source code and converts it into an Abstract Syntax Tree (AST)
2. **Bytecode compilation**: The engine compiles the AST into bytecode - a lower-level representation that's faster to execute
3. **Execution**: The engine's interpreter or JIT compiler executes the bytecode

Bytecode is an intermediate representation - it's lower-level than JavaScript source code, but higher-level than machine code. Think of it as assembly language for a virtual machine. Each bytecode instruction represents a single operation like "load this variable," "add two numbers," or "call this function."

All of this happens **every time** you run your code. A CLI tool that runs 100 times a day gets parsed 100 times; a serverless function gets parsed on every cold start.

With bytecode caching, Bun moves steps 1 and 2 to the build step. At runtime, the engine loads the pre-compiled bytecode and jumps straight to execution.

### Why lazy parsing makes this even better

Modern JavaScript engines use an optimization called **lazy parsing**. They don't parse all your code upfront. Instead, they parse each function only when it's first called:

```js theme={"theme":{"light":"github-light","dark":"dracula"}}
// Without bytecode caching:
function rarely_used() {
  // This 500-line function is only parsed
  // when it's actually called
}

function main() {
  console.log("Starting app");
  // rarely_used() is never called, so it's never parsed
}
```

Lazy parsing means parsing overhead isn't just a startup cost. It happens throughout your application's lifetime as different code paths execute. With bytecode caching, Bun **pre-compiles all functions**, even the ones the engine would otherwise parse lazily.

## The bytecode format

### Inside a .jsc file

A `.jsc` file contains a serialized bytecode structure.

**Header section** (validated on every load):

* **Cache version**: A hash tied to the JavaScriptCore framework version. This ensures bytecode generated with one version of Bun only runs with that exact version.
* **Code block type tag**: Identifies whether this is a Program, Module, Eval, or Function code block.

**SourceCodeKey** (validates bytecode matches source):

* **Source code hash**: A hash of the original JavaScript source code. Bun verifies this matches before using the bytecode.
* **Source code length**: The exact length of the source, for additional validation.
* **Compilation flags**: Compilation context such as strict mode, script vs. module, and eval context type. The same source compiled with different flags produces different bytecode.

**Bytecode instructions**:

* **Instruction stream**: The bytecode opcodes - the compiled representation of your JavaScript, stored as a variable-length sequence of instructions.
* **Metadata table**: Each opcode has associated metadata such as profiling counters, type hints, and execution counts (even if not yet populated).
* **Jump targets**: Pre-computed addresses for control flow (if/else, loops, switch statements).
* **Switch tables**: Optimized lookup tables for switch statements.

**Constants and identifiers**:

* **Constant pool**: All literal values in your code - numbers, strings, booleans, null, undefined. These are stored as JavaScript values (JSValues) so they don't need to be parsed from source at runtime.
* **Identifier table**: All variable and function names used in the code. Stored as deduplicated strings.
* **Source code representation markers**: Flags indicating how constants should be represented (as integers, doubles, big ints, etc.).

**Function metadata** (for each function in your code):

* **Register allocation**: How many registers (local variables) the function needs - `thisRegister`, `scopeRegister`, `numVars`, `numCalleeLocals`, `numParameters`.
* **Code features**: A bitmask of function characteristics: is it a constructor? an arrow function? does it use `super`? does it have tail calls? These affect how the engine executes the function.
* **Lexically scoped features**: Strict mode and other lexical context.
* **Parse mode**: The mode in which the function was parsed (normal, async, generator, async generator).

**Nested structures**:

* **Function declarations and expressions**: Each nested function gets its own bytecode block, recursively. A file with 100 functions has 100 separate bytecode blocks, all nested in the structure.
* **Exception handlers**: Try/catch/finally blocks with their boundaries and handler addresses pre-computed.
* **Expression info**: Maps bytecode positions back to source code locations for error reporting and debugging.

### What bytecode does NOT contain

**Bytecode does not embed your source code**. Instead:

* The JavaScript source is stored separately (in the `.js` file)
* The bytecode only stores a hash and length of the source
* At load time, Bun validates the bytecode matches the current source code

This is why you need to deploy both the `.js` and `.jsc` files: the `.jsc` file is useless without its corresponding `.js` file.

## The tradeoff: file size

Bytecode files are typically 2-8x larger than the source code.

### Why is bytecode so much larger?

**Bytecode instructions are verbose**:
A single line of minified JavaScript might compile to dozens of bytecode instructions. For example:

```js theme={"theme":{"light":"github-light","dark":"dracula"}}
const sum = arr.reduce((a, b) => a + b, 0);
```

Compiles to bytecode that:

* Loads the `arr` variable
* Gets the `reduce` property
* Creates the arrow function (which itself has bytecode)
* Loads the initial value `0`
* Sets up the call with the right number of arguments
* Performs the call
* Stores the result in `sum`

Each of these steps is a separate bytecode instruction with its own metadata.

**Constant pools store everything**:
Every string literal, number, property name - everything gets stored in the constant pool. Even if your source code has `"hello"` a hundred times, the constant pool stores it once. The identifier table and constant references still add overhead.

**Per-function metadata**:
Each function - even small one-line functions - gets its own complete metadata:

* Register allocation info
* Code features bitmask
* Parse mode
* Exception handlers
* Expression info for debugging

A file with 1,000 small functions has 1,000 sets of metadata.

**Profiling data structures**:
Even though profiling data isn't populated yet, the *structures* to hold profiling data are allocated. This includes:

* Value profile slots (tracking what types flow through each operation)
* Array profile slots (tracking array access patterns)
* Binary arithmetic profile slots (tracking number types in math operations)
* Unary arithmetic profile slots

These take up space even when empty.

**Pre-computed control flow**:
Jump targets, switch tables, and exception handler boundaries are all pre-computed and stored. This makes execution faster but increases file size.

### Mitigation strategies

**Compression**:
Bytecode compresses well with gzip/brotli (60-70% compression). The repetitive structure and metadata compress efficiently.

**Minification first**:
Using `--minify` before bytecode generation helps:

* Shorter identifiers → smaller identifier table
* Dead code elimination → less bytecode generated
* Constant folding → fewer constants in the pool

**The tradeoff**:
You're trading 2-8x larger files for 2-4x faster startup. For CLIs, this is usually worth it. For long-running servers where a few megabytes of disk space don't matter, it's even less of an issue.

## Versioning and portability

### Cross-architecture portability: ✅

Bytecode is **architecture-independent**. You can:

* Build on macOS ARM64, deploy to Linux x64
* Build on Linux x64, deploy to AWS Lambda ARM64
* Build on Windows x64, deploy to macOS ARM64

The bytecode contains abstract instructions that work on any architecture. Architecture-specific optimizations happen during JIT compilation at runtime, not in the cached bytecode.

### Cross-version portability: ❌

Bytecode is **not stable across Bun versions**. Here's why:

**Bytecode format changes**:
JavaScriptCore's bytecode format changes from version to version. New opcodes get added, old ones get removed or changed, metadata structures change.

**Version validation**:
The cache version in the `.jsc` file header is a hash of the JavaScriptCore framework. When Bun loads bytecode:

1. It extracts the cache version from the `.jsc` file
2. It computes the current JavaScriptCore version
3. If they don't match, Bun **silently rejects** the bytecode
4. Bun falls back to parsing the `.js` source code

**Graceful degradation**:
This design means bytecode caching "fails open." If anything goes wrong (version mismatch, corrupted file, missing file), your code still runs normally. You might see slower startup, but you won't see errors.

## Unlinked vs. linked bytecode

JavaScriptCore distinguishes between "unlinked" and "linked" bytecode. This separation is what makes bytecode caching possible:

### Unlinked bytecode (what's cached)

The bytecode saved in `.jsc` files is **unlinked bytecode**. It contains:

* The compiled bytecode instructions
* Structural information about the code
* Constants and identifiers
* Control flow information

But it **doesn't** contain:

* Pointers to actual runtime objects
* JIT-compiled machine code
* Profiling data from previous runs
* Call link information (which functions call which)

Unlinked bytecode is **immutable and shareable**. Multiple executions of the same code can all reference the same unlinked bytecode.

### Linked bytecode (runtime execution)

When Bun runs bytecode, it "links" it - creating a runtime wrapper that adds:

* **Call link information**: As your code runs, the engine learns which functions call which and optimizes those call sites.
* **Profiling data**: The engine tracks how many times each instruction executes, what types of values flow through the code, array access patterns, etc.
* **JIT compilation state**: References to baseline JIT or optimizing JIT (DFG/FTL) compiled versions of hot code.
* **Runtime objects**: Pointers to actual JavaScript objects, prototypes, scopes, etc.

Bun creates this linked representation fresh every time you run your code. This separation allows:

1. **Caching the expensive work** (parsing and compilation to unlinked bytecode)
2. **Still collecting runtime profiling data** to guide optimizations
3. **Still applying JIT optimizations** based on actual execution patterns

For production CLIs and serverless deployments, the combination of `--bytecode --minify --sourcemap` gives you the best startup time while keeping errors mapped to your original source.


# CSS
Source: https://bun.com/docs/bundler/css

Bun's bundler has built-in support for CSS with modern features

Bun's bundler has built-in support for CSS with the following features:

* Transpiling modern/future features to work on all browsers (including vendor prefixing)
* Minification
* CSS Modules
* Tailwind (through a native bundler plugin)

## Transpiling

Transpiling and vendor prefixing are enabled by default, so you can use modern and future CSS features without worrying about browser compatibility.

Bun's CSS parser and bundler is a direct port of LightningCSS, with a bundling approach inspired by esbuild. The transpiler converts modern CSS syntax into backwards-compatible equivalents that work across browsers.

<Note>Thanks to the authors of LightningCSS and esbuild for their work.</Note>

## Browser Compatibility

By default, Bun's CSS bundler targets the following browsers:

* Edge 80+
* Firefox 78+
* Chrome 80+
* Safari 14+
* Opera 67+

## Syntax Lowering

### Nesting

With CSS Nesting, you write child styles directly inside their parent blocks instead of repeating parent selectors across your CSS file.

```scss title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
/* With nesting */
.card {
  background: white;
  border-radius: 4px;

  .title {
    font-size: 1.2rem;
    font-weight: bold;
  }

  .content {
    padding: 1rem;
  }
}
```

Bun's CSS bundler automatically converts this nested syntax into traditional flat CSS that works in all browsers:

```css title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
/* Compiled output */
.card {
  background: white;
  border-radius: 4px;
}

.card .title {
  font-size: 1.2rem;
  font-weight: bold;
}

.card .content {
  padding: 1rem;
}
```

You can also nest media queries and other at-rules inside selectors:

```scss title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
.responsive-element {
  display: block;

  @media (min-width: 768px) {
    display: flex;
  }
}
```

This compiles to:

```css title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
.responsive-element {
  display: block;
}

@media (min-width: 768px) {
  .responsive-element {
    display: flex;
  }
}
```

### Color mix

The `color-mix()` function blends two colors at a given ratio in a chosen color space. Use it to create color variations without calculating the resulting values yourself.

```scss title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
.button {
  /* Mix blue and red in the RGB color space with a 30/70 proportion */
  background-color: color-mix(in srgb, blue 30%, red);

  /* Create a lighter variant for hover state */
  &:hover {
    background-color: color-mix(in srgb, blue 30%, red, white 20%);
  }
}
```

Bun's CSS bundler evaluates these color mixes at build time when all color values are known (not CSS variables), generating static color values that work in all browsers:

```css title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
.button {
  /* Computed to the exact resulting color */
  background-color: #b31a1a;
}

.button:hover {
  background-color: #c54747;
}
```

### Relative colors

Relative color syntax modifies individual components of an existing color. Adjust attributes like lightness, saturation, or individual channels without recalculating the entire color.

```css title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
.theme-color {
  /* Start with a base color and increase lightness by 15% */
  --accent: lch(from purple calc(l + 15%) c h);

  /* Take our brand blue and make a desaturated version */
  --subtle-blue: oklch(from var(--brand-blue) l calc(c * 0.8) h);
}
```

Bun's CSS bundler computes these relative color modifications at build time (when not using CSS variables) and generates static color values for browser compatibility:

```css theme={"theme":{"light":"github-light","dark":"dracula"}}
.theme-color {
  --accent: lch(69.32% 58.34 328.37);
  --subtle-blue: oklch(60.92% 0.112 240.01);
}
```

Use it for theme generation, accessible color variants, or color scales derived from a base color instead of hard-coding each value.

### LAB colors

Modern CSS supports the perceptually uniform color spaces LAB, LCH, OKLAB, and OKLCH, which can represent colors outside the standard RGB gamut.

```css title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
.vibrant-element {
  /* A vibrant red that exceeds sRGB gamut boundaries */
  color: lab(55% 78 35);

  /* A smooth gradient using perceptual color space */
  background: linear-gradient(to right, oklch(65% 0.25 10deg), oklch(65% 0.25 250deg));
}
```

Bun's CSS bundler converts these color formats to backwards-compatible alternatives for browsers that don't support them:

```css title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
.vibrant-element {
  /* Fallback to closest RGB approximation */
  color: #ff0f52;
  /* P3 fallback for browsers with wider gamut support */
  color: color(display-p3 1 0.12 0.37);
  /* Original value preserved for browsers that support it */
  color: lab(55% 78 35);

  background: linear-gradient(to right, #cd4e15, #3887ab);
  background: linear-gradient(to right, oklch(65% 0.25 10deg), oklch(65% 0.25 250deg));
}
```

### Color function

The `color()` function specifies colors in predefined color spaces beyond traditional RGB, giving you access to wider color gamuts.

```css title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
.vivid-element {
  /* Using the Display P3 color space for wider gamut colors */
  color: color(display-p3 1 0.1 0.3);

  /* Using A98 RGB color space */
  background-color: color(a98-rgb 0.44 0.5 0.37);
}
```

For browsers that don't support these color spaces, Bun's CSS bundler adds RGB fallbacks:

```css title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
.vivid-element {
  /* RGB fallback first for maximum compatibility */
  color: #fa1a4c;
  /* Keep original for browsers that support it */
  color: color(display-p3 1 0.1 0.3);

  background-color: #6a805d;
  background-color: color(a98-rgb 0.44 0.5 0.37);
}
```

### HWB colors

The HWB (Hue, Whiteness, Blackness) color model expresses colors based on how much white or black is mixed with a pure hue. This makes tints and shades more direct to create than with RGB or HSL values.

```css title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
.easy-theming {
  /* Pure cyan with no white or black added */
  --primary: hwb(180 0% 0%);

  /* Same hue, but with 20% white added (tint) */
  --primary-light: hwb(180 20% 0%);

  /* Same hue, but with 30% black added (shade) */
  --primary-dark: hwb(180 0% 30%);

  /* Muted version with both white and black added */
  --primary-muted: hwb(180 30% 20%);
}
```

Bun's CSS bundler converts HWB colors to RGB for compatibility with all browsers:

```css title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
.easy-theming {
  --primary: #00ffff;
  --primary-light: #33ffff;
  --primary-dark: #00b3b3;
  --primary-muted: #339999;
}
```

### Color notation

Modern CSS supports space-separated RGB and HSL values (no commas) and hex colors with an alpha channel.

```css title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
.modern-styling {
  /* Space-separated RGB notation (no commas) */
  color: rgb(50 100 200);

  /* Space-separated RGB with alpha */
  border-color: rgba(100 50 200 / 75%);

  /* Hex with alpha channel (8 digits) */
  background-color: #00aaff80;

  /* HSL with simplified notation */
  box-shadow: 0 5px 10px hsl(200 50% 30% / 40%);
}
```

Bun's CSS bundler converts these formats for older browsers:

```css title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
.modern-styling {
  /* Converted to comma format for older browsers */
  color: rgb(50, 100, 200);

  /* Alpha channels handled appropriately */
  border-color: rgba(100, 50, 200, 0.75);

  /* Hex+alpha converted to rgba when needed */
  background-color: rgba(0, 170, 255, 0.5);

  box-shadow: 0 5px 10px rgba(38, 115, 153, 0.4);
}
```

### light-dark() color function

The `light-dark()` function takes two colors and applies one based on the current color scheme, so styles respect the user's system preference without media queries.

```css title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
:root {
  /* Define color scheme support */
  color-scheme: light dark;
}

.themed-component {
  /* Automatically picks the right color based on system preference */
  background-color: light-dark(#ffffff, #121212);
  color: light-dark(#333333, #eeeeee);
  border-color: light-dark(#dddddd, #555555);
}

/* Override system preference when needed */
.light-theme {
  color-scheme: light;
}

.dark-theme {
  color-scheme: dark;
}
```

For browsers that don't support `light-dark()`, Bun's CSS bundler converts it to CSS variables with fallbacks:

```css title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
:root {
  --buncss-light: initial;
  --buncss-dark: ;
  color-scheme: light dark;
}

@media (prefers-color-scheme: dark) {
  :root {
    --buncss-light: ;
    --buncss-dark: initial;
  }
}

.light-theme {
  --buncss-light: initial;
  --buncss-dark: ;
  color-scheme: light;
}

.dark-theme {
  --buncss-light: ;
  --buncss-dark: initial;
  color-scheme: dark;
}

.themed-component {
  background-color: var(--buncss-light, #ffffff) var(--buncss-dark, #121212);
  color: var(--buncss-light, #333333) var(--buncss-dark, #eeeeee);
  border-color: var(--buncss-light, #dddddd) var(--buncss-dark, #555555);
}
```

### Logical properties

CSS logical properties define layout, spacing, and sizing relative to the document's writing mode and text direction rather than physical screen directions, so layouts adapt to different writing systems.

```css title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
.multilingual-component {
  /* Margin that adapts to writing direction */
  margin-inline-start: 1rem;

  /* Padding that makes sense regardless of text direction */
  padding-block: 1rem 2rem;

  /* Border radius for the starting corner at the top */
  border-start-start-radius: 4px;

  /* Size that respects the writing mode */
  inline-size: 80%;
  block-size: auto;
}
```

For browsers that don't fully support logical properties, Bun's CSS bundler compiles them to physical properties for each text direction:

```css title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
/* For left-to-right languages */
.multilingual-component:dir(ltr) {
  margin-left: 1rem;
  padding-top: 1rem;
  padding-bottom: 2rem;
  border-top-left-radius: 4px;
  width: 80%;
  height: auto;
}

/* For right-to-left languages */
.multilingual-component:dir(rtl) {
  margin-right: 1rem;
  padding-top: 1rem;
  padding-bottom: 2rem;
  border-top-right-radius: 4px;
  width: 80%;
  height: auto;
}
```

If the `:dir()` selector isn't supported, Bun generates additional fallbacks.

### :dir() selector

The `:dir()` pseudo-class styles elements based on their text direction (RTL or LTR), as determined by the document or explicit direction attributes. Use it to write direction-aware styles without JavaScript.

```css title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
/* Apply different styles based on text direction */
.nav-arrow:dir(ltr) {
  transform: rotate(0deg);
}

.nav-arrow:dir(rtl) {
  transform: rotate(180deg);
}

/* Position elements based on text flow */
.sidebar:dir(ltr) {
  border-right: 1px solid #ddd;
}

.sidebar:dir(rtl) {
  border-left: 1px solid #ddd;
}
```

For browsers that don't support the `:dir()` selector, Bun's CSS bundler converts it to the more widely supported `:lang()` selector with appropriate language mappings:

```css title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
/* Converted to use language-based selectors as fallback */
.nav-arrow:lang(en, fr, de, es, it, pt, nl) {
  transform: rotate(0deg);
}

.nav-arrow:lang(ar, he, fa, ur) {
  transform: rotate(180deg);
}

.sidebar:lang(en, fr, de, es, it, pt, nl) {
  border-right: 1px solid #ddd;
}

.sidebar:lang(ar, he, fa, ur) {
  border-left: 1px solid #ddd;
}
```

If multiple arguments to `:lang()` aren't supported, Bun generates further fallbacks.

### :lang() selector

The `:lang()` pseudo-class targets elements based on their language. To group rules for related languages, pass multiple language codes to a single `:lang()`.

```css title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
/* Typography adjustments for CJK languages */
:lang(zh, ja, ko) {
  line-height: 1.8;
  font-size: 1.05em;
}

/* Different quote styles by language group */
blockquote:lang(fr, it, es, pt) {
  font-style: italic;
}

blockquote:lang(de, nl, da, sv) {
  font-weight: 500;
}
```

For browsers that don't support multiple arguments in the `:lang()` selector, Bun's CSS bundler converts this syntax to the `:is()` selector with the same behavior:

```css title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
/* Multiple languages grouped with :is() for better browser support */
:is(:lang(zh), :lang(ja), :lang(ko)) {
  line-height: 1.8;
  font-size: 1.05em;
}

blockquote:is(:lang(fr), :lang(it), :lang(es), :lang(pt)) {
  font-style: italic;
}

blockquote:is(:lang(de), :lang(nl), :lang(da), :lang(sv)) {
  font-weight: 500;
}
```

If needed, Bun can generate additional fallbacks for `:is()` as well.

### :is() selector

The `:is()` pseudo-class function (formerly `:matches()`) takes a selector list and matches if any selector in the list matches.

```css title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
/* Instead of writing these separately */
/* 
.article h1,
.article h2,
.article h3 {
  margin-top: 1.5em;
}
*/

/* You can write this */
.article :is(h1, h2, h3) {
  margin-top: 1.5em;
}

/* Complex example with multiple groups */
:is(header, main, footer) :is(h1, h2, .title) {
  font-family: "Heading Font", sans-serif;
}
```

For browsers that don't support `:is()`, Bun's CSS bundler provides fallbacks using vendor-prefixed alternatives:

```css theme={"theme":{"light":"github-light","dark":"dracula"}}
/* Fallback using -webkit-any */
.article :-webkit-any(h1, h2, h3) {
  margin-top: 1.5em;
}

/* Fallback using -moz-any */
.article :-moz-any(h1, h2, h3) {
  margin-top: 1.5em;
}

/* Original preserved for modern browsers */
.article :is(h1, h2, h3) {
  margin-top: 1.5em;
}

/* Complex example with fallbacks */
:-webkit-any(header, main, footer) :-webkit-any(h1, h2, .title) {
  font-family: "Heading Font", sans-serif;
}

:-moz-any(header, main, footer) :-moz-any(h1, h2, .title) {
  font-family: "Heading Font", sans-serif;
}

:is(header, main, footer) :is(h1, h2, .title) {
  font-family: "Heading Font", sans-serif;
}
```

<Warning>
  The vendor-prefixed versions have limitations compared to the standardized `:is()` selector, particularly with complex
  selectors. Bun only uses the prefixed versions when they work correctly.
</Warning>

### :not() selector

The `:not()` pseudo-class excludes elements that match a selector. The modern version accepts multiple arguments to exclude several patterns with one `:not()`.

```css title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
/* Select all buttons except primary and secondary variants */
button:not(.primary, .secondary) {
  background-color: #f5f5f5;
  border: 1px solid #ddd;
}

/* Apply styles to all headings except those inside sidebars or footers */
h2:not(.sidebar *, footer *) {
  margin-top: 2em;
}
```

For browsers that don't support multiple arguments in `:not()`, Bun's CSS bundler converts this syntax to a more compatible form with the same behavior:

```css title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
/* Converted to use :not with :is() for compatibility */
button:not(:is(.primary, .secondary)) {
  background-color: #f5f5f5;
  border: 1px solid #ddd;
}

h2:not(:is(.sidebar *, footer *)) {
  margin-top: 2em;
}
```

And if `:is()` isn't supported, Bun can generate further fallbacks:

```css title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
/* Even more fallbacks for maximum compatibility */
button:not(:-webkit-any(.primary, .secondary)) {
  background-color: #f5f5f5;
  border: 1px solid #ddd;
}

button:not(:-moz-any(.primary, .secondary)) {
  background-color: #f5f5f5;
  border: 1px solid #ddd;
}

button:not(:is(.primary, .secondary)) {
  background-color: #f5f5f5;
  border: 1px solid #ddd;
}
```

The converted selectors keep the specificity and behavior of the original.

### Math functions

CSS includes the following math functions:

* Standard math functions: `round()`, `mod()`, `rem()`, `abs()`, `sign()`
* Trigonometric functions: `sin()`, `cos()`, `tan()`, `asin()`, `acos()`, `atan()`, `atan2()`
* Exponential functions: `pow()`, `sqrt()`, `exp()`, `log()`, `hypot()`

```css title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
.dynamic-sizing {
  /* Clamp a value between minimum and maximum */
  width: clamp(200px, 50%, 800px);

  /* Round to the nearest multiple */
  padding: round(14.8px, 5px);

  /* Trigonometry for animations or layouts */
  transform: rotate(calc(sin(45deg) * 50deg));

  /* Complex math with multiple functions */
  --scale-factor: pow(1.25, 3);
  font-size: calc(16px * var(--scale-factor));
}
```

Bun's CSS bundler evaluates these expressions at build time when all values are known constants (not variables):

```css title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
.dynamic-sizing {
  width: clamp(200px, 50%, 800px);
  padding: 15px;
  transform: rotate(35.36deg);
  --scale-factor: 1.953125;
  font-size: calc(16px * var(--scale-factor));
}
```

### Media query ranges

Media query range syntax expresses breakpoints with comparison operators (`<`, `>`, `<=`, `>=`) instead of the more verbose `min-` and `max-` prefixes.

```css title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
/* Modern syntax with comparison operators */
@media (width >= 768px) {
  .container {
    max-width: 720px;
  }
}

/* Inclusive range using <= and >= */
@media (768px <= width <= 1199px) {
  .sidebar {
    display: flex;
  }
}

/* Exclusive range using < and > */
@media (width > 320px) and (width < 768px) {
  .mobile-only {
    display: block;
  }
}
```

Bun's CSS bundler converts range queries to traditional media query syntax for compatibility with all browsers:

```css title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
/* Converted to traditional min/max syntax */
@media (min-width: 768px) {
  .container {
    max-width: 720px;
  }
}

@media (min-width: 768px) and (max-width: 1199px) {
  .sidebar {
    display: flex;
  }
}

@media (min-width: 321px) and (max-width: 767px) {
  .mobile-only {
    display: block;
  }
}
```

### Shorthands

CSS has introduced several shorthand properties that combine multiple longhand properties.

```css title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
/* Alignment shorthands */
.flex-container {
  /* Shorthand for align-items and justify-items */
  place-items: center start;

  /* Shorthand for align-content and justify-content */
  place-content: space-between center;
}

.grid-item {
  /* Shorthand for align-self and justify-self */
  place-self: end center;
}

/* Two-value overflow */
.content-box {
  /* First value for horizontal, second for vertical */
  overflow: hidden auto;
}

/* Enhanced text-decoration */
.fancy-link {
  /* Combines multiple text decoration properties */
  text-decoration: underline dotted blue 2px;
}

/* Two-value display syntax */
.component {
  /* Outer display type + inner display type */
  display: inline flex;
}
```

For browsers that don't support these shorthands, Bun converts them to their component longhand properties:

```css title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
.flex-container {
  /* Expanded alignment properties */
  align-items: center;
  justify-items: start;

  align-content: space-between;
  justify-content: center;
}

.grid-item {
  align-self: end;
  justify-self: center;
}

.content-box {
  /* Separate overflow properties */
  overflow-x: hidden;
  overflow-y: auto;
}

.fancy-link {
  /* Individual text decoration properties */
  text-decoration-line: underline;
  text-decoration-style: dotted;
  text-decoration-color: blue;
  text-decoration-thickness: 2px;
}

.component {
  /* Single value display */
  display: inline-flex;
}
```

### Double position gradients

Double position gradient syntax specifies the same color at two adjacent positions to create a hard color stop: a sharp transition instead of a smooth fade. Use it for stripes, color bands, and other multi-color designs.

```css title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
.striped-background {
  /* Creates a sharp transition from green to red at 30%-40% */
  background: linear-gradient(
    to right,
    yellow 0%,
    green 20%,
    green 30%,
    red 30%,
    /* Double position creates hard stop */ red 70%,
    blue 70%,
    blue 100%
  );
}

.progress-bar {
  /* Creates distinct color sections */
  background: linear-gradient(
    to right,
    #4caf50 0% 25%,
    /* Green from 0% to 25% */ #ffc107 25% 50%,
    /* Yellow from 25% to 50% */ #2196f3 50% 75%,
    /* Blue from 50% to 75% */ #9c27b0 75% 100% /* Purple from 75% to 100% */
  );
}
```

For browsers that don't support this syntax, Bun's CSS bundler converts it to the traditional format by duplicating color stops:

```css title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
.striped-background {
  background: linear-gradient(
    to right,
    yellow 0%,
    green 20%,
    green 30%,
    red 30%,
    /* Split into two color stops */ red 70%,
    blue 70%,
    blue 100%
  );
}

.progress-bar {
  background: linear-gradient(
    to right,
    #4caf50 0%,
    #4caf50 25%,
    /* Two stops for green section */ #ffc107 25%,
    #ffc107 50%,
    /* Two stops for yellow section */ #2196f3 50%,
    #2196f3 75%,
    /* Two stops for blue section */ #9c27b0 75%,
    #9c27b0 100% /* Two stops for purple section */
  );
}
```

### system-ui font

The `system-ui` generic font family uses the device's native UI font.

```css title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
.native-interface {
  /* Use the system's default UI font */
  font-family: system-ui;
}

.fallback-aware {
  /* System UI font with explicit fallbacks */
  font-family: system-ui, sans-serif;
}
```

For browsers that don't support `system-ui`, Bun's CSS bundler expands it to a cross-platform font stack:

```css title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
.native-interface {
  /* Expanded to support all major platforms */
  font-family:
    system-ui,
    -apple-system,
    BlinkMacSystemFont,
    "Segoe UI",
    Roboto,
    "Noto Sans",
    Ubuntu,
    Cantarell,
    "Helvetica Neue";
}

.fallback-aware {
  /* Preserves the original fallback after the expanded stack */
  font-family:
    system-ui,
    -apple-system,
    BlinkMacSystemFont,
    "Segoe UI",
    Roboto,
    "Noto Sans",
    Ubuntu,
    Cantarell,
    "Helvetica Neue",
    sans-serif;
}
```

The expanded stack includes system fonts for macOS/iOS, Windows, Android, and Linux, plus fallbacks for older browsers.

## CSS Modules

Bun's bundler also supports CSS modules, with the following features:

* Detecting CSS module files (`.module.css`) with no configuration
* Composition (`composes` property)
* Importing CSS modules into JSX/TSX
* Warnings/errors for invalid usages of CSS modules

A CSS module is a CSS file (with the `.module.css` extension) where all class names and animations are scoped to the file. This helps you avoid class name collisions, as CSS declarations are globally scoped by default.

Bun's bundler transforms locally scoped class names into unique identifiers.

### Getting started

Create a CSS file with the `.module.css` extension:

```css title="styles.module.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
.button {
  color: red;
}
```

```css title="other-styles.module.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
.button {
  color: blue;
}
```

You can then import this file, for example into a TSX file:

```tsx title="app.tsx" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import styles from "./styles.module.css";
import otherStyles from "./other-styles.module.css";

export default function App() {
  return (
    <>
      <button className={styles.button}>Red button!</button>
      <button className={otherStyles.button}>Blue button!</button>
    </>
  );
}
```

Importing a CSS module gives you an object that maps each class name to its unique identifier:

```ts title="app.tsx" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import styles from "./styles.module.css";
import otherStyles from "./other-styles.module.css";

console.log(styles);
console.log(otherStyles);
```

This outputs:

```ts title="app.tsx" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
{
  button: "button_123";
}

{
  button: "button_456";
}
```

The class names are unique to each file, so they don't collide.

### Composition

CSS modules can compose class selectors together to reuse style rules across multiple classes.

For example:

```css title="styles.module.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
.button {
  composes: background;
  color: red;
}

.background {
  background-color: blue;
}
```

This is the same as writing:

```css title="styles.module.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
.button {
  background-color: blue;
  color: red;
}

.background {
  background-color: blue;
}
```

Two rules apply when using `composes`:

<Info>
  **Composition Rules:**

  * A `composes` property must come before any regular CSS properties or declarations
  * You can only use `composes` on a simple selector with a single class name
</Info>

```css title="styles.module.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
#button {
  /* Invalid! `#button` is not a class selector */
  composes: background;
}

.button,
.button-secondary {
  /* Invalid! `.button, .button-secondary` is not a simple selector */
  composes: background;
}
```

### Composing from a separate CSS module file

You can also compose from a separate CSS module file:

```css title="background.module.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
.background {
  background-color: blue;
}
```

```css title="styles.module.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
.button {
  composes: background from "./background.module.css";
  color: red;
}
```

<Warning>
  When composing classes from separate files, make sure they do not contain the same properties.

  The CSS module spec says that composing classes from separate files with conflicting properties is undefined behavior: the output may differ and be unreliable.
</Warning>


# esbuild
Source: https://bun.com/docs/bundler/esbuild

Migration guide from esbuild to Bun's bundler

Bun's bundler API is heavily inspired by esbuild. This page is a side-by-side comparison of the two APIs.

A few behaviors differ:

<Note>
  **Bundling by default.** Unlike esbuild, Bun bundles by default; no `--bundle` flag is needed. To transpile each file
  individually, use `Bun.Transpiler`.
</Note>

<Note>
  **Bundler only.** Unlike esbuild, Bun's bundler has no built-in development server. Use it with `Bun.serve` and other
  runtime APIs to get the same effect. esbuild's HTTP options don't apply.
</Note>

## Performance

Bun's bundler is 1.75x faster than esbuild on esbuild's three.js benchmark.

<Info>Bundling 10 copies of three.js from scratch, with sourcemaps and minification</Info>

## CLI API

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
# esbuild
esbuild <entrypoint> --outdir=out --bundle

# bun
bun build <entrypoint> --outdir=out
```

In Bun's CLI, boolean flags like `--minify` take no argument. Flags that take one, like `--outdir <path>`, can be written as `--outdir out` or `--outdir=out`. Some flags, like `--define`, can be repeated: `--define foo=bar --define bar=baz`.

| esbuild                | bun build                  | Notes                                                                                                                                                                                                                                                                                                                                  |
| ---------------------- | -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `--bundle`             | n/a                        | Bun always bundles; use `--no-bundle` to disable it.                                                                                                                                                                                                                                                                                   |
| `--define:K=V`         | `--define K=V`             | Small syntax difference; no colon.<br />`esbuild --define:foo=bar`<br />`bun build --define foo=bar`                                                                                                                                                                                                                                   |
| `--external:<pkg>`     | `--external <pkg>`         | Small syntax difference; no colon.<br />`esbuild --external:react`<br />`bun build --external react`                                                                                                                                                                                                                                   |
| `--format`             | `--format`                 | Bun supports `"esm"`, `"cjs"`, and `"iife"`. esbuild defaults to `"iife"`.                                                                                                                                                                                                                                                             |
| `--loader:.ext=loader` | `--loader .ext:loader`     | Bun supports a different set of built-in loaders than esbuild; see [loaders](/docs/bundler/loaders). The esbuild loaders `dataurl`, `binary`, `base64`, `copy`, and `empty` are not implemented.<br /><br />The syntax for `--loader` differs.<br />`esbuild app.ts --bundle --loader:.svg=text`<br />`bun build app.ts --loader .svg:text` |
| `--minify`             | `--minify`                 | No differences                                                                                                                                                                                                                                                                                                                         |
| `--outdir`             | `--outdir`                 | No differences                                                                                                                                                                                                                                                                                                                         |
| `--outfile`            | `--outfile`                | No differences                                                                                                                                                                                                                                                                                                                         |
| `--packages`           | `--packages`               | No differences                                                                                                                                                                                                                                                                                                                         |
| `--platform`           | `--target`                 | Renamed to `--target` for consistency with tsconfig. Does not support `neutral`.                                                                                                                                                                                                                                                       |
| `--serve`              | n/a                        | Not applicable                                                                                                                                                                                                                                                                                                                         |
| `--sourcemap`          | `--sourcemap`              | No differences                                                                                                                                                                                                                                                                                                                         |
| `--splitting`          | `--splitting`              | No differences                                                                                                                                                                                                                                                                                                                         |
| `--target`             | n/a                        | Not supported. Bun's bundler performs no syntactic down-leveling.                                                                                                                                                                                                                                                                      |
| `--watch`              | `--watch`                  | No differences                                                                                                                                                                                                                                                                                                                         |
| `--allow-overwrite`    | n/a                        | Bun never allows overwriting                                                                                                                                                                                                                                                                                                           |
| `--analyze`            | n/a                        | Not supported                                                                                                                                                                                                                                                                                                                          |
| `--asset-names`        | `--asset-naming`           | Renamed for consistency with naming in JS API                                                                                                                                                                                                                                                                                          |
| `--banner`             | `--banner`                 | Only applies to js bundles                                                                                                                                                                                                                                                                                                             |
| `--footer`             | `--footer`                 | Only applies to js bundles                                                                                                                                                                                                                                                                                                             |
| `--certfile`           | n/a                        | Not applicable                                                                                                                                                                                                                                                                                                                         |
| `--charset=utf8`       | n/a                        | Not supported                                                                                                                                                                                                                                                                                                                          |
| `--chunk-names`        | `--chunk-naming`           | Renamed for consistency with naming in JS API                                                                                                                                                                                                                                                                                          |
| `--color`              | n/a                        | Always enabled                                                                                                                                                                                                                                                                                                                         |
| `--drop`               | `--drop`                   |                                                                                                                                                                                                                                                                                                                                        |
| n/a                    | `--feature`                | Bun-specific. Enables feature flags for compile-time dead-code elimination through `import { feature } from "bun:bundle"`                                                                                                                                                                                                              |
| `--entry-names`        | `--entry-naming`           | Renamed for consistency with naming in JS API                                                                                                                                                                                                                                                                                          |
| `--global-name`        | n/a                        | Not supported                                                                                                                                                                                                                                                                                                                          |
| `--ignore-annotations` | `--ignore-dce-annotations` |                                                                                                                                                                                                                                                                                                                                        |
| `--inject`             | n/a                        | Not supported                                                                                                                                                                                                                                                                                                                          |
| `--jsx`                | `--jsx-runtime <runtime>`  | Supports `"automatic"` (uses jsx transform) and `"classic"` (uses `React.createElement`)                                                                                                                                                                                                                                               |
| `--jsx-dev`            | n/a                        | Bun reads `compilerOptions.jsx` from `tsconfig.json` to determine a default. If `compilerOptions.jsx` is `"react-jsx"`, or if `NODE_ENV=production`, Bun uses the jsx transform. Otherwise, it uses `jsxDEV`. The bundler does not support `preserve`.                                                                                 |
| `--jsx-factory`        | `--jsx-factory`            |                                                                                                                                                                                                                                                                                                                                        |
| `--jsx-fragment`       | `--jsx-fragment`           |                                                                                                                                                                                                                                                                                                                                        |
| `--jsx-import-source`  | `--jsx-import-source`      |                                                                                                                                                                                                                                                                                                                                        |
| `--jsx-side-effects`   | `--jsx-side-effects`       |                                                                                                                                                                                                                                                                                                                                        |
| `--keep-names`         | `--keep-names`             |                                                                                                                                                                                                                                                                                                                                        |
| `--keyfile`            | n/a                        | Not applicable                                                                                                                                                                                                                                                                                                                         |
| `--legal-comments`     | n/a                        | Not supported                                                                                                                                                                                                                                                                                                                          |
| `--log-level`          | n/a                        | Not supported. You can set the log level in `bunfig.toml` as `logLevel`.                                                                                                                                                                                                                                                               |
| `--log-limit`          | n/a                        | Not supported                                                                                                                                                                                                                                                                                                                          |
| `--log-override:X=Y`   | n/a                        | Not supported                                                                                                                                                                                                                                                                                                                          |
| `--main-fields`        | n/a                        | Not supported                                                                                                                                                                                                                                                                                                                          |
| `--mangle-cache`       | n/a                        | Not supported                                                                                                                                                                                                                                                                                                                          |
| `--mangle-props`       | n/a                        | Not supported                                                                                                                                                                                                                                                                                                                          |
| `--mangle-quoted`      | n/a                        | Not supported                                                                                                                                                                                                                                                                                                                          |
| `--metafile`           | `--metafile`               |                                                                                                                                                                                                                                                                                                                                        |
| `--minify-whitespace`  | `--minify-whitespace`      |                                                                                                                                                                                                                                                                                                                                        |
| `--minify-identifiers` | `--minify-identifiers`     |                                                                                                                                                                                                                                                                                                                                        |
| `--minify-syntax`      | `--minify-syntax`          |                                                                                                                                                                                                                                                                                                                                        |
| `--out-extension`      | n/a                        | Not supported                                                                                                                                                                                                                                                                                                                          |
| `--outbase`            | `--root`                   |                                                                                                                                                                                                                                                                                                                                        |
| `--preserve-symlinks`  | n/a                        | Not supported                                                                                                                                                                                                                                                                                                                          |
| `--public-path`        | `--public-path`            |                                                                                                                                                                                                                                                                                                                                        |
| `--pure`               | n/a                        | Not supported                                                                                                                                                                                                                                                                                                                          |
| `--reserve-props`      | n/a                        | Not supported                                                                                                                                                                                                                                                                                                                          |
| `--resolve-extensions` | n/a                        | Not supported                                                                                                                                                                                                                                                                                                                          |
| `--servedir`           | n/a                        | Not applicable                                                                                                                                                                                                                                                                                                                         |
| `--source-root`        | n/a                        | Not supported                                                                                                                                                                                                                                                                                                                          |
| `--sourcefile`         | n/a                        | Not supported. Bun does not support stdin input.                                                                                                                                                                                                                                                                                       |
| `--sources-content`    | n/a                        | Not supported                                                                                                                                                                                                                                                                                                                          |
| `--supported`          | n/a                        | Not supported                                                                                                                                                                                                                                                                                                                          |
| `--tree-shaking`       | n/a                        | Always true                                                                                                                                                                                                                                                                                                                            |
| `--tsconfig`           | `--tsconfig-override`      |                                                                                                                                                                                                                                                                                                                                        |
| `--version`            | n/a                        | Run `bun --version` to see the version of Bun.                                                                                                                                                                                                                                                                                         |

## JavaScript API

| esbuild.build()     | Bun.build()                | Notes                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| ------------------- | -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `absWorkingDir`     | n/a                        | Always set to `process.cwd()`                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| `alias`             | n/a                        | Not supported                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| `allowOverwrite`    | n/a                        | Always false                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| `assetNames`        | `naming.asset`             | Uses the same templating syntax as esbuild, but you must include `[ext]` explicitly.<br /><br />`ts<br/>Bun.build({<br/>  entrypoints: ["./index.tsx"],<br/>  naming: {<br/>    asset: "[name].[ext]",<br/>  },<br/>});<br/>`                                                                                                                                                                                                                                                                |
| `banner`            | `banner`                   | Only applies to js bundles                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
| `bundle`            | n/a                        | Always true. Use `Bun.Transpiler` to transpile without bundling.                                                                                                                                                                                                                                                                                                                                                                                                                             |
| `charset`           | n/a                        | Not supported                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| `chunkNames`        | `naming.chunk`             | Uses the same templating syntax as esbuild, but you must include `[ext]` explicitly.<br /><br />`ts<br/>Bun.build({<br/>  entrypoints: ["./index.tsx"],<br/>  naming: {<br/>    chunk: "[name].[ext]",<br/>  },<br/>});<br/>`                                                                                                                                                                                                                                                                |
| `color`             | n/a                        | Bun returns logs in the `logs` property of the build result.                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| `conditions`        | `conditions`               | No differences                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| `define`            | `define`                   |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| `drop`              | `drop`                     |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| `entryNames`        | `naming` or `naming.entry` | Bun supports a `naming` key that can either be a string or an object. Uses the same templating syntax as esbuild, but you must include `[ext]` explicitly.<br /><br />`ts<br/>Bun.build({<br/>  entrypoints: ["./index.tsx"],<br/>  // when string, this is equivalent to entryNames<br/>  naming: "[name].[ext]",<br/><br/>  // granular naming options<br/>  naming: {<br/>    entry: "[name].[ext]",<br/>    asset: "[name].[ext]",<br/>    chunk: "[name].[ext]",<br/>  },<br/>});<br/>` |
| `entryPoints`       | `entrypoints`              | Capitalization difference                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| `external`          | `external`                 | No differences                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| `footer`            | `footer`                   | Only applies to js bundles                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
| `format`            | `format`                   | Supports `"esm"`, `"cjs"`, and `"iife"`.                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| `globalName`        | n/a                        | Not supported                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| `ignoreAnnotations` | `ignoreDCEAnnotations`     |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| `inject`            | n/a                        | Not supported                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| `jsx`               | `jsx.runtime`              | Supports `"automatic"` and `"classic"`                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
| `jsxDev`            | `jsx.development`          |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| `jsxFactory`        | `jsx.factory`              |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| `jsxFragment`       | `jsx.fragment`             |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| `jsxImportSource`   | `jsx.importSource`         |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| `jsxSideEffects`    | `jsx.sideEffects`          |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| `keepNames`         | `minify.keepNames`         |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| `legalComments`     | n/a                        | Not supported                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| `loader`            | `loader`                   | Bun supports a different set of built-in loaders than esbuild; see [loaders](/docs/bundler/loaders). Bun does not implement the esbuild loaders `dataurl`, `binary`, `base64`, `copy`, and `empty`.                                                                                                                                                                                                                                                                                               |
| `logLevel`          | n/a                        | Not supported                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| `logLimit`          | n/a                        | Not supported                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| `logOverride`       | n/a                        | Not supported                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| `mainFields`        | n/a                        | Not supported                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| `mangleCache`       | n/a                        | Not supported                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| `mangleProps`       | n/a                        | Not supported                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| `mangleQuoted`      | n/a                        | Not supported                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| `metafile`          | `metafile`                 |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| `minify`            | `minify`                   | In Bun, `minify` can be a boolean or an object.<br /><br />`ts<br/>await Bun.build({<br/>  entrypoints: ['./index.tsx'],<br/>  // enable all minification<br/>  minify: true<br/><br/>  // granular options<br/>  minify: {<br/>    identifiers: true,<br/>    syntax: true,<br/>    whitespace: true<br/>  }<br/>})<br/>`                                                                                                                                                                   |
| `minifyIdentifiers` | `minify.identifiers`       | See `minify`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| `minifySyntax`      | `minify.syntax`            | See `minify`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| `minifyWhitespace`  | `minify.whitespace`        | See `minify`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| `nodePaths`         | n/a                        | Not supported                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| `outExtension`      | n/a                        | Not supported                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| `outbase`           | `root`                     | Different name                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| `outdir`            | `outdir`                   | No differences                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| `outfile`           | `outfile`                  | No differences                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| `packages`          | `packages`                 | No differences                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| `platform`          | `target`                   | Supports `"bun"`, `"node"` and `"browser"` (the default). Does not support `"neutral"`.                                                                                                                                                                                                                                                                                                                                                                                                      |
| `plugins`           | `plugins`                  | Bun's plugin API is a subset of esbuild's. Some esbuild plugins work with Bun without modification.                                                                                                                                                                                                                                                                                                                                                                                          |
| `preserveSymlinks`  | n/a                        | Not supported                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| `publicPath`        | `publicPath`               | No differences                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| `pure`              | n/a                        | Not supported                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| `reserveProps`      | n/a                        | Not supported                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| `resolveExtensions` | n/a                        | Not supported                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| `sourceRoot`        | n/a                        | Not supported                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| `sourcemap`         | `sourcemap`                | Supports `"none"`, `"linked"`, `"inline"`, and `"external"`                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| `sourcesContent`    | n/a                        | Not supported                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| `splitting`         | `splitting`                | No differences                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| `stdin`             | n/a                        | Not supported                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| `supported`         | n/a                        | Not supported                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| `target`            | n/a                        | No support for syntax downleveling                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| `treeShaking`       | `treeShaking`              | Defaults to `true`                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| `tsconfig`          | `tsconfig`                 |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| `write`             | n/a                        | Set to true if `outdir`/`outfile` is set, otherwise false                                                                                                                                                                                                                                                                                                                                                                                                                                    |

## Plugin API

Bun's plugin API is designed to be esbuild-compatible. Bun doesn't support esbuild's entire plugin API surface, but it implements the core functionality. Many third-party esbuild plugins work with Bun without modification.

<Note>
  Long term, we aim for feature parity with esbuild's API. If something doesn't work, file an issue to help us
  prioritize.
</Note>

In both Bun and esbuild, you define plugins with a builder object.

```ts title="myPlugin.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import type { BunPlugin } from "bun";

const myPlugin: BunPlugin = {
  name: "my-plugin",
  setup(builder) {
    // define plugin
  },
};
```

The builder object's methods hook into parts of the bundling process. Bun implements `onStart`, `onEnd`, `onResolve`, and `onLoad`; it does not implement the esbuild hooks `onDispose` and `resolve`. Bun partially implements `initialOptions`: the object is read-only and exposes only a subset of esbuild's options. Use `config` (the same thing in Bun's `BuildConfig` format) instead.

```ts title="myPlugin.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import type { BunPlugin } from "bun";
const myPlugin: BunPlugin = {
  name: "my-plugin",
  setup(builder) {
    builder.onStart(() => {
      /* called when the bundle starts */
    });
    builder.onResolve(
      {
        /* onResolve.options */
      },
      args => {
        return {
          /* onResolve.results */
        };
      },
    );
    builder.onLoad(
      {
        /* onLoad.options */
      },
      args => {
        return {
          /* onLoad.results */
        };
      },
    );
    builder.onEnd(result => {
      /* called when the bundle is complete */
    });
  },
};
```

### onResolve

<Tabs>
  <Tab title="options">
    * 🟢 `filter`
    * 🟢 `namespace`
  </Tab>

  <Tab title="arguments">
    * 🟢 `path`
    * 🟢 `importer`
    * 🟢 `namespace`
    * 🟢 `resolveDir`
    * 🟢 `kind`
    * 🔴 `pluginData`
  </Tab>

  <Tab title="results">
    * 🟢 `namespace`
    * 🟢 `path`
    * 🔴 `errors`
    * 🟢 `external`
    * 🔴 `pluginData`
    * 🔴 `pluginName`
    * 🔴 `sideEffects`
    * 🔴 `suffix`
    * 🔴 `warnings`
    * 🔴 `watchDirs`
    * 🔴 `watchFiles`
  </Tab>
</Tabs>

### onLoad

<Tabs>
  <Tab title="options">
    * 🟢 `filter`
    * 🟢 `namespace`
  </Tab>

  <Tab title="arguments">
    * 🟢 `path`
    * 🟢 `namespace`
    * 🔴 `suffix`
    * 🔴 `pluginData`
  </Tab>

  <Tab title="results">
    * 🟢 `contents`
    * 🟢 `loader`
    * 🔴 `errors`
    * 🔴 `pluginData`
    * 🔴 `pluginName`
    * 🔴 `resolveDir`
    * 🔴 `warnings`
    * 🔴 `watchDirs`
    * 🔴 `watchFiles`
  </Tab>
</Tabs>


# Single-file executable
Source: https://bun.com/docs/bundler/executables

Generate standalone executables from TypeScript or JavaScript files with Bun

Bun's bundler implements a `--compile` flag for generating a standalone binary from a TypeScript or JavaScript file.

<Tabs>
  <Tab title="CLI">
    ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
    bun build ./cli.ts --compile --outfile mycli
    ```
  </Tab>

  <Tab title="JavaScript">
    ```ts build.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
    await Bun.build({
      entrypoints: ["./cli.ts"],
      compile: {
        outfile: "./mycli",
      },
    });
    ```
  </Tab>
</Tabs>

```ts cli.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
console.log("Hello world!");
```

Bun bundles `cli.ts` into an executable you can run directly:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
./mycli
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
Hello world!
```

Bun bundles all imported files and packages into the executable, along with a copy of the Bun runtime. All built-in Bun and Node.js APIs are supported.

***

## Cross-compile to other platforms

Use the `--target` flag to compile your standalone executable for a different operating system, architecture, or version of Bun than the machine you're running `bun build` on.

To build for Linux x64 (most servers):

<Tabs>
  <Tab title="CLI">
    ```bash icon="terminal" terminal theme={"theme":{"light":"github-light","dark":"dracula"}}
    bun build --compile --target=bun-linux-x64 ./index.ts --outfile myapp

    # To support CPUs from before 2013, use the baseline version (nehalem)
    bun build --compile --target=bun-linux-x64-baseline ./index.ts --outfile myapp

    # To explicitly only support CPUs from 2013 and later, use the modern version (haswell)
    # modern is faster, but baseline is more compatible.
    bun build --compile --target=bun-linux-x64-modern ./index.ts --outfile myapp
    ```
  </Tab>

  <Tab title="JavaScript">
    ```ts build.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
    // Standard Linux x64
    await Bun.build({
      entrypoints: ["./index.ts"],
      compile: {
        target: "bun-linux-x64",
        outfile: "./myapp",
      },
    });

    // Baseline (pre-2013 CPUs)
    await Bun.build({
      entrypoints: ["./index.ts"],
      compile: {
        target: "bun-linux-x64-baseline",
        outfile: "./myapp",
      },
    });

    // Modern (2013+ CPUs, faster)
    await Bun.build({
      entrypoints: ["./index.ts"],
      compile: {
        target: "bun-linux-x64-modern",
        outfile: "./myapp",
      },
    });
    ```
  </Tab>
</Tabs>

To build for Linux ARM64 (for example, Graviton or Raspberry Pi):

<Tabs>
  <Tab title="CLI">
    ```bash icon="terminal" terminal theme={"theme":{"light":"github-light","dark":"dracula"}}
    # Note: the default architecture is x64 if no architecture is specified.
    bun build --compile --target=bun-linux-arm64 ./index.ts --outfile myapp
    ```
  </Tab>

  <Tab title="JavaScript">
    ```ts build.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
    await Bun.build({
      entrypoints: ["./index.ts"],
      compile: {
        target: "bun-linux-arm64",
        outfile: "./myapp",
      },
    });
    ```
  </Tab>
</Tabs>

To build for Windows x64:

<Tabs>
  <Tab title="CLI">
    ```bash icon="terminal" terminal theme={"theme":{"light":"github-light","dark":"dracula"}}
    bun build --compile --target=bun-windows-x64 ./path/to/my/app.ts --outfile myapp

    # To support CPUs from before 2013, use the baseline version (nehalem)
    bun build --compile --target=bun-windows-x64-baseline ./path/to/my/app.ts --outfile myapp

    # To explicitly only support CPUs from 2013 and later, use the modern version (haswell)
    bun build --compile --target=bun-windows-x64-modern ./path/to/my/app.ts --outfile myapp

    # note: if no .exe extension is provided, Bun adds it automatically for Windows executables
    ```
  </Tab>

  <Tab title="JavaScript">
    ```ts build.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
    // Standard Windows x64
    await Bun.build({
      entrypoints: ["./path/to/my/app.ts"],
      compile: {
        target: "bun-windows-x64",
        outfile: "./myapp", // .exe added automatically
      },
    });

    // Baseline or modern variants
    await Bun.build({
      entrypoints: ["./path/to/my/app.ts"],
      compile: {
        target: "bun-windows-x64-baseline",
        outfile: "./myapp",
      },
    });
    ```
  </Tab>
</Tabs>

To build for Windows arm64:

<Tabs>
  <Tab title="CLI">
    ```bash icon="terminal" terminal theme={"theme":{"light":"github-light","dark":"dracula"}}
    bun build --compile --target=bun-windows-arm64 ./path/to/my/app.ts --outfile myapp

    # note: if no .exe extension is provided, Bun adds it automatically for Windows executables
    ```
  </Tab>

  <Tab title="JavaScript">
    ```ts build.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
    await Bun.build({
      entrypoints: ["./path/to/my/app.ts"],
      compile: {
        target: "bun-windows-arm64",
        outfile: "./myapp", // .exe added automatically
      },
    });
    ```
  </Tab>
</Tabs>

To build for macOS arm64:

<Tabs>
  <Tab title="CLI">
    ```bash icon="terminal" terminal theme={"theme":{"light":"github-light","dark":"dracula"}}
    bun build --compile --target=bun-darwin-arm64 ./path/to/my/app.ts --outfile myapp
    ```
  </Tab>

  <Tab title="JavaScript">
    ```ts build.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
    await Bun.build({
      entrypoints: ["./path/to/my/app.ts"],
      compile: {
        target: "bun-darwin-arm64",
        outfile: "./myapp",
      },
    });
    ```
  </Tab>
</Tabs>

To build for macOS x64:

<Tabs>
  <Tab title="CLI">
    ```bash icon="terminal" terminal theme={"theme":{"light":"github-light","dark":"dracula"}}
    bun build --compile --target=bun-darwin-x64 ./path/to/my/app.ts --outfile myapp
    ```
  </Tab>

  <Tab title="JavaScript">
    ```ts build.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
    await Bun.build({
      entrypoints: ["./path/to/my/app.ts"],
      compile: {
        target: "bun-darwin-x64",
        outfile: "./myapp",
      },
    });
    ```
  </Tab>
</Tabs>

### Supported targets

The segments of the `--target` value can appear in any order, as long as they're delimited by `-`.

| --target             | Operating System | Architecture | Modern | Baseline | Libc  |
| -------------------- | ---------------- | ------------ | ------ | -------- | ----- |
| bun-linux-x64        | Linux            | x64          | ✅      | ✅        | glibc |
| bun-linux-arm64      | Linux            | arm64        | ✅      | N/A      | glibc |
| bun-windows-x64      | Windows          | x64          | ✅      | ✅        | -     |
| bun-windows-arm64    | Windows          | arm64        | ✅      | N/A      | -     |
| bun-darwin-x64       | macOS            | x64          | ✅      | ✅        | -     |
| bun-darwin-arm64     | macOS            | arm64        | ✅      | N/A      | -     |
| bun-linux-x64-musl   | Linux            | x64          | ✅      | ✅        | musl  |
| bun-linux-arm64-musl | Linux            | arm64        | ✅      | N/A      | musl  |

<Warning>
  On x64 platforms, Bun uses SIMD optimizations that require a CPU with AVX2 instructions. The `-baseline` build of Bun
  is for older CPUs without them. The Bun installer detects which version to use, but when cross-compiling you might not
  know the target CPU. This mostly matters on Windows x64 and Linux x64, rarely on Darwin x64. If you or your users see
  `"Illegal instruction"` errors, you might need to use the baseline version.
</Warning>

***

## Build-time constants

Use the `--define` flag to inject build-time constants into your executable, such as version numbers, build timestamps, or configuration values:

<Tabs>
  <Tab title="CLI">
    ```bash icon="terminal" terminal theme={"theme":{"light":"github-light","dark":"dracula"}}
    bun build --compile --define BUILD_VERSION='"1.2.3"' --define BUILD_TIME='"2024-01-15T10:30:00Z"' src/cli.ts --outfile mycli
    ```
  </Tab>

  <Tab title="JavaScript">
    ```ts build.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
    await Bun.build({
      entrypoints: ["./src/cli.ts"],
      compile: {
        outfile: "./mycli",
      },
      define: {
        BUILD_VERSION: JSON.stringify("1.2.3"),
        BUILD_TIME: JSON.stringify("2024-01-15T10:30:00Z"),
      },
    });
    ```
  </Tab>
</Tabs>

Bun inlines these constants into the binary at build time, so they cost nothing at runtime and enable dead code elimination.

<Note>For more examples and patterns, see the [Build-time constants guide](/docs/guides/runtime/build-time-constants).</Note>

***

## Deploying to production

Compiled executables reduce memory usage and improve Bun's start time.

Normally, Bun reads and transpiles JavaScript and TypeScript files on `import` and `require`. This is part of what makes so much of Bun "just work", but it's not free: reading files from disk, resolving paths, parsing, transpiling, and printing source code costs time and memory.

Compiled executables move that cost from runtime to build time.

When deploying to production, we recommend the following:

<Tabs>
  <Tab title="CLI">
    ```bash icon="terminal" terminal theme={"theme":{"light":"github-light","dark":"dracula"}}
    bun build --compile --minify --sourcemap ./path/to/my/app.ts --outfile myapp
    ```
  </Tab>

  <Tab title="JavaScript">
    ```ts build.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
    await Bun.build({
      entrypoints: ["./path/to/my/app.ts"],
      compile: {
        outfile: "./myapp",
      },
      minify: true,
      sourcemap: "linked",
    });
    ```
  </Tab>
</Tabs>

### Bytecode compilation

To improve startup time, enable bytecode compilation:

<Tabs>
  <Tab title="CLI">
    ```bash icon="terminal" terminal theme={"theme":{"light":"github-light","dark":"dracula"}}
    bun build --compile --minify --sourcemap --bytecode ./path/to/my/app.ts --outfile myapp
    ```
  </Tab>

  <Tab title="JavaScript">
    ```ts build.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
    await Bun.build({
      entrypoints: ["./path/to/my/app.ts"],
      compile: {
        outfile: "./myapp",
      },
      minify: true,
      sourcemap: "linked",
      bytecode: true,
    });
    ```
  </Tab>
</Tabs>

Using bytecode compilation, `tsc` starts 2x faster:

<Frame>
  ![Bytecode performance comparison](https://github.com/user-attachments/assets/dc8913db-01d2-48f8-a8ef-ac4e984f9763)
</Frame>

Bytecode compilation moves parsing overhead for large input files from runtime to bundle time. Your app starts faster, in exchange for making the `bun build` command a little slower. Bytecode compilation doesn't obscure source code.

<Note>Bytecode compilation supports both `cjs` and `esm` formats when used with `--compile`.</Note>

### What do these flags do?

The `--minify` argument reduces the size of the transpiled output code. For a large application, this can save megabytes of space. For smaller applications, it might still improve start time a little.

The `--sourcemap` argument embeds a sourcemap compressed with zstd, so that errors & stacktraces point to their original locations instead of the transpiled location. Bun decompresses & resolves the sourcemap automatically when an error occurs.

The `--bytecode` argument enables bytecode compilation. Every time you run JavaScript code in Bun, JavaScriptCore (the engine) compiles your source code into bytecode. `--bytecode` moves that parsing work from runtime to bundle time, which shortens startup.

***

## Embedding runtime arguments

**`--compile-exec-argv="args"`** - Embed runtime arguments, available at runtime in `process.execArgv`:

<Tabs>
  <Tab title="CLI">
    ```bash icon="terminal" terminal theme={"theme":{"light":"github-light","dark":"dracula"}}
    bun build --compile --compile-exec-argv="--smol --user-agent=MyBot" ./app.ts --outfile myapp
    ```
  </Tab>

  <Tab title="JavaScript">
    ```ts build.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
    await Bun.build({
      entrypoints: ["./app.ts"],
      compile: {
        execArgv: ["--smol", "--user-agent=MyBot"],
        outfile: "./myapp",
      },
    });
    ```
  </Tab>
</Tabs>

```ts app.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
// In the compiled app
console.log(process.execArgv); // ["--smol", "--user-agent=MyBot"]
```

### Runtime arguments via `BUN_OPTIONS`

Standalone executables read the `BUN_OPTIONS` environment variable, so you can pass runtime flags without recompiling:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
# Enable CPU profiling on a compiled executable
BUN_OPTIONS="--cpu-prof" ./myapp

# Enable heap profiling with markdown output
BUN_OPTIONS="--heap-prof-md" ./myapp

# Combine multiple flags
BUN_OPTIONS="--smol --cpu-prof-md" ./myapp
```

***

## Automatic config loading

Standalone executables can automatically load configuration files from the directory where they are run. By default:

* **`tsconfig.json`** and **`package.json`** loading is **disabled** — these are typically only needed at development time, and the bundler already uses them when compiling
* **`.env`** and **`bunfig.toml`** loading is **enabled** — these often contain runtime configuration that may vary per deployment

<Note>
  In a future version of Bun, `.env` and `bunfig.toml` may also be disabled by default for more deterministic behavior.
</Note>

### Enabling config loading at runtime

If your executable needs to read `tsconfig.json` or `package.json` at runtime, opt in with these flags:

```bash icon="terminal" terminal theme={"theme":{"light":"github-light","dark":"dracula"}}
# Enable runtime loading of tsconfig.json
bun build --compile --compile-autoload-tsconfig ./app.ts --outfile myapp

# Enable runtime loading of package.json
bun build --compile --compile-autoload-package-json ./app.ts --outfile myapp

# Enable both
bun build --compile --compile-autoload-tsconfig --compile-autoload-package-json ./app.ts --outfile myapp
```

### Disabling config loading at runtime

To disable `.env` or `bunfig.toml` loading for deterministic execution:

<Tabs>
  <Tab title="CLI">
    ```bash icon="terminal" terminal theme={"theme":{"light":"github-light","dark":"dracula"}}
    # Disable .env loading
    bun build --compile --no-compile-autoload-dotenv ./app.ts --outfile myapp

    # Disable bunfig.toml loading
    bun build --compile --no-compile-autoload-bunfig ./app.ts --outfile myapp

    # Disable all config loading
    bun build --compile --no-compile-autoload-dotenv --no-compile-autoload-bunfig ./app.ts --outfile myapp
    ```
  </Tab>

  <Tab title="JavaScript">
    ```ts build.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
    await Bun.build({
      entrypoints: ["./app.ts"],
      compile: {
        // tsconfig.json and package.json are disabled by default
        autoloadTsconfig: true, // Enable tsconfig.json loading
        autoloadPackageJson: true, // Enable package.json loading

        // .env and bunfig.toml are enabled by default
        autoloadDotenv: false, // Disable .env loading
        autoloadBunfig: false, // Disable bunfig.toml loading
        outfile: "./myapp",
      },
    });
    ```
  </Tab>
</Tabs>

***

## Act as the Bun CLI

<Note>New in Bun v1.2.16</Note>

Set the `BUN_BE_BUN=1` environment variable to run a standalone executable as if it were the `bun` CLI itself. The executable ignores its bundled entrypoint and exposes the full `bun` CLI instead.

For example, consider an executable compiled from this script:

```bash icon="terminal" terminal theme={"theme":{"light":"github-light","dark":"dracula"}}
echo "console.log(\"you shouldn't see this\");" > such-bun.js
bun build --compile ./such-bun.js
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
[3ms] bundle 1 modules
[89ms] compile such-bun
```

Normally, running `./such-bun` with arguments executes the script.

```bash icon="terminal" terminal theme={"theme":{"light":"github-light","dark":"dracula"}}
# Executable runs its own entrypoint by default
./such-bun install
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
you shouldn't see this
```

However, with the `BUN_BE_BUN=1` environment variable, the executable acts like the `bun` binary:

```bash icon="terminal" terminal theme={"theme":{"light":"github-light","dark":"dracula"}}
# With the env var, the executable acts like the `bun` CLI
BUN_BE_BUN=1 ./such-bun install
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
bun install v1.2.16-canary.1 (1d1db811)
Checked 63 installs across 64 packages (no changes) [5.00ms]
```

CLI tools built on top of Bun can use this to install packages, bundle dependencies, or run other files without downloading a separate binary or installing Bun.

***

## Full-stack executables

<Note>New in Bun v1.2.17</Note>

The `--compile` flag can create a standalone executable that contains both server and client code, which suits full-stack applications. When you import an HTML file in your server code, Bun bundles the frontend assets (JavaScript, CSS, and so on) and embeds them into the executable.

<CodeGroup>
  ```ts server.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
  import { serve } from "bun";
  import index from "./index.html";

  const server = serve({
    routes: {
      "/": index,
      "/api/hello": { GET: () => Response.json({ message: "Hello from API" }) },
    },
  });

  console.log(`Server running at http://localhost:${server.port}`);
  ```

  ```html index.html icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
  <!DOCTYPE html>
  <html>
    <head>
      <title>My App</title>
      <link rel="stylesheet" href="./styles.css" />
    </head>
    <body>
      <h1>Hello World</h1>
      <script src="./app.ts"></script>
    </body>
  </html>
  ```

  ```ts app.ts icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
  console.log("Hello from the client!");
  ```

  ```css styles.css icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
  body {
    background-color: #f0f0f0;
  }
  ```
</CodeGroup>

To build this into a single executable:

<Tabs>
  <Tab title="CLI">
    ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
    bun build --compile ./server.ts --outfile myapp
    ```
  </Tab>

  <Tab title="JavaScript">
    ```ts build.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
    await Bun.build({
      entrypoints: ["./server.ts"],
      compile: {
        outfile: "./myapp",
      },
    });
    ```
  </Tab>
</Tabs>

This creates a self-contained binary that includes:

* Your server code
* The Bun runtime
* All frontend assets (HTML, CSS, JavaScript)
* Any npm packages used by your server

The result is a single file you can deploy anywhere without installing Node.js, Bun, or any dependencies:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
./myapp
```

Bun serves the frontend assets with the correct MIME types and cache headers. Bun replaces the HTML import with a manifest object that `Bun.serve` uses to serve the pre-bundled assets.

For more on building full-stack applications, see the [full-stack guide](/docs/bundler/fullstack).

***

## Worker

To use workers in a standalone executable, add the worker's entrypoint to the build:

<Tabs>
  <Tab title="CLI">
    ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
    bun build --compile ./index.ts ./my-worker.ts --outfile myapp
    ```
  </Tab>

  <Tab title="JavaScript">
    ```ts build.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
    await Bun.build({
      entrypoints: ["./index.ts", "./my-worker.ts"],
      compile: {
        outfile: "./myapp",
      },
    });
    ```
  </Tab>
</Tabs>

Then, reference the worker in your code:

```ts index.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
console.log("Hello from Bun!");

// Any of these will work:
new Worker("./my-worker.ts");
new Worker(new URL("./my-worker.ts", import.meta.url));
new Worker(new URL("./my-worker.ts", import.meta.url).href);
```

When you add multiple entrypoints to a standalone executable, Bun bundles each one separately into the executable.

We may eventually detect statically-known paths in `new Worker(path)` and bundle them automatically. For now, you need to list the worker file as an entrypoint, as in the earlier example.

If you use a relative path to a file not included in the standalone executable, Bun loads that path from disk relative to the process's current working directory, and errors if it doesn't exist.

***

## SQLite

You can use `bun:sqlite` imports with `bun build --compile`.

By default, Bun resolves the database relative to the current working directory of the process.

```ts index.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import db from "./my.db" with { type: "sqlite" };

console.log(db.query("select * from users LIMIT 1").get());
```

That means if the executable is at `/usr/bin/hello` and the user's terminal is in `/home/me/Desktop`, Bun looks for `/home/me/Desktop/my.db`.

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
cd /home/me/Desktop
./hello
```

***

## Embed assets & files

Standalone executables can embed files directly into the binary, so a single executable can ship images, JSON configs, templates, or any other assets your application needs.

### How it works

Use the `with { type: "file" }` [import attribute](https://github.com/tc39/proposal-import-attributes) to embed a file:

```ts index.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import icon from "./icon.png" with { type: "file" };

console.log(icon);
// During development: "./icon.png"
// After compilation: "/$bunfs/root/icon-a1b2c3d4.png" (internal path)
```

The import returns a **path string** that points to the embedded file. At build time, Bun:

1. Reads the file contents
2. Embeds the data into the executable
3. Replaces the import with an internal path (prefixed with `/$bunfs/`)

You can then read this embedded file using `Bun.file()` or Node.js `fs` APIs.

### Reading embedded files with Bun.file()

`Bun.file()` is the recommended way to read embedded files:

```ts index.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import icon from "./icon.png" with { type: "file" };
import { file } from "bun";

// Get file contents as different types
const bytes = await file(icon).arrayBuffer(); // ArrayBuffer
const text = await file(icon).text(); // string (for text files)
const blob = file(icon); // Blob

// Stream the file in a response
export default {
  fetch(req) {
    return new Response(file(icon), {
      headers: { "Content-Type": "image/png" },
    });
  },
};
```

### Reading embedded files with Node.js fs

Embedded files work with the Node.js file system APIs:

```ts index.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import icon from "./icon.png" with { type: "file" };
import config from "./config.json" with { type: "file" };
import { readFileSync, promises as fs } from "node:fs";

// Synchronous read
const iconBuffer = readFileSync(icon);

// Async read
const configData = await fs.readFile(config, "utf-8");
const parsed = JSON.parse(configData);

// Check file stats
const stats = await fs.stat(icon);
console.log(`Icon size: ${stats.size} bytes`);
```

### Practical examples

#### Embedding a JSON config file

```ts index.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import configPath from "./default-config.json" with { type: "file" };
import { file } from "bun";

// Load the embedded default configuration
const defaultConfig = await file(configPath).json();

// Merge with user config if it exists
const userConfig = await file("./user-config.json")
  .json()
  .catch(() => ({}));
const config = { ...defaultConfig, ...userConfig };
```

#### Serving static assets in an HTTP server

Use static routes in `Bun.serve()` for efficient static file serving:

```ts server.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import favicon from "./favicon.ico" with { type: "file" };
import logo from "./logo.png" with { type: "file" };
import styles from "./styles.css" with { type: "file" };
import { file, serve } from "bun";

serve({
  routes: {
    "/favicon.ico": file(favicon),
    "/logo.png": file(logo),
    "/styles.css": file(styles),
  },
  fetch(req) {
    return new Response("Not found", { status: 404 });
  },
});
```

Bun automatically handles Content-Type headers and caching for static routes.

#### Embedding templates

```ts index.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import templatePath from "./email-template.html" with { type: "file" };
import { file } from "bun";

async function sendWelcomeEmail(user: { name: string; email: string }) {
  const template = await file(templatePath).text();
  const html = template.replace("{{name}}", user.name).replace("{{email}}", user.email);

  // Send email with the rendered template...
}
```

#### Embedding binary files

```ts index.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import wasmPath from "./processor.wasm" with { type: "file" };
import fontPath from "./font.ttf" with { type: "file" };
import { file } from "bun";

// Load a WebAssembly module
const wasmBytes = await file(wasmPath).arrayBuffer();
const wasmModule = await WebAssembly.instantiate(wasmBytes);

// Read binary font data
const fontData = await file(fontPath).bytes();
```

### Embed SQLite databases

To embed a SQLite database into the compiled executable, set `type: "sqlite"` in the import attribute and the `embed` attribute to `"true"`.

The database file must already exist on disk. Then, import it in your code:

```ts index.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import myEmbeddedDb from "./my.db" with { type: "sqlite", embed: "true" };

console.log(myEmbeddedDb.query("select * from users LIMIT 1").get());
```

Finally, compile it into a standalone executable:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun build --compile ./index.ts --outfile mycli
```

<Note>
  The database file must exist on disk when you run `bun build --compile`. The `embed: "true"` attribute tells the
  bundler to include the database contents inside the compiled executable. When running normally with `bun run`, Bun
  loads the database file from disk like a regular SQLite import.
</Note>

In the compiled executable, the embedded database is read-write. Because the database is stored in memory, all changes are lost when the executable exits.

### Embed N-API Addons

You can embed `.node` files into executables.

```ts index.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
const addon = require("./addon.node");

console.log(addon.hello());
```

If you're using `@mapbox/node-pre-gyp` or similar tools, require the `.node` file directly, or it won't bundle correctly.

### Embed directories

Use `--asset` (or `compile.assets` in the JavaScript API) to embed a file or directory tree into the executable under its original relative path. The embedded files live under `import.meta.dir` at runtime and are reachable via `node:fs` (`existsSync`, `statSync`, `readdirSync`, `readFileSync`) and `Bun.file()`.

<Tabs>
  <Tab title="CLI">
    ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
    bun build --compile ./index.ts --asset ./public --outfile myapp
    ```
  </Tab>

  <Tab title="JavaScript">
    ```ts build.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
    await Bun.build({
      entrypoints: ["./index.ts"],
      compile: {
        outfile: "./myapp",
        assets: ["./public"],
      },
    });
    ```
  </Tab>
</Tabs>

```ts index.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import fs from "node:fs";
import path from "node:path";

const publicDir = path.join(import.meta.dir, "public");

for (const entry of fs.readdirSync(publicDir, { withFileTypes: true })) {
  console.log(entry.name, entry.isDirectory() ? "(dir)" : fs.statSync(path.join(publicDir, entry.name)).size);
}

const html = await Bun.file(path.join(publicDir, "index.html")).text();
```

Pass `--asset` multiple times to embed several directories (for example `--asset ./client --asset ./prerendered` for a SvelteKit build). Bun embeds only regular files; it skips symlinks and empty subdirectories inside the tree.

You can also embed individual files via the `with { type: "file" }` import attribute or by adding them as extra entry points. Bun renames imported assets according to `--asset-naming` (default `[name]-[hash].[ext]`):

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
import icon from "./public/assets/icon.png" with { type: "file" };
```

### Detecting standalone mode at runtime

Use `Bun.isStandaloneExecutable` to check whether the current process is running from a compiled binary:

```ts index.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
if (Bun.isStandaloneExecutable) {
  // Running from `bun build --compile` output
} else {
  // Running via `bun <file>` or as a library
}
```

Unlike `Bun.embeddedFiles.length > 0`, this check does not allocate `Blob` objects for each embedded file, so it is safe to call at startup in binaries that embed large assets.

### Listing embedded files

`Bun.embeddedFiles` exposes all embedded files as `Blob` objects:

```ts index.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import "./icon.png" with { type: "file" };
import "./data.json" with { type: "file" };
import "./template.html" with { type: "file" };
import { embeddedFiles } from "bun";

// List all embedded files
for (const blob of embeddedFiles) {
  console.log(`${blob.name} - ${blob.size} bytes`);
}
// Output:
//   icon-a1b2c3d4.png - 4096 bytes
//   data-e5f6g7h8.json - 256 bytes
//   template-i9j0k1l2.html - 1024 bytes
```

Each item in `Bun.embeddedFiles` is a `Blob` with a `name` property:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
embeddedFiles: ReadonlyArray<Blob>;
```

Use it to serve every embedded asset through static routes:

```ts server.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import "./public/favicon.ico" with { type: "file" };
import "./public/logo.png" with { type: "file" };
import "./public/styles.css" with { type: "file" };
import { embeddedFiles, serve } from "bun";

// Build static routes from all embedded files
const staticRoutes: Record<string, Response> = {};
for (const blob of embeddedFiles) {
  // Remove hash from filename: "icon-a1b2c3d4.png" -> "icon.png"
  const name = blob.name.replace(/-[a-z0-9]+\./, ".");
  // embeddedFiles are plain Blobs, which routes does not accept directly
  staticRoutes[`/${name}`] = new Response(blob);
}

serve({
  routes: staticRoutes,
  fetch(req) {
    return new Response("Not found", { status: 404 });
  },
});
```

<Note>
  `Bun.embeddedFiles` excludes bundled source code (`.ts`, `.js`, etc.) to help protect your application's source.
</Note>

#### Content hash

By default, Bun appends a content hash to the name of each embedded file, which helps with cache invalidation when you serve the files from a URL or CDN. To keep the original name instead, configure asset naming:

<Tabs>
  <Tab title="CLI">
    ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
    bun build --compile --asset-naming="[name].[ext]" ./index.ts
    ```
  </Tab>

  <Tab title="JavaScript">
    ```ts build.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
    await Bun.build({
      entrypoints: ["./index.ts"],
      compile: {
        outfile: "./myapp",
      },
      naming: {
        asset: "[name].[ext]",
      },
    });
    ```
  </Tab>
</Tabs>

***

## Minification

To trim down the size of the executable, enable minification:

<Tabs>
  <Tab title="CLI">
    ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
    bun build --compile --minify ./index.ts --outfile myapp
    ```
  </Tab>

  <Tab title="JavaScript">
    ```ts build.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
    await Bun.build({
      entrypoints: ["./index.ts"],
      compile: {
        outfile: "./myapp",
      },
      minify: true, // Enable all minification
    });

    // Or granular control:
    await Bun.build({
      entrypoints: ["./index.ts"],
      compile: {
        outfile: "./myapp",
      },
      minify: {
        whitespace: true,
        syntax: true,
        identifiers: true,
      },
    });
    ```
  </Tab>
</Tabs>

This uses Bun's minifier to reduce the code size. Overall though, Bun's binary is still way too big and we need to make it smaller.

***

## Windows-specific flags

When compiling a standalone executable on Windows, platform-specific options customize metadata on the generated `.exe` file:

<Tabs>
  <Tab title="CLI">
    ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
    # Custom icon
    bun build --compile --windows-icon=path/to/icon.ico ./app.ts --outfile myapp

    # Hide console window (for GUI apps)
    bun build --compile --windows-hide-console ./app.ts --outfile myapp
    ```
  </Tab>

  <Tab title="JavaScript">
    ```ts build.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
    await Bun.build({
      entrypoints: ["./app.ts"],
      compile: {
        outfile: "./myapp",
        windows: {
          icon: "./path/to/icon.ico",
          hideConsole: true,
          // Additional Windows metadata:
          title: "My Application",
          publisher: "My Company",
          version: "1.0.0",
          description: "A standalone Windows application",
          copyright: "Copyright 2024",
        },
      },
    });
    ```
  </Tab>
</Tabs>

Available Windows options:

* `icon` - Path to `.ico` file for the executable icon
* `hideConsole` - Disable the background terminal (for GUI apps)
* `title` - Application title in file properties
* `publisher` - Publisher name in file properties
* `version` - Version string in file properties
* `description` - Description in file properties
* `copyright` - Copyright notice in file properties

<Warning>
  Except for `hideConsole`, you can't use these flags when cross-compiling because they depend on Windows APIs.
</Warning>

***

## Code signing on macOS

To codesign a standalone executable on macOS (which fixes Gatekeeper warnings), use the `codesign` command.

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
codesign --deep --force -vvvv --sign "XXXXXXXXXX" ./myapp
```

We recommend including an `entitlements.plist` file with JIT permissions.

```xml icon="xml" title="entitlements.plist" theme={"theme":{"light":"github-light","dark":"dracula"}}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>com.apple.security.cs.allow-jit</key>
    <true/>
    <key>com.apple.security.cs.allow-unsigned-executable-memory</key>
    <true/>
    <key>com.apple.security.cs.disable-executable-page-protection</key>
    <true/>
    <key>com.apple.security.cs.allow-dyld-environment-variables</key>
    <true/>
    <key>com.apple.security.cs.disable-library-validation</key>
    <true/>
</dict>
</plist>
```

To codesign with JIT support, pass the `--entitlements` flag to `codesign`.

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
codesign --deep --force -vvvv --sign "XXXXXXXXXX" --entitlements entitlements.plist ./myapp
```

After codesigning, verify the executable:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
codesign -vvv --verify ./myapp
./myapp: valid on disk
./myapp: satisfies its Designated Requirement
```

<Warning>Codesign support requires Bun v1.2.4 or newer.</Warning>

***

## Code splitting

Standalone executables support code splitting. Use `--compile` with `--splitting` to create an executable that loads code-split chunks at runtime.

<Tabs>
  <Tab title="CLI">
    ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
    bun build --compile --splitting ./src/entry.ts --outfile ./build/entry
    ```
  </Tab>

  <Tab title="JavaScript">
    ```ts build.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
    await Bun.build({
      entrypoints: ["./src/entry.ts"],
      compile: true,
      splitting: true,
      outdir: "./build",
    });
    ```
  </Tab>
</Tabs>

<CodeGroup>
  ```ts src/entry.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
  console.log("Entrypoint loaded");
  const lazy = await import("./lazy.ts");
  lazy.hello();
  ```

  ```ts src/lazy.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
  export function hello() {
    console.log("Lazy module loaded");
  }
  ```
</CodeGroup>

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
./build/entry
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
Entrypoint loaded
Lazy module loaded
```

***

## Using plugins

Plugins work with standalone executables; use them to transform files during the build:

```ts build.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import type { BunPlugin } from "bun";

const envPlugin: BunPlugin = {
  name: "env-loader",
  setup(build) {
    build.onLoad({ filter: /\.env\.json$/ }, async args => {
      // Transform .env.json files into validated config objects
      const env = await Bun.file(args.path).json();

      return {
        contents: `export default ${JSON.stringify(env)};`,
        loader: "js",
      };
    });
  },
};

await Bun.build({
  entrypoints: ["./cli.ts"],
  compile: {
    outfile: "./mycli",
  },
  plugins: [envPlugin],
});
```

Example use case - embedding environment config at build time:

```ts cli.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import config from "./config.env.json";

console.log(`Running in ${config.environment} mode`);
console.log(`API endpoint: ${config.apiUrl}`);
```

Plugins can perform any transformation: compile YAML/TOML configs, inline SQL queries, generate type-safe API clients, or preprocess templates. See the [plugin documentation](/docs/bundler/plugins).

***

## Unsupported CLI arguments

The `--compile` flag does not support the following flags:

* `--outdir` — use `outfile` instead.
* `--public-path`
* `--target=node`
* `--target=browser` (without HTML entrypoints — see [Standalone HTML](/docs/bundler/standalone-html) for `--compile --target=browser` with `.html` files)
* `--no-bundle` - Bun always bundles everything into the executable.

***

## API reference

The `compile` option in `Bun.build()` accepts three forms:

```ts title="types" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
interface BuildConfig {
  entrypoints: string[];
  compile: boolean | Bun.Build.CompileTarget | CompileBuildOptions;
  // ... other BuildConfig options (minify, sourcemap, define, plugins, etc.)
}

interface CompileBuildOptions {
  target?: Bun.Build.CompileTarget; // Cross-compilation target
  outfile?: string; // Output executable path
  assets?: string[]; // Files/directories to embed under import.meta.dir
  execArgv?: string[]; // Runtime arguments (process.execArgv)
  executablePath?: string; // Bun executable to use instead of downloading one for target
  autoloadTsconfig?: boolean; // Load tsconfig.json (default: false)
  autoloadPackageJson?: boolean; // Load package.json (default: false)
  autoloadDotenv?: boolean; // Load .env files (default: true)
  autoloadBunfig?: boolean; // Load bunfig.toml (default: true)
  windows?: {
    icon?: string; // Path to .ico file
    hideConsole?: boolean; // Hide console window
    title?: string; // Application title
    publisher?: string; // Publisher name
    version?: string; // Version string
    description?: string; // Description
    copyright?: string; // Copyright notice
  };
}
```

Usage forms:

```ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
// Simple boolean - compile for current platform (uses entrypoint name as output)
compile: true

// Target string - cross-compile (uses entrypoint name as output)
compile: "bun-linux-x64"

// Full options object - specify outfile and other options
compile: {
  target: "bun-linux-x64",
  outfile: "./myapp",
}
```

### Supported targets

```ts title="Bun.Build.CompileTarget" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
type CompileTarget =
  | "bun-darwin-x64"
  | "bun-darwin-x64-baseline"
  | "bun-darwin-arm64"
  | "bun-linux-x64"
  | "bun-linux-x64-baseline"
  | "bun-linux-x64-modern"
  | "bun-linux-arm64"
  | "bun-linux-x64-musl"
  | "bun-linux-x64-baseline-musl"
  | "bun-linux-arm64-musl"
  | "bun-windows-x64"
  | "bun-windows-x64-baseline"
  | "bun-windows-x64-modern"
  | "bun-windows-arm64";
```

### Complete example

```ts build.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import type { BunPlugin } from "bun";

const myPlugin: BunPlugin = {
  name: "my-plugin",
  setup(build) {
    // Plugin implementation
  },
};

const result = await Bun.build({
  entrypoints: ["./src/cli.ts"],
  compile: {
    target: "bun-linux-x64",
    outfile: "./dist/mycli",
    execArgv: ["--smol"],
    autoloadDotenv: false,
    autoloadBunfig: false,
  },
  minify: true,
  sourcemap: "linked",
  bytecode: true,
  define: {
    "process.env.NODE_ENV": JSON.stringify("production"),
    VERSION: JSON.stringify("1.0.0"),
  },
  plugins: [myPlugin],
});

if (result.success) {
  console.log("Build successful:", result.outputs[0].path);
}
```


# Fullstack dev server
Source: https://bun.com/docs/bundler/fullstack

Build fullstack applications with Bun's integrated dev server that bundles frontend assets and handles API routes

To get started, import HTML files and pass them to the `routes` option in `Bun.serve()`.

```ts title="app.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { serve, sql } from "bun";
import dashboard from "./dashboard.html";
import homepage from "./index.html";

const server = serve({
  routes: {
    // ** HTML imports **
    // Bundle & route index.html to "/". This uses HTMLRewriter to scan
    // the HTML for `<script>` and `<link>` tags, runs Bun's JavaScript
    // & CSS bundler on them, transpiles any TypeScript, JSX, and TSX,
    // downlevels CSS with Bun's CSS parser and serves the result.
    "/": homepage,
    // Bundle & route dashboard.html to "/dashboard"
    "/dashboard": dashboard,

    // ** API endpoints ** (Bun v1.2.3+ required)
    "/api/users": {
      async GET(req) {
        const users = await sql`SELECT * FROM users`;
        return Response.json(users);
      },
      async POST(req) {
        const { name, email } = await req.json();
        const [user] = await sql`INSERT INTO users (name, email) VALUES (${name}, ${email})`;
        return Response.json(user);
      },
    },
    "/api/users/:id": async req => {
      const { id } = req.params;
      const [user] = await sql`SELECT * FROM users WHERE id = ${id}`;
      return Response.json(user);
    },
  },

  // Enable development mode for:
  // - Detailed error messages
  // - Hot reloading (Bun v1.2.3+ required)
  development: true,
});

console.log(`Listening on ${server.url}`);
```

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun run app.ts
```

## HTML Routes

### HTML Imports as Routes

To specify entrypoints to your frontend, import HTML files into your JavaScript/TypeScript/TSX/JSX files.

```ts title="app.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import dashboard from "./dashboard.html";
import homepage from "./index.html";
```

Pass the imported HTML files as routes to `Bun.serve()`.

```ts title="app.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
Bun.serve({
  routes: {
    "/": homepage,
    "/dashboard": dashboard,
  },

  fetch(req) {
    // ... api requests
  },
});
```

When you make a request to `/dashboard` or `/`, Bun automatically bundles the `<script>` and `<link>` tags in the HTML files, exposes them as static routes, and serves the result.

### HTML Processing Example

An `index.html` file like this:

```html title="index.html" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
<!DOCTYPE html>
<html>
  <head>
    <title>Home</title>
    <link rel="stylesheet" href="./reset.css" />
    <link rel="stylesheet" href="./styles.css" />
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="./sentry-and-preloads.ts"></script>
    <script type="module" src="./my-app.tsx"></script>
  </body>
</html>
```

Becomes something like this:

```html title="index.html" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
<!DOCTYPE html>
<html>
  <head>
    <title>Home</title>
    <link rel="stylesheet" href="/index-[hash].css" />
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/index-[hash].js"></script>
  </body>
</html>
```

## React Integration

To use React in your client-side code, import `react-dom/client` and render your app.

<CodeGroup>
  ```ts title="src/backend.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
  import dashboard from "../public/dashboard.html";
  import { serve } from "bun";

  serve({
    routes: {
      "/": dashboard,
    },
    async fetch(req) {
      // ...api requests
      return new Response("hello world");
    },
  });
  ```

  ```tsx title="src/frontend.tsx" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
  import { createRoot } from "react-dom/client";
  import App from "./app";

  const container = document.getElementById("root");
  const root = createRoot(container!);
  root.render(<App />);
  ```

  ```html title="public/dashboard.html" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
  <!DOCTYPE html>
  <html>
    <head>
      <title>Dashboard</title>
      <link rel="stylesheet" href="../src/styles.css" />
    </head>
    <body>
      <div id="root"></div>
      <script type="module" src="../src/frontend.tsx"></script>
    </body>
  </html>
  ```

  ```tsx title="src/app.tsx" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
  import { useState } from "react";

  export default function App() {
    const [count, setCount] = useState(0);

    return (
      <div>
        <h1>Dashboard</h1>
        <button onClick={() => setCount(count + 1)}>Count: {count}</button>
      </div>
    );
  }
  ```
</CodeGroup>

## Development Mode

When building locally, enable development mode by setting `development: true` in `Bun.serve()`.

```ts title="src/backend.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import homepage from "./index.html";
import dashboard from "./dashboard.html";

Bun.serve({
  routes: {
    "/": homepage,
    "/dashboard": dashboard,
  },

  development: true,

  fetch(req) {
    // ... api requests
  },
});
```

### Development Mode Features

When `development` is `true`, Bun:

* Includes the SourceMap header in the response so that devtools can show the original source code
* Disables minification
* Re-bundles assets on each request to a `.html` file
* Enables hot module reloading (unless you set `hmr: false`)

### Advanced Development Configuration

To echo console logs from the browser to the terminal, pass `console: true` in the `development` object in `Bun.serve()`.

```ts title="src/backend.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import homepage from "./index.html";

Bun.serve({
  // development can also be an object.
  development: {
    // Enable Hot Module Reloading
    hmr: true,

    // Echo console logs from the browser to the terminal
    console: true,
  },

  routes: {
    "/": homepage,
  },
});
```

Bun sends the logs over the existing HMR WebSocket connection.

### Development vs Production

| Feature             | Development            | Production |
| ------------------- | ---------------------- | ---------- |
| **Source maps**     | ✅ Enabled              | ❌ Disabled |
| **Minification**    | ❌ Disabled             | ✅ Enabled  |
| **Hot reloading**   | ✅ Enabled              | ❌ Disabled |
| **Asset bundling**  | 🔄 On each request     | 💾 Cached  |
| **Console logging** | 🖥️ Browser → Terminal | ❌ Disabled |
| **Error details**   | 📝 Detailed            | 🔒 Minimal |

## Production Mode

Hot reloading and `development: true` help you iterate quickly, but in production your server should be as fast as possible and have as few external dependencies as possible.

### Ahead of Time Bundling (Recommended)

As of Bun v1.2.17, you can use `Bun.build` or `bun build` to bundle your fullstack application ahead of time.

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun build --target=bun --production --outdir=dist ./src/index.ts
```

When Bun's bundler sees an HTML import from server-side code, it bundles the referenced JavaScript/TypeScript/TSX/JSX and CSS files into a manifest object that `Bun.serve()` can use to serve the assets.

```ts title="src/backend.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { serve } from "bun";
import index from "./index.html";

serve({
  routes: { "/": index },
});
```

### Runtime Bundling

If you'd rather not add a build step, set `development: false` in `Bun.serve()`.

With this setting, Bun:

* Enables in-memory caching of bundled assets. Bun bundles assets lazily on the first request to an `.html` file and caches the result in memory until the server restarts.
* Enables `Cache-Control` and `ETag` headers
* Minifies JavaScript/TypeScript/TSX/JSX files

```ts title="src/backend.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { serve } from "bun";
import homepage from "./index.html";

serve({
  routes: {
    "/": homepage,
  },

  // Production mode
  development: false,
});
```

## API Routes

### HTTP Method Handlers

Define API endpoints with HTTP method handlers:

```ts title="src/backend.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { serve } from "bun";

serve({
  routes: {
    "/api/users": {
      async GET(req) {
        // Handle GET requests
        const users = await getUsers();
        return Response.json(users);
      },

      async POST(req) {
        // Handle POST requests
        const userData = await req.json();
        const user = await createUser(userData);
        return Response.json(user, { status: 201 });
      },

      async PUT(req) {
        // Handle PUT requests
        const userData = await req.json();
        const user = await updateUser(userData);
        return Response.json(user);
      },

      async DELETE(req) {
        // Handle DELETE requests
        await deleteUser(req.params.id);
        return new Response(null, { status: 204 });
      },
    },
  },
});
```

### Dynamic Routes

Use URL parameters in your routes:

```ts title="src/backend.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
serve({
  routes: {
    // Single parameter
    "/api/users/:id": async req => {
      const { id } = req.params;
      const user = await getUserById(id);
      return Response.json(user);
    },

    // Multiple parameters
    "/api/users/:userId/posts/:postId": async req => {
      const { userId, postId } = req.params;
      const post = await getPostByUser(userId, postId);
      return Response.json(post);
    },

    // Wildcard routes
    "/api/files/*": async req => {
      const filePath = req.params["*"];
      const file = await getFile(filePath);
      return new Response(file);
    },
  },
});
```

### Request Handling

```ts title="src/backend.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
serve({
  routes: {
    "/api/data": {
      async POST(req) {
        // Parse JSON body
        const body = await req.json();

        // Access headers
        const auth = req.headers.get("Authorization");

        // Access URL parameters
        const { id } = req.params;

        // Access query parameters
        const url = new URL(req.url);
        const page = url.searchParams.get("page") || "1";

        // Return response
        return Response.json({
          message: "Data processed",
          page: parseInt(page),
          authenticated: !!auth,
        });
      },
    },
  },
});
```

## Plugins

Bun also supports bundler plugins when bundling static routes.

To configure plugins for `Bun.serve`, add a `plugins` array in the `[serve.static]` section of your `bunfig.toml`.

### TailwindCSS Plugin

To use TailwindCSS, install the `tailwindcss` package and the `bun-plugin-tailwind` plugin.

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun add tailwindcss bun-plugin-tailwind
```

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[serve.static]
plugins = ["bun-plugin-tailwind"]
```

You can now use TailwindCSS utility classes in your HTML and CSS files. Import `tailwindcss` somewhere in your project:

```html title="index.html" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
<!doctype html>
<html>
  <head>
    <!-- [!code ++] -->
    <link rel="stylesheet" href="tailwindcss" />
  </head>
  <!-- the rest of your HTML... -->
</html>
```

Alternatively, you can import TailwindCSS in your CSS file:

```css title="style.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
@import "tailwindcss";

.custom-class {
  @apply bg-red-500 text-white;
}
```

```html index.html icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
<!doctype html>
<html>
  <head>
    <!-- [!code ++] -->
    <link rel="stylesheet" href="./style.css" />
  </head>
  <!-- the rest of your HTML... -->
</html>
```

### Custom Plugins

The plugins array accepts any JS file or module that exports a valid bundler plugin object (an object with a `name` and a `setup` field):

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[serve.static]
plugins = ["./my-plugin-implementation.ts"]
```

```ts title="my-plugin-implementation.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import type { BunPlugin } from "bun";

const myPlugin: BunPlugin = {
  name: "my-custom-plugin",
  setup(build) {
    // Plugin implementation
    build.onLoad({ filter: /\.custom$/ }, async args => {
      const text = await Bun.file(args.path).text();
      return {
        contents: `export default ${JSON.stringify(text)};`,
        loader: "js",
      };
    });
  },
};

export default myPlugin;
```

Bun lazily resolves and loads each plugin and uses them to bundle your routes.

<Note>
  Plugins live in `bunfig.toml` so that the `bun build` CLI, once it supports them, can know statically which plugins
  are in use. These plugins work in `Bun.build()`'s JS API, but not yet in the CLI.
</Note>

## Inline Environment Variables

Bun can replace `process.env.*` references in your frontend JavaScript and TypeScript with their values at build time. Configure the `env` option in your `bunfig.toml`:

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[serve.static]
env = "PUBLIC_*"  # only inline env vars starting with PUBLIC_ (recommended)
# env = "inline"  # inline all environment variables
# env = "disable" # disable env var replacement (default)
```

<Note>
  Bun only replaces literal `process.env.FOO` references, not `import.meta.env` or indirect access like `const env =
      process.env; env.FOO`.

  If an environment variable is not set, you may see runtime errors like `ReferenceError: process
      is not defined` in the browser.
</Note>

See [HTML & static sites](/docs/bundler/html-static#inline-environment-variables) for build-time configuration and examples.

## Sourcemaps

In development, Bun generates linked sourcemaps for bundled routes and serves them alongside the JavaScript and CSS chunks. In production (`development: false`), sourcemaps are disabled by default so the server does not expose your original source code.

To override the default, set the `sourcemap` option in your `bunfig.toml`:

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[serve.static]
sourcemap = "linked" # serve sourcemaps in production too
# sourcemap = "inline"   # embed sourcemaps in the chunks
# sourcemap = "external" # emit .map files without a sourceMappingURL comment
# sourcemap = false      # never generate sourcemaps
```

## How It Works

Bun uses `HTMLRewriter` to scan for `<script>` and `<link>` tags in HTML files and uses them as entrypoints for Bun's bundler. Bun then generates an optimized bundle for the JavaScript/TypeScript/TSX/JSX and CSS files and serves the result.

### Processing Pipeline

<Steps>
  <Step title="1. <script> Processing">
    * Transpiles TypeScript, JSX, and TSX in `<script>` tags
    * Bundles imported dependencies
    * Generates sourcemaps for debugging in development
    * Minifies when `development` is not `true` in `Bun.serve()`

    ```html title="index.html" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
    <script type="module" src="./counter.tsx"></script>
    ```
  </Step>

  <Step title="2. <link> Processing">
    * Processes CSS imports and `<link>` tags
    * Concatenates CSS files
    * Rewrites url and asset paths to include content-addressable hashes in URLs

    ```html title="index.html" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
    <link rel="stylesheet" href="./styles.css" />
    ```
  </Step>

  <Step title="3. <img> & Asset Processing">
    * Rewrites links to assets to include content-addressable hashes in URLs
    * Inlines small assets in CSS files into `data:` URLs, reducing the total number of HTTP requests sent over the wire
  </Step>

  <Step title="4. HTML Rewriting">
    * Combines all `<script>` tags into a single `<script>` tag with a content-addressable hash in the URL
    * Combines all `<link>` tags into a single `<link>` tag with a content-addressable hash in the URL
    * Outputs a new HTML file
  </Step>

  <Step title="5. Serving">
    * Exposes all the output files from the bundler as static routes, using the same mechanism internally as when you pass a Response object to `routes` in `Bun.serve()`.
    * This pipeline works similarly to how `Bun.build` processes HTML files.
  </Step>
</Steps>

## Complete Example

```ts title="server.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { serve } from "bun";
import { Database } from "bun:sqlite";
import homepage from "./public/index.html";
import dashboard from "./public/dashboard.html";

// Initialize database
const db = new Database("app.db");
db.exec(`
  CREATE TABLE IF NOT EXISTS users (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    name TEXT NOT NULL,
    email TEXT UNIQUE NOT NULL,
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP
  )
`);

const server = serve({
  routes: {
    // Frontend routes
    "/": homepage,
    "/dashboard": dashboard,

    // API routes
    "/api/users": {
      async GET() {
        const users = db.query("SELECT * FROM users").all();
        return Response.json(users);
      },

      async POST(req) {
        const { name, email } = await req.json();

        try {
          const result = db.query("INSERT INTO users (name, email) VALUES (?, ?) RETURNING *").get(name, email);

          return Response.json(result, { status: 201 });
        } catch (error) {
          return Response.json({ error: "Email already exists" }, { status: 400 });
        }
      },
    },

    "/api/users/:id": {
      async GET(req) {
        const { id } = req.params;
        const user = db.query("SELECT * FROM users WHERE id = ?").get(id);

        if (!user) {
          return Response.json({ error: "User not found" }, { status: 404 });
        }

        return Response.json(user);
      },

      async DELETE(req) {
        const { id } = req.params;
        const result = db.query("DELETE FROM users WHERE id = ?").run(id);

        if (result.changes === 0) {
          return Response.json({ error: "User not found" }, { status: 404 });
        }

        return new Response(null, { status: 204 });
      },
    },

    // Health check endpoint
    "/api/health": {
      GET() {
        return Response.json({
          status: "ok",
          timestamp: new Date().toISOString(),
        });
      },
    },
  },

  // Enable development mode
  development: {
    hmr: true,
    console: true,
  },

  // Fallback for unmatched routes
  fetch(req) {
    return new Response("Not Found", { status: 404 });
  },
});

console.log(`🚀 Server running on ${server.url}`);
```

```html title="public/index.html" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>Fullstack Bun App</title>
    <link rel="stylesheet" href="../src/styles.css" />
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="../src/main.tsx"></script>
  </body>
</html>
```

```tsx title="src/main.tsx" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { createRoot } from "react-dom/client";
import { App } from "./App";

const container = document.getElementById("root")!;
const root = createRoot(container);
root.render(<App />);
```

```tsx title="src/App.tsx" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { useState, useEffect } from "react";

interface User {
  id: number;
  name: string;
  email: string;
  created_at: string;
}

export function App() {
  const [users, setUsers] = useState<User[]>([]);
  const [name, setName] = useState("");
  const [email, setEmail] = useState("");
  const [loading, setLoading] = useState(false);

  const fetchUsers = async () => {
    const response = await fetch("/api/users");
    const data = await response.json();
    setUsers(data);
  };

  const createUser = async (e: React.FormEvent) => {
    e.preventDefault();
    setLoading(true);

    try {
      const response = await fetch("/api/users", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ name, email }),
      });

      if (response.ok) {
        setName("");
        setEmail("");
        await fetchUsers();
      } else {
        const error = await response.json();
        alert(error.error);
      }
    } catch (error) {
      alert("Failed to create user");
    } finally {
      setLoading(false);
    }
  };

  const deleteUser = async (id: number) => {
    if (!confirm("Are you sure?")) return;

    try {
      const response = await fetch(`/api/users/${id}`, {
        method: "DELETE",
      });

      if (response.ok) {
        await fetchUsers();
      }
    } catch (error) {
      alert("Failed to delete user");
    }
  };

  useEffect(() => {
    fetchUsers();
  }, []);

  return (
    <div className="container">
      <h1>User Management</h1>

      <form onSubmit={createUser} className="form">
        <input type="text" placeholder="Name" value={name} onChange={e => setName(e.target.value)} required />
        <input type="email" placeholder="Email" value={email} onChange={e => setEmail(e.target.value)} required />
        <button type="submit" disabled={loading}>
          {loading ? "Creating..." : "Create User"}
        </button>
      </form>

      <div className="users">
        <h2>Users ({users.length})</h2>
        {users.map(user => (
          <div key={user.id} className="user-card">
            <div>
              <strong>{user.name}</strong>
              <br />
              <span>{user.email}</span>
            </div>
            <button onClick={() => deleteUser(user.id)} className="delete-btn">
              Delete
            </button>
          </div>
        ))}
      </div>
    </div>
  );
}
```

```css title="src/styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

body {
  font-family: -apple-system, BlinkMacSystemFont, sans-serif;
  background: #f5f5f5;
  color: #333;
}

.container {
  max-width: 800px;
  margin: 0 auto;
  padding: 2rem;
}

h1 {
  color: #2563eb;
  margin-bottom: 2rem;
}

.form {
  background: white;
  padding: 1.5rem;
  border-radius: 8px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  margin-bottom: 2rem;
  display: flex;
  gap: 1rem;
  flex-wrap: wrap;
}

.form input {
  flex: 1;
  min-width: 200px;
  padding: 0.75rem;
  border: 1px solid #ddd;
  border-radius: 4px;
}

.form button {
  padding: 0.75rem 1.5rem;
  background: #2563eb;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

.form button:hover {
  background: #1d4ed8;
}

.form button:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.users {
  background: white;
  padding: 1.5rem;
  border-radius: 8px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

.user-card {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 1rem;
  border-bottom: 1px solid #eee;
}

.user-card:last-child {
  border-bottom: none;
}

.delete-btn {
  padding: 0.5rem 1rem;
  background: #dc2626;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

.delete-btn:hover {
  background: #b91c1c;
}
```

## Best Practices

### Project Structure

```
my-app/
├── src/
│   ├── components/
│   │   ├── Header.tsx
│   │   └── UserList.tsx
│   ├── styles/
│   │   ├── globals.css
│   │   └── components.css
│   ├── utils/
│   │   └── api.ts
│   ├── App.tsx
│   └── main.tsx
├── public/
│   ├── index.html
│   ├── dashboard.html
│   └── favicon.ico
├── server/
│   ├── routes/
│   │   ├── users.ts
│   │   └── auth.ts
│   ├── db/
│   │   └── schema.sql
│   └── index.ts
├── bunfig.toml
└── package.json
```

### Environment-Based Configuration

```ts title="server/config.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
export const config = {
  development: process.env.NODE_ENV !== "production",
  port: process.env.PORT || 3000,
  database: {
    url: process.env.DATABASE_URL || "./dev.db",
  },
  cors: {
    origin: process.env.CORS_ORIGIN || "*",
  },
};
```

### Error Handling

```ts title="server/middleware.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
export function errorHandler(error: Error, req: Request) {
  console.error("Server error:", error);

  if (process.env.NODE_ENV === "production") {
    return Response.json({ error: "Internal server error" }, { status: 500 });
  }

  return Response.json(
    {
      error: error.message,
      stack: error.stack,
    },
    { status: 500 },
  );
}
```

### API Response Helpers

```ts title="server/utils.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
export function json(data: any, status = 200) {
  return Response.json(data, { status });
}

export function error(message: string, status = 400) {
  return Response.json({ error: message }, { status });
}

export function notFound(message = "Not found") {
  return error(message, 404);
}

export function unauthorized(message = "Unauthorized") {
  return error(message, 401);
}
```

### Type Safety

```ts title="types/api.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
export interface User {
  id: number;
  name: string;
  email: string;
  created_at: string;
}

export interface CreateUserRequest {
  name: string;
  email: string;
}

export interface ApiResponse<T> {
  data?: T;
  error?: string;
}
```

## Deployment

### Production Build

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
# Build for production
bun build --target=bun --production --outdir=dist ./server/index.ts

# Run production server
NODE_ENV=production bun dist/index.js
```

### Docker Deployment

```dockerfile title="Dockerfile" icon="docker" theme={"theme":{"light":"github-light","dark":"dracula"}}
FROM oven/bun:1 as base
WORKDIR /usr/src/app

# Install dependencies
COPY package.json bun.lock ./
RUN bun install --frozen-lockfile

# Copy source code
COPY . .

# Build application
RUN bun build --target=bun --production --outdir=dist ./server/index.ts

# Production stage
FROM oven/bun:1-slim
WORKDIR /usr/src/app
COPY --from=base /usr/src/app/dist ./
COPY --from=base /usr/src/app/public ./public

EXPOSE 3000
CMD ["bun", "index.js"]
```

### Environment Variables

```ini title=".env.production" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
NODE_ENV=production
PORT=3000
DATABASE_URL=postgresql://user:pass@localhost:5432/myapp
CORS_ORIGIN=https://myapp.com
```

## Migration from Other Frameworks

### From Express + Webpack

```ts title="server.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
// Before (Express + Webpack)
app.use(express.static("dist"));
app.get("/api/users", (req, res) => {
  res.json(users);
});

// After (Bun fullstack)
serve({
  routes: {
    "/": homepage, // Replaces express.static
    "/api/users": {
      GET() {
        return Response.json(users);
      },
    },
  },
});
```

### From Next.js API Routes

```ts title="server.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
// Before (Next.js)
export default function handler(req, res) {
  if (req.method === 'GET') {
    res.json(users);
  }
}

// After (Bun)
"/api/users": {
  GET() { return Response.json(users); }
}
```

## Limitations and Future Plans

### Current Limitations

* Auto-discovery of API routes is not implemented
* Server-side rendering (SSR) is not built-in

### Planned Features

* File-based routing for API endpoints
* Built-in SSR support
* Enhanced plugin ecosystem

<Note>The fullstack dev server is a work in progress. Features and APIs may change.</Note>


# Hot reloading
Source: https://bun.com/docs/bundler/hot-reloading

Hot Module Replacement (HMR) for Bun's development server

Hot Module Replacement (HMR) updates modules in a running application without a full page reload, preserving application state.

<Note>HMR is enabled by default when using Bun's full-stack development server.</Note>

## `import.meta.hot` API Reference

Bun implements a client-side HMR API modeled after [Vite's `import.meta.hot` API](https://vite.dev/guide/api-hmr). You can check for it with `if (import.meta.hot)`, which tree-shakes it in production.

```ts title="index.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
if (import.meta.hot) {
  // HMR APIs are available.
}
```

This check is often unnecessary, since Bun dead-code-eliminates calls to all of the HMR APIs in production builds.

```ts title="index.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
// This entire function call is removed in production.
import.meta.hot.dispose(() => {
  console.log("dispose");
});
```

<Warning>
  For this dead-code elimination to work, Bun forces these APIs to be called without indirection. That means the following do not work:

  ```ts title="index.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
  // INVALID: Assigning `hot` to a variable
  const hot = import.meta.hot;
  hot.accept();

  // INVALID: Assigning `import.meta` to a variable
  const meta = import.meta;
  meta.hot.accept();
  console.log(meta.hot.data);

  // INVALID: Passing to a function
  doSomething(import.meta.hot.dispose);

  // OK: The full phrase "import.meta.hot.<API>" must be called directly:
  import.meta.hot.accept();

  // OK: `data` can be passed to functions:
  doSomething(import.meta.hot.data);
  ```
</Warning>

<Note>
  The HMR API is still a work in progress. Some features are missing. To disable HMR in `Bun.serve`, set the development option to `{ hmr: false }`.
</Note>

## API Methods

| Method             | Status | Notes                                                                 |
| ------------------ | ------ | --------------------------------------------------------------------- |
| `hot.accept()`     | ✅      | Indicate that a hot update can be replaced gracefully.                |
| `hot.data`         | ✅      | Persist data between module evaluations.                              |
| `hot.dispose()`    | ✅      | Add a callback function to run when a module is about to be replaced. |
| `hot.invalidate()` | ❌      |                                                                       |
| `hot.on()`         | ✅      | Attach an event listener.                                             |
| `hot.off()`        | ✅      | Remove an event listener from `on`.                                   |
| `hot.send()`       | ❌      |                                                                       |
| `hot.prune()`      | 🚧     | Callback is currently never called.                                   |
| `hot.decline()`    | ✅      | No-op to match Vite's `import.meta.hot`.                              |

## import.meta.hot.accept()

The `accept()` method indicates that a module can be hot-replaced. Called without arguments, it means Bun can replace this module by re-evaluating the file. After a hot update, Bun automatically patches the module's importers.

```ts title="index.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
// index.ts
import { getCount } from "./foo.ts";

console.log("count is ", getCount());

import.meta.hot.accept();

export function getNegativeCount() {
  return -getCount();
}
```

This call creates a hot-reloading boundary for all of the files that `index.ts` imports. Whenever you save `foo.ts` or any of its dependencies, the update bubbles up to `index.ts`, which re-evaluates. Bun then patches the files that import `index.ts` to import the new version of `getNegativeCount()`. If you update only `index.ts`, Bun re-evaluates only that one file, and the counter in `foo.ts` is reused.

Combine this with `import.meta.hot.data` to transfer state from the previous module to the new one.

<Info>
  When no modules call `import.meta.hot.accept()` (and there isn't React Fast Refresh or a plugin calling it for you),
  the page reloads when the file updates. A console warning shows which files were invalidated. This warning is safe to
  ignore if it makes more sense to rely on full page reloads.
</Info>

### With callback

When passed a callback, `import.meta.hot.accept` works as it does in Vite. Instead of patching the importers of this module, it calls the callback with the new module.

```ts title="index.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
export const count = 0;

import.meta.hot.accept(newModule => {
  if (newModule) {
    // newModule is undefined when SyntaxError happened
    console.log("updated: count is now ", newModule.count);
  }
});
```

<Tip>Prefer `import.meta.hot.accept()` without an argument; it usually makes your code clearer.</Tip>

### Accepting other modules

```ts title="index.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { count } from "./foo";

import.meta.hot.accept("./foo", newModule => {
  if (!newModule) return;

  console.log("updated: count is now ", count);
});
```

Indicates that a dependency's module can be accepted. When the dependency is updated, Bun calls the callback with the new module.

### With multiple dependencies

```ts title="index.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import.meta.hot.accept(["./foo", "./bar"], newModules => {
  // newModules is an array where each item corresponds to the updated module
  // or undefined if that module had a syntax error
});
```

This variant accepts an array of dependencies. The callback receives the updated modules, and `undefined` for any that had errors.

## import.meta.hot.data

`import.meta.hot.data` carries state from the previous version of a module to the new one across a hot replacement. Writing to `import.meta.hot.data` also marks the module as self-accepting (equivalent to calling `import.meta.hot.accept()`).

```tsx title="index.tsx" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { createRoot } from "react-dom/client";
import { App } from "./app";

const root = (import.meta.hot.data.root ??= createRoot(elem));
root.render(<App />); // re-use an existing root
```

In production, Bun inlines `data` as `{}`, so you cannot use it as a state holder.

<Tip>
  We recommend this pattern for stateful modules because Bun can minify `{}.prop ??= value` into `value` in production.
</Tip>

## import.meta.hot.dispose()

Attaches an on-dispose callback. Bun calls it:

* Just before the module is replaced with another copy (before the next is loaded)
* After the module is detached (removing all imports to this module, see `import.meta.hot.prune()`)

```ts title="index.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
const sideEffect = setupSideEffect();

import.meta.hot.dispose(() => {
  sideEffect.cleanup();
});
```

<Warning>Bun does not call this callback on route navigation or when the browser tab closes.</Warning>

Returning a promise delays module replacement until the module is disposed. Bun calls all dispose callbacks in parallel.

## import.meta.hot.prune()

Attaches an on-prune callback. Bun calls it when all imports to this module are removed, but the module was previously loaded.

Use it to clean up resources that were created when the module was loaded. Unlike `import.meta.hot.dispose()`, it pairs better with `accept` and `data` for managing stateful resources. A full example managing a WebSocket:

```ts title="index.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { something } from "./something";

// Initialize or re-use a WebSocket connection
export const ws = (import.meta.hot.data.ws ??= new WebSocket(location.origin));

// If the module's import is removed, clean up the WebSocket connection.
import.meta.hot.prune(() => {
  ws.close();
});
```

<Info>
  If you used `dispose` instead, the WebSocket would close and re-open on every hot update. Both versions of the code
  prevent page reloads when you update imported files.
</Info>

## import.meta.hot.on() and off()

Use `on()` and `off()` to listen for events from the HMR runtime. Event names carry a prefix so that plugins do not conflict with each other.

```ts title="index.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import.meta.hot.on("bun:beforeUpdate", () => {
  console.log("before a hot update");
});
```

When a file is replaced, Bun automatically removes all of its event listeners.

### Built-in events

| Event                  | Emitted when                                                                                    |
| ---------------------- | ----------------------------------------------------------------------------------------------- |
| `bun:beforeUpdate`     | before a hot update is applied.                                                                 |
| `bun:afterUpdate`      | after a hot update is applied.                                                                  |
| `bun:beforeFullReload` | before a full page reload happens.                                                              |
| `bun:beforePrune`      | before prune callbacks are called.                                                              |
| `bun:invalidate`       | when a module is invalidated with `import.meta.hot.invalidate()`.                               |
| `bun:error`            | when a build or runtime error occurs.                                                           |
| `bun:ws:disconnect`    | when the HMR WebSocket connection is lost. This can indicate the development server is offline. |
| `bun:ws:connect`       | when the HMR WebSocket connects or re-connects.                                                 |

<Note>For compatibility with Vite, these events are also available with the `vite:*` prefix instead of `bun:*`.</Note>


# HTML & static sites
Source: https://bun.com/docs/bundler/html-static

Build static sites, landing pages, and web applications with Bun's bundler

Bun's bundler has first-class support for HTML. Build static sites, landing pages, and web applications with zero configuration: point Bun at your HTML file and it bundles the scripts, stylesheets, and assets the file references.

```html title="index.html" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
<!doctype html>
<html>
  <head>
    <link rel="stylesheet" href="./styles.css" />
    <script src="./app.ts" type="module"></script>
  </head>
  <body>
    <img src="./logo.png" />
  </body>
</html>
```

To get started, pass HTML files to `bun`.

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun ./index.html
```

```
Bun v1.3.3
ready in 6.62ms
→ http://localhost:3000/
Press h + Enter to show shortcuts
```

With no configuration, Bun's development server provides:

* **Automatic Bundling** - Bundles and serves your HTML, JavaScript, and CSS
* **Multi-Entry Support** - Handles multiple HTML entry points and glob entry points
* **Modern JavaScript** - TypeScript & JSX support by default
* **Smart Configuration** - Reads `tsconfig.json` for paths, JSX options, and experimental decorators
* **Plugins** - Plugin support, including TailwindCSS
* **ESM & CommonJS** - Use ESM and CommonJS in your JavaScript, TypeScript, and JSX files
* **CSS Bundling & Minification** - Bundles CSS from `<link>` tags and `@import` statements
* **Asset Management** - Copies and hashes images and assets, and rewrites asset paths in JavaScript, CSS, and HTML

## Single Page Apps (SPA)

When you pass a single `.html` file to Bun, Bun uses it as a fallback route for all paths. This suits single page apps that use client-side routing:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun index.html
```

```
Bun v1.3.3
ready in 6.62ms
→ http://localhost:3000/
Press h + Enter to show shortcuts
```

Your React or other SPA works with no configuration. Routes like `/about` and `/users/123` serve the same HTML file, so your client-side router handles the navigation.

```html title="index.html" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
<!doctype html>
<html>
  <head>
    <title>My SPA</title>
    <script src="./app.tsx" type="module"></script>
  </head>
  <body>
    <div id="root"></div>
  </body>
</html>
```

## Multi-page apps (MPA)

Some projects have several separate routes or HTML files as entry points. To support multiple entry points, pass them all to `bun`:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun ./index.html ./about.html
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
Bun v1.3.3
ready in 6.62ms
→ http://localhost:3000/
Routes:
  / ./index.html
  /about ./about.html
Press h + Enter to show shortcuts
```

This serves:

* `index.html` at `/`
* `about.html` at `/about`

### Glob patterns

To specify multiple files, use a glob pattern that ends in `.html`:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun ./**/*.html
```

```
Bun v1.3.3
ready in 6.62ms
→ http://localhost:3000/
Routes:
  / ./index.html
  /about ./about.html
Press h + Enter to show shortcuts
```

### Path normalization

Bun chooses the base path from the longest common prefix among all the files.

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun ./index.html ./about/index.html ./about/foo/index.html
```

```
Bun v1.3.3
ready in 6.62ms
→ http://localhost:3000/
Routes:
  / ./index.html
  /about ./about/index.html
  /about/foo ./about/foo/index.html
Press h + Enter to show shortcuts
```

## JavaScript, TypeScript, and JSX

Bun's transpiler natively implements JavaScript, TypeScript, and JSX support. See [loaders](/docs/bundler/loaders).

<Note>Bun also uses the same transpiler at runtime.</Note>

### ES Modules & CommonJS

You can use ESM and CommonJS in your JavaScript, TypeScript, and JSX files. Bun transpiles and bundles them automatically.

There is no pre-build or separate optimization step. Bun does it all at the same time.

See [module resolution](/docs/runtime/module-resolution).

## CSS

Bun's CSS parser is also natively implemented (about 70,000 lines of Rust).

It's also a CSS bundler. You can use `@import` in your CSS files to import other CSS files.

For example:

<CodeGroup>
  ```css styles.css icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
  @import "./abc.css";

  .container {
    background-color: blue;
  }
  ```

  ```css abc.css icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
  body {
    background-color: red;
  }
  ```
</CodeGroup>

This outputs:

```css styles.css icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
body {
  background-color: red;
}

.container {
  background-color: blue;
}
```

### Referencing local assets in CSS

```css styles.css icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
body {
  background-image: url("./logo.png");
}
```

Bun copies `./logo.png` to the output directory and rewrites the path in the CSS file to include a content hash.

```css styles.css icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
body {
  background-image: url("./logo-[ABC123].png");
}
```

### Importing CSS in JavaScript

To associate a CSS file with a JavaScript file, import it from the JavaScript file.

```ts app.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import "./styles.css";
import "./more-styles.css";
```

This generates `./app.css` and `./app.js` in the output directory. Bun bundles all CSS files imported from JavaScript into a single CSS file per entry point. If you import the same CSS file from multiple JavaScript files, Bun includes it only once in the output CSS file.

## Plugins

The dev server supports plugins.

### Tailwind CSS

To use TailwindCSS, install the `bun-plugin-tailwind` plugin:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
# Or any npm client
bun install --dev bun-plugin-tailwind
```

Then, add the plugin to your `bunfig.toml`:

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[serve.static]
plugins = ["bun-plugin-tailwind"]
```

Then, reference TailwindCSS in your HTML with a `<link>` tag, an `@import` in CSS, or an import in JavaScript.

<Tabs>
  <Tab title="index.html">
    ```html title="index.html" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
    <!-- Reference TailwindCSS in your HTML -->
    <link rel="stylesheet" href="tailwindcss" />
    ```
  </Tab>

  <Tab title="styles.css">
    ```css title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
    @import "tailwindcss";
    ```
  </Tab>

  <Tab title="app.ts">
    ```ts title="app.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
    import "tailwindcss";
    ```
  </Tab>
</Tabs>

<Info>Only one of these is necessary, not all three.</Info>

## Inline environment variables

Bun can replace `process.env.*` references in your JavaScript and TypeScript with their actual values at build time. Use this to inject configuration like API URLs or feature flags into your frontend code.

### Dev server (runtime)

To inline environment variables when using `bun ./index.html`, configure the `env` option in your `bunfig.toml`:

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[serve.static]
env = "PUBLIC_*"  # only inline env vars starting with PUBLIC_ (recommended)
# env = "inline"  # inline all environment variables
# env = "disable" # disable env var replacement (default)
```

<Note>
  Inlining only works with literal `process.env.FOO` references, not `import.meta.env` or indirect access like `const env =
      process.env; env.FOO`.

  If an environment variable is not set, you may see runtime errors like `ReferenceError: process
      is not defined` in the browser.
</Note>

Then run the dev server:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
PUBLIC_API_URL=https://api.example.com bun ./index.html
```

### Build for production

When building static HTML for production, use the `env` option to inline environment variables:

<Tabs>
  <Tab title="CLI">
    ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
    # Inline all environment variables
    bun build ./index.html --outdir=dist --env=inline

    # Only inline env vars with a specific prefix (recommended)
    bun build ./index.html --outdir=dist --env=PUBLIC_*
    ```
  </Tab>

  <Tab title="API">
    ```ts title="build.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
    // Inline all environment variables
    await Bun.build({
      entrypoints: ["./index.html"],
      outdir: "./dist",
      env: "inline", // [!code highlight]
    });

    // Only inline env vars with a specific prefix (recommended)
    await Bun.build({
      entrypoints: ["./index.html"],
      outdir: "./dist",
      env: "PUBLIC_*", // [!code highlight]
    });
    ```
  </Tab>
</Tabs>

### Example

Given this source file:

```ts title="app.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
const apiUrl = process.env.PUBLIC_API_URL;
console.log(`API URL: ${apiUrl}`);
```

And running with `PUBLIC_API_URL=https://api.example.com`:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
PUBLIC_API_URL=https://api.example.com bun build ./index.html --outdir=dist --env=PUBLIC_*
```

The bundled output contains:

```js title="dist/app.js" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/javascript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=5148f41bbc784f9828f1363dab67340f" theme={"theme":{"light":"github-light","dark":"dracula"}}
const apiUrl = "https://api.example.com";
console.log(`API URL: ${apiUrl}`);
```

## Echo console logs from browser to terminal

Bun's dev server can stream console logs from the browser to the terminal. To enable this, pass the `--console` CLI flag.

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun ./index.html --console
```

```
Bun v1.3.3
ready in 6.62ms
→ http://localhost:3000/
Press h + Enter to show shortcuts
```

Bun broadcasts each `console.log` or `console.error` call to the terminal that started the server, so browser errors show up in the same place you run your server. This also helps AI agents that watch terminal output.

Internally, Bun reuses the existing WebSocket connection from hot module replacement (HMR) to send the logs.

## Edit files in the browser

Bun's frontend dev server supports Automatic Workspace Folders in Chrome DevTools, so you can save edits to files from the browser.

## Keyboard Shortcuts

While the server is running:

* `o + Enter` - Open in browser
* `c + Enter` - Clear console
* `q + Enter` (or `Ctrl+C`) - Quit server

## Build for Production

When you're ready to deploy, use `bun build` to create optimized production bundles:

<Tabs>
  <Tab title="CLI">
    ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
    bun build ./index.html --minify --outdir=dist
    ```
  </Tab>

  <Tab title="API">
    ```ts title="build.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
    await Bun.build({
      entrypoints: ["./index.html"],
      outdir: "./dist",
      minify: true,
    });
    ```
  </Tab>
</Tabs>

<Warning>
  Plugins are only supported through `Bun.build`'s API or through `bunfig.toml` with the frontend dev server, not
  through `bun build`'s CLI.
</Warning>

### Watch Mode

Run `bun build --watch` to watch for changes and rebuild automatically. This works well for library development.

<Info>You've never seen a watch mode this fast.</Info>

## Plugin API

For more control, configure the bundler through the JavaScript API and use Bun's built-in `HTMLRewriter` to preprocess HTML.

```ts title="build.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
await Bun.build({
  entrypoints: ["./index.html"],
  outdir: "./dist",
  minify: true,

  plugins: [
    {
      // A plugin that makes every HTML tag lowercase
      name: "lowercase-html-plugin",
      setup({ onLoad }) {
        const rewriter = new HTMLRewriter().on("*", {
          element(element) {
            element.tagName = element.tagName.toLowerCase();
          },
          text(element) {
            element.replace(element.text.toLowerCase());
          },
        });

        onLoad({ filter: /\.html$/ }, async args => {
          const html = await Bun.file(args.path).text();

          return {
            // Bun's bundler will scan the HTML for <script> tags, <link rel="stylesheet"> tags, and other assets
            // and bundle them automatically
            contents: rewriter.transform(html),
            loader: "html",
          };
        });
      },
    },
  ],
});
```

## What Gets Processed?

Bun automatically handles all common web assets:

* **Scripts** (`<script src>`) are run through Bun's JavaScript/TypeScript/JSX bundler
* **Stylesheets** (`<link rel="stylesheet">`) are run through Bun's CSS parser & bundler
* **Images** (`<img>`, `<picture>`) are copied and hashed
* **Media** (`<video>`, `<audio>`, `<source>`) are copied and hashed
* Any `<link>` tag with an `href` attribute pointing to a local file is rewritten to the new path, and hashed

Bun resolves all paths relative to your HTML file, so you can organize your project however you want.

<Warning>
  **This is a work in progress**

  * Need more plugins
  * Need more configuration options for things like asset handling
  * Need a way to configure CORS, headers, etc.
</Warning>

## How this works

The dev server is a small wrapper around Bun's support for [HTML imports](/docs/bundler/fullstack) in JavaScript.

## Standalone HTML

You can bundle your entire frontend into a **single self-contained `.html` file** with no external dependencies using `--compile --target=browser`. Bun inlines all JavaScript, CSS, and images directly into the HTML.

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun build --compile --target=browser ./index.html --outdir=dist
```

Learn more in the [Standalone HTML docs](/docs/bundler/standalone-html).

## Adding a backend to your frontend

To add a backend to your frontend, use the `routes` option in `Bun.serve`. See the [full-stack docs](/docs/bundler/fullstack).


# Bundler
Source: https://bun.com/docs/bundler/index

Bun's fast native bundler for JavaScript, TypeScript, JSX, and more

Use Bun's native bundler through the `bun build` CLI command or the `Bun.build()` JavaScript API.

### At a Glance

* JS API: `await Bun.build({ entrypoints, outdir })`
* CLI: `bun build <entry> --outdir ./out`
* Watch: `--watch` for incremental rebuilds
* Targets: `--target browser|bun|node`
* Formats: `--format esm|cjs|iife` (experimental for cjs/iife)

<Tabs>
  <Tab title="JavaScript">
    ```ts title="build.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
    await Bun.build({
      entrypoints: ['./index.tsx'],
      outdir: './build',
    });
    ```
  </Tab>

  <Tab title="CLI">
    ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
    bun build ./index.tsx --outdir ./build
    ```
  </Tab>
</Tabs>

It's fast. The following numbers are from esbuild's [three.js benchmark](https://github.com/oven-sh/bun/tree/main/bench/bundle).

<Frame>
  <img />
</Frame>

## Why bundle?

Bundlers solve several problems:

* **Reducing HTTP requests.** A single package in `node_modules` may consist of hundreds of files, and large applications may have dozens of such dependencies. Loading each of these files with a separate HTTP request becomes untenable, so bundlers convert your application source code into a smaller number of self-contained "bundles" that can be loaded with a single request.
* **Code transforms.** Modern apps are commonly built with languages or tools like TypeScript, JSX, and CSS modules. All of these must be converted into plain JavaScript and CSS before a browser can consume them. The bundler is the natural place to configure these transformations.
* **Framework features.** Frameworks rely on bundler plugins & code transformations to implement common patterns like file-system routing, client-server code co-location (think `getServerSideProps` or Remix loaders), and server components.
* **Full-stack Applications.** Bun's bundler can handle both server and client code in a single command, enabling optimized production builds and single-file executables. With build-time HTML imports, you can bundle your entire application — frontend assets and backend server — into a single deployable unit.

<Note>The Bun bundler is not intended to replace `tsc` for typechecking or generating type declarations.</Note>

## Basic example

Build your first bundle. You have the following two files, which implement a client-side rendered React app.

<CodeGroup>
  ```tsx index.tsx icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
  import * as ReactDOM from "react-dom/client";
  import { Component } from "./Component";

  const root = ReactDOM.createRoot(document.getElementById("root")!);
  root.render(<Component message="Sup!" />);
  ```

  ```tsx Component.tsx icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
  export function Component(props: { message: string }) {
    return <h1>{props.message}</h1>;
  }
  ```
</CodeGroup>

Here, `index.tsx` is the "entrypoint" to the application: the file the bundler starts from. Commonly, this is a script that performs some side effect, like starting a server or, in this case, initializing a React root. Because these files use TypeScript and JSX, the code must be bundled before it can be sent to the browser.

To create the bundle:

<CodeGroup>
  ```ts build.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
  await Bun.build({
    entrypoints: ["./index.tsx"],
    outdir: "./out",
  });
  ```

  ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
  bun build ./index.tsx --outdir ./out
  ```
</CodeGroup>

For each file specified in `entrypoints`, Bun generates a new bundle and writes it to the `./out` directory (as resolved from the current working directory). After running the build, the file system looks like this:

```text title="file system" icon="folder-tree" theme={"theme":{"light":"github-light","dark":"dracula"}}
.
├── index.tsx
├── Component.tsx
└── out
    └── index.js
```

The contents of `out/index.js` look something like this:

```js title="out/index.js" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/javascript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=5148f41bbc784f9828f1363dab67340f" theme={"theme":{"light":"github-light","dark":"dracula"}}
// out/index.js
// ...
// ~20k lines of code
// including the contents of `react-dom/client` and all its dependencies
// this is where the $jsxDEV and $createRoot functions are defined

// Component.tsx
function Component(props) {
  return $jsxDEV(
    "h1",
    {
      children: props.message,
    },
    undefined,
    false,
    undefined,
    this,
  );
}

// index.tsx
var rootNode = document.getElementById("root");
var root = $createRoot(rootNode);
root.render(
  $jsxDEV(
    Component,
    {
      message: "Sup!",
    },
    undefined,
    false,
    undefined,
    this,
  ),
);
```

## Watch mode

Like the runtime and test runner, the bundler supports watch mode natively.

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun build ./index.tsx --outdir ./out --watch
```

## Content types

Like the Bun runtime, the bundler supports a range of file types by default. The following table lists the bundler's standard "loaders". See [loaders](/docs/bundler/loaders).

| Extensions                                            | Details                                                                                                                                                                                                                                                                                                                     |
| ----------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `.js` `.jsx` `.cjs` `.mjs` `.mts` `.cts` `.ts` `.tsx` | Uses Bun's built-in transpiler to parse the file and transpile TypeScript/JSX syntax to vanilla JavaScript. The bundler executes a set of default transforms including dead code elimination and tree shaking. Bun does not down-convert syntax; if you use recent ECMAScript syntax, it appears as-is in the bundled code. |
| `.json`                                               | JSON files are parsed and inlined into the bundle as a JavaScript object.<br /><br />`js<br/>import pkg from "./package.json";<br/>pkg.name; // => "my-package"<br/>`                                                                                                                                                       |
| `.jsonc`                                              | JSON with comments. Files are parsed and inlined into the bundle as a JavaScript object.<br /><br />`js<br/>import config from "./config.jsonc";<br/>config.name; // => "my-config"<br/>`                                                                                                                                   |
| `.toml`                                               | TOML files are parsed and inlined into the bundle as a JavaScript object.<br /><br />`js<br/>import config from "./bunfig.toml";<br/>config.logLevel; // => "debug"<br/>`                                                                                                                                                   |
| `.yaml` `.yml`                                        | YAML files are parsed and inlined into the bundle as a JavaScript object.<br /><br />`js<br/>import config from "./config.yaml";<br/>config.name; // => "my-app"<br/>`                                                                                                                                                      |
| `.txt`                                                | The contents of the text file are read and inlined into the bundle as a string.<br /><br />`js<br/>import contents from "./file.txt";<br/>console.log(contents); // => "Hello, world!"<br/>`                                                                                                                                |
| `.html`                                               | HTML files are processed and any referenced assets (scripts, stylesheets, images) are bundled.                                                                                                                                                                                                                              |
| `.css`                                                | CSS files are bundled together into a single `.css` file in the output directory.                                                                                                                                                                                                                                           |
| `.node` `.wasm`                                       | The Bun runtime supports these files, but the bundler treats them as assets.                                                                                                                                                                                                                                                |

### Assets

If the bundler encounters an import with an unrecognized extension, it treats the imported file as an external file. The bundler copies the referenced file as-is into `outdir` and resolves the import as a path to the file.

<CodeGroup>
  ```ts Input icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
  // bundle entrypoint
  import logo from "./logo.svg";
  console.log(logo);
  ```

  ```ts Output icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/javascript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=5148f41bbc784f9828f1363dab67340f" theme={"theme":{"light":"github-light","dark":"dracula"}}
  // bundled output
  var logo = "./logo-a7305bdef.svg";
  console.log(logo);
  ```
</CodeGroup>

The exact behavior of the file loader also depends on [`naming`](#naming) and [`publicPath`](#publicpath).

<Info>See [loaders](/docs/bundler/loaders) for more on the file loader.</Info>

### Plugins

Plugins can override or extend the behavior described in this table. See [loaders](/docs/bundler/loaders).

## API

### entrypoints

<Badge>Required</Badge>

An array of paths corresponding to the entrypoints of your application. Bun generates one bundle per entrypoint.

<Tabs>
  <Tab title="JavaScript">
    ```ts title="build.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
    const result = await Bun.build({
      entrypoints: ["./index.ts"],
    });
    // => { success: boolean, outputs: BuildArtifact[], logs: BuildMessage[] }
    ```
  </Tab>

  <Tab title="CLI">
    ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
    bun build ./index.ts
    ```
  </Tab>
</Tabs>

### files

A map of file paths to their contents for in-memory bundling: bundle virtual files that don't exist on disk, or override the contents of files that do. This option is only available in the JavaScript API.

You can provide file contents as a `string`, `Blob`, `TypedArray`, or `ArrayBuffer`.

#### Bundle entirely from memory

You can bundle code without any files on disk by providing all sources in `files`:

```ts title="build.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
const result = await Bun.build({
  entrypoints: ["/app/index.ts"],
  files: {
    "/app/index.ts": `
      import { greet } from "./greet.ts";
      console.log(greet("World"));
    `,
    "/app/greet.ts": `
      export function greet(name: string) {
        return "Hello, " + name + "!";
      }
    `,
  },
});

const output = await result.outputs[0].text();
console.log(output);
```

When all entrypoints are in the `files` map, Bun uses the current working directory as the root.

#### Override files on disk

In-memory files take priority over files on disk, so you can override specific files while keeping the rest of your codebase unchanged:

```ts title="build.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
// Assume ./src/config.ts exists on disk with development settings
await Bun.build({
  entrypoints: ["./src/index.ts"],
  files: {
    // Override config.ts with production values
    "./src/config.ts": `
      export const API_URL = "https://api.production.com";
      export const DEBUG = false;
    `,
  },
  outdir: "./dist",
});
```

#### Mix disk and virtual files

Real files on disk can import virtual files, and virtual files can import real files:

```ts title="build.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
// ./src/index.ts exists on disk and imports "./generated.ts"
await Bun.build({
  entrypoints: ["./src/index.ts"],
  files: {
    // Provide a virtual file that index.ts imports
    "./src/generated.ts": `
      export const BUILD_ID = "${crypto.randomUUID()}";
      export const BUILD_TIME = ${Date.now()};
    `,
  },
  outdir: "./dist",
});
```

Use this for code generation, injecting build-time constants, or testing with mock modules.

### outdir

The directory where output files are written.

<Tabs>
  <Tab title="JavaScript">
    ```ts title="build.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
    const result = await Bun.build({
      entrypoints: ['./index.ts'],
      outdir: './out'
    });
    // => { success: boolean, outputs: BuildArtifact[], logs: BuildMessage[] }
    ```
  </Tab>

  <Tab title="CLI">
    ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
    bun build ./index.ts --outdir ./out
    ```
  </Tab>
</Tabs>

If you don't pass `outdir` to the JavaScript API, Bun does not write bundled code to disk. It returns the bundled files in an array of `BuildArtifact` objects. These objects are Blobs with extra properties; see [Outputs](#outputs).

```ts title="build.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
const result = await Bun.build({
  entrypoints: ["./index.ts"],
});

for (const res of result.outputs) {
  // Can be consumed as blobs
  await res.text();

  // Bun sets Content-Type and Etag headers
  new Response(res);

  // Can be written manually, but you should use `outdir` in this case.
  Bun.write(path.join("out", res.path), res);
}
```

When `outdir` is set, the `path` property on a `BuildArtifact` is the absolute path it was written to.

### target

The intended execution environment for the bundle.

<Tabs>
  <Tab title="JavaScript">
    ```ts title="build.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
    await Bun.build({
      entrypoints: ['./index.ts'],
      outdir: './out',
      target: 'browser', // default
    })
    ```
  </Tab>

  <Tab title="CLI">
    ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
    bun build ./index.ts --outdir ./out --target browser
    ```
  </Tab>
</Tabs>

Depending on the target, Bun applies different module resolution rules and optimizations.

<Card title="browser" icon="globe">
  **Default.** For bundles that run in a browser. Prioritizes the `"browser"` export condition when resolving imports.
  Importing built-in modules like `node:events` or `node:path` works, but calling some functions, like `fs.readFile`,
  does not.
</Card>

<Card title="bun" icon="server">
  For bundles that run in the Bun runtime. In many cases, it isn't necessary to bundle server-side code; you can directly execute the source code without modification. However, bundling your server code can reduce startup times and improve running performance. Use this target for full-stack applications with build-time HTML imports, where server and client code are bundled together.

  All bundles generated with `target: "bun"` are marked with a `// @bun` pragma, which tells the Bun runtime that there's no need to re-transpile the file before execution.

  If any entrypoint contains a Bun shebang (`#!/usr/bin/env bun`), the bundler defaults to `target: "bun"` instead of `"browser"`.

  When you use `target: "bun"` and `format: "cjs"` together, the bundler adds the `// @bun @bun-cjs` pragma, and the CommonJS wrapper function is not compatible with Node.js.
</Card>

<Card title="node" icon="node">
  For bundles that run in Node.js. Prioritizes the `"node"` export condition when resolving imports. Bun does not
  polyfill the `Bun` global or the built-in `bun:*` modules.
</Card>

### format

Specifies the module format of the generated bundles.

Bun defaults to `"esm"`, and provides experimental support for `"cjs"` and `"iife"`.

#### format: "esm" - ES Module

The default format. Supports ES Module syntax, including top-level await and `import.meta`.

<Tabs>
  <Tab title="JavaScript">
    ```ts title="build.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
    await Bun.build({
      entrypoints: ['./index.tsx'],
      outdir: './out',
      format: "esm",
    })
    ```
  </Tab>

  <Tab title="CLI">
    ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
    bun build ./index.tsx --outdir ./out --format esm
    ```
  </Tab>
</Tabs>

To use ES Module syntax in browsers, set `format` to `"esm"` and load the bundle with a `<script type="module">` tag.

#### format: "cjs" - CommonJS

To build a CommonJS module, set `format` to `"cjs"`. When you choose `"cjs"`, the default target changes from `"browser"` (esm) to `"node"` (cjs). CommonJS modules transpiled with `format: "cjs"`, `target: "node"` run in both Bun and Node.js (assuming both support the APIs in use).

<Tabs>
  <Tab title="JavaScript">
    ```ts title="build.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
    await Bun.build({
      entrypoints: ['./index.tsx'],
      outdir: './out',
      format: "cjs",
    })
    ```
  </Tab>

  <Tab title="CLI">
    ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
    bun build ./index.tsx --outdir ./out --format cjs
    ```
  </Tab>
</Tabs>

#### format: "iife" - IIFE

To build an IIFE bundle, set `format` to `"iife"`. Bun wraps the bundle in an immediately invoked function expression and does not support exposing its exports under a global name.

### `jsx`

Configures how JSX is compiled.

**Classic runtime example** (uses `factory` and `fragment`):

<CodeGroup>
  ```ts index.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
  await Bun.build({
    entrypoints: ["./app.tsx"],
    outdir: "./out",
    jsx: {
      factory: "h",
      fragment: "Fragment",
      runtime: "classic",
    },
  });
  ```

  ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
  # JSX configuration is handled via bunfig.toml or tsconfig.json
  bun build ./app.tsx --outdir ./out
  ```
</CodeGroup>

**Automatic runtime example** (uses `importSource`):

<CodeGroup>
  ```ts index.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
  await Bun.build({
    entrypoints: ["./app.tsx"],
    outdir: "./out",
    jsx: {
      importSource: "preact",
      runtime: "automatic",
    },
  });
  ```

  ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
  # JSX configuration is handled via bunfig.toml or tsconfig.json
  bun build ./app.tsx --outdir ./out
  ```
</CodeGroup>

### splitting

Whether to enable code splitting.

<Tabs>
  <Tab title="JavaScript">
    ```ts title="build.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
    await Bun.build({
      entrypoints: ['./index.tsx'],
      outdir: './out',
      splitting: false, // default
    })
    ```
  </Tab>

  <Tab title="CLI">
    ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
    bun build ./index.tsx --outdir ./out --splitting
    ```
  </Tab>
</Tabs>

When `true`, the bundler enables code splitting. When multiple entrypoints import the same file or module, the bundler can split that shared code into a separate bundle, known as a **chunk**. Consider the following files:

<CodeGroup>
  ```ts entry-a.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
  import { shared } from "./shared.ts";
  ```

  ```ts entry-b.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
  import { shared } from "./shared.ts";
  ```

  ```ts shared.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
  export const shared = "shared";
  ```
</CodeGroup>

To bundle `entry-a.ts` and `entry-b.ts` with code-splitting enabled:

<Tabs>
  <Tab title="JavaScript">
    ```ts title="build.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
    await Bun.build({
      entrypoints: ['./entry-a.ts', './entry-b.ts'],
      outdir: './out',
      splitting: true,
    })
    ```
  </Tab>

  <Tab title="CLI">
    ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
    bun build ./entry-a.ts ./entry-b.ts --outdir ./out --splitting
    ```
  </Tab>
</Tabs>

Running this build results in the following files:

```text title="file system" icon="folder-tree" theme={"theme":{"light":"github-light","dark":"dracula"}}
.
├── entry-a.ts
├── entry-b.ts
├── shared.ts
└── out
    ├── entry-a.js
    ├── entry-b.js
    └── chunk-2fce6291bf86559d.js
```

The generated `chunk-2fce6291bf86559d.js` file contains the shared code. To avoid collisions, the file name includes a content hash by default. Customize this with [`naming`](#naming).

### plugins

A list of plugins to use during bundling.

```ts title="build.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
await Bun.build({
  entrypoints: ["./index.tsx"],
  outdir: "./out",
  plugins: [
    /* ... */
  ],
});
```

The runtime and the bundler share Bun's plugin system. See [plugins](/docs/bundler/plugins).

### env

Controls how environment variables are handled during bundling. Internally, this option uses `define` to inject environment variables into the bundle; `env` is a shorthand for specifying which ones.

#### env: "inline"

Injects environment variables into the bundled output by converting `process.env.FOO` references to string literals containing the actual environment variable values.

<Tabs>
  <Tab title="JavaScript">
    ```ts title="build.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
    await Bun.build({
      entrypoints: ['./index.tsx'],
      outdir: './out',
      env: "inline",
    })
    ```
  </Tab>

  <Tab title="CLI">
    ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
    bun build ./index.tsx --outdir ./out --env inline
    ```
  </Tab>
</Tabs>

For the input below:

```js title="input.js" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/javascript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=5148f41bbc784f9828f1363dab67340f" theme={"theme":{"light":"github-light","dark":"dracula"}}
// input.js
console.log(process.env.FOO);
console.log(process.env.BAZ);
```

The generated bundle contains the following code:

```js title="output.js" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/javascript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=5148f41bbc784f9828f1363dab67340f" theme={"theme":{"light":"github-light","dark":"dracula"}}
// output.js
console.log("bar");
console.log("123");
```

#### env: "PUBLIC\_\*" (prefix)

Inlines environment variables matching the given prefix (the part before the `*` character), replacing `process.env.FOO` with the actual environment variable value. Use a prefix to inline public values, like public-facing URLs or client-side tokens, without injecting private credentials into output bundles.

<Tabs>
  <Tab title="JavaScript">
    ```ts title="build.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
    await Bun.build({
      entrypoints: ['./index.tsx'],
      outdir: './out',
      
      // Inline all env vars that start with "ACME_PUBLIC_"
      env: "ACME_PUBLIC_*",
    })
    ```
  </Tab>

  <Tab title="CLI">
    ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
    bun build ./index.tsx --outdir ./out --env ACME_PUBLIC_*
    ```
  </Tab>
</Tabs>

For example, given the following environment variables:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
FOO=bar BAZ=123 ACME_PUBLIC_URL=https://acme.com
```

And source code:

```tsx index.tsx icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
console.log(process.env.FOO);
console.log(process.env.ACME_PUBLIC_URL);
console.log(process.env.BAZ);
```

The generated bundle contains the following code:

```js title="output.js" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/javascript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=5148f41bbc784f9828f1363dab67340f" theme={"theme":{"light":"github-light","dark":"dracula"}}
console.log(process.env.FOO);
console.log("https://acme.com");
console.log(process.env.BAZ);
```

#### env: "disable"

Disables environment variable injection entirely.

### sourcemap

Specifies the type of sourcemap to generate.

<Tabs>
  <Tab title="JavaScript">
    ```ts title="build.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
    await Bun.build({
      entrypoints: ['./index.tsx'],
      outdir: './out',
      sourcemap: 'linked', // default 'none'
    })
    ```
  </Tab>

  <Tab title="CLI">
    ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
    bun build ./index.tsx --outdir ./out --sourcemap=linked
    ```
  </Tab>
</Tabs>

| Value        | Description                                                                                                                                                                                                                                                                                                                                                                                         |
| ------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `"none"`     | Default. No sourcemap is generated.                                                                                                                                                                                                                                                                                                                                                                 |
| `"linked"`   | A separate `*.js.map` file is created alongside each `*.js` bundle using a `//# sourceMappingURL` comment to link the two. Requires `--outdir` to be set. You can customize the base URL in this comment with `--public-path`.<br /><br />`js<br/>// <bundled code here><br/><br/>//# sourceMappingURL=bundle.js.map<br/>`                                                                          |
| `"external"` | A separate `*.js.map` file is created alongside each `*.js` bundle without inserting a `//# sourceMappingURL` comment.<br /><br />Generated bundles contain a debug id that can be used to associate a bundle with its corresponding sourcemap. This `debugId` is added as a comment at the bottom of the file.<br /><br />`js<br/>// <generated bundle code><br/><br/>//# debugId=<DEBUG ID><br/>` |
| `"inline"`   | A sourcemap is generated and appended to the end of the generated bundle as a base64 payload.<br /><br />`js<br/>// <bundled code here><br/><br/>//# sourceMappingURL=data:application/json;base64,<encoded sourcemap here><br/>`                                                                                                                                                                   |

The associated `*.js.map` sourcemap is a JSON file containing an equivalent `debugId` property.

### minify

Whether to enable minification. Default `false`.

To enable all minification options:

<Tabs>
  <Tab title="JavaScript">
    ```ts title="build.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
    await Bun.build({
      entrypoints: ['./index.tsx'],
      outdir: './out',
      minify: true, // default false
    })
    ```
  </Tab>

  <Tab title="CLI">
    ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
    bun build ./index.tsx --outdir ./out --minify
    ```
  </Tab>
</Tabs>

To granularly enable certain minifications:

<Tabs>
  <Tab title="JavaScript">
    ```ts title="build.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
    await Bun.build({
      entrypoints: ['./index.tsx'],
      outdir: './out',
      minify: {
        whitespace: true,
        identifiers: true,
        syntax: true,
      },
    })
    ```
  </Tab>

  <Tab title="CLI">
    ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
    bun build ./index.tsx --outdir ./out --minify-whitespace --minify-identifiers --minify-syntax
    ```
  </Tab>
</Tabs>

### external

A list of import paths to consider external. Defaults to `[]`.

<Tabs>
  <Tab title="JavaScript">
    ```ts title="build.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
    await Bun.build({
      entrypoints: ['./index.tsx'],
      outdir: './out',
      external: ["lodash", "react"], // default: []
    })
    ```
  </Tab>

  <Tab title="CLI">
    ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
    bun build ./index.tsx --outdir ./out --external lodash --external react
    ```
  </Tab>
</Tabs>

An external import is not included in the final bundle. Instead, the bundler leaves the import statement as-is, to be resolved at runtime.

For instance, consider the following entrypoint file:

```tsx index.tsx icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import _ from "lodash";
import { z } from "zod";

const value = z.string().parse("Hello world!");
console.log(_.upperCase(value));
```

Normally, bundling `index.tsx` would generate a bundle containing the entire source code of the "zod" package. To leave the import statement as-is instead, mark it as external:

<Tabs>
  <Tab title="JavaScript">
    ```ts title="build.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
    await Bun.build({
      entrypoints: ['./index.tsx'],
      outdir: './out',
      external: ['zod'],
    })
    ```
  </Tab>

  <Tab title="CLI">
    ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
    bun build ./index.tsx --outdir ./out --external zod
    ```
  </Tab>
</Tabs>

The generated bundle looks something like this:

```js title="out/index.js" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/javascript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=5148f41bbc784f9828f1363dab67340f" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { z } from "zod";

// ...
// the contents of the "lodash" package
// including the `_.upperCase` function

var value = z.string().parse("Hello world!");
console.log(_.upperCase(value));
```

To mark all imports as external, use the wildcard `*`:

<Tabs>
  <Tab title="JavaScript">
    ```ts title="build.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
    await Bun.build({
      entrypoints: ['./index.tsx'],
      outdir: './out',
      external: ['*'],
    })
    ```
  </Tab>

  <Tab title="CLI">
    ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
    bun build ./index.tsx --outdir ./out --external '*'
    ```
  </Tab>
</Tabs>

### packages

Controls whether package dependencies are included in the bundle. Possible values: `bundle` (default), `external`. Bun treats any import whose path does not start with `.`, `..`, or `/` as a package.

<Tabs>
  <Tab title="JavaScript">
    ```ts title="build.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
    await Bun.build({
      entrypoints: ['./index.ts'],
      packages: 'external',
    })
    ```
  </Tab>

  <Tab title="CLI">
    ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
    bun build ./index.ts --packages external
    ```
  </Tab>
</Tabs>

### naming

Customizes the generated file names. Defaults to `[dir]/[name].[ext]`.

<Tabs>
  <Tab title="JavaScript">
    ```ts title="build.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
    await Bun.build({
      entrypoints: ['./index.tsx'],
      outdir: './out',
      naming: "[dir]/[name].[ext]", // default
    })
    ```
  </Tab>

  <Tab title="CLI">
    ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
    bun build ./index.tsx --outdir ./out --entry-naming "[dir]/[name].[ext]"
    ```
  </Tab>
</Tabs>

By default, the names of the generated bundles are based on the name of the associated entrypoint.

```text title="file system" icon="folder-tree" theme={"theme":{"light":"github-light","dark":"dracula"}}
.
├── index.tsx
└── out
    └── index.js
```

With multiple entrypoints, the generated file hierarchy reflects the directory structure of the entrypoints.

```text title="file system" icon="folder-tree" theme={"theme":{"light":"github-light","dark":"dracula"}}
.
├── index.tsx
└── nested
    └── index.tsx
└── out
    ├── index.js
    └── nested
        └── index.js
```

The `naming` field customizes the names and locations of the generated files. It accepts a template string. Bun uses the template for all bundles that correspond to entrypoints and replaces the following tokens with their values:

* `[name]` - The name of the entrypoint file, without the extension.
* `[ext]` - The extension of the generated bundle.
* `[hash]` - A hash of the bundle contents.
* `[dir]` - The relative path from the project root to the parent directory of the source file.

For example:

| Token               | `[name]` | `[ext]` | `[hash]`   | `[dir]`             |
| ------------------- | -------- | ------- | ---------- | ------------------- |
| `./index.tsx`       | `index`  | `js`    | `a1b2c3d4` | `""` (empty string) |
| `./nested/entry.ts` | `entry`  | `js`    | `c3d4e5f6` | `"nested"`          |

Combine these tokens to create a template string. For instance, to include the hash in the generated bundle names:

<Tabs>
  <Tab title="JavaScript">
    ```ts title="build.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
    await Bun.build({
      entrypoints: ['./index.tsx'],
      outdir: './out',
      naming: 'files/[dir]/[name]-[hash].[ext]',
    })
    ```
  </Tab>

  <Tab title="CLI">
    ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
    bun build ./index.tsx --outdir ./out --entry-naming 'files/[dir]/[name]-[hash].[ext]'
    ```
  </Tab>
</Tabs>

This build would result in the following file structure:

```text title="file system" icon="folder-tree" theme={"theme":{"light":"github-light","dark":"dracula"}}
.
├── index.tsx
└── out
    └── files
        └── index-a1b2c3d4.js
```

When you provide a string for the `naming` field, Bun uses it only for bundles that correspond to entrypoints. The names of chunks and copied assets are not affected. In the JavaScript API, you can specify a separate template string for each type of generated file.

<Tabs>
  <Tab title="JavaScript">
    ```ts title="build.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
    await Bun.build({
      entrypoints: ['./index.tsx'],
      outdir: './out',
      naming: {
        // default values
        entry: '[dir]/[name].[ext]',
        chunk: '[name]-[hash].[ext]',
        asset: '[name]-[hash].[ext]',
      },
    })
    ```
  </Tab>

  <Tab title="CLI">
    ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
    bun build ./index.tsx --outdir ./out \
      --entry-naming '[dir]/[name].[ext]' \
      --chunk-naming '[name]-[hash].[ext]' \
      --asset-naming '[name]-[hash].[ext]'
    ```
  </Tab>
</Tabs>

### root

The root directory of the project.

<Tabs>
  <Tab title="JavaScript">
    ```ts title="build.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
    await Bun.build({
      entrypoints: ['./pages/a.tsx', './pages/b.tsx'],
      outdir: './out',
      root: '.',
    })
    ```
  </Tab>

  <Tab title="CLI">
    ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
    bun build ./pages/a.tsx ./pages/b.tsx --outdir ./out --root .
    ```
  </Tab>
</Tabs>

If unspecified, Bun uses the first common ancestor of all entrypoint files as the root. Consider the following file structure:

```text title="file system" icon="folder-tree" theme={"theme":{"light":"github-light","dark":"dracula"}}
.
└── pages
  └── index.tsx
  └── settings.tsx
```

Build both entrypoints in the `pages` directory:

<Tabs>
  <Tab title="JavaScript">
    ```js theme={"theme":{"light":"github-light","dark":"dracula"}}
    await Bun.build({
      entrypoints: ['./pages/index.tsx', './pages/settings.tsx'],
      outdir: './out',
    })
    ```
  </Tab>

  <Tab title="CLI">
    ```bash theme={"theme":{"light":"github-light","dark":"dracula"}}
    bun build ./pages/index.tsx ./pages/settings.tsx --outdir ./out
    ```
  </Tab>
</Tabs>

This would result in a file structure like this:

```text title="file system" icon="folder-tree" theme={"theme":{"light":"github-light","dark":"dracula"}}
.
└── pages
  └── index.tsx
  └── settings.tsx
└── out
  └── index.js
  └── settings.js
```

The `pages` directory is the first common ancestor of the entrypoint files, so Bun treats it as the project root. As a result, the generated bundles live at the top level of the `out` directory; there is no `out/pages` directory.

Override this by specifying the `root` option:

<Tabs>
  <Tab title="JavaScript">
    ```js theme={"theme":{"light":"github-light","dark":"dracula"}}
    await Bun.build({
      entrypoints: ['./pages/index.tsx', './pages/settings.tsx'],
      outdir: './out',
      root: '.',
    })
    ```
  </Tab>

  <Tab title="CLI">
    ```bash theme={"theme":{"light":"github-light","dark":"dracula"}}
    bun build ./pages/index.tsx ./pages/settings.tsx --outdir ./out --root .
    ```
  </Tab>
</Tabs>

With `.` as `root`, the generated file structure looks like this:

```
.
└── pages
  └── index.tsx
  └── settings.tsx
└── out
  └── pages
    └── index.js
    └── settings.js
```

### publicPath

A prefix added to any import paths in bundled code.

In many cases, generated bundles contain no import statements; the goal of bundling is to combine all of the code into a single file. In a few cases, though, the generated bundles contain import statements:

* **Asset imports** — When importing an unrecognized file type like `*.svg`, the bundler defers to the file loader, which copies the file into `outdir` as is. The import is converted into a variable.
* **External modules** — Files and modules marked as external are not included in the bundle. Instead, the bundler leaves the import statement in the final bundle.
* **Chunking.** When `splitting` is enabled, the bundler may generate separate "chunk" files that represent code that is shared among multiple entrypoints.

In any of these cases, the final bundles may contain paths to other files. By default these imports are relative. Here is an example of an asset import:

<CodeGroup>
  ```ts Input icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
  import logo from "./logo.svg";
  console.log(logo);
  ```

  ```ts Output icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/javascript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=5148f41bbc784f9828f1363dab67340f" theme={"theme":{"light":"github-light","dark":"dracula"}}
  var logo = "./logo-a7305bdef.svg";
  console.log(logo);
  ```
</CodeGroup>

Setting `publicPath` prefixes all file paths with the specified value.

<Tabs>
  <Tab title="JavaScript">
    ```ts title="build.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
    await Bun.build({
      entrypoints: ['./index.tsx'],
      outdir: './out',
      publicPath: 'https://cdn.example.com/', // default is undefined
    })
    ```
  </Tab>

  <Tab title="CLI">
    ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
    bun build ./index.tsx --outdir ./out --public-path 'https://cdn.example.com/'
    ```
  </Tab>
</Tabs>

The output file would now look something like this.

```js title="out/index.js" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/javascript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=5148f41bbc784f9828f1363dab67340f" theme={"theme":{"light":"github-light","dark":"dracula"}}
var logo = "https://cdn.example.com/logo-a7305bdef.svg";
```

### define

A map of global identifiers to be replaced at build time. Keys of this object are identifier names, and values are JSON strings, identifiers, or property paths that are inlined.

<Tabs>
  <Tab title="JavaScript">
    ```ts title="build.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
    await Bun.build({
      entrypoints: ['./index.tsx'],
      outdir: './out',
      define: {
        STRING: JSON.stringify("value"),
        "nested.boolean": "true",
      },
    })
    ```
  </Tab>

  <Tab title="CLI">
    ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
    bun build ./index.tsx --outdir ./out --define STRING='"value"' --define nested.boolean=true
    ```
  </Tab>
</Tabs>

### loader

A map of file extensions to built-in loader names. Use this to customize how certain files are loaded.

<Tabs>
  <Tab title="JavaScript">
    ```ts title="build.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
    await Bun.build({
      entrypoints: ['./index.tsx'],
      outdir: './out',
      loader: {
        ".png": "dataurl",
        ".txt": "file",
      },
    })
    ```
  </Tab>

  <Tab title="CLI">
    ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
    bun build ./index.tsx --outdir ./out --loader .png:dataurl --loader .txt:file
    ```
  </Tab>
</Tabs>

### banner

A banner added to the final bundle. This can be a directive like `"use client"` for React, or a comment block such as a license.

<Tabs>
  <Tab title="JavaScript">
    ```ts title="build.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
    await Bun.build({
      entrypoints: ['./index.tsx'],
      outdir: './out',
      banner: '"use client";'
    })
    ```
  </Tab>

  <Tab title="CLI">
    ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
    bun build ./index.tsx --outdir ./out --banner '"use client";'
    ```
  </Tab>
</Tabs>

### footer

A footer added to the final bundle. This can be a comment block for a license or a fun easter egg.

<Tabs>
  <Tab title="JavaScript">
    ```ts title="build.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
    await Bun.build({
      entrypoints: ['./index.tsx'],
      outdir: './out',
      footer: '// built with love in SF'
    })
    ```
  </Tab>

  <Tab title="CLI">
    ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
    bun build ./index.tsx --outdir ./out --footer '// built with love in SF'
    ```
  </Tab>
</Tabs>

### drop

Removes function calls from a bundle. For example, `--drop=console` removes all calls to `console.log`. Bun also removes the arguments to dropped calls, even if they have side effects. Dropping `debugger` removes all `debugger` statements.

<Tabs>
  <Tab title="JavaScript">
    ```ts title="build.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
    await Bun.build({
      entrypoints: ['./index.tsx'],
      outdir: './out',
      drop: ["console", "debugger", "anyIdentifier.or.propertyAccess"],
    })
    ```
  </Tab>

  <Tab title="CLI">
    ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
    bun build ./index.tsx --outdir ./out --drop console --drop debugger
    ```
  </Tab>
</Tabs>

### features

Enable compile-time feature flags for dead code elimination: conditionally include or exclude code paths at bundle time using `import { feature } from "bun:bundle"`.

```ts title="app.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { feature } from "bun:bundle";

if (feature("PREMIUM")) {
  // Only included when PREMIUM flag is enabled
  initPremiumFeatures();
}

if (feature("DEBUG")) {
  // Only included when DEBUG flag is enabled
  console.log("Debug mode");
}
```

<Tabs>
  <Tab title="JavaScript">
    ```ts title="build.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
    await Bun.build({
      entrypoints: ['./app.ts'],
      outdir: './out',
      features: ["PREMIUM"],  // PREMIUM=true, DEBUG=false
    })
    ```
  </Tab>

  <Tab title="CLI">
    ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
    bun build ./app.ts --outdir ./out --feature PREMIUM
    ```
  </Tab>
</Tabs>

Bun replaces the `feature()` function with `true` or `false` at bundle time. When minification is also enabled, Bun eliminates the unreachable code:

```ts title="Input" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { feature } from "bun:bundle";
const mode = feature("PREMIUM") ? "premium" : "free";
```

```js title="Output (with --feature PREMIUM --minify)" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/javascript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=5148f41bbc784f9828f1363dab67340f" theme={"theme":{"light":"github-light","dark":"dracula"}}
var mode = "premium";
```

```js title="Output (without --feature PREMIUM, with --minify)" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/javascript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=5148f41bbc784f9828f1363dab67340f" theme={"theme":{"light":"github-light","dark":"dracula"}}
var mode = "free";
```

**Key behaviors:**

* `feature()` requires a string literal argument — dynamic values are not supported
* Bun completely removes the `bun:bundle` import from the output
* Works with `bun build`, `bun run`, and `bun test`
* You can enable multiple flags: `--feature FLAG_A --feature FLAG_B`
* For type safety, augment the `Registry` interface to restrict `feature()` to known flags

**Use cases:**

* Platform-specific code (`feature("SERVER")` vs `feature("CLIENT")`)
* Environment-based features (`feature("DEVELOPMENT")`)
* Gradual feature rollouts
* A/B testing variants
* Paid tier features

**Type safety:** By default, `feature()` accepts any string. To get autocomplete and catch typos at compile time, create an `env.d.ts` file (or add to an existing `.d.ts`) and augment the `Registry` interface:

```ts title="env.d.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
declare module "bun:bundle" {
  interface Registry {
    features: "DEBUG" | "PREMIUM" | "BETA_FEATURES";
  }
}
```

Ensure the file is included in your `tsconfig.json` (for example, `"include": ["src", "env.d.ts"]`). Now `feature()` only accepts those flags, and invalid strings like `feature("TYPO")` become type errors.

### optimizeImports

Skip parsing unused submodules of barrel files (re-export index files). When you import only a few named exports from a large library, normally the bundler parses every file the barrel re-exports. With `optimizeImports`, the bundler parses only the submodules you use.

```ts title="build.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
await Bun.build({
  entrypoints: ["./app.ts"],
  outdir: "./out",
  optimizeImports: ["antd", "@mui/material", "lodash-es"],
});
```

For example, `import { Button } from 'antd'` normally parses all \~3000 modules that `antd/index.js` re-exports. With `optimizeImports: ['antd']`, the bundler parses only the `Button` submodule.

This works for **pure barrel files** — files where every named export is a re-export (`export { X } from './x'`). If a barrel file has any local exports (`export const foo = ...`), or if any importer uses `import *`, the bundler loads all submodules.

The bundler always loads `export *` re-exports (it never defers them) to avoid circular resolution issues. It defers only named re-exports (`export { X } from './x'`) that no importer uses.

**Automatic mode:** Packages with `"sideEffects": false` in their `package.json` get barrel optimization automatically — no `optimizeImports` config needed. Use `optimizeImports` for packages that don't have this field.

**Plugins:** Resolve and load plugins work with barrel optimization. Deferred submodules go through the plugin pipeline when they are eventually loaded.

### metafile

Generate metadata about the build in a structured format. The metafile describes every input and output file: sizes, imports, and exports. Use it for:

* **Bundle analysis**: Understand what's contributing to bundle size
* **Visualization**: Feed into tools like [esbuild's bundle analyzer](https://esbuild.github.io/analyze/)
* **Dependency tracking**: See the full import graph of your application
* **CI integration**: Track bundle size changes over time

<Tabs>
  <Tab title="JavaScript">
    ```ts title="build.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
    const result = await Bun.build({
      entrypoints: ['./src/index.ts'],
      outdir: './dist',
      metafile: true,
    });

    if (result.metafile) {
      // Analyze inputs
      for (const [path, meta] of Object.entries(result.metafile.inputs)) {
        console.log(`${path}: ${meta.bytes} bytes`);
      }

      // Analyze outputs
      for (const [path, meta] of Object.entries(result.metafile.outputs)) {
        console.log(`${path}: ${meta.bytes} bytes`);
      }

      // Save for external analysis tools
      await Bun.write('./dist/meta.json', JSON.stringify(result.metafile));
    }
    ```
  </Tab>

  <Tab title="CLI">
    ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
    bun build ./src/index.ts --outdir ./dist --metafile=./dist/meta.json
    ```
  </Tab>
</Tabs>

#### Markdown metafile

Use `--metafile-md` to generate a markdown metafile, which is LLM-friendly and readable in the terminal:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun build ./src/index.ts --outdir ./dist --metafile-md=./dist/meta.md
```

You can use both `--metafile` and `--metafile-md` together:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun build ./src/index.ts --outdir ./dist --metafile=./dist/meta.json --metafile-md=./dist/meta.md
```

#### `metafile` option formats

In the JavaScript API, `metafile` accepts several forms:

```ts title="build.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
// Boolean — include metafile in the result object
await Bun.build({
  entrypoints: ["./src/index.ts"],
  outdir: "./dist",
  metafile: true,
});

// String — write JSON metafile to a specific path
await Bun.build({
  entrypoints: ["./src/index.ts"],
  outdir: "./dist",
  metafile: "./dist/meta.json",
});

// Object — specify separate paths for JSON and markdown output
await Bun.build({
  entrypoints: ["./src/index.ts"],
  outdir: "./dist",
  metafile: {
    json: "./dist/meta.json",
    markdown: "./dist/meta.md",
  },
});
```

The metafile structure contains:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
interface BuildMetafile {
  inputs: {
    [path: string]: {
      bytes: number;
      imports: Array<{
        path: string;
        kind: ImportKind;
        original?: string; // Original specifier before resolution
        external?: boolean;
      }>;
      format?: "esm" | "cjs" | "json" | "css";
    };
  };
  outputs: {
    [path: string]: {
      bytes: number;
      inputs: {
        [path: string]: { bytesInOutput: number };
      };
      imports: Array<{ path: string; kind: ImportKind }>;
      exports: string[];
      entryPoint?: string;
      cssBundle?: string; // Associated CSS file for JS entry points
    };
  };
}
```

## Outputs

The `Bun.build` function returns a `Promise<BuildOutput>`, defined as:

```ts title="build.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
interface BuildOutput {
  outputs: BuildArtifact[];
  success: boolean;
  logs: Array<object>; // see docs for details
  metafile?: BuildMetafile; // only when metafile: true
}

interface BuildArtifact extends Blob {
  kind: "entry-point" | "chunk" | "asset" | "sourcemap" | "bytecode";
  path: string;
  loader: Loader;
  hash: string | null;
  sourcemap: BuildArtifact | null;
}
```

The `outputs` array contains all the files generated by the build. Each artifact implements the Blob interface.

```ts title="build.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
const build = await Bun.build({
  /* */
});

for (const output of build.outputs) {
  await output.arrayBuffer(); // => ArrayBuffer
  await output.bytes(); // => Uint8Array
  await output.text(); // string
}
```

Each artifact also contains the following properties:

| Property    | Description                                                                                                                                                |
| ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `kind`      | What kind of build output this file is. A build generates bundled entrypoints, code-split "chunks", sourcemaps, bytecode, and copied assets (like images). |
| `path`      | Absolute path to the file on disk                                                                                                                          |
| `loader`    | The loader used to interpret the file. See [loaders](/docs/bundler/loaders) for how Bun maps file extensions to built-in loaders.                               |
| `hash`      | The hash of the file contents. Always defined for assets.                                                                                                  |
| `sourcemap` | The sourcemap file corresponding to this file, if generated. Only defined for entrypoints and chunks.                                                      |

Similar to `BunFile`, `BuildArtifact` objects can be passed directly into `new Response()`.

```ts title="build.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
const build = await Bun.build({
  /* */
});

const artifact = build.outputs[0];

// Content-Type header is automatically set
return new Response(artifact);
```

The Bun runtime pretty-prints `BuildArtifact` objects to help with debugging.

<CodeGroup>
  ```ts build.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
  // build.ts
  const build = await Bun.build({
    /* */
  });

  const artifact = build.outputs[0];
  console.log(artifact);
  ```

  ```bash Shell output theme={"theme":{"light":"github-light","dark":"dracula"}}
  bun run build.ts

  BuildArtifact (entry-point) {
    path: "./index.js",
    loader: "tsx",
    kind: "entry-point",
    hash: "824a039620219640",
    Blob (74756 bytes) {
      type: "text/javascript;charset=utf-8"
    },
    sourcemap: BuildArtifact (sourcemap) {
      path: "./index.js.map",
      loader: "file",
      kind: "sourcemap",
      hash: "e7178cda3e72e301",
      Blob (24765 bytes) {
        type: "application/json;charset=utf-8"
      },
      sourcemap: null
    }
  }
  ```
</CodeGroup>

## Bytecode

The `bytecode: boolean` option generates bytecode for any JavaScript/TypeScript entrypoints, which can greatly improve startup times for large applications. Requires `"target": "bun"` and a matching version of Bun.

* **CommonJS**: Works with or without `compile: true`. Generates a `.jsc` file alongside each entrypoint.
* **ESM**: Requires `compile: true`. Bun embeds the bytecode and module metadata in the standalone executable.

Without an explicit `format`, bytecode defaults to CommonJS.

<Tabs>
  <Tab title="JavaScript">
    ```ts title="build.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
    // CommonJS bytecode (generates .jsc files)
    await Bun.build({
      entrypoints: ["./index.tsx"],
      outdir: "./out",
      bytecode: true,
    })

    // ESM bytecode (requires compile)
    await Bun.build({
      entrypoints: ["./index.tsx"],
      outfile: "./mycli",
      bytecode: true,
      format: "esm",
      compile: true,
    })
    ```
  </Tab>

  <Tab title="CLI">
    ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
    # CommonJS bytecode
    bun build ./index.tsx --outdir ./out --bytecode

    # ESM bytecode (requires --compile)
    bun build ./index.tsx --outfile ./mycli --bytecode --format=esm --compile
    ```
  </Tab>
</Tabs>

## Executables

Bun supports "compiling" a JavaScript/TypeScript entrypoint into a standalone executable. This executable contains a copy of the Bun binary.

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun build ./cli.tsx --outfile mycli --compile
./mycli
```

See [standalone executables](/docs/bundler/executables).

## Logs and errors

On failure, `Bun.build` returns a rejected promise with an `AggregateError`. Log it to the console to pretty-print the error list, or read it programmatically with a try/catch block.

```ts title="build.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
try {
  const result = await Bun.build({
    entrypoints: ["./index.tsx"],
    outdir: "./out",
  });
} catch (e) {
  // TypeScript does not allow annotations on the catch clause
  const error = e as AggregateError;
  console.error("Build Failed");

  // Example: Using the built-in formatter
  console.error(error);

  // Example: Serializing the failure as a JSON string.
  console.error(JSON.stringify(error, null, 2));
}
```

Most of the time, an explicit try/catch is not needed, as Bun prints uncaught exceptions. You can use a top-level await on the `Bun.build` call instead.

Each item in `error.errors` is an instance of `BuildMessage` or `ResolveMessage` (subclasses of `Error`), containing detailed information for each error.

```ts title="build.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
class BuildMessage {
  name: string;
  position?: Position;
  message: string;
  level: "error" | "warning" | "info" | "debug" | "verbose";
}

class ResolveMessage extends BuildMessage {
  code: string;
  referrer: string;
  specifier: string;
  importKind: ImportKind;
}
```

On build success, the returned object contains a `logs` property, which contains bundler warnings and info messages.

```ts title="build.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
const result = await Bun.build({
  entrypoints: ["./index.tsx"],
  outdir: "./out",
});

if (result.logs.length > 0) {
  console.warn("Build succeeded with warnings:");
  for (const message of result.logs) {
    // Bun pretty-prints the message object
    console.warn(message);
  }
}
```

## Reference

```ts Typescript Definitions icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" expandable theme={"theme":{"light":"github-light","dark":"dracula"}}
interface Bun {
  build(options: BuildOptions): Promise<BuildOutput>;
}

interface BuildConfig {
  entrypoints: string[]; // list of file path
  outdir?: string; // output directory
  target?: Target; // default: "browser"
  /**
   * Output module format. Top-level await is only supported for `"esm"`.
   *
   * Can be:
   * - `"esm"`
   * - `"cjs"` (**experimental**)
   * - `"iife"` (**experimental**)
   *
   * @default "esm"
   */
  format?: "esm" | "cjs" | "iife";
  /**
   * JSX configuration object for controlling JSX transform behavior
   */
  jsx?: {
    runtime?: "automatic" | "classic";
    importSource?: string;
    factory?: string;
    fragment?: string;
    sideEffects?: boolean;
    development?: boolean;
  };
  naming?:
    | string
    | {
        chunk?: string;
        entry?: string;
        asset?: string;
      };
  root?: string; // project root
  splitting?: boolean; // default false, enable code splitting
  plugins?: BunPlugin[];
  external?: string[];
  packages?: "bundle" | "external";
  publicPath?: string;
  define?: Record<string, string>;
  loader?: { [k in string]: Loader };
  sourcemap?: "none" | "linked" | "inline" | "external" | boolean; // default: "none", true -> "inline"
  /**
   * package.json `exports` conditions used when resolving imports
   *
   * Equivalent to `--conditions` in `bun build` or `bun run`.
   *
   * https://nodejs.org/api/packages.html#exports
   */
  conditions?: Array<string> | string;

  /**
   * Controls how environment variables are handled during bundling.
   *
   * Can be one of:
   * - `"inline"`: Injects environment variables into the bundled output by converting `process.env.FOO`
   *   references to string literals containing the actual environment variable values
   * - `"disable"`: Disables environment variable injection entirely
   * - A string ending in `*`: Inlines environment variables that match the given prefix.
   *   For example, `"MY_PUBLIC_*"` will only include env vars starting with "MY_PUBLIC_"
   */
  env?: "inline" | "disable" | `${string}*`;
  minify?:
    | boolean
    | {
        whitespace?: boolean;
        syntax?: boolean;
        identifiers?: boolean;
      };
  /**
   * Ignore dead code elimination/tree-shaking annotations such as @__PURE__ and package.json
   * "sideEffects" fields. This should only be used as a temporary workaround for incorrect
   * annotations in libraries.
   */
  ignoreDCEAnnotations?: boolean;
  /**
   * Force emitting @__PURE__ annotations even if minify.whitespace is true.
   */
  emitDCEAnnotations?: boolean;

  /**
   * Generate bytecode for the output. This can dramatically improve cold
   * start times, but will make the final output larger and slightly increase
   * memory usage.
   *
   * - CommonJS: works with or without `compile: true`
   * - ESM: requires `compile: true`
   *
   * Without an explicit `format`, defaults to CommonJS.
   *
   * Must be `target: "bun"`
   * @default false
   */
  bytecode?: boolean;
  /**
   * Add a banner to the bundled code such as "use client";
   */
  banner?: string;
  /**
   * Add a footer to the bundled code such as a comment block like
   *
   * `// made with bun!`
   */
  footer?: string;

  /**
   * Drop function calls to matching property accesses.
   */
  drop?: string[];

  /**
   * - When set to `true`, the returned promise rejects with an AggregateError when a build failure happens.
   * - When set to `false`, returns a {@link BuildOutput} with `{success: false}`
   *
   * @default true
   */
  throw?: boolean;

  /**
   * Custom tsconfig.json file path to use for path resolution.
   * Equivalent to `--tsconfig-override` in the CLI.
   */
  tsconfig?: string;

  outdir?: string;
}

interface BuildOutput {
  outputs: BuildArtifact[];
  success: boolean;
  logs: Array<BuildMessage | ResolveMessage>;
}

interface BuildArtifact extends Blob {
  path: string;
  loader: Loader;
  hash: string | null;
  kind: "entry-point" | "chunk" | "asset" | "sourcemap" | "bytecode";
  sourcemap: BuildArtifact | null;
}

type Loader =
  | "js"
  | "jsx"
  | "ts"
  | "tsx"
  | "css"
  | "json"
  | "jsonc"
  | "toml"
  | "yaml"
  | "text"
  | "file"
  | "napi"
  | "wasm"
  | "html";

interface BuildOutput {
  outputs: BuildArtifact[];
  success: boolean;
  logs: Array<BuildMessage | ResolveMessage>;
}

declare class ResolveMessage {
  readonly name: "ResolveMessage";
  readonly position: Position | null;
  readonly code: string;
  readonly message: string;
  readonly referrer: string;
  readonly specifier: string;
  readonly importKind:
    | "entry_point"
    | "stmt"
    | "require"
    | "import"
    | "dynamic"
    | "require_resolve"
    | "at"
    | "at_conditional"
    | "url"
    | "internal";
  readonly level: "error" | "warning" | "info" | "debug" | "verbose";

  toString(): string;
}
```

***

## CLI Usage

```bash theme={"theme":{"light":"github-light","dark":"dracula"}}
bun build <entry points>
```

### General Configuration

<ParamField type="boolean">
  Set <code>NODE\_ENV=production</code> and enable minification
</ParamField>

<ParamField type="boolean">
  Use a bytecode cache when compiling
</ParamField>

<ParamField type="string">
  Intended execution environment for the bundle. One of <code>browser</code>, <code>bun</code>, or <code>node</code>
</ParamField>

<ParamField type="string">
  Pass custom resolution conditions
</ParamField>

<ParamField type="string">
  Inline environment variables into the bundle as <code>process.env.\$</code>. To inline variables matching a
  prefix, use a glob like <code>FOO\_PUBLIC\_\*</code>
</ParamField>

### Output & File Handling

<ParamField type="string">
  Output directory (used when building multiple entry points)
</ParamField>

<ParamField type="string">
  Write output to a specific file
</ParamField>

<ParamField type="string">
  Write a JSON file with metadata about the build
</ParamField>

<ParamField type="string">
  Write a markdown file with a visualization of the module graph (LLM-friendly)
</ParamField>

<ParamField type="string">
  Generate source maps. One of <code>linked</code>, <code>inline</code>, <code>external</code>, or <code>none</code>
</ParamField>

<ParamField type="string">
  Add a banner to the output (e.g. <code>"use client"</code> for React Server Components)
</ParamField>

<ParamField type="string">
  Add a footer to the output (e.g. <code>// built with bun!</code>)
</ParamField>

<ParamField type="string">
  Module format of the output bundle. One of <code>esm</code>, <code>cjs</code>, or <code>iife</code>. Defaults to
  <code>cjs</code> when <code>--bytecode</code> is used.
</ParamField>

### File Naming

<ParamField type="string">
  Customize entry point filenames
</ParamField>

<ParamField type="string">
  Customize chunk filenames
</ParamField>

<ParamField type="string">
  Customize asset filenames
</ParamField>

### Bundling Options

<ParamField type="string">
  Root directory used when bundling multiple entry points
</ParamField>

<ParamField type="boolean">
  Enable code splitting for shared modules
</ParamField>

<ParamField type="string">
  Prefix the bundler adds to import paths in bundled code
</ParamField>

<ParamField type="string">
  Exclude modules from the bundle (supports wildcards). Alias: <code>-e</code>
</ParamField>

<ParamField type="string">
  Allow unresolved dynamic import()/require() specifiers matching these glob patterns. Pass <code>''</code> to allow
  opaque specifiers
</ParamField>

<ParamField type="boolean">
  Fail the build on any dynamic import()/require() specifier that cannot be resolved at build time
</ParamField>

<ParamField type="string">
  How to treat dependencies: <code>external</code> or <code>bundle</code>
</ParamField>

<ParamField type="boolean">
  Transpile only — do not bundle
</ParamField>

<ParamField type="boolean">
  Chunk CSS files together to reduce duplication (only when multiple entry points import CSS)
</ParamField>

### Minification & Optimization

<ParamField type="boolean">
  Re-emit Dead Code Elimination annotations. Disabled when <code>--minify-whitespace</code> is used
</ParamField>

<ParamField type="boolean">
  Enable all minification options
</ParamField>

<ParamField type="boolean">
  Minify syntax and inline constants
</ParamField>

<ParamField type="boolean">
  Minify whitespace
</ParamField>

<ParamField type="boolean">
  Minify variable and function identifiers
</ParamField>

<ParamField type="boolean">
  Preserve original function and class names when minifying
</ParamField>

### Development Features

<ParamField type="boolean">
  Rebuild automatically when files change
</ParamField>

<ParamField type="boolean">
  Don’t clear the terminal when rebuilding with <code>--watch</code>
</ParamField>

<ParamField type="boolean">
  Enable React Fast Refresh transform (for development testing)
</ParamField>

<ParamField type="boolean">
  Run the React Compiler over `.jsx`/`.tsx` files, automatically memoizing components and hooks. The bundler derives the
  output mode from `--target` (`browser` → client, `bun`/`node` → ssr). Experimental.
</ParamField>

### Standalone Executables

<ParamField type="boolean">
  Generate a standalone Bun executable containing the bundle
</ParamField>

<ParamField type="string">
  Prepend arguments to the standalone executable’s <code>execArgv</code>
</ParamField>

<ParamField type="boolean">
  Enable autoloading of <code>.env</code> files in the standalone executable
</ParamField>

<ParamField type="boolean">
  Disable autoloading of <code>.env</code> files in the standalone executable
</ParamField>

<ParamField type="boolean">
  Enable autoloading of <code>bunfig.toml</code> in the standalone executable
</ParamField>

<ParamField type="boolean">
  Disable autoloading of <code>bunfig.toml</code> in the standalone executable
</ParamField>

<ParamField type="boolean">
  Enable autoloading of <code>tsconfig.json</code> at runtime in the standalone executable
</ParamField>

<ParamField type="boolean">
  Disable autoloading of <code>tsconfig.json</code> at runtime in the standalone executable
</ParamField>

<ParamField type="boolean">
  Enable autoloading of <code>package.json</code> at runtime in the standalone executable
</ParamField>

<ParamField type="boolean">
  Disable autoloading of <code>package.json</code> at runtime in the standalone executable
</ParamField>

<ParamField type="string">
  Path to a Bun executable to use for cross-compilation instead of downloading
</ParamField>

<ParamField type="string">
  Embed a file or directory into the compiled executable, preserving its relative path (requires <code>--compile</code>)
</ParamField>

### Windows Executable Details

<ParamField type="boolean">
  Prevent a console window from opening when running a compiled Windows executable
</ParamField>

<ParamField type="string">
  Set an icon for the Windows executable
</ParamField>

<ParamField type="string">
  Set the Windows executable product name
</ParamField>

<ParamField type="string">
  Set the Windows executable company name
</ParamField>

<ParamField type="string">
  Set the Windows executable version (e.g. <code>1.2.3.4</code>)
</ParamField>

<ParamField type="string">
  Set the Windows executable description
</ParamField>

<ParamField type="string">
  Set the Windows executable copyright notice
</ParamField>

### Experimental & App Building

<ParamField type="boolean">
  <b>(EXPERIMENTAL)</b> Build a web app for production using Bun Bake
</ParamField>

<ParamField type="boolean">
  <b>(EXPERIMENTAL)</b> Enable React Server Components
</ParamField>

<ParamField type="boolean">
  When <code>--app</code> is set, dump all server files to disk even for static builds
</ParamField>

<ParamField type="boolean">
  When <code>--app</code> is set, disable all minification
</ParamField>


# Loaders
Source: https://bun.com/docs/bundler/loaders

Built-in loaders for the Bun bundler and runtime

The Bun bundler has a set of built-in loaders.

> As a rule of thumb: **the bundler and the runtime both support the same set of file types by default.**

`.js` `.cjs` `.mjs` `.mts` `.cts` `.ts` `.tsx` `.jsx` `.css` `.json` `.jsonc` `.json5` `.toml` `.yaml` `.yml` `.xml` `.txt` `.text` `.md` `.markdown` `.wasm` `.node` `.html` `.sh`

Bun uses the file extension to choose which built-in loader parses the file. Every loader has a name, such as `js`, `tsx`, or `json`. Plugins that extend Bun with custom loaders refer to these names.

To specify a loader explicitly, use the `'type'` import attribute.

```ts title="index.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import my_toml from "./my_file" with { type: "toml" };
// or with dynamic imports
const { default: my_toml } = await import("./my_file", { with: { type: "toml" } });
```

## Built-in loaders

### `js`

**JavaScript loader.** Default for `.cjs` and `.mjs`.

Parses the code and applies a set of default transforms like dead-code elimination and tree shaking. Bun does not down-convert syntax.

***

### `jsx`

**JavaScript + JSX loader.** Default for `.js` and `.jsx`.

Same as the `js` loader, but JSX syntax is supported. By default, Bun down-converts JSX to plain JavaScript. The exact output depends on the `jsx*` compiler options in your `tsconfig.json`. See the [TypeScript documentation on JSX](https://www.typescriptlang.org/tsconfig#jsx).

***

### `ts`

**TypeScript loader.** Default for `.ts`, `.mts`, and `.cts`.

Strips out all TypeScript syntax, then behaves identically to the `js` loader. Bun does not perform typechecking.

***

### `tsx`

**TypeScript + JSX loader.** Default for `.tsx`.

Transpiles both TypeScript and JSX to vanilla JavaScript.

***

### `json`

**JSON loader.** Default for `.json`.

JSON files can be directly imported.

```js theme={"theme":{"light":"github-light","dark":"dracula"}}
import pkg from "./package.json";
pkg.name; // => "my-package"
```

During bundling, Bun inlines the parsed JSON into the bundle as a JavaScript object.

```js theme={"theme":{"light":"github-light","dark":"dracula"}}
const pkg = {
  name: "my-package",
  // ... other fields
};

pkg.name;
```

If you pass a `.json` file as an entrypoint to the bundler, Bun converts it to a `.js` module that `export default`s the parsed object.

<CodeGroup>
  ```json Input theme={"theme":{"light":"github-light","dark":"dracula"}}
  {
    "name": "John Doe",
    "age": 35,
    "email": "johndoe@example.com"
  }
  ```

  ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}}
  export default {
    name: "John Doe",
    age: 35,
    email: "johndoe@example.com",
  };
  ```
</CodeGroup>

***

### `jsonc`

**JSON with Comments loader.** Default for `.jsonc`.

JSONC (JSON with Comments) files can be directly imported. Bun parses them, stripping out comments and trailing commas.

```js theme={"theme":{"light":"github-light","dark":"dracula"}}
import config from "./config.jsonc";
console.log(config);
```

During bundling, Bun inlines the parsed JSONC into the bundle as a JavaScript object, identical to the `json` loader.

```js theme={"theme":{"light":"github-light","dark":"dracula"}}
var config = {
  option: "value",
};
```

<Note>
  Bun automatically uses the `jsonc` loader for `tsconfig.json`, `jsconfig.json`, `package.json`, and `bun.lock` files.
</Note>

***

### `toml`

**TOML loader.** Default for `.toml`.

TOML files can be directly imported. Bun parses them with its native TOML parser.

```js theme={"theme":{"light":"github-light","dark":"dracula"}}
import config from "./bunfig.toml";
config.logLevel; // => "debug"

// with an import attribute:
// import myCustomTOML from './my.config' with {type: "toml"};
```

During bundling, Bun inlines the parsed TOML into the bundle as a JavaScript object.

```js theme={"theme":{"light":"github-light","dark":"dracula"}}
var config = {
  logLevel: "debug",
  // ...other fields
};
config.logLevel;
```

If you pass a `.toml` file as an entrypoint, Bun converts it to a `.js` module that `export default`s the parsed object.

<CodeGroup>
  ```toml Input theme={"theme":{"light":"github-light","dark":"dracula"}}
  name = "John Doe"
  age = 35
  email = "johndoe@example.com"
  ```

  ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}}
  export default {
    name: "John Doe",
    age: 35,
    email: "johndoe@example.com",
  };
  ```
</CodeGroup>

***

### `yaml`

**YAML loader.** Default for `.yaml` and `.yml`.

YAML files can be directly imported. Bun parses them with its native YAML parser.

```js theme={"theme":{"light":"github-light","dark":"dracula"}}
import config from "./config.yaml";
console.log(config);

// with an import attribute:
import data from "./data.txt" with { type: "yaml" };
```

During bundling, Bun inlines the parsed YAML into the bundle as a JavaScript object.

```js theme={"theme":{"light":"github-light","dark":"dracula"}}
var config = {
  name: "my-app",
  version: "1.0.0",
  // ...other fields
};
```

If you pass a `.yaml` or `.yml` file as an entrypoint, Bun converts it to a `.js` module that `export default`s the parsed object.

<CodeGroup>
  ```yaml Input theme={"theme":{"light":"github-light","dark":"dracula"}}
  name: John Doe
  age: 35
  email: johndoe@example.com
  ```

  ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}}
  export default {
    name: "John Doe",
    age: 35,
    email: "johndoe@example.com",
  };
  ```
</CodeGroup>

***

### `xml`

**XML loader.** Default for `.xml`.

XML files can be directly imported. Bun parses them with its native XML 1.0 parser into the compact object shape of [`Bun.XML.parse`](/docs/runtime/xml):

* One key for the root element
* `"@name"` keys for attributes
* Arrays for repeated child elements
* `"#text"` for text next to attributes or children
* Every value is a string

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
import doc from "./config.xml";
console.log(doc.config["@version"]);

// via import attribute:
import feed from "./export.rss" with { type: "xml" };
```

During bundling, Bun inlines the parsed XML into the bundle as a JavaScript object.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
var doc = {
  config: {
    "@version": "2",
    // ...other fields
  },
};
```

If you pass a `.xml` file as an entrypoint, Bun converts it to a `.js` module that `export default`s the parsed object.

<CodeGroup>
  ```xml Input theme={"theme":{"light":"github-light","dark":"dracula"}}
  <user id="1">
    <name>John Doe</name>
    <email>johndoe@example.com</email>
    <role>admin</role>
    <role>editor</role>
  </user>
  ```

  ```ts Output theme={"theme":{"light":"github-light","dark":"dracula"}}
  export default {
    user: {
      "@id": "1",
      name: "John Doe",
      email: "johndoe@example.com",
      role: ["admin", "editor"],
    },
  };
  ```
</CodeGroup>

***

### `text`

**Text loader.** Default for `.txt` and `.text`.

Text files can be directly imported. Bun reads the file and returns it as a string.

```js theme={"theme":{"light":"github-light","dark":"dracula"}}
import contents from "./file.txt";
console.log(contents); // => "Hello, world!"

// To import an html file as text
// The "type" attribute overrides the default loader.
import html from "./index.html" with { type: "text" };
```

When the file is referenced during a build, Bun inlines the contents into the bundle as a string.

```js theme={"theme":{"light":"github-light","dark":"dracula"}}
var contents = `Hello, world!`;
console.log(contents);
```

If you pass a `.txt` file as an entrypoint, Bun converts it to a `.js` module that `export default`s the file contents.

<CodeGroup>
  ```txt Input theme={"theme":{"light":"github-light","dark":"dracula"}}
  Hello, world!
  ```

  ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}}
  export default "Hello, world!";
  ```
</CodeGroup>

***

### `napi`

**Native addon loader.** Default for `.node`.

In the runtime, native addons can be directly imported.

```js theme={"theme":{"light":"github-light","dark":"dracula"}}
import addon from "./addon.node";
console.log(addon);
```

<Note>In the bundler, Bun handles `.node` files with the `file` loader.</Note>

***

### `sqlite`

**SQLite loader.** Requires `with { "type": "sqlite" }` import attribute.

In the runtime and bundler, SQLite databases can be directly imported. Bun loads the database with `bun:sqlite`.

```js theme={"theme":{"light":"github-light","dark":"dracula"}}
import db from "./my.db" with { type: "sqlite" };
```

<Warning>The `sqlite` loader is only supported when the target is `bun`.</Warning>

By default, Bun does not bundle the database file on disk into the final output. The database file stays external to the bundle, so you can use a database loaded elsewhere.

You can change this behavior with the `"embed"` attribute:

```js theme={"theme":{"light":"github-light","dark":"dracula"}}
// embed the database into the bundle
import db from "./my.db" with { type: "sqlite", embed: "true" };
```

<Info>
  With a standalone executable, Bun embeds the database into the single-file executable.

  Otherwise, the database to embed is copied into the `outdir` with a hashed filename.
</Info>

***

### `html`

**HTML loader.** Default for `.html`.

The `html` loader processes HTML files and bundles any referenced assets. It:

* Bundles and hashes referenced JavaScript files (`<script src="...">`)
* Bundles and hashes referenced CSS files (`<link rel="stylesheet" href="...">`)
* Hashes referenced images (`<img src="...">`)
* Preserves external URLs (by default, anything starting with `http://` or `https://`)

For example, given this HTML file:

```html title="src/index.html" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
<!DOCTYPE html>
<html>
  <body>
    <img src="./image.jpg" alt="Local image" />
    <img src="https://example.com/image.jpg" alt="External image" />
    <script type="module" src="./script.js"></script>
  </body>
</html>
```

Bun outputs a new HTML file with the bundled assets:

```html title="dist/index.html" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
<!DOCTYPE html>
<html>
  <body>
    <img src="./image-HASHED.jpg" alt="Local image" />
    <img src="https://example.com/image.jpg" alt="External image" />
    <script type="module" src="./output-ALSO-HASHED.js"></script>
  </body>
</html>
```

The loader uses [`lol-html`](https://github.com/cloudflare/lol-html) to extract script and link tags as entrypoints, and other assets as external.

<Accordion title="List of supported HTML selectors">
  The selectors are:

  * `audio[src]`
  * `img[src]`
  * `img[srcset]`
  * `link[as='font'][href], link[type^='font/'][href]`
  * `link[as='image'][href]`
  * `link[as='style'][href]`
  * `link[as='video'][href], link[as='audio'][href]`
  * `link[as='worker'][href]`
  * `link[rel='icon'][href], link[rel='apple-touch-icon'][href]`
  * `link[rel='manifest'][href]`
  * `link[rel='stylesheet'][href]`
  * `script[src]`
  * `source[src]`
  * `source[srcset]`
  * `video[poster]`
  * `video[src]`
</Accordion>

<Note>
  **HTML Loader Behavior in Different Contexts**

  The `html` loader behaves differently depending on how it's used:

  * Static Build: When you run `bun build ./index.html`, Bun produces a static site with all assets bundled and hashed.
  * Runtime: When you run `bun run server.ts` (where `server.ts` imports an HTML file), Bun bundles assets on the fly during development, enabling features like hot module replacement.
  * Full-stack Build: When you run `bun build --target=bun server.ts` (where `server.ts` imports an HTML file), the import resolves to a manifest object that `Bun.serve` uses to serve pre-bundled assets in production.
</Note>

***

### `css`

**CSS loader.** Default for `.css`.

CSS files can be directly imported. The bundler parses and bundles them, handling `@import` statements and `url()` references.

```js theme={"theme":{"light":"github-light","dark":"dracula"}}
import "./styles.css";
```

During bundling, Bun combines all imported CSS files into a single `.css` file in the output directory.

```css theme={"theme":{"light":"github-light","dark":"dracula"}}
.my-class {
  background: url("./image.png");
}
```

***

### `sh`

**Bun Shell loader.** Default for `.sh` files.

This loader parses Bun Shell scripts. It's only supported when starting Bun itself, so it's not available in the bundler or in the runtime.

```bash theme={"theme":{"light":"github-light","dark":"dracula"}}
bun run ./script.sh
```

***

### `file`

**File loader.** Default for all unrecognized file types.

The file loader resolves the import as a path/URL to the imported file. It's commonly used for referencing media or font assets.

```js theme={"theme":{"light":"github-light","dark":"dracula"}}
// logo.ts
import logo from "./logo.svg";
console.log(logo);
```

In the runtime, Bun checks that `logo.svg` exists and resolves the import to its absolute path on disk.

```bash theme={"theme":{"light":"github-light","dark":"dracula"}}
bun run logo.ts
# Output: /path/to/project/logo.svg
```

In the bundler, Bun copies the file into `outdir` as-is, and the import resolves to a relative path pointing to the copied file.

```js theme={"theme":{"light":"github-light","dark":"dracula"}}
// Output
var logo = "./logo.svg";
console.log(logo);
```

If `publicPath` is set, the import uses its value as a prefix to construct an absolute path/URL.

| Public path                  | Resolved import                    |
| ---------------------------- | ---------------------------------- |
| `""` (default)               | `./logo.svg`                       |
| `"/assets/"`                 | `/assets/logo.svg`                 |
| `"https://cdn.example.com/"` | `https://cdn.example.com/logo.svg` |

<Note>The value of `naming.asset` determines the location and file name of the copied file.</Note>


# Macros
Source: https://bun.com/docs/bundler/macros

Run JavaScript functions at bundle-time with Bun macros

Macros are JavaScript functions that run at bundle-time. Bun inlines their return values directly into your bundle.

As a toy example, consider this function that returns a random number.

```ts title="random.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
export function random() {
  return Math.random();
}
```

This is a regular function in a regular file, but you can use it as a macro:

```tsx title="cli.tsx" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { random } from "./random.ts" with { type: "macro" };

console.log(`Your random number is ${random()}`);
```

<Note>
  Macros are marked with import attribute syntax, a Stage 4 TC39 proposal for attaching additional metadata to import
  statements.
</Note>

Bundle the file with `bun build`. Bun prints the bundled file to stdout.

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun build ./cli.tsx
```

```js theme={"theme":{"light":"github-light","dark":"dracula"}}
console.log(`Your random number is ${0.6805550949689833}`);
```

The source code of the `random` function occurs nowhere in the bundle. Instead, the function runs during bundling and Bun replaces the call (`random()`) with its result. Since the source code is never included in the bundle, macros can safely perform privileged operations like reading from a database.

## When to use macros

For small things you would otherwise write a one-off build script for, bundle-time code execution can be easier to maintain. It lives with the rest of your code and runs with the rest of the build. Bun parallelizes it automatically, and if it fails, the build fails too.

If you find yourself running a lot of code at bundle-time though, consider running a server instead.

## Import attributes

Macros are import statements annotated with either:

* `with { type: 'macro' }` — an import attribute, a Stage 4 ECMAScript proposal
* `assert { type: 'macro' }` — an import assertion, an earlier incarnation of import attributes that has now been abandoned (but several browsers and runtimes already support it)

## Security considerations

You must explicitly import a macro with `{ type: "macro" }` for it to run at bundle-time. These imports have no effect unless you call them, unlike regular JavaScript imports which may have side effects.

You can disable macros entirely with the `--no-macros` flag. It produces a build error like this:

```
error: Macros are disabled

foo();
^
./hello.js:3:1 53
```

To reduce the potential attack surface for malicious packages, Bun does not let code inside `node_modules/**/*` invoke macros. If a package attempts to invoke a macro, you'll see an error like this:

```
error: For security reasons, macros cannot be run from node_modules.

beEvil();
^
node_modules/evil/index.js:3:1 50
```

Your application code can still import macros from `node_modules` and invoke them.

```ts title="cli.tsx" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { macro } from "some-package" with { type: "macro" };

macro();
```

## Export condition "macro"

When shipping a library containing a macro to npm or another package registry, use the `"macro"` export condition to provide a version of your package exclusively for the macro environment.

```json title="package.json" icon="file-json" theme={"theme":{"light":"github-light","dark":"dracula"}}
{
  "name": "my-package",
  "exports": {
    "import": "./index.js",
    "require": "./index.js",
    "default": "./index.js",
    "macro": "./index.macro.js"
  }
}
```

With this configuration, users can consume your package at runtime or at bundle-time using the same import specifier:

```ts title="index.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import pkg from "my-package"; // runtime import
import { macro } from "my-package" with { type: "macro" }; // macro import
```

The first import resolves to `./node_modules/my-package/index.js`; Bun's bundler resolves the second to `./node_modules/my-package/index.macro.js`.

## Execution

When Bun's transpiler sees a macro import, it calls the function using Bun's JavaScript runtime and converts the return value into an AST node.

Macros run synchronously in the transpiler during the visiting phase, after the transpiler parses the file into an AST. They run in the order they are imported. The transpiler waits for each macro to finish before continuing, and awaits any Promise a macro returns.

Bun's bundler is multi-threaded, so macros execute in parallel in multiple spawned JavaScript "workers".

## Dead code elimination

The bundler performs dead code elimination after running and inlining macros. Given the following macro:

```ts title="returnFalse.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
export function returnFalse() {
  return false;
}
```

...bundling the following file produces an empty bundle, provided that the minify syntax option is enabled.

```ts title="index.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { returnFalse } from "./returnFalse.ts" with { type: "macro" };

if (returnFalse()) {
  console.log("This code is eliminated");
}
```

## Serializability

Bun's transpiler must be able to serialize the result of the macro to inline it into the AST. All JSON-compatible data structures are supported:

```ts title="macro.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
export function getObject() {
  return {
    foo: "bar",
    baz: 123,
    array: [1, 2, { nested: "value" }],
  };
}
```

Macros can be async, or return Promise instances. Bun's transpiler awaits the Promise and inlines the result.

```ts title="macro.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
export async function getText() {
  return "async value";
}
```

The transpiler implements special logic for serializing common data formats like `Response` and `Blob`.

* **Response**: Bun reads the `Content-Type` and serializes accordingly. For example, it parses a Response with type `application/json` into an object and inlines `text/plain` as a string. Bun base64-encodes Responses with an unrecognized or undefined type.
* **Blob**: As with Response, the serialization depends on the `type` property.

The result of `fetch` is `Promise<Response>`, so a macro can return it directly.

```ts title="macro.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
export function getObject() {
  return fetch("https://bun.com");
}
```

Functions and instances of most classes (except those listed earlier) are not serializable.

```ts title="macro.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
export function getText(url: string) {
  // this doesn't work!
  return () => {};
}
```

## Arguments

Macros can accept inputs, but only in limited cases. The value must be statically known. For example, the following is not allowed:

```ts title="index.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { getText } from "./getText.ts" with { type: "macro" };

export function howLong() {
  // the value of `foo` cannot be statically known
  const foo = Math.random() ? "foo" : "bar";

  const text = getText(`https://example.com/${foo}`);
  console.log("The page is ", text.length, " characters long");
}
```

However, if the value of `foo` is known at bundle-time (say, if it's a constant or the result of another macro), then the call is allowed:

```ts title="index.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { getText } from "./getText.ts" with { type: "macro" };
import { getFoo } from "./getFoo.ts" with { type: "macro" };

export function howLong() {
  // this works because getFoo() is statically known
  const foo = getFoo();
  const text = getText(`https://example.com/${foo}`);
  console.log("The page is", text.length, "characters long");
}
```

This outputs:

```js theme={"theme":{"light":"github-light","dark":"dracula"}}
function howLong() {
  console.log("The page is", 1322, "characters long");
}
export { howLong };
```

## Examples

### Embed latest git commit hash

```ts title="getGitCommitHash.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
export function getGitCommitHash() {
  const { stdout } = Bun.spawnSync({
    cmd: ["git", "rev-parse", "HEAD"],
    stdout: "pipe",
  });

  return stdout.toString();
}
```

When you build it, Bun replaces the `getGitCommitHash` call with the result of calling the function:

<CodeGroup>
  ```ts input theme={"theme":{"light":"github-light","dark":"dracula"}}
  import { getGitCommitHash } from "./getGitCommitHash.ts" with { type: "macro" };

  console.log(`The current Git commit hash is ${getGitCommitHash()}`);
  ```

  ```ts output theme={"theme":{"light":"github-light","dark":"dracula"}}
  console.log(`The current Git commit hash is 3ee3259104e4507cf62c160f0ff5357ec4c7a7f8`);
  ```
</CodeGroup>

<Info>
  You're probably thinking "Why not use `process.env.GIT_COMMIT_HASH`?" Well, you can do that too. But can you do this
  with an environment variable?
</Info>

### Make fetch() requests at bundle-time

This example makes an outgoing HTTP request with `fetch()`, parses the HTML response with `HTMLRewriter`, and returns an object containing the title and meta tags, all at bundle-time.

```ts title="meta.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
export async function extractMetaTags(url: string) {
  const response = await fetch(url);
  const meta = {
    title: "",
  };
  new HTMLRewriter()
    .on("title", {
      text(element) {
        meta.title += element.text;
      },
    })
    .on("meta", {
      element(element) {
        const name =
          element.getAttribute("name") || element.getAttribute("property") || element.getAttribute("itemprop");

        if (name) meta[name] = element.getAttribute("content");
      },
    })
    .transform(response);

  return meta;
}
```

Bun erases the `extractMetaTags` function at bundle-time and replaces it with the result of the function call. The fetch request happens at bundle-time, and Bun embeds the result in the bundle. Bun also eliminates the branch throwing the error since it's unreachable, provided that the minify syntax option is enabled.

<CodeGroup>
  ```jsx input theme={"theme":{"light":"github-light","dark":"dracula"}}
  import { extractMetaTags } from "./meta.ts" with { type: "macro" };

  export const Head = () => {
    const headTags = extractMetaTags("https://example.com");

    if (headTags.title !== "Example Domain") {
      throw new Error("Expected title to be 'Example Domain'");
    }

    return (
      <head>
        <title>{headTags.title}</title>
        <meta name="viewport" content={headTags.viewport} />
      </head>
    );
  };
  ```

  ```jsx output theme={"theme":{"light":"github-light","dark":"dracula"}}
  export const Head = () => {
    const headTags = {
      title: "Example Domain",
      viewport: "width=device-width, initial-scale=1",
    };

    return (
      <head>
        <title>{headTags.title}</title>
        <meta name="viewport" content={headTags.viewport} />
      </head>
    );
  };
  ```
</CodeGroup>


# Minifier
Source: https://bun.com/docs/bundler/minifier

Reduce bundle sizes with Bun's JavaScript and TypeScript minifier

Bun includes a fast JavaScript and TypeScript minifier. Depending on the codebase, it can reduce bundle sizes by 80% or more. It can also make output code run faster. The minifier performs dozens of optimizations including constant folding, dead code elimination, and syntax transformations. Unlike other minifiers, Bun's minifier makes `bun build` run faster since there's less code to print.

## CLI Usage

### Enable all minification

Use the `--minify` flag to enable all minification modes:

```bash theme={"theme":{"light":"github-light","dark":"dracula"}}
bun build ./index.ts --minify --outfile=out.js
```

The `--minify` flag enables:

* Whitespace minification
* Syntax minification
* Identifier minification

### Production mode

The `--production` flag automatically enables minification:

```bash theme={"theme":{"light":"github-light","dark":"dracula"}}
bun build ./index.ts --production --outfile=out.js
```

The `--production` flag also:

* Sets `process.env.NODE_ENV` to `production`
* Enables the production-mode JSX import & transform

### Granular control

Enable specific minification modes individually:

```bash theme={"theme":{"light":"github-light","dark":"dracula"}}
# Only remove whitespace
bun build ./index.ts --minify-whitespace --outfile=out.js

# Only minify syntax
bun build ./index.ts --minify-syntax --outfile=out.js

# Only minify identifiers
bun build ./index.ts --minify-identifiers --outfile=out.js

# Combine specific modes
bun build ./index.ts --minify-whitespace --minify-syntax --outfile=out.js
```

## JavaScript API

When using Bun's bundler programmatically, configure minification through the `minify` option:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
await Bun.build({
  entrypoints: ["./index.ts"],
  outdir: "./out",
  minify: true, // Enable all minification modes
});
```

For granular control, pass an object:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
await Bun.build({
  entrypoints: ["./index.ts"],
  outdir: "./out",
  minify: {
    whitespace: true,
    syntax: true,
    identifiers: true,
  },
});
```

## Minification Modes

Bun's minifier has three independent modes that you can enable separately or together.

### Whitespace minification (`--minify-whitespace`)

Removes all unnecessary whitespace, newlines, and formatting from the output.

### Syntax minification (`--minify-syntax`)

Rewrites JavaScript syntax to shorter equivalent forms and performs constant folding, dead code elimination, and other optimizations.

### Identifier minification (`--minify-identifiers`)

Renames local variables and function names to shorter identifiers using frequency-based optimization.

## All Transformations

### Boolean literal shortening

**Mode:** `--minify-syntax`

Converts boolean literals to shorter expressions.

```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}}
true
false
```

```js Output theme={"theme":{"light":"github-light","dark":"dracula"}}
!0
!1
```

### Boolean algebra optimizations

**Mode:** `--minify-syntax`

Simplifies boolean expressions using logical rules.

```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}}
if (!!x) y;
if (x && true) y;
if (x || false) y;
!true
!false
```

```js Output theme={"theme":{"light":"github-light","dark":"dracula"}}
if(x)y;
if(x)y;
if(x)y;
!1
!0
```

### Undefined shortening

**Mode:** `--minify-syntax`

Replaces `undefined` with shorter equivalent.

```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}}
undefined
let x = undefined;
```

```js Output theme={"theme":{"light":"github-light","dark":"dracula"}}
void 0
let x=void 0;
```

### Undefined equality optimization

**Mode:** `--minify-syntax`

Optimizes loose equality checks with undefined.

```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}}
x == undefined
x != undefined
```

```js Output theme={"theme":{"light":"github-light","dark":"dracula"}}
x == null
x != null
```

### Infinity shortening

**Mode:** `--minify-syntax`

Converts Infinity to mathematical expressions.

```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}}
Infinity
-Infinity
```

```js Output theme={"theme":{"light":"github-light","dark":"dracula"}}
1/0
-1/0
```

### Typeof optimizations

**Mode:** `--minify-syntax`

Optimizes typeof comparisons and evaluates constant typeof expressions.

```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}}
typeof x === 'undefined'
typeof x !== 'undefined'
typeof require
typeof null
typeof true
typeof 123
typeof "str"
typeof 123n
```

```js Output theme={"theme":{"light":"github-light","dark":"dracula"}}
typeof x>'u'
typeof x<'u'
"function"
"object"
"boolean"
"number"
"string"
"bigint"
```

### Number formatting

**Mode:** `--minify-syntax`

Formats numbers in the most compact representation.

```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}}
10000
100000
1000000
1.0
-42.0
```

```js Output theme={"theme":{"light":"github-light","dark":"dracula"}}
1e4
1e5
1e6
1
-42
```

### Arithmetic constant folding

**Mode:** `--minify-syntax`

Evaluates arithmetic operations at compile time.

```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}}
1 + 2
10 - 5
3 * 4
10 / 2
10 % 3
2 ** 3
```

```js Output theme={"theme":{"light":"github-light","dark":"dracula"}}
3
5
12
5
1
8
```

### Bitwise constant folding

**Mode:** `--minify-syntax`

Evaluates bitwise operations at compile time.

```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}}
5 & 3
5 | 3
5 ^ 3
8 << 2
32 >> 2
~5
```

```js Output theme={"theme":{"light":"github-light","dark":"dracula"}}
1
7
6
32
8
-6
```

### String concatenation

**Mode:** `--minify-syntax`

Combines string literals at compile time.

```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}}
"a" + "b"
"x" + 123
"foo" + "bar" + "baz"
```

```js Output theme={"theme":{"light":"github-light","dark":"dracula"}}
"ab"
"x123"
"foobarbaz"
```

### String indexing

**Mode:** `--minify-syntax`

Evaluates string character access at compile time.

```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}}
"foo"[2]
"hello"[0]
```

```js Output theme={"theme":{"light":"github-light","dark":"dracula"}}
"o"
"h"
```

### Template literal folding

**Mode:** `--minify-syntax`

Evaluates template literals with constant expressions.

```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}}
`a${123}b`
`result: ${5 + 10}`
```

```js Output theme={"theme":{"light":"github-light","dark":"dracula"}}
"a123b"
"result: 15"
```

### Template literal to string conversion

**Mode:** `--minify-syntax`

Converts template literals with no substitutions to regular strings.

```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}}
`Hello World`
`Line 1
Line 2`
```

```js Output theme={"theme":{"light":"github-light","dark":"dracula"}}
"Hello World"
"Line 1\nLine 2"
```

### String quote optimization

**Mode:** `--minify-syntax`

Chooses the optimal quote character to minimize escapes.

```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}}
"It's a string"
'He said "hello"'
`Simple string`
```

```js Output theme={"theme":{"light":"github-light","dark":"dracula"}}
"It's a string"
'He said "hello"'
"Simple string"
```

### Array spread inlining

**Mode:** `--minify-syntax`

Inlines array spread operations with constant arrays.

```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}}
[1, ...[2, 3], 4]
[...[a, b]]
```

```js Output theme={"theme":{"light":"github-light","dark":"dracula"}}
[1,2,3,4]
[a,b]
```

### Array indexing

**Mode:** `--minify-syntax`

Evaluates constant array access at compile time.

```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}}
[x][0]
['a', 'b', 'c'][1]
['a', , 'c'][1]
```

```js Output theme={"theme":{"light":"github-light","dark":"dracula"}}
x
'b'
void 0
```

### Property access optimization

**Mode:** `--minify-syntax`

Converts bracket notation to dot notation when possible.

```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}}
obj["property"]
obj["validName"]
obj["123"]
obj["invalid-name"]
```

```js Output theme={"theme":{"light":"github-light","dark":"dracula"}}
obj.property
obj.validName
obj["123"]
obj["invalid-name"]
```

### Comparison folding

**Mode:** `--minify-syntax`

Evaluates constant comparisons at compile time.

```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}}
3 < 5
5 > 3
3 <= 3
5 >= 6
"a" < "b"
```

```js Output theme={"theme":{"light":"github-light","dark":"dracula"}}
!0
!0
!0
!1
!0
```

### Logical operation folding

**Mode:** `--minify-syntax`

Simplifies logical operations with constant values.

```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}}
true && x
false && x
true || x
false || x
```

```js Output theme={"theme":{"light":"github-light","dark":"dracula"}}
x
!1
!0
x
```

### Nullish coalescing folding

**Mode:** `--minify-syntax`

Evaluates nullish coalescing with known values.

```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}}
null ?? x
undefined ?? x
42 ?? x
```

```js Output theme={"theme":{"light":"github-light","dark":"dracula"}}
x
x
42
```

### Comma expression simplification

**Mode:** `--minify-syntax`

Removes side-effect-free expressions from comma sequences.

```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}}
(0, x)
(123, "str", x)
```

```js Output theme={"theme":{"light":"github-light","dark":"dracula"}}
x
x
```

### Ternary conditional folding

**Mode:** `--minify-syntax`

Evaluates conditional expressions with constant conditions.

```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}}
true ? a : b
false ? a : b
x ? true : false
x ? false : true
```

```js Output theme={"theme":{"light":"github-light","dark":"dracula"}}
a
b
x ? !0 : !1
x ? !1 : !0
```

### Unary expression folding

**Mode:** `--minify-syntax`

Simplifies unary operations.

```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}}
+123
+"123"
-(-x)
~~x
!!x
```

```js Output theme={"theme":{"light":"github-light","dark":"dracula"}}
123
123
- -x
~~x
!!x
```

### Double negation removal

**Mode:** `--minify-syntax`

Removes unnecessary double negations.

```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}}
if (!!x) y;
!!!x
```

```js Output theme={"theme":{"light":"github-light","dark":"dracula"}}
if(x)y;
!x
```

### If statement optimization

**Mode:** `--minify-syntax`

Optimizes if statements with constant conditions.

```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}}
if (true) x;
if (false) x;
if (x) { a; }
if (x) {} else y;
```

```js Output theme={"theme":{"light":"github-light","dark":"dracula"}}
x;
// removed
if(x)a;
if(!x)y;
```

### Dead code elimination

**Mode:** `--minify-syntax`

Removes unreachable code and code without side effects.

```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}}
if (false) {
  unreachable();
}
function foo() {
  return x;
  deadCode();
}
```

```js Output theme={"theme":{"light":"github-light","dark":"dracula"}}
function foo(){return x}
```

### Unreachable branch removal

**Mode:** `--minify-syntax`

Removes branches that can never execute.

```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}}
while (false) {
  neverRuns();
}
```

```js Output theme={"theme":{"light":"github-light","dark":"dracula"}}
// removed entirely
```

### Empty block removal

**Mode:** `--minify-syntax`

Removes empty blocks and unnecessary braces.

```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}}
{ }
if (x) { }
```

```js Output theme={"theme":{"light":"github-light","dark":"dracula"}}
;
// removed
```

### Single statement block unwrapping

**Mode:** `--minify-syntax`

Removes unnecessary braces around single statements.

```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}}
if (condition) {
  doSomething();
}
```

```js Output theme={"theme":{"light":"github-light","dark":"dracula"}}
if(condition)doSomething();
```

### TypeScript enum inlining

**Mode:** `--minify-syntax`

Inlines TypeScript enum values at compile time.

```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}}
enum Color { Red, Green, Blue }
const x = Color.Red;
```

```js Output theme={"theme":{"light":"github-light","dark":"dracula"}}
const x=0;
```

### Pure annotation support

**Mode:** Always active

Respects `/*@__PURE__*/` annotations for tree shaking (removing unused code).

```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}}
const x = /*@__PURE__*/ expensive();
// If x is unused...
```

```js Output theme={"theme":{"light":"github-light","dark":"dracula"}}
// removed entirely
```

### Identifier renaming

**Mode:** `--minify-identifiers`

Renames local variables to shorter names based on usage frequency.

```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}}
function calculateSum(firstNumber, secondNumber) {
  const result = firstNumber + secondNumber;
  return result;
}
```

```js Output theme={"theme":{"light":"github-light","dark":"dracula"}}
function a(b,c){const d=b+c;return d}
```

**Naming strategy:**

* Most frequently used identifiers get the shortest names (a, b, c...)
* Single letters: a-z (26 names)
* Double letters: aa-zz (676 names)
* Triple letters and beyond as needed

**Preserved identifiers:**

* JavaScript keywords and reserved words
* Global identifiers
* Named exports (to maintain API)
* CommonJS names: `exports`, `module`

### Whitespace removal

**Mode:** `--minify-whitespace`

Removes all unnecessary whitespace.

```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}}
function add(a, b) {
    return a + b;
}
let x = 10;
```

```js Output theme={"theme":{"light":"github-light","dark":"dracula"}}
function add(a,b){return a+b}let x=10;
```

### Semicolon optimization

**Mode:** `--minify-whitespace`

Inserts semicolons only when necessary.

```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}}
let a = 1;
let b = 2;
return a + b;
```

```js Output theme={"theme":{"light":"github-light","dark":"dracula"}}
let a=1;let b=2;return a+b
```

### Operator spacing removal

**Mode:** `--minify-whitespace`

Removes spaces around operators.

```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}}
a + b
x = y * z
foo && bar || baz
```

```js Output theme={"theme":{"light":"github-light","dark":"dracula"}}
a+b
x=y*z
foo&&bar||baz
```

### Comment removal

**Mode:** `--minify-whitespace`

Removes comments except important license comments.

```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}}
// This comment is removed
/* So is this */
/*! But this license comment is kept */
function test() { /* inline comment */ }
```

```js Output theme={"theme":{"light":"github-light","dark":"dracula"}}
/*! But this license comment is kept */
function test(){}
```

### Object and array formatting

**Mode:** `--minify-whitespace`

Removes whitespace in object and array literals.

```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}}
const obj = {
    name: "John",
    age: 30
};
const arr = [1, 2, 3];
```

```js Output theme={"theme":{"light":"github-light","dark":"dracula"}}
const obj={name:"John",age:30};const arr=[1,2,3];
```

### Control flow formatting

**Mode:** `--minify-whitespace`

Removes whitespace in control structures.

```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}}
if (condition) {
    doSomething();
}
for (let i = 0; i < 10; i++) {
    console.log(i);
}
```

```js Output theme={"theme":{"light":"github-light","dark":"dracula"}}
if(condition)doSomething();for(let i=0;i<10;i++)console.log(i);
```

### Function formatting

**Mode:** `--minify-whitespace`

Removes whitespace in function declarations.

```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}}
function myFunction(param1, param2) {
    return param1 + param2;
}
const arrow = (a, b) => a + b;
```

```js Output theme={"theme":{"light":"github-light","dark":"dracula"}}
function myFunction(param1,param2){return param1+param2}const arrow=(a,b)=>a+b;
```

### Parentheses minimization

**Mode:** Always active

Only adds parentheses when necessary for operator precedence.

```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}}
(a + b) * c
a + (b * c)
((x))
```

```js Output theme={"theme":{"light":"github-light","dark":"dracula"}}
(a+b)*c
a+b*c
x
```

### Template literal value folding

**Mode:** `--minify-syntax`

Converts non-string interpolated values to strings and folds them into the template.

```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}}
`hello ${123}`
`value: ${true}`
`result: ${null}`
`status: ${undefined}`
`big: ${10n}`
```

```js Output theme={"theme":{"light":"github-light","dark":"dracula"}}
"hello 123"
"value: true"
"result: null"
"status: undefined"
"big: 10"
```

### String length constant folding

**Mode:** `--minify-syntax`

Evaluates `.length` property on string literals at compile time.

```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}}
"hello world".length
"test".length
```

```js Output theme={"theme":{"light":"github-light","dark":"dracula"}}
11
4
```

### Constructor call simplification

**Mode:** `--minify-syntax`

Simplifies constructor calls for built-in types.

```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}}
new Object()
new Object(null)
new Object({a: 1})
new Array()
new Array(x, y)
```

```js Output theme={"theme":{"light":"github-light","dark":"dracula"}}
{}
{}
{a:1}
[]
[x,y]
```

### Single property object inlining

**Mode:** `--minify-syntax`

Inlines property access for objects with a single property.

```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}}
({fn: () => console.log('hi')}).fn
```

```js Output theme={"theme":{"light":"github-light","dark":"dracula"}}
() => console.log('hi')
```

### String charCodeAt constant folding

**Mode:** Always active

Evaluates `charCodeAt()` on string literals for ASCII characters.

```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}}
"hello".charCodeAt(1)
"A".charCodeAt(0)
```

```js Output theme={"theme":{"light":"github-light","dark":"dracula"}}
101
65
```

### Void 0 equality to null equality

**Mode:** `--minify-syntax`

Converts loose equality checks with `void 0` to `null` since they're equivalent.

```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}}
x == void 0
x != void 0
```

```js Output theme={"theme":{"light":"github-light","dark":"dracula"}}
x == null
x != null
```

### Negation operator optimization

**Mode:** `--minify-syntax`

Moves negation operator through comma expressions.

```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}}
-(a, b)
-(x, y, z)
```

```js Output theme={"theme":{"light":"github-light","dark":"dracula"}}
a,-b
x,y,-z
```

### Import.meta property inlining

**Mode:** Bundle mode

Inlines `import.meta` properties at build time when values are known.

```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}}
import.meta.dir
import.meta.file
import.meta.path
import.meta.url
```

```js Output theme={"theme":{"light":"github-light","dark":"dracula"}}
"/path/to/directory"
"filename.js"
"/full/path/to/file.js"
"file:///full/path/to/file.js"
```

### Variable declaration merging

**Mode:** `--minify-syntax`

Merges adjacent variable declarations of the same type.

```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}}
let a = 1;
let b = 2;
const c = 3;
const d = 4;
```

```js Output theme={"theme":{"light":"github-light","dark":"dracula"}}
let a=1,b=2;
const c=3,d=4;
```

### Expression statement merging

**Mode:** `--minify-syntax`

Merges adjacent expression statements using comma operator.

```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}}
console.log(1);
console.log(2);
console.log(3);
```

```js Output theme={"theme":{"light":"github-light","dark":"dracula"}}
console.log(1),console.log(2),console.log(3);
```

### Return statement merging

**Mode:** `--minify-syntax`

Merges expressions before return with comma operator.

```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}}
console.log(x);
return y;
```

```js Output theme={"theme":{"light":"github-light","dark":"dracula"}}
return console.log(x),y;
```

### Throw statement merging

**Mode:** `--minify-syntax`

Merges expressions before throw with comma operator.

```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}}
console.log(x);
throw new Error();
```

```js Output theme={"theme":{"light":"github-light","dark":"dracula"}}
throw(console.log(x),new Error());
```

### TypeScript enum cross-module inlining

**Mode:** `--minify-syntax` (bundle mode)

Inlines enum values across module boundaries.

```ts Input  theme={"theme":{"light":"github-light","dark":"dracula"}}
// lib.ts
export enum Color { Red, Green, Blue }

// Input (main.ts)
import { Color } from './lib';
const x = Color.Red;
```

```js Output theme={"theme":{"light":"github-light","dark":"dracula"}}
const x=0;
```

### Computed property enum inlining

**Mode:** `--minify-syntax`

Inlines enum values used as computed object properties.

```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}}
enum Keys { FOO = 'foo' }
const obj = { [Keys.FOO]: value }
```

```js Output theme={"theme":{"light":"github-light","dark":"dracula"}}
const obj={foo:value}
```

### Arrow function body shortening

**Mode:** `--minify-syntax`

Uses expression body syntax when an arrow function only returns a value.

```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}}
() => { return x; }
(a) => { return a + 1; }
```

```js Output theme={"theme":{"light":"github-light","dark":"dracula"}}
() => x
a => a + 1
```

### Object property shorthand

**Mode:** Always active

Uses shorthand syntax when property name and value identifier match.

```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}}
{ x: x, y: y }
{ name: name, age: age }
```

```js Output theme={"theme":{"light":"github-light","dark":"dracula"}}
{ x, y }
{ name, age }
```

### Drop debugger statements

**Mode:** `--drop=debugger`

Removes `debugger` statements from code.

```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}}
function test() {
  debugger;
  return x;
}
```

```js Output theme={"theme":{"light":"github-light","dark":"dracula"}}
function test(){return x}
```

### Drop console calls

**Mode:** `--drop=console`

Removes all `console.*` method calls from code.

```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}}
console.log("debug");
console.warn("warning");
x = console.error("error");
```

```js Output theme={"theme":{"light":"github-light","dark":"dracula"}}
void 0;
void 0;
x=void 0;
```

### Drop custom function calls

**Mode:** `--drop=<name>`

Removes calls to specified global functions and their methods.

```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}}
assert(condition);
assert.equal(a, b);
```

```js Output with --drop=assert theme={"theme":{"light":"github-light","dark":"dracula"}}
void 0;
void 0;
```

## Keep Names

To keep original function and class names for debugging while minifying identifiers, use the `--keep-names` flag:

```bash theme={"theme":{"light":"github-light","dark":"dracula"}}
bun build ./index.ts --minify --keep-names --outfile=out.js
```

Or in the JavaScript API:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
await Bun.build({
  entrypoints: ["./index.ts"],
  outdir: "./out",
  minify: {
    identifiers: true,
    keepNames: true,
  },
});
```

`--keep-names` preserves the `.name` property on functions and classes while still minifying the identifiers themselves.

## Combined Example

Using all three minification modes together:

```ts input.ts (176 bytes) theme={"theme":{"light":"github-light","dark":"dracula"}}
const myVariable = 42;

const myFunction = () => {
  const isValid = true;
  const result = undefined;
  return isValid ? myVariable : result;
};

const output = myFunction();
```

```js output.js theme={"theme":{"light":"github-light","dark":"dracula"}}
// Output with --minify (20 bytes, 89% reduction)
var t=()=>42,e=t();
```

## When to Use Minification

**Use `--minify` for:**

* Production bundles
* Reducing CDN bandwidth costs
* Improving page load times

**Use individual modes for:**

* **`--minify-whitespace`:** Size reduction without semantic changes
* **`--minify-syntax`:** Smaller output while keeping readable identifiers for debugging
* **`--minify-identifiers`:** Maximum size reduction (combine with `--keep-names` for better stack traces)

**Avoid minification for:**

* Development builds (harder to debug)
* When you need readable error messages
* Libraries where consumers may read the source


# Plugins
Source: https://bun.com/docs/bundler/plugins

Universal plugin API for extending Bun's runtime and bundler

Bun's universal plugin API extends both the runtime and the bundler.

Plugins intercept imports and perform custom loading logic, such as reading files or transpiling code. They can add support for additional file types, like `.scss`. In the bundler, plugins can implement framework-level features like CSS extraction, macros, and client-server code co-location.

## Lifecycle hooks

Plugins register callbacks that run at various points in the lifecycle of a bundle:

* `onStart()`: Run once the bundler has started a bundle
* `onResolve()`: Run before the bundler resolves a module
* `onLoad()`: Run before the bundler loads a module
* `onBeforeParse()`: Run zero-copy native addons in the parser thread before the bundler parses a file
* `onEnd()`: Run after the bundle is complete

## Reference

A rough overview of the types (see Bun's `bun.d.ts` for the full type definitions):

```ts title="bun.d.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
type PluginBuilder = {
  onStart(callback: () => void | Promise<void>): void;
  onResolve: (
    args: { filter: RegExp; namespace?: string },
    callback: (args: { path: string; importer: string }) => {
      path: string;
      namespace?: string;
    } | void,
  ) => void;
  onLoad: (
    args: { filter: RegExp; namespace?: string },
    callback: (args: { path: string; defer: () => Promise<void> }) => {
      loader?: Loader;
      contents?: string;
      exports?: Record<string, any>;
    },
  ) => void;
  onEnd(callback: (result: BuildOutput) => void | Promise<void>): void;
  config: BuildConfig;
};

type Loader =
  | "js"
  | "jsx"
  | "ts"
  | "tsx"
  | "json"
  | "jsonc"
  | "toml"
  | "yaml"
  | "file"
  | "napi"
  | "wasm"
  | "text"
  | "css"
  | "html";
```

## Usage

A plugin is a JavaScript object with a `name` property and a `setup` function.

```ts title="myPlugin.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import type { BunPlugin } from "bun";

const myPlugin: BunPlugin = {
  name: "Custom loader",
  setup(build) {
    // implementation
  },
};
```

Pass it in the `plugins` array when calling `Bun.build`.

```ts title="index.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
await Bun.build({
  entrypoints: ["./app.ts"],
  outdir: "./out",
  plugins: [myPlugin],
});
```

## Plugin lifecycle

### Namespaces

`onLoad` and `onResolve` accept an optional `namespace` string.

Every module has a namespace. Namespaces prefix the import in transpiled code; for example, a loader with a `filter: /\.yaml$/` and `namespace: "yaml:"` transforms an import from `./myfile.yaml` into `yaml:./myfile.yaml`.

The default namespace is `"file"` and you don't need to specify it: `import myModule from "./my-module.ts"` is the same as `import myModule from "file:./my-module.ts"`.

Other common namespaces are:

* `"bun"`: for Bun-specific modules (`"bun:test"`, `"bun:sqlite"`)
* `"node"`: for Node.js modules (`"node:fs"`, `"node:path"`)

### onStart

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
onStart(callback: () => void | Promise<void>): void;
```

Registers a callback that runs when the bundler starts a new bundle.

```ts title="index.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { plugin } from "bun";

plugin({
  name: "onStart example",

  setup(build) {
    build.onStart(() => {
      console.log("Bundle started!");
    });
  },
});
```

The callback can return a Promise. After the bundle process has initialized, the bundler waits until all `onStart()` callbacks have completed before continuing.

For example:

```ts title="index.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
const result = await Bun.build({
  entrypoints: ["./app.ts"],
  outdir: "./dist",
  sourcemap: "external",
  plugins: [
    {
      name: "Sleep for 10 seconds",
      setup(build) {
        build.onStart(async () => {
          await Bun.sleep(10_000);
        });
      },
    },
    {
      name: "Log bundle time to a file",
      setup(build) {
        build.onStart(async () => {
          const now = Date.now();
          await Bun.$`echo ${now} > bundle-time.txt`;
        });
      },
    },
  ],
});
```

In this example, Bun waits for both `onStart()` callbacks to complete: the 10-second sleep and the write to `bundle-time.txt`.

<Note>
  `onStart()` callbacks (like every other lifecycle callback) cannot modify the `build.config` object. To mutate
  `build.config`, do so directly in the `setup()` function.
</Note>

### onResolve

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
onResolve(
  args: { filter: RegExp; namespace?: string },
  callback: (args: { path: string; importer: string }) => {
    path: string;
    namespace?: string;
  } | void,
): void;
```

To bundle your project, Bun walks down the dependency tree of all modules in your project. For each imported module, Bun has to find and read that module. The "finding" part is known as "resolving" a module.

The `onResolve()` plugin lifecycle callback configures how Bun resolves a module.

The first argument to `onResolve()` is an object with a `filter` and `namespace` property. The `filter` is a regular expression run on the import string. Together, these select which modules your custom resolution logic applies to.

The second argument to `onResolve()` is a callback. Bun runs the callback for each module import it finds that matches the filter and namespace defined in the first argument.

The callback receives the path to the matching module and can return a new path for the module. Bun reads the contents of the new path and parses it as a module.

For example, redirecting all imports to `images/` to `./public/images/`:

```ts title="index.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { plugin } from "bun";

plugin({
  name: "onResolve example",
  setup(build) {
    build.onResolve({ filter: /.*/, namespace: "file" }, args => {
      if (args.path.startsWith("images/")) {
        return {
          path: args.path.replace("images/", "./public/images/"),
        };
      }
    });
  },
});
```

### onLoad

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
onLoad(
  args: { filter: RegExp; namespace?: string },
  callback: (args: { path: string; namespace: string; loader: Loader; defer: () => Promise<void> }) => {
    loader?: Loader;
    contents?: string;
    exports?: Record<string, any>;
  },
): void;
```

After Bun's bundler has resolved a module, it reads and parses the module's contents.

The `onLoad()` plugin lifecycle callback modifies the contents of a module before Bun reads and parses it.

Like `onResolve()`, the first argument to `onLoad()` selects which modules this invocation of `onLoad()` applies to.

The second argument to `onLoad()` is a callback that runs for each matching module before Bun loads its contents into memory.

The callback receives the path to the matching module, its namespace, its default loader, and a `defer` function.

The callback can return a new `contents` string for the module as well as a new `loader`.

For example:

```ts title="index.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import type { BunPlugin } from "bun";

const envPlugin: BunPlugin = {
  name: "env plugin",
  setup(build) {
    build.onLoad({ filter: /env/, namespace: "file" }, args => {
      return {
        contents: `export default ${JSON.stringify(process.env)}`,
        loader: "js",
      };
    });
  },
};

Bun.build({
  entrypoints: ["./app.ts"],
  outdir: "./dist",
  plugins: [envPlugin],
});

// import env from "env"
// env.FOO === "bar"
```

This plugin transforms all imports of the form `import env from "env"` into a JavaScript module that exports the current environment variables.

#### .defer()

One of the arguments passed to the `onLoad` callback is a `defer` function. It returns a Promise that resolves once Bun has loaded all other modules. Await it when a module's contents depend on other modules.

<Accordion title="Example: tracking and reporting unused exports">
  ```ts title="index.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
  import { plugin } from "bun";

  plugin({
    name: "track imports",
    setup(build) {
      const transpiler = new Bun.Transpiler();

      let trackedImports: Record<string, number> = {};

      // Each module that goes through this onLoad callback
      // will record its imports in `trackedImports`
      build.onLoad({ filter: /\.ts/ }, async ({ path }) => {
        const contents = await Bun.file(path).arrayBuffer();

        const imports = transpiler.scanImports(contents);

        for (const i of imports) {
          trackedImports[i.path] = (trackedImports[i.path] || 0) + 1;
        }

        return undefined;
      });

      build.onLoad({ filter: /stats\.json/ }, async ({ defer }) => {
        // Wait for all files to be loaded, ensuring
        // that every file goes through the above `onLoad()` function
        // and their imports tracked
        await defer();

        // Emit JSON containing the stats of each import
        return {
          contents: `export default ${JSON.stringify(trackedImports)}`,
          loader: "json",
        };
      });
    },
  });
  ```
</Accordion>

<Warning>You can call the `.defer()` function only once per `onLoad` callback.</Warning>

### onEnd

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
onEnd(callback: (result: BuildOutput) => void | Promise<void>): void;
```

Registers a callback that runs after the bundle is complete. The callback receives the [`BuildOutput`](/docs/bundler#outputs) object containing the build results, including output files and any build messages.

```ts title="index.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
const result = await Bun.build({
  entrypoints: ["./app.ts"],
  outdir: "./dist",
  plugins: [
    {
      name: "onEnd example",
      setup(build) {
        build.onEnd(result => {
          console.log(`Build completed with ${result.outputs.length} files`);
          for (const log of result.logs) {
            console.log(log);
          }
        });
      },
    },
  ],
});
```

The callback can return a `Promise`. The promise returned by `Bun.build()` does not resolve until all `onEnd()` callbacks have completed.

```ts title="index.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
const result = await Bun.build({
  entrypoints: ["./app.ts"],
  outdir: "./dist",
  plugins: [
    {
      name: "Upload to S3",
      setup(build) {
        build.onEnd(async result => {
          if (!result.success) return;
          for (const output of result.outputs) {
            await uploadToS3(output);
          }
        });
      },
    },
  ],
});
```

## Native plugins

Bun's bundler is written in native code and uses multiple threads to load and parse modules in parallel. JavaScript plugins run on a single thread, because JavaScript itself is single-threaded.

Native plugins are NAPI modules that expose lifecycle hooks as C ABI functions. They can run on multiple threads, so they run much faster than JavaScript plugins. They also skip work such as the UTF-8 -> UTF-16 conversion needed to pass strings to JavaScript.

These lifecycle hooks are available to native plugins:

* `onBeforeParse()`: Called on any thread before Bun's bundler parses a file.

To create a native plugin, export a C ABI function that matches the signature of the native lifecycle hook you want to implement.

### Creating a native plugin in Rust

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun add -g @napi-rs/cli
napi new
```

Then install this crate:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
cargo add bun-native-plugin
```

Inside `lib.rs`, use the `bun_native_plugin::bun` proc macro to define the function that implements your native plugin.

Here's an example implementing the `onBeforeParse` hook:

```rust title="lib.rs" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/rust.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=c48e2f9ffc38d0c1d77ef723c617aca8" theme={"theme":{"light":"github-light","dark":"dracula"}}
use bun_native_plugin::{define_bun_plugin, OnBeforeParse, bun, Result, anyhow};
use napi_derive::napi;

/// Define the plugin and its name
define_bun_plugin!("replace-foo-with-bar");

/// Here we'll implement `onBeforeParse` with code that replaces all occurrences of
/// `foo` with `bar`.
///
/// We use the #[bun] macro to generate some of the boilerplate code.
///
/// The argument of the function (`handle: &mut OnBeforeParse`) tells
/// the macro that this function implements the `onBeforeParse` hook.
#[bun]
pub fn replace_foo_with_bar(handle: &mut OnBeforeParse) -> Result<()> {
  // Fetch the input source code.
  let input_source_code = handle.input_source_code()?;

  // Get the Loader for the file
  let loader = handle.output_loader();

  let output_source_code = input_source_code.replace("foo", "bar");

  handle.set_output_source_code(output_source_code, loader);

  Ok(())
}
```

To use it in `Bun.build()`:

```ts title="index.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import myNativeAddon from "./my-native-addon";

Bun.build({
  entrypoints: ["./app.tsx"],
  plugins: [
    {
      name: "my-plugin",

      setup(build) {
        build.onBeforeParse(
          {
            namespace: "file",
            filter: /\.tsx$/,
          },
          {
            napiModule: myNativeAddon,
            symbol: "replace_foo_with_bar",
            // external: myNativeAddon.getSharedState()
          },
        );
      },
    },
  ],
});
```

### onBeforeParse

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
onBeforeParse(
  args: { filter: RegExp; namespace?: string },
  callback: { napiModule: NapiModule; symbol: string; external?: unknown },
): void;
```

The `onBeforeParse()` callback runs immediately before Bun's bundler parses a file.

It receives the file's contents and can optionally return new source code.

<Info>Bun can call this callback from any thread, so the NAPI module implementation must be thread-safe.</Info>


# Standalone HTML
Source: https://bun.com/docs/bundler/standalone-html

Bundle a single-page app into a single self-contained .html file with no external dependencies

Bun can bundle your entire frontend into a **single `.html` file** with zero external dependencies. JavaScript, TypeScript, JSX, CSS, images, fonts, videos, WASM: Bun inlines everything into one file.

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun build --compile --target=browser ./index.html --outdir=dist
```

The output is a self-contained HTML document: no relative paths, no external files, no server required.

## One file. Upload anywhere.

The output is a single `.html` file you can put anywhere:

* **Upload it to S3** or any static file host — no directory structure to maintain, one file
* **Double-click it from your desktop** — it opens in the browser and works offline, no localhost server needed
* **Embed it in your webview** — no relative files to deal with
* **Insert it in an `<iframe>`** — embed interactive content in another page with a single file URL
* **Serve it from anywhere** — any HTTP server, CDN, or file share

There's nothing to install, no `node_modules` to deploy, no build artifacts to coordinate, no relative paths to think about.

## Truly one file

Normally, distributing a web page means managing a folder of assets — the HTML, the JavaScript bundles, the CSS files, the images. Move the HTML without the rest and everything breaks. Browsers have tried to solve this before. Safari's `.webarchive` and Chrome's `.mhtml` save a page as a single file. In practice, only some browsers can open them, which defeats the purpose.

Standalone HTML output is a plain `.html` file: not an archive, not a folder. Bun embeds every image, every font, and every line of CSS and JavaScript directly in the HTML using standard `<style>` tags, `<script>` tags, and `data:` URIs. Any browser can open it and any server can host it.

You can distribute the page the same way you'd distribute a PDF: a single file you can move, copy, upload, or share without worrying about broken paths or missing assets.

## Quick start

<CodeGroup>
  ```html index.html icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
  <!doctype html>
  <html>
    <head>
      <link rel="stylesheet" href="./styles.css" />
    </head>
    <body>
      <div id="root"></div>
      <script src="./app.tsx"></script>
    </body>
  </html>
  ```

  ```tsx app.tsx icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
  import React from "react";
  import { createRoot } from "react-dom/client";

  function App() {
    return <h1>Hello from a single HTML file!</h1>;
  }

  createRoot(document.getElementById("root")!).render(<App />);
  ```

  ```css styles.css icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
  body {
    margin: 0;
    font-family: system-ui, sans-serif;
    background: #f5f5f5;
  }
  ```
</CodeGroup>

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun build --compile --target=browser ./index.html --outdir=dist
```

Open `dist/index.html` — the React app works with no server.

## Everything gets inlined

Bun inlines every local asset it finds in your HTML: anything with a relative path, of any file type, is embedded into the output file.

### What gets inlined

| In your source                                   | In the output                                                            |
| ------------------------------------------------ | ------------------------------------------------------------------------ |
| `<script src="./app.tsx">`                       | `<script type="module">...bundled code...</script>`                      |
| `<link rel="stylesheet" href="./styles.css">`    | `<style>...bundled CSS...</style>`                                       |
| `<img src="./logo.png">`                         | `<img src="data:image/png;base64,...">`                                  |
| `<img src="./icon.svg">`                         | `<img src="data:image/svg+xml;base64,...">`                              |
| `<video src="./demo.mp4">`                       | `<video src="data:video/mp4;base64,...">`                                |
| `<audio src="./click.wav">`                      | `<audio src="data:audio/x-wav;base64,...">`                              |
| `<source src="./clip.webm">`                     | `<source src="data:video/webm;base64,...">`                              |
| `<video poster="./thumb.jpg">`                   | `<video poster="data:image/jpeg;base64,...">`                            |
| `<link rel="icon" href="./favicon.ico">`         | `<link rel="icon" href="data:image/x-icon;base64,...">`                  |
| `<link rel="manifest" href="./app.webmanifest">` | `<link rel="manifest" href="data:application/manifest+json;base64,...">` |
| CSS `url("./bg.png")`                            | CSS `url(data:image/png;base64,...)`                                     |
| CSS `@import "./reset.css"`                      | Flattened into the `<style>` tag                                         |
| CSS `url("./font.woff2")`                        | CSS `url(data:font/woff2;base64,...)`                                    |
| JS `import "./styles.css"`                       | Merged into the `<style>` tag                                            |

Images, fonts, WASM binaries, videos, audio files, SVGs: Bun base64-encodes any file referenced by a relative path into a `data:` URI and embeds it directly in the HTML. Bun detects the MIME type from the file extension.

Bun leaves external URLs (like CDN links or absolute URLs) untouched.

## Using with React

React apps need no extra configuration: Bun transpiles JSX and resolves npm packages.

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun install react react-dom
```

<CodeGroup>
  ```html index.html icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
  <!doctype html>
  <html>
    <head>
      <meta charset="utf-8" />
      <meta name="viewport" content="width=device-width, initial-scale=1" />
      <title>My App</title>
      <link rel="stylesheet" href="./styles.css" />
    </head>
    <body>
      <div id="root"></div>
      <script src="./app.tsx"></script>
    </body>
  </html>
  ```

  ```tsx app.tsx icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
  import React, { useState } from "react";
  import { createRoot } from "react-dom/client";
  import { Counter } from "./components/Counter.tsx";

  function App() {
    return (
      <main>
        <h1>Single-file React App</h1>
        <Counter />
      </main>
    );
  }

  createRoot(document.getElementById("root")!).render(<App />);
  ```

  ```tsx components/Counter.tsx icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
  import React, { useState } from "react";

  export function Counter() {
    const [count, setCount] = useState(0);
    return <button onClick={() => setCount(count + 1)}>Count: {count}</button>;
  }
  ```
</CodeGroup>

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun build --compile --target=browser ./index.html --outdir=dist
```

Bun bundles all of React, your components, and your CSS into `dist/index.html`. Upload that one file anywhere and it works.

## Using with Tailwind CSS

Install the plugin and reference Tailwind in your HTML or CSS:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun install --dev bun-plugin-tailwind
```

<CodeGroup>
  ```html index.html icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
  <!doctype html>
  <html>
    <head>
      <link rel="stylesheet" href="tailwindcss" />
    </head>
    <body class="bg-gray-100 flex items-center justify-center min-h-screen">
      <div id="root"></div>
      <script src="./app.tsx"></script>
    </body>
  </html>
  ```

  ```tsx app.tsx icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
  import React from "react";
  import { createRoot } from "react-dom/client";

  function App() {
    return (
      <div className="bg-white rounded-lg shadow-lg p-8 max-w-md">
        <h1 className="text-2xl font-bold text-gray-800">Hello Tailwind</h1>
        <p className="text-gray-600 mt-2">This is a single HTML file.</p>
      </div>
    );
  }

  createRoot(document.getElementById("root")!).render(<App />);
  ```
</CodeGroup>

Build with the plugin using the JavaScript API:

```ts build.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
await Bun.build({
  entrypoints: ["./index.html"],
  compile: true,
  target: "browser",
  outdir: "./dist",
  plugins: [require("bun-plugin-tailwind")],
});
```

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun run build.ts
```

Bun inlines the generated Tailwind CSS directly into the HTML file as a `<style>` tag.

## How it works

When you pass `--compile --target=browser` with an HTML entrypoint, Bun:

1. Parses the HTML and discovers all `<script>`, `<link>`, `<img>`, `<video>`, `<audio>`, `<source>`, and other asset references
2. Bundles all JavaScript/TypeScript/JSX into a single module
3. Bundles all CSS (including `@import` chains and CSS imported from JS) into a single stylesheet
4. Converts every relative asset reference into a base64 `data:` URI
5. Inlines the bundled JS as `<script type="module">` before `</body>`
6. Inlines the bundled CSS as `<style>` in `<head>`
7. Outputs a single `.html` file with no external dependencies

## Minification

Add `--minify` to minify the JavaScript and CSS:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun build --compile --target=browser --minify ./index.html --outdir=dist
```

Or with the JavaScript API:

```ts build.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
await Bun.build({
  entrypoints: ["./index.html"],
  compile: true,
  target: "browser",
  outdir: "./dist",
  minify: true,
});
```

## JavaScript API

Use `Bun.build()` to produce standalone HTML programmatically:

```ts build.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
const result = await Bun.build({
  entrypoints: ["./index.html"],
  compile: true,
  target: "browser",
  outdir: "./dist", // optional — omit to get output as BuildArtifact
  minify: true,
});

if (!result.success) {
  console.error("Build failed:");
  for (const log of result.logs) {
    console.error(log);
  }
} else {
  console.log("Built:", result.outputs[0].path);
}
```

When you omit `outdir`, the output is available as a `BuildArtifact` in `result.outputs`:

```ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
const result = await Bun.build({
  entrypoints: ["./index.html"],
  compile: true,
  target: "browser",
});

const html = await result.outputs[0].text();
await Bun.write("output.html", html);
```

## Multiple HTML files

You can pass multiple HTML files as entrypoints. Each produces its own standalone HTML file:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun build --compile --target=browser ./index.html ./about.html --outdir=dist
```

## Environment variables

Use `--env` to inline environment variables into the bundled JavaScript:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
API_URL=https://api.example.com bun build --compile --target=browser --env=inline ./index.html --outdir=dist
```

Bun replaces references to `process.env.API_URL` in your JavaScript with the literal value at build time.

## Limitations

* **Code splitting** is not supported — `--splitting` cannot be used with `--compile --target=browser`
* **Large assets** increase file size since they're base64-encoded (33% overhead vs the raw binary)
* **External URLs** (CDN links, absolute URLs) stay as-is: Bun inlines only relative paths


# Feedback
Source: https://bun.com/docs/feedback

Share feedback, bug reports, and feature requests

Here's how to open a helpful issue for a bug, a performance problem, or a feature request:

<Callout icon="discord">For general questions, join the [Discord](https://bun.com/discord).</Callout>

## Reporting Issues

<Steps>
  <Step title="Upgrade Bun">
    Upgrade Bun to the latest version with `bun upgrade`. This might fix your problem without opening an issue.

    ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
    bun upgrade
    ```

    You can also try the latest canary release, which includes changes and bug fixes that haven't reached a stable release yet.

    ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
    bun upgrade --canary

    # To revert to the stable release
    bun upgrade --stable
    ```

    If the issue persists after upgrading, continue to the next step.
  </Step>

  <Step title="Review Existing Issues">
    Check whether the issue has already been reported before opening a new one. Checking first saves time for everyone and helps us focus on fixing things.

    * 🔍 [**Search existing issues**](https://github.com/oven-sh/bun/issues)
    * 💬 [**Check discussions**](https://github.com/oven-sh/bun/discussions)

    If you find a related issue, add a 👍 reaction or comment with extra details instead of opening a new one.
  </Step>

  <Step title="Report the Issue">
    If no one has reported the issue, open a new one or suggest an improvement.

    * 🐞 [**Report a Bug**](https://github.com/oven-sh/bun/issues/new?template=2-bug-report.yml)
    * ⚡ [**Suggest an Improvement**](https://github.com/oven-sh/bun/issues/new?template=4-feature-request.yml)

    Provide as much detail as possible, including:

    * A clear and concise title
    * A code example or steps to reproduce the issue
    * The version of Bun you are using (run `bun --version`)
    * A description of the issue (what you expected to happen and what actually happened)
    * The operating system and version you are using
      <Note>
        * For macOS and Linux: copy the output of `uname -mprs`
        * For Windows: copy the output of this command in the PowerShell console:
          `"$([Environment]::OSVersion | ForEach-Object VersionString) $(if ([Environment]::Is64BitOperatingSystem) { "x64" } else { "x86" })"`
      </Note>
  </Step>
</Steps>

The Bun team will review the issue and get back to you as soon as possible.


# Convert an ArrayBuffer to an array of numbers
Source: https://bun.com/docs/guides/binary/arraybuffer-to-array



To retrieve the contents of an `ArrayBuffer` as an array of numbers, create a [`Uint8Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array) over the buffer, then use [`Array.from()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/from) to convert it to an array.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const buf = new ArrayBuffer(64);
const arr = new Uint8Array(buf);
arr.length; // 64
arr[0]; // 0 (instantiated with all zeros)
```

***

The `Uint8Array` class supports array indexing and iteration. To convert the instance to a regular `Array`, use `Array.from()`. This is likely slower than using the `Uint8Array` directly.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const buf = new ArrayBuffer(64);
const uintArr = new Uint8Array(buf);
const regularArr = Array.from(uintArr);
// number[]
```

***

See [Binary Data](/docs/runtime/binary-data#conversion).


# Convert an ArrayBuffer to a Blob
Source: https://bun.com/docs/guides/binary/arraybuffer-to-blob



You can construct a [`Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Blob) from an array of "chunks", where each chunk is a string, binary data structure, or another `Blob`.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const buf = new ArrayBuffer(64);
const blob = new Blob([buf]);
```

***

By default the `type` of the resulting `Blob` is unset. Set it with the `type` option.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const buf = new ArrayBuffer(64);
const blob = new Blob([buf], { type: "application/octet-stream" });
blob.type; // => "application/octet-stream"
```

***

See [Binary Data](/docs/runtime/binary-data#conversion).


# Convert an ArrayBuffer to a Buffer
Source: https://bun.com/docs/guides/binary/arraybuffer-to-buffer



The Node.js [`Buffer`](https://nodejs.org/api/buffer.html) API predates the introduction of `ArrayBuffer` into the JavaScript language. Bun implements both.

Use the static `Buffer.from()` method to create a `Buffer` from an `ArrayBuffer`.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const arrBuffer = new ArrayBuffer(64);
const nodeBuffer = Buffer.from(arrBuffer);
```

***

To create a `Buffer` that only views a portion of the underlying buffer, pass the offset and length to `Buffer.from()`.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const arrBuffer = new ArrayBuffer(64);
const nodeBuffer = Buffer.from(arrBuffer, 0, 16); // view first 16 bytes
```

***

See [Binary Data](/docs/runtime/binary-data#conversion).


# Convert an ArrayBuffer to a string
Source: https://bun.com/docs/guides/binary/arraybuffer-to-string



Bun implements the Web-standard [`TextDecoder`](https://developer.mozilla.org/en-US/docs/Web/API/TextDecoder) class for converting between binary data types and strings.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const buf = new ArrayBuffer(64);
const decoder = new TextDecoder();
const str = decoder.decode(buf);
```

***

See [Binary Data](/docs/runtime/binary-data#conversion).


# Convert an ArrayBuffer to a Uint8Array
Source: https://bun.com/docs/guides/binary/arraybuffer-to-typedarray



A `Uint8Array` is a *typed array*, a view over the data in an underlying `ArrayBuffer`.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const buffer = new ArrayBuffer(64);
const arr = new Uint8Array(buffer);
```

***

Create instances of other typed arrays the same way.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const buffer = new ArrayBuffer(64);

const arr1 = new Uint8Array(buffer);
const arr2 = new Uint16Array(buffer);
const arr3 = new Uint32Array(buffer);
const arr4 = new Float32Array(buffer);
const arr5 = new Float64Array(buffer);
const arr6 = new BigInt64Array(buffer);
const arr7 = new BigUint64Array(buffer);
```

***

To create a typed array that only views a portion of the underlying buffer, pass the offset and length to the constructor.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const buffer = new ArrayBuffer(64);
const arr = new Uint8Array(buffer, 0, 16); // view first 16 bytes
```

***

See [Binary Data](/docs/runtime/binary-data#conversion).


# Convert a Blob to an ArrayBuffer
Source: https://bun.com/docs/guides/binary/blob-to-arraybuffer



The [`Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Blob) class provides several methods for consuming its contents in different formats, including `.arrayBuffer()`.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const blob = new Blob(["hello world"]);
const buf = await blob.arrayBuffer();
```

***

See [Binary Data](/docs/runtime/binary-data#conversion).


# Convert a Blob to a DataView
Source: https://bun.com/docs/guides/binary/blob-to-dataview



The [`Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Blob) class provides several methods for consuming its contents in different formats. Read the contents into an `ArrayBuffer` with `.arrayBuffer()`, then create a `DataView` from the buffer.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const blob = new Blob(["hello world"]);
const arr = new DataView(await blob.arrayBuffer());
```

***

See [Binary Data](/docs/runtime/binary-data#conversion).


# Convert a Blob to a ReadableStream
Source: https://bun.com/docs/guides/binary/blob-to-stream



The [`Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Blob) class provides several methods for consuming its contents in different formats, including `.stream()`, which returns a `ReadableStream`.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const blob = new Blob(["hello world"]);
const stream = blob.stream();
```

***

See [Binary Data](/docs/runtime/binary-data#conversion).


# Convert a Blob to a string
Source: https://bun.com/docs/guides/binary/blob-to-string



The [`Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Blob) class provides several methods for consuming its contents in different formats, including `.text()`.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const blob = new Blob(["hello world"]);
const str = await blob.text();
// => "hello world"
```

***

See [Binary Data](/docs/runtime/binary-data#conversion).


# Convert a Blob to a Uint8Array
Source: https://bun.com/docs/guides/binary/blob-to-typedarray



The [`Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Blob) class provides several methods for consuming its contents in different formats. Use `.bytes()` to read the contents as a `Uint8Array`.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const blob = new Blob(["hello world"]);
const arr = await blob.bytes();
```

Alternatively, read the contents into an `ArrayBuffer` with `.arrayBuffer()`, then create a `Uint8Array` from the buffer.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const blob = new Blob(["hello world"]);
const arr = new Uint8Array(await blob.arrayBuffer());
```

***

See [Binary Data](/docs/runtime/binary-data#conversion).


# Convert a Buffer to an ArrayBuffer
Source: https://bun.com/docs/guides/binary/buffer-to-arraybuffer



The Node.js [`Buffer`](https://nodejs.org/api/buffer.html) class views and manipulates data in an underlying `ArrayBuffer`. The `buffer` property returns that `ArrayBuffer`.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const nodeBuf = Buffer.alloc(64);
const arrBuf = nodeBuf.buffer;
```

***

See [Binary Data](/docs/runtime/binary-data#conversion).


# Convert a Buffer to a blob
Source: https://bun.com/docs/guides/binary/buffer-to-blob



You can construct a [`Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Blob) from an array of "chunks", where each chunk is a string, binary data structure (including `Buffer`), or another `Blob`.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const buf = Buffer.from("hello");
const blob = new Blob([buf]);
```

***

See [Binary Data](/docs/runtime/binary-data#conversion).


# Convert a Buffer to a ReadableStream
Source: https://bun.com/docs/guides/binary/buffer-to-readablestream



The naive approach to creating a [`ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream) from a [`Buffer`](https://nodejs.org/api/buffer.html) is to use the `ReadableStream` constructor and enqueue the entire array as a single chunk. For a large buffer, this approach may be undesirable because it doesn't stream the data in smaller chunks.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const buf = Buffer.from("hello world");
const stream = new ReadableStream({
  start(controller) {
    controller.enqueue(buf);
    controller.close();
  },
});
```

***

To stream the data in smaller chunks, first create a `Blob` instance from the `Buffer`, then use [`Blob.stream()`](https://developer.mozilla.org/en-US/docs/Web/API/Blob/stream) to create a `ReadableStream`.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const buf = Buffer.from("hello world");
const blob = new Blob([buf]);
const stream = blob.stream();
```

***

Pass a number to `.stream()` to set the chunk size.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const buf = Buffer.from("hello world");
const blob = new Blob([buf]);

// set chunk size of 1024 bytes
const stream = blob.stream(1024);
```

***

See [Binary Data](/docs/runtime/binary-data#conversion).


# Convert a Buffer to a string
Source: https://bun.com/docs/guides/binary/buffer-to-string



The [`Buffer`](https://nodejs.org/api/buffer.html) class provides a `.toString()` method that converts a `Buffer` to a string.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const buf = Buffer.from("hello");
const str = buf.toString();
// => "hello"
```

***

You can optionally specify an encoding and byte range.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const buf = Buffer.from("hello world!");
const str = buf.toString("utf8", 0, 5);
// => "hello"
```

***

See [Binary Data](/docs/runtime/binary-data#conversion).


# Convert a Buffer to a Uint8Array
Source: https://bun.com/docs/guides/binary/buffer-to-typedarray



The Node.js [`Buffer`](https://nodejs.org/api/buffer.html) class extends [`Uint8Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array), so no conversion is needed. All properties and methods on `Uint8Array` are available on `Buffer`.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const buf = Buffer.alloc(64);
buf instanceof Uint8Array; // => true
```

***

See [Binary Data](/docs/runtime/binary-data#conversion).


# Convert a DataView to a string
Source: https://bun.com/docs/guides/binary/dataview-to-string



If a [`DataView`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView) contains ASCII-encoded text, use the [`TextDecoder`](https://developer.mozilla.org/en-US/docs/Web/API/TextDecoder) class to convert it to a string.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const dv: DataView = ...;
const decoder = new TextDecoder();
const str = decoder.decode(dv);
```

***

See [Binary Data](/docs/runtime/binary-data#conversion).


# Convert a Uint8Array to an ArrayBuffer
Source: https://bun.com/docs/guides/binary/typedarray-to-arraybuffer



A [`Uint8Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array) is a *typed array*, a view over data in an underlying `ArrayBuffer`. The `buffer` property returns that `ArrayBuffer`.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const arr = new Uint8Array(64);
arr.buffer; // => ArrayBuffer(64)
```

***

The `Uint8Array` may be a view over a *subset* of the data in the underlying `ArrayBuffer`. In this case, the `buffer` property returns the entire buffer, and the `byteOffset` and `byteLength` properties indicate the subset.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const arr = new Uint8Array(new ArrayBuffer(64), 16, 32);
arr.buffer; // => ArrayBuffer(64)
arr.byteOffset; // => 16
arr.byteLength; // => 32
```

***

See [Binary Data](/docs/runtime/binary-data#conversion).


# Convert a Uint8Array to a Blob
Source: https://bun.com/docs/guides/binary/typedarray-to-blob



You can construct a [`Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Blob) from an array of "chunks", where each chunk is a string, binary data structure (including `Uint8Array`), or another `Blob`.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const arr = new Uint8Array([0x68, 0x65, 0x6c, 0x6c, 0x6f]);
const blob = new Blob([arr]);
console.log(await blob.text());
// => "hello"
```

***

See [Binary Data](/docs/runtime/binary-data#conversion).


# Convert a Uint8Array to a Buffer
Source: https://bun.com/docs/guides/binary/typedarray-to-buffer



The [`Buffer`](https://nodejs.org/api/buffer.html) class extends [`Uint8Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array) with additional methods. Use `Buffer.from()` to create a `Buffer` instance from a `Uint8Array`.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const arr: Uint8Array = ...
const buf = Buffer.from(arr);
```

***

See [Binary Data](/docs/runtime/binary-data#conversion).


# Convert a Uint8Array to a DataView
Source: https://bun.com/docs/guides/binary/typedarray-to-dataview



A [`Uint8Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array) is a *typed array*, a view over data in an underlying `ArrayBuffer`. To convert it to a `DataView`, create one over the same range of data.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const arr: Uint8Array = ...
const dv = new DataView(arr.buffer, arr.byteOffset, arr.byteLength);
```

***

See [Binary Data](/docs/runtime/binary-data#conversion).


# Convert a Uint8Array to a ReadableStream
Source: https://bun.com/docs/guides/binary/typedarray-to-readablestream



The naive approach to creating a [`ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream) from a [`Uint8Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array) is to use the [`ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream) constructor and enqueue the entire array as a single chunk. For a large array, this approach may be undesirable because it doesn't stream the data in smaller chunks.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const arr = new Uint8Array(64);
const stream = new ReadableStream({
  start(controller) {
    controller.enqueue(arr);
    controller.close();
  },
});
```

***

To stream the data in smaller chunks, first create a `Blob` instance from the `Uint8Array`, then use [`Blob.stream()`](https://developer.mozilla.org/en-US/docs/Web/API/Blob/stream) to create a `ReadableStream`.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const arr = new Uint8Array(64);
const blob = new Blob([arr]);
const stream = blob.stream();
```

***

Pass a number to `.stream()` to set the chunk size.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const arr = new Uint8Array(64);
const blob = new Blob([arr]);

// set chunk size of 1024 bytes
const stream = blob.stream(1024);
```

***

See [Binary Data](/docs/runtime/binary-data#conversion).


# Convert a Uint8Array to a string
Source: https://bun.com/docs/guides/binary/typedarray-to-string



Bun implements the Web-standard [`TextDecoder`](https://developer.mozilla.org/en-US/docs/Web/API/TextDecoder) class for converting binary data types like `Uint8Array` to strings.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const arr = new Uint8Array([104, 101, 108, 108, 111]);
const decoder = new TextDecoder();
const str = decoder.decode(arr);
// => "hello"
```

***

See [Binary Data](/docs/runtime/binary-data#conversion).


# Deploy a Bun application on AWS Lambda
Source: https://bun.com/docs/guides/deployment/aws-lambda



[AWS Lambda](https://aws.amazon.com/lambda/) is a serverless compute service that lets you run code without provisioning or managing servers.

This guide deploys a Bun HTTP server to AWS Lambda using a `Dockerfile`.

<Note>
  Before continuing, make sure you have:

  * A Bun application ready for deployment
  * An [AWS account](https://aws.amazon.com/)
  * [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-getting-started.html) installed and configured
  * [Docker](https://docs.docker.com/get-started/get-docker/) installed and added to your `PATH`
</Note>

***

<Steps>
  <Step title="Create a new Dockerfile">
    Create a new `Dockerfile` in the root of your project. This file contains the instructions to initialize the container, copy your local project files into it, install dependencies, and start the application.

    ```docker Dockerfile icon="docker" theme={"theme":{"light":"github-light","dark":"dracula"}}
    # Use the official AWS Lambda adapter image to handle the Lambda runtime
    FROM public.ecr.aws/awsguru/aws-lambda-adapter:1.0.1 AS aws-lambda-adapter

    # Use the official Bun image to run the application
    FROM oven/bun:debian AS bun_latest

    # Copy the Lambda adapter into the container
    COPY --from=aws-lambda-adapter /lambda-adapter /opt/extensions/lambda-adapter

    # Set the port to 8080. This is required for the AWS Lambda adapter.
    ENV PORT=8080

    # Set the work directory to `/var/task`. This is the default work directory for Lambda.
    WORKDIR "/var/task"

    # Copy the package.json and bun.lock into the container
    COPY package.json bun.lock ./

    # Install the dependencies
    RUN bun install --production --frozen-lockfile

    # Copy the rest of the application into the container
    COPY . /var/task

    # Run the application.
    CMD ["bun", "index.ts"]
    ```

    <Note>
      Make sure that the start command corresponds to your application's entry point. The start command can also be `CMD ["bun", "run", "start"]` if you have a start script in your `package.json`.

      If your app doesn't have dependencies, you can omit the `COPY package.json bun.lock ./` and `RUN bun install --production --frozen-lockfile` lines. Bun doesn't write a `bun.lock` for a project with no dependencies.
    </Note>

    Create a new `.dockerignore` file in the root of your project. It lists the files and directories to *exclude* from the container image, such as `node_modules`. Excluding them keeps builds faster and smaller:

    ```docker .dockerignore icon="Docker" theme={"theme":{"light":"github-light","dark":"dracula"}}
    node_modules
    Dockerfile*
    .dockerignore
    .git
    .gitignore
    README.md
    LICENSE
    .vscode
    .env
    # Any other files or directories you want to exclude
    ```
  </Step>

  <Step title="Build the Docker image">
    Make sure you're in the directory containing your `Dockerfile`, then build the Docker image. This example names the image `bun-lambda-demo` and tags it as `latest`.

    ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
    # cd /path/to/your/app
    docker build --provenance=false --platform linux/amd64 -t bun-lambda-demo:latest .
    ```
  </Step>

  <Step title="Create an ECR repository">
    Before pushing the image, create an [ECR repository](https://aws.amazon.com/ecr/) to push it to.

    The following command:

    * Creates an ECR repository named `bun-lambda-demo` in the `us-east-1` region
    * Exports the repository URI as an environment variable. The export is optional, but it makes the next steps easier.

    ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
    export ECR_URI=$(aws ecr create-repository --repository-name bun-lambda-demo --region us-east-1 --query 'repository.repositoryUri' --output text)
    echo $ECR_URI
    ```

    ```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
    [id].dkr.ecr.us-east-1.amazonaws.com/bun-lambda-demo
    ```

    <Note>
      If you're using IAM Identity Center (SSO) or have configured AWS CLI with profiles, add the `--profile` flag to your AWS CLI commands.

      For example, if your profile is named `my-sso-app`, use `--profile my-sso-app`. Run `aws configure list-profiles` to see your available profiles.

      ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
      export ECR_URI=$(aws ecr create-repository --repository-name bun-lambda-demo --region us-east-1 --profile my-sso-app --query 'repository.repositoryUri' --output text)
      echo $ECR_URI
      ```
    </Note>
  </Step>

  <Step title="Authenticate with the ECR repository">
    Log in to the ECR repository:

    ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
    aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin $ECR_URI
    ```

    ```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
    Login Succeeded
    ```

    <Note>
      If using a profile, use the `--profile` flag:

      ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
      aws ecr get-login-password --region us-east-1 --profile my-sso-app | docker login --username AWS --password-stdin $ECR_URI
      ```
    </Note>
  </Step>

  <Step title="Tag and push the docker image to the ECR repository">
    Make sure you're in the directory containing your `Dockerfile`, then tag the Docker image with the ECR repository URI.

    ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
    docker tag bun-lambda-demo:latest ${ECR_URI}:latest
    ```

    Then, push the image to the ECR repository.

    ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
    docker push ${ECR_URI}:latest
    ```
  </Step>

  <Step title="Create an AWS Lambda function">
    Go to **AWS Console** > **Lambda** > [**Create Function**](https://us-east-1.console.aws.amazon.com/lambda/home?region=us-east-1#/create/function?intent=authorFromImage) > Select **Container image**

    <Warning>Make sure you've selected the right region. This URL defaults to `us-east-1`.</Warning>

    <Frame>
      <img alt="Create Function" />
    </Frame>

    Give the function a name, like `my-bun-function`.
  </Step>

  <Step title="Select the container image">
    Go to the **Container image URI** section and click **Browse images**. Select the image you pushed to the ECR repository.

    <Frame>
      <img alt="Select Container Repository" />
    </Frame>

    Then, select the `latest` image and click **Select image**.

    <Frame>
      <img alt="Select Container Image" />
    </Frame>
  </Step>

  <Step title="Configure the function">
    To get a public URL for the function, go to **Additional configurations** > **Networking** > **Function URL**.

    Set this to **Enable**, with Auth Type **NONE**.

    <Frame>
      <img alt="Set the Function URL" />
    </Frame>
  </Step>

  <Step title="Create the function">
    Click **Create function** at the bottom of the page.

    <Frame>
      <img alt="Create Function" />
    </Frame>
  </Step>

  <Step title="Get the function URL">
    Once the function is created, you're redirected to its page. The function URL is in the **"Function URL"** section.

    <Frame>
      <img alt="Function URL" />
    </Frame>
  </Step>

  <Step title="Test the function">
    Your app is now live. To test the function, either go to the **Test** tab or call the function URL directly.

    ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
    curl -X GET https://[your-function-id].lambda-url.us-east-1.on.aws/
    ```

    ```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
    Hello from Bun on Lambda!
    ```
  </Step>
</Steps>


# Deploy a Bun application on DigitalOcean
Source: https://bun.com/docs/guides/deployment/digital-ocean



[DigitalOcean](https://www.digitalocean.com/) is a cloud platform that provides a range of services for building and deploying applications.

This guide deploys a Bun HTTP server to DigitalOcean using a `Dockerfile`.

<Note>
  Before continuing, make sure you have:

  * A Bun application ready for deployment
  * A [DigitalOcean account](https://www.digitalocean.com/)
  * [DigitalOcean CLI](https://docs.digitalocean.com/reference/doctl/how-to/install/#step-1-install-doctl) installed and configured
  * [Docker](https://docs.docker.com/get-started/get-docker/) installed and added to your `PATH`
</Note>

***

<Steps>
  <Step title="Create a new DigitalOcean Container Registry">
    Create a new Container Registry to store the Docker image.

    <Tabs>
      <Tab title="Through the DigitalOcean dashboard">
        In the DigitalOcean dashboard, go to [**Container Registry**](https://cloud.digitalocean.com/registry), and enter the details for the new registry.

        <Frame>
          <img alt="DigitalOcean registry dashboard" />
        </Frame>

        Make sure the details are correct, then click **Create Registry**.
      </Tab>

      <Tab title="Through the DigitalOcean CLI">
        ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
        doctl registry create bun-digitalocean-demo
        ```

        ```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
        Name                     Endpoint                                           Region slug
        bun-digitalocean-demo    registry.digitalocean.com/bun-digitalocean-demo    sfo2
        ```
      </Tab>
    </Tabs>

    You should see the new registry in the [**DigitalOcean registry dashboard**](https://cloud.digitalocean.com/registry):

    <Frame>
      <img alt="DigitalOcean registry dashboard" />
    </Frame>
  </Step>

  <Step title="Create a new Dockerfile">
    Create a new `Dockerfile` in the root of your project. This file contains the instructions to initialize the container, copy your local project files into it, install dependencies, and start the application.

    ```docker Dockerfile icon="docker" theme={"theme":{"light":"github-light","dark":"dracula"}}
    # Use the official Bun image to run the application
    FROM oven/bun:debian

    # Set the work directory to `/app`
    WORKDIR /app

    # Copy the package.json and bun.lock into the container
    COPY package.json bun.lock ./

    # Install the dependencies
    RUN bun install --production --frozen-lockfile

    # Copy the rest of the application into the container
    COPY . .

    # Expose the port (DigitalOcean will set PORT env var)
    EXPOSE 8080

    # Run the application
    CMD ["bun", "index.ts"]
    ```

    <Note>
      Make sure that the start command corresponds to your application's entry point. The start command can also be `CMD ["bun", "run", "start"]` if you have a start script in your `package.json`.

      If your app doesn't have dependencies, you can omit the `COPY package.json bun.lock ./` and `RUN bun install --production --frozen-lockfile` lines. Bun doesn't write a `bun.lock` for a project with no dependencies.
    </Note>

    Create a new `.dockerignore` file in the root of your project. It lists the files and directories to *exclude* from the container image, such as `node_modules`. Excluding them keeps builds faster and smaller:

    ```docker .dockerignore icon="Docker" theme={"theme":{"light":"github-light","dark":"dracula"}}
    node_modules
    Dockerfile*
    .dockerignore
    .git
    .gitignore
    README.md
    LICENSE
    .vscode
    .env
    # Any other files or directories you want to exclude
    ```
  </Step>

  <Step title="Authenticate Docker with DigitalOcean registry">
    Before building and pushing the Docker image, authenticate Docker with the DigitalOcean Container Registry:

    ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
    doctl registry login
    ```

    ```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
    Logging Docker in to registry.digitalocean.com
    Notice: Login valid for 30 days. Use the --expiry-seconds flag to set a shorter expiration or --never-expire for no expiration.
    ```

    <Note>
      This command authenticates Docker with DigitalOcean's registry using your DigitalOcean credentials. Without this step, the build and push command fails with a 401 authentication error.
    </Note>
  </Step>

  <Step title="Build and push the Docker image to the DigitalOcean registry">
    Make sure you're in the directory containing your `Dockerfile`, then build and push the Docker image to the DigitalOcean registry in one command:

    ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
    docker buildx build --platform=linux/amd64 -t registry.digitalocean.com/bun-digitalocean-demo/bun-digitalocean-demo:latest --push .
    ```

    <Note>
      If you're building on an ARM Mac (M1/M2), you must use `docker buildx` with `--platform=linux/amd64` for compatibility with DigitalOcean's infrastructure. Using `docker build` without the platform flag creates an ARM64 image that won't run on DigitalOcean.
    </Note>

    Once the image is pushed, you should see it in the [**DigitalOcean registry dashboard**](https://cloud.digitalocean.com/registry):

    <Frame>
      <img alt="DigitalOcean registry dashboard" />
    </Frame>
  </Step>

  <Step title="Create a new DigitalOcean App Platform project">
    In the DigitalOcean dashboard, go to [**App Platform**](https://cloud.digitalocean.com/apps) > **Create App**. You can create a project directly from the container image.

    <Frame>
      <img alt="DigitalOcean App Platform project dashboard" />
    </Frame>

    Make sure the details are correct, then click **Next**.

    <Frame>
      <img alt="DigitalOcean App Platform service dashboard" />
    </Frame>

    Review and configure resource settings, then click **Create app**.

    <Frame>
      <img alt="DigitalOcean App Platform service dashboard" />
    </Frame>
  </Step>

  <Step title="Visit your live application">
    Your app is now live. Once the app is created, you should see it in the App Platform dashboard with its public URL.

    <Frame>
      <img alt="DigitalOcean App Platform app dashboard" />
    </Frame>
  </Step>
</Steps>


# Deploy a Bun application on Google Cloud Run
Source: https://bun.com/docs/guides/deployment/google-cloud-run



[Google Cloud Run](https://cloud.google.com/run) is a managed platform for deploying and scaling serverless applications. Google handles the infrastructure for you.

This guide deploys a Bun HTTP server to Google Cloud Run using a `Dockerfile`.

<Note>
  Before continuing, make sure you have:

  * A Bun application ready for deployment
  * A [Google Cloud account](https://cloud.google.com/) with billing enabled
  * [Google Cloud CLI](https://cloud.google.com/sdk/docs/install) installed and configured
</Note>

***

<Steps>
  <Step title={<span>Initialize <code>gcloud</code> by selecting or creating a project</span>}>
    Make sure that you've initialized the Google Cloud CLI. `gcloud init` logs you in and prompts you to either select an existing project or create a new one.

    For more help with the Google Cloud CLI, see the [official documentation](https://docs.cloud.google.com/sdk/gcloud/reference/init).

    ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
    gcloud init
    ```

    ```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
    Welcome! This command will take you through the configuration of gcloud.

    You must sign in to continue. Would you like to sign in (Y/n)? Y
    You are signed in as [email@example.com].

    Pick cloud project to use:
     [1] existing-bun-app-1234
     [2] Enter a project ID
     [3] Create a new project
    Please enter numeric choice or text value (must exactly match list item): 3

    Enter a Project ID. my-bun-app
    Your current project has been set to: [my-bun-app]

    The Google Cloud CLI is configured and ready to use!
    ```
  </Step>

  <Step title="(Optional) Store your project info in environment variables">
    Set variables for your project ID and number so you can reuse them in the following steps.

    ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
    PROJECT_ID=$(gcloud projects list --format='value(projectId)' --filter='projectId=my-bun-app')
    PROJECT_NUMBER=$(gcloud projects list --format='value(projectNumber)' --filter='projectId=my-bun-app')

    echo $PROJECT_ID $PROJECT_NUMBER
    ```

    ```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
    my-bun-app [PROJECT_NUMBER]
    ```
  </Step>

  <Step title="Link a billing account">
    List your available billing accounts and link one to your project:

    ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
    gcloud billing accounts list
    ```

    ```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
    ACCOUNT_ID            NAME                OPEN  MASTER_ACCOUNT_ID
    [BILLING_ACCOUNT_ID]  My Billing Account  True
    ```

    Link your billing account to your project. Replace `[BILLING_ACCOUNT_ID]` with the ID of your billing account.

    ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
    gcloud billing projects link $PROJECT_ID --billing-account=[BILLING_ACCOUNT_ID]
    ```

    ```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
    billingAccountName: billingAccounts/[BILLING_ACCOUNT_ID]
    billingEnabled: true
    name: projects/my-bun-app/billingInfo
    projectId: my-bun-app
    ```
  </Step>

  <Step title="Enable APIs and configure IAM roles">
    Activate the necessary services and grant Cloud Build permissions:

    ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
    gcloud services enable run.googleapis.com cloudbuild.googleapis.com
    gcloud projects add-iam-policy-binding $PROJECT_ID \
      --member=serviceAccount:$PROJECT_NUMBER-compute@developer.gserviceaccount.com \
      --role=roles/run.builder
    ```

    <Note>
      These commands enable Cloud Run (`run.googleapis.com`) and Cloud Build (`cloudbuild.googleapis.com`). Deploying from source requires both. Cloud Run runs your containerized app, while Cloud Build builds and packages it.

      The IAM binding grants the Compute Engine service account (`$PROJECT_NUMBER-compute@developer.gserviceaccount.com`) permission to build and deploy images on your behalf.
    </Note>
  </Step>

  <Step title="Add a Dockerfile">
    Create a new `Dockerfile` in the root of your project. This file contains the instructions to initialize the container, copy your local project files into it, install dependencies, and start the application.

    ```docker Dockerfile icon="docker" theme={"theme":{"light":"github-light","dark":"dracula"}}
    # Use the official Bun image to run the application
    FROM oven/bun:latest

    # Copy the package.json and bun.lock into the container
    COPY package.json bun.lock ./

    # Install the dependencies
    RUN bun install --production --frozen-lockfile

    # Copy the rest of the application into the container
    COPY . .

    # Run the application
    CMD ["bun", "index.ts"]
    ```

    <Note>
      Make sure that the start command corresponds to your application's entry point. The start command can also be `CMD ["bun", "run", "start"]` if you have a start script in your `package.json`.

      If your app doesn't have dependencies, you can omit the `COPY package.json bun.lock ./` and `RUN bun install --production --frozen-lockfile` lines. Bun doesn't write a `bun.lock` for a project with no dependencies.
    </Note>

    Create a new `.dockerignore` file in the root of your project. It lists the files and directories to *exclude* from the container image, such as `node_modules`. Excluding them keeps builds faster and smaller:

    ```docker .dockerignore icon="Docker" theme={"theme":{"light":"github-light","dark":"dracula"}}
    node_modules
    Dockerfile*
    .dockerignore
    .git
    .gitignore
    README.md
    LICENSE
    .vscode
    .env
    # Any other files or directories you want to exclude
    ```
  </Step>

  <Step title="Deploy your service">
    Make sure you're in the directory containing your `Dockerfile`, then deploy directly from your local source:

    <Note>Update the `--region` flag to your preferred region, or omit it to select a region interactively.</Note>

    ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
    gcloud run deploy my-bun-app --source . --region=us-west1 --allow-unauthenticated
    ```

    ```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
    Deploying from source requires an Artifact Registry Docker repository to store built containers. A repository named
    [cloud-run-source-deploy] in region [us-west1] will be created.

    Do you want to continue (Y/n)? Y

    Building using Dockerfile and deploying container to Cloud Run service [my-bun-app] in project [my-bun-app] region [us-west1]
    ✓ Building and deploying... Done.
      ✓ Validating configuration...
      ✓ Uploading sources...
      ✓ Building Container... Logs are available at [https://console.cloud.google.com/cloud-build/builds...].
      ✓ Creating Revision...
      ✓ Routing traffic...
      ✓ Setting IAM Policy...
    Done.
    Service [my-bun-app] revision [my-bun-app-...] has been deployed and is serving 100 percent of traffic.
    Service URL: https://my-bun-app-....us-west1.run.app
    ```
  </Step>

  <Step title="Visit your live application">
    Your Bun application is now live.

    Visit the Service URL (`https://my-bun-app-....us-west1.run.app`) to confirm everything works as expected.
  </Step>
</Steps>


# Deploy a Bun application on Railway
Source: https://bun.com/docs/guides/deployment/railway

Deploy a Bun application to Railway from the CLI or dashboard, with optional PostgreSQL setup and automatic SSL

Railway is an infrastructure platform: you provision infrastructure, develop against it locally, then deploy to the cloud. Railway deploys from GitHub with zero configuration, handles SSL automatically, and provisions databases.

This guide deploys a Bun application with an optional PostgreSQL database, the same setup the following template provides.

You can either follow this guide step-by-step or deploy the pre-configured template with one click:

<a href="https://railway.com/deploy/bun-react-postgres?referralCode=Bun&utm_medium=integration&utm_source=template&utm_campaign=bun">
  <img alt="Deploy on Railway" />
</a>

***

**Prerequisites**:

* A Bun application ready for deployment
* A [Railway account](https://railway.com/)
* Railway CLI (for CLI deployment method)
* A GitHub account (for Dashboard deployment method)

***

## Method 1: Deploy via CLI

<Steps>
  <Step title="Step 1">
    Install the Railway CLI.

    ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
    bun install -g @railway/cli
    ```
  </Step>

  <Step title="Step 2">
    Log into your Railway account.

    ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
    railway login
    ```
  </Step>

  <Step title="Step 3">
    After authenticating, initialize a new project.

    ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
    railway init
    ```
  </Step>

  <Step title="Step 4">
    After initializing the project, add a new database and service.

    <Note>Step 4 is only necessary if your application uses a database. If you don't need PostgreSQL, skip to Step 5.</Note>

    ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
    # Add PostgreSQL database. Make sure to add this first!
    railway add --database postgres

    # Add your application service.
    railway add --service bun-react-db --variables DATABASE_URL=\${{Postgres.DATABASE_URL}}
    ```
  </Step>

  <Step title="Step 5">
    After creating and connecting the services, deploy the application to Railway. By default, services are only accessible within Railway's private network, so generate a public domain to make your app publicly accessible.

    ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
    # Deploy your application
    railway up

    # Generate public domain
    railway domain
    ```
  </Step>
</Steps>

Your app is now live. `railway up` deploys your local directory rather than a GitHub repository. To have Railway auto-deploy on every GitHub push, connect the service to your repository with `railway service source connect --repo <owner>/<repo> --branch <branch>`.

***

## Method 2: Deploy via Dashboard

<Steps>
  <Step title="Step 1">
    Create a new project

    1. Go to [Railway Dashboard](https://railway.com/dashboard?utm_medium=integration\&utm_source=docs\&utm_campaign=bun)
    2. Click **"+ New"** → **"GitHub repo"**
    3. Choose your repository
  </Step>

  <Step title="Step 2">
    Add a PostgreSQL database, and connect this database to the service

    <Note>Step 2 is only necessary if your application uses a database. If you don't need PostgreSQL, skip to Step 3.</Note>

    1. Click **"+ New"** → **"Database"** → **"Add PostgreSQL"**
    2. After Railway creates the database, select your service (not the database)
    3. Go to **"Variables"** tab
    4. Click **"+ New Variable"** → **"Add Reference"**
    5. Select `DATABASE_URL` from postgres
  </Step>

  <Step title="Step 3">
    Generate a public domain

    1. Select your service
    2. Go to **"Settings"** tab
    3. Under **"Networking"**, click **"Generate Domain"**
  </Step>
</Steps>

Your app is now live. Railway auto-deploys on every GitHub push.

***

## Configuration (Optional)

By default, Railway uses [Railpack](https://docs.railway.com/builds/railpack) to automatically detect and build your Bun application with zero configuration.

Railpack detects Bun from your `bun.lock` and installs the latest version of Bun unless you pin one with the `engines.bun` or `packageManager` field in `package.json`. Railway's previous builder, [Nixpacks](https://github.com/railwayapp/nixpacks), is in maintenance mode.

If your service still builds with Nixpacks, switch it to Railpack by adding the following to your `railway.json`:

```json railway.json icon="file-json" theme={"theme":{"light":"github-light","dark":"dracula"}}
{
  "$schema": "https://railway.com/railway.schema.json",
  "build": {
    "builder": "RAILPACK"
  }
}
```

For more build configuration settings, see the [Railway documentation](https://docs.railway.com/builds/build-configuration).


# Deploy a Bun application on Render
Source: https://bun.com/docs/guides/deployment/render



[Render](https://render.com/) is a cloud platform for building, deploying, and scaling apps.

It provides auto deploys from GitHub, a global CDN, private networks, automatic HTTPS setup, and managed PostgreSQL and Redis-compatible Key Value stores.

Render supports Bun natively. You can deploy Bun apps as web services, background workers, cron jobs, and more.

***

As an example, this guide deploys an Express HTTP server to Render.

<Steps>
  <Step title="Step 1">
    Create a new GitHub repo named `myapp`. Git clone it locally.

    ```sh theme={"theme":{"light":"github-light","dark":"dracula"}}
    git clone git@github.com:my-github-username/myapp.git
    cd myapp
    ```
  </Step>

  <Step title="Step 2">
    Add the Express library.

    ```sh theme={"theme":{"light":"github-light","dark":"dracula"}}
    bun add express
    ```
  </Step>

  <Step title="Step 3">
    Define a server with Express:

    ```ts app.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
    import express from "express";

    const app = express();
    const port = process.env.PORT || 3001;

    app.get("/", (req, res) => {
    	res.send("Hello World!");
    });

    app.listen(port, () => {
    	console.log(`Listening on port ${port}...`);
    });
    ```
  </Step>

  <Step title="Step 4">
    Commit your changes and push to GitHub.

    ```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
    git add app.ts bun.lock package.json
    git commit -m "Create simple Express app"
    git push origin main
    ```
  </Step>

  <Step title="Step 5">
    In your [Render Dashboard](https://dashboard.render.com/), click `New` > `Web Service` and connect your `myapp` repo.
  </Step>

  <Step title="Step 6">
    In the Render UI, provide the following values during web service creation:

    |                   |               |
    | ----------------- | ------------- |
    | **Runtime**       | `Node`        |
    | **Build Command** | `bun install` |
    | **Start Command** | `bun app.ts`  |
  </Step>
</Steps>

Once the build finishes, your web service is live at its assigned `onrender.com` URL.

View the [deploy logs](https://docs.render.com/logging#logs-for-an-individual-deploy-or-job) for details. See [Render's documentation](https://docs.render.com/deploys) for more on deploys.


# Deploy a Bun application on Vercel
Source: https://bun.com/docs/guides/deployment/vercel



[Vercel](https://vercel.com/) is a cloud platform for building, deploying, and scaling apps. Vercel Functions can run on the Bun runtime, either behind a framework that Vercel supports or as a [`Bun.serve()`](/docs/runtime/http/server) server.

<Warning>
  The Bun runtime on Vercel is in Beta. Automatic source maps, bytecode caching, and request metrics for `node:http` and
  `node:https` are not supported yet (request metrics for `fetch` are). See [feature
  support](https://vercel.com/docs/functions/runtimes/bun#feature-support) in the Vercel documentation.
</Warning>

***

<Steps>
  <Step title="Configure Bun in vercel.json">
    To run your Functions on Bun, add a [`bunVersion`](https://vercel.com/docs/project-configuration/vercel-json#bunversion) field to your `vercel.json` file:

    ```json vercel.json icon="file-json" theme={"theme":{"light":"github-light","dark":"dracula"}}
    {
    	"bunVersion": "1.x" // [!code ++]
    }
    ```

    The value must be `"1.x"`; Vercel manages the minor and patch versions.

    For best results, match your local Bun version with the version Vercel uses.
  </Step>

  <Step title="Add a server">
    Choose how requests reach your code.

    <Tabs>
      <Tab title="Bun.serve() for the whole app">
        Vercel's Bun framework preset sends every request for the deployment to a single `Bun.serve()` server. Vercel uses the preset when the project sets `bunVersion`, has a `bun.lock` file, and has a server entrypoint at one of these paths:

        * `server.{js,cjs,mjs,ts,cts,mts}`
        * `src/server.{js,cjs,mjs,ts,cts,mts}`

        `bun install` creates `bun.lock` on Bun 1.2 or later. On older versions, run `bun install --save-text-lockfile`. The preset does not detect the binary `bun.lockb` format.

        Call `Bun.serve()` once while the module loads. Vercel detects that call and routes incoming requests to it. Vercel supports the `fetch`, [`routes`](/docs/runtime/http/routing), `error`, and `websocket` options:

        ```ts server.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
        Bun.serve({
        	routes: {
        		"/health": () => Response.json({ status: "ok" }),
        	},
        	fetch() {
        		return new Response("Hello from Bun on Vercel");
        	},
        });
        ```

        A minimal project is `package.json`, `bun.lock`, `server.ts`, and the `vercel.json` from the previous step. It doesn't need an `api/` directory or any routing configuration.

        <Note>
          `port` and `hostname` only apply when you run the server locally; they don't configure the deployed endpoint. Unix sockets and [HTML imports](/docs/runtime/http/server#html-imports) in `routes` are not supported on Vercel.

          To serve WebSocket connections, see the [Bun example in Vercel's WebSockets documentation](https://vercel.com/docs/functions/websockets#bun).
        </Note>
      </Tab>

      <Tab title="Bun.serve() under /api">
        To add a Bun server to a project that also has a frontend, create `api/server.ts` and call `Bun.serve()` once while the module loads. Vercel deploys it as a single Function at `/api/server`. Unlike the framework preset, only requests for `/api/server` reach this server.

        ```ts api/server.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
        Bun.serve({
        	fetch(request) {
        		const url = new URL(request.url);

        		return Response.json({
        			message: "Hello from Bun on Vercel",
        			pathname: url.pathname,
        		});
        	},
        });
        ```

        This setup only needs the `bunVersion` setting from the previous step; it doesn't use the framework preset or require a `bun.lock` file. To send other paths to this server, add route overrides to `vercel.json`. Each override must use the full request path, including the `/api/server` prefix. See [the Vercel Bun runtime documentation](https://vercel.com/docs/functions/runtimes/bun) for details.
      </Tab>

      <Tab title="Next.js or another framework">
        Frameworks that Vercel supports, such as Next.js, Express, Hono, and Nitro, run on Bun once you set `bunVersion`.

        If you're deploying a **Next.js** project (including ISR), also update the `package.json` scripts so the Next.js CLI runs under Bun:

        ```json package.json icon="file-json" theme={"theme":{"light":"github-light","dark":"dracula"}}
        {
        	"scripts": {
        		"dev": "bun --bun next dev", // [!code ++]
        		"build": "bun --bun next build" // [!code ++]
        	}
        }
        ```

        <Note>
          The `--bun` flag runs the Next.js CLI under Bun. Bundling (with Turbopack or Webpack) is unchanged.
        </Note>
      </Tab>
    </Tabs>
  </Step>

  <Step title="Deploy your app">
    Connect your repository to Vercel, or deploy from the CLI:

    ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
    # Using bunx (no global install)
    bunx vercel login
    bunx vercel deploy
    ```

    Or install the Vercel CLI globally:

    ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
    bun i -g vercel
    vercel login
    vercel deploy
    ```

    [Learn more in the Vercel Deploy CLI documentation →](https://vercel.com/docs/cli/deploy)
  </Step>

  <Step title="Verify the runtime">
    To confirm your deployment uses Bun, log the Bun version:

    ```ts server.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
    console.log("runtime", process.versions.bun);
    ```

    ```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
    runtime 1.3.14
    ```

    [See the Vercel Bun Runtime documentation for feature support →](https://vercel.com/docs/functions/runtimes/bun#feature-support)
  </Step>
</Steps>

***

* [Fluid compute](https://vercel.com/docs/fluid-compute): Both Bun and Node.js runtimes run on Fluid compute and support the same core Vercel Functions features.
* [Middleware](https://vercel.com/docs/routing-middleware): To run Routing Middleware with Bun, set the runtime to `nodejs`:

```ts middleware.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
export const config = { runtime: "nodejs" }; // [!code ++]
```


# Build an app with Astro and Bun
Source: https://bun.com/docs/guides/ecosystem/astro



Initialize a fresh Astro app with `bun create astro`. The `create-astro` package detects when you are using `bunx` and installs dependencies with `bun`.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun create astro
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
 astro   Launch sequence initiated.

╭─────╮  Houston:
│ ◠ ◡ ◠  We're glad to have you on board.
╰─────╯

   dir   Where should we create your new project?
         ./fumbling-field

  tmpl   How would you like to start your new project?
         Use blog template

  deps   Install dependencies?
         Yes

   git   Initialize a new git repository?
         Yes

      ✔  Project initialized!
         ■ Template copied
         ■ Dependencies installed
         ■ Git initialized

  next   Liftoff confirmed. Explore your project!

         Enter your project directory using cd ./fumbling-field
         Run `bun run dev` to start the dev server. q + ENTER to stop.
         Add frameworks like react or tailwind using astro add.

         Stuck? Join us at https://astro.build/chat

╭─────╮  Houston:
│ ◠ ◡ ◠  Good luck out there, astronaut! 🚀
╰─────╯
```

***

Start the dev server with `bunx`.

By default, Bun runs the dev server with Node.js. To use the Bun runtime instead, pass the `--bun` flag.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bunx --bun astro dev
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
 astro  v7.2.2 ready in 200 ms
┃ Local    http://localhost:4321/
┃ Network  use --host to expose
```

***

Open [http://localhost:4321](http://localhost:4321) in your browser to see the result. Astro hot-reloads the app as you edit your source files.

<Frame>
  <img />
</Frame>

***

See the [Astro docs](https://docs.astro.build/en/getting-started/).


# Create a Discord bot
Source: https://bun.com/docs/guides/ecosystem/discordjs



Discord.js runs on Bun with no extra setup. This guide builds a bot that answers a `/ping` slash command: you register the command once, then start the bot and use it in your server. If this is your first bot, copy each block as you reach it.

***

Create a folder for your bot and set it up with `bun init`. Pick the defaults when it asks.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
mkdir my-bot
cd my-bot
bun init
```

***

Add Discord.js to the project.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun add discord.js
```

***

Your bot needs its own account, which you create in Discord's developer portal. Open the [developer portal](https://discord.com/developers/applications), sign in, and create an **Application**. Discord adds a bot user to every new application automatically; it's on the **Bot** tab. The Discord.js guide's [setup walkthrough](https://discordjs.guide/legacy/preparations/app-setup) has screenshots if you get lost.

Copy two values from the portal:

* The **token** on the **Bot** tab: click **Reset Token** to generate it (Discord only shows it once). It's the password your code uses to log in, so treat it like one and keep it to yourself.
* The **Application ID** on the **General Information** tab. Discord uses it to tie your commands to this app.

***

A bot can't do anything in a server until you invite it. In the portal's **OAuth2** section, generate an invite URL with the `bot` and `applications.commands` scopes, open it, and add the bot to a server you manage. Slash commands need the `applications.commands` scope, so include it. A personal server you make for testing is a good place to start, and the Discord.js [guide to adding a bot](https://discordjs.guide/legacy/preparations/adding-your-app) walks through it with screenshots.

***

You also need your server's ID to register the command there. In Discord, turn on **Settings > Advanced > Developer Mode**, then right-click your server's icon and choose **Copy Server ID**.

***

Save all three values in `.env.local`. Bun reads this file on startup and loads it into `process.env`, so nothing secret lives in your code.

```ini .env.local icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
DISCORD_TOKEN=your-bot-token
DISCORD_CLIENT_ID=your-application-id
DISCORD_GUILD_ID=your-server-id
```

***

Add `.env.local` to your `.gitignore` before you commit anything. Anyone who reads the token can control your bot, so the token should never land in version control.

```txt .gitignore icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
node_modules
.env.local
```

***

Discord has to know about a command before anyone can use it. Register `/ping` with a short script named `deploy-commands.ts`.

```ts deploy-commands.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { REST, Routes, SlashCommandBuilder } from "discord.js";

const { DISCORD_TOKEN, DISCORD_CLIENT_ID, DISCORD_GUILD_ID } = process.env;
if (!DISCORD_TOKEN || !DISCORD_CLIENT_ID || !DISCORD_GUILD_ID) {
  throw new Error("Set DISCORD_TOKEN, DISCORD_CLIENT_ID, and DISCORD_GUILD_ID in .env.local");
}

// the commands you want to register
const commands = [new SlashCommandBuilder().setName("ping").setDescription("Replies with Pong!").toJSON()];

const rest = new REST().setToken(DISCORD_TOKEN);

// register them in your test server
await rest.put(Routes.applicationGuildCommands(DISCORD_CLIENT_ID, DISCORD_GUILD_ID), { body: commands });

console.log("Registered /ping");
```

Run it once.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun run deploy-commands.ts
```

You only run this again when you add a command or change its name or description, not every time the bot starts. Registering to your server instead of globally keeps the command scoped to where you're testing.

***

Now the bot itself. Save it as `bot.ts`.

```ts bot.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { Client, Events, GatewayIntentBits } from "discord.js";

const { DISCORD_TOKEN } = process.env;
if (!DISCORD_TOKEN) {
  throw new Error("Set DISCORD_TOKEN in .env.local");
}

const client = new Client({ intents: [GatewayIntentBits.Guilds] });

// runs once, right after the bot connects
client.once(Events.ClientReady, readyClient => {
  console.log(`Logged in as ${readyClient.user.tag}`);
});

// runs every time someone uses a slash command
client.on(Events.InteractionCreate, async interaction => {
  if (!interaction.isChatInputCommand()) return;

  if (interaction.commandName === "ping") {
    await interaction.reply("Pong!");
  }
});

client.login(DISCORD_TOKEN);
```

The ready handler logs a line once the bot connects. After that, `interactionCreate` runs whenever someone uses a slash command; it confirms the command was `/ping` and replies with `Pong!`.

***

Start the bot with `bun run`.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun run bot.ts
```

The first connection takes a few seconds. Once the login line prints, switch to Discord and type `/ping` in your server.

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
Logged in as my-bot#1234
```

The bot replies with `Pong!`. You've got a working Discord bot.

***

To add another command, define it in `deploy-commands.ts`, run that script again, and add an `if` branch for its name in `bot.ts`. The [Discord.js docs](https://discord.js.org/docs) cover command options, permissions, buttons, embeds, and the rest of the API.

***

When you deploy, there's no build or bundling step. Bun runs `bot.ts` and every file it imports directly, so you ship your source as-is and start it with the same `bun run bot.ts` you use while developing.

`deploy-commands.ts` registers `/ping` in your test server, which is the right scope while you're building. To publish the bot to every server it joins, register globally instead: change the route to `Routes.applicationCommands(DISCORD_CLIENT_ID)`. Global registration doesn't use a server, so you can also drop `DISCORD_GUILD_ID` from the script's check and from `.env.local`.

To keep the bot online and bring it back after a crash or reboot, run it under a process manager.

<Columns>
  <Card title="systemd" href="/guides/ecosystem/systemd" icon="server">
    Run your bot as a Linux daemon
  </Card>

  <Card title="PM2" href="/guides/ecosystem/pm2" icon="cog">
    Manage your bot with PM2
  </Card>
</Columns>


# Containerize a Bun application with Docker
Source: https://bun.com/docs/guides/ecosystem/docker



<Note>
  This guide assumes you already have [Docker Desktop](https://www.docker.com/products/docker-desktop/) installed.
</Note>

[Docker](https://www.docker.com) is a platform for packaging and running an application as a lightweight, portable *container* that encapsulates all the necessary dependencies.

***

To *containerize* the application, define a `Dockerfile`. It lists the instructions to initialize the container, copy your local project files into it, install dependencies, and start the application.

```docker Dockerfile icon="docker" theme={"theme":{"light":"github-light","dark":"dracula"}}
# use the official Bun image
# see all versions at https://hub.docker.com/r/oven/bun/tags
FROM oven/bun:1 AS base
WORKDIR /usr/src/app

# install dependencies into temp directory
# this will cache them and speed up future builds
FROM base AS install
RUN mkdir -p /temp/dev
COPY package.json bun.lock /temp/dev/
RUN cd /temp/dev && bun install --frozen-lockfile

# install with --production (exclude devDependencies)
RUN mkdir -p /temp/prod
COPY package.json bun.lock /temp/prod/
RUN cd /temp/prod && bun install --frozen-lockfile --production

# copy node_modules from temp directory
# then copy all (non-ignored) project files into the image
FROM base AS prerelease
COPY --from=install /temp/dev/node_modules node_modules
COPY . .

# [optional] tests & build
ENV NODE_ENV=production
RUN bun test
RUN bun run build

# copy production dependencies and source code into final image
FROM base AS release
COPY --from=install /temp/prod/node_modules node_modules
COPY --from=prerelease /usr/src/app/index.ts .
COPY --from=prerelease /usr/src/app/package.json .

# run the app
USER bun
EXPOSE 3000/tcp
ENTRYPOINT [ "bun", "run", "index.ts" ]
```

***

Next, add a `.dockerignore` file. It uses a syntax similar to `.gitignore` and lists the files and directories to exclude from every stage of the Docker build. For example:

```txt .dockerignore icon="docker" theme={"theme":{"light":"github-light","dark":"dracula"}}
node_modules
Dockerfile*
docker-compose*
.dockerignore
.git
.gitignore
README.md
LICENSE
.vscode
Makefile
helm-charts
.env
.editorconfig
.idea
coverage*
```

***

Run `docker build` to convert this `Dockerfile` into a *Docker image*, a self-contained template containing all the dependencies and configuration required to run the application.

The `-t` flag names the image, and `--pull` tells Docker to download the latest version of the base image (`oven/bun`). The initial build takes longer, since Docker downloads all the base images and dependencies.

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
docker build --pull -t bun-hello-world .
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
[+] Building 0.9s (21/21) FINISHED
 => [internal] load build definition from Dockerfile                                                                                     0.0s
 => => transferring dockerfile: 37B                                                                                                      0.0s
 => [internal] load .dockerignore                                                                                                        0.0s
 => => transferring context: 35B                                                                                                         0.0s
 => [internal] load metadata for docker.io/oven/bun:1                                                                                    0.8s
 => [auth] oven/bun:pull token for registry-1.docker.io                                                                                  0.0s
 => [base 1/2] FROM docker.io/oven/bun:1@sha256:373265748d3cd3624cb3f3ee6004f45b1fc3edbd07a622aeeec17566d2756997                         0.0s
 => [internal] load build context                                                                                                        0.0s
 => => transferring context: 155B                                                                                                        0.0s
 # ...lots of commands...
 => exporting to image                                                                                                                   0.0s
 => => exporting layers                                                                                                                  0.0s
 => => writing image sha256:360663f7fdcd6f11e8e94761d5592e2e4dfc8d167f034f15cd5a863d5dc093c4                                             0.0s
 => => naming to docker.io/library/bun-hello-world                                                                                       0.0s
```

***

Now start a running *container* from the `bun-hello-world` image with `docker run`. The `-d` flag runs it in *detached* mode, and `-p 3000:3000` maps the container's port 3000 to port 3000 on your machine.

The `run` command prints the *container ID*.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
docker run -d -p 3000:3000 bun-hello-world
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
7f03e212a15ede8644379bce11a13589f563d3909a9640446c5bbefce993678d
```

***

The container is now running in the background. Visit [localhost:3000](http://localhost:3000). You should see your application's response.

***

To stop the container, run `docker stop <container-id>`.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
docker stop 7f03e212a15ede8644379bce11a13589f563d3909a9640446c5bbefce993678d
```

***

If you can't find the container ID, `docker ps` lists all running containers.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
docker ps
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                    NAMES
7f03e212a15e        bun-hello-world     "bun run index.ts"       2 minutes ago       Up 2 minutes        0.0.0.0:3000->3000/tcp   flamboyant_cerf
```

***

See the [Docker documentation](https://docs.docker.com/) for more advanced usage.


# Use Drizzle ORM with Bun
Source: https://bun.com/docs/guides/ecosystem/drizzle



Drizzle is an ORM that supports both a SQL-like "query builder" API and an ORM-like [Queries API](https://orm.drizzle.team/docs/rqb). It supports the `bun:sqlite` built-in module.

***

Create a fresh project with `bun init` and install Drizzle.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun init -y
bun add drizzle-orm
bun add -D drizzle-kit
```

***

Then connect to a SQLite database with the `bun:sqlite` module and create the Drizzle database instance.

```ts db.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { drizzle } from "drizzle-orm/bun-sqlite";
import { Database } from "bun:sqlite";

const sqlite = new Database("sqlite.db");
export const db = drizzle({ client: sqlite });
```

***

To see the database in action, add these lines to `index.ts`.

```ts index.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { db } from "./db";
import { sql } from "drizzle-orm";

const query = sql`select "hello world" as text`;
const result = db.all<{ text: string }>(query);
console.log(result);
```

***

Then run `index.ts` with Bun. Bun creates `sqlite.db` and executes the query.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun run index.ts
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
[
  {
    text: "hello world",
  }
]
```

***

Now give the database a schema. Create a `schema.ts` file and define a `movies` table.

```ts schema.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { sqliteTable, text, integer } from "drizzle-orm/sqlite-core";

export const movies = sqliteTable("movies", {
  id: integer("id").primaryKey(),
  title: text("name"),
  releaseYear: integer("release_year"),
});
```

***

Generate an initial SQL migration with the `drizzle-kit` CLI.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bunx drizzle-kit generate --dialect sqlite --schema ./schema.ts
```

***

The command creates a `drizzle` directory containing a `.sql` migration file and a `meta` directory.

```txt File Tree icon="folder-tree" theme={"theme":{"light":"github-light","dark":"dracula"}}
drizzle
├── 0000_ordinary_beyonder.sql
└── meta
    ├── 0000_snapshot.json
    └── _journal.json
```

***

Execute these migrations with a `migrate.ts` script. It connects to `sqlite.db`, then executes all unexecuted migrations in the `drizzle` directory.

```ts migrate.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { migrate } from "drizzle-orm/bun-sqlite/migrator";

import { drizzle } from "drizzle-orm/bun-sqlite";
import { Database } from "bun:sqlite";

const sqlite = new Database("sqlite.db");
const db = drizzle({ client: sqlite });
migrate(db, { migrationsFolder: "./drizzle" });
```

***

Run the script with `bun` to execute the migration.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun run migrate.ts
```

***

Now add some data to the database. Create a `seed.ts` file with the following contents.

```ts seed.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { db } from "./db";
import * as schema from "./schema";

await db.insert(schema.movies).values([
  {
    title: "The Matrix",
    releaseYear: 1999,
  },
  {
    title: "The Matrix Reloaded",
    releaseYear: 2003,
  },
  {
    title: "The Matrix Revolutions",
    releaseYear: 2003,
  },
]);

console.log(`Seeding complete.`);
```

***

Then run this file.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun run seed.ts
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
Seeding complete.
```

***

The database now has a schema and some sample data. Query it with Drizzle by replacing the contents of `index.ts` with the following.

```ts index.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import * as schema from "./schema";
import { db } from "./db";

const result = await db.select().from(schema.movies);
console.log(result);
```

***

Then run the file. You should see the three movies you inserted.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun run index.ts
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
[
  {
    id: 1,
    title: "The Matrix",
    releaseYear: 1999,
  }, {
    id: 2,
    title: "The Matrix Reloaded",
    releaseYear: 2003,
  }, {
    id: 3,
    title: "The Matrix Revolutions",
    releaseYear: 2003,
  }
]
```

***

See the [Drizzle docs](https://orm.drizzle.team/docs/overview).


# Build an HTTP server using Elysia and Bun
Source: https://bun.com/docs/guides/ecosystem/elysia



[Elysia](https://elysiajs.com) is a Bun-first web framework built on Bun's HTTP, file system, and hot reloading APIs. Get started with `bun create`.

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun create elysia myapp
cd myapp
bun run dev
```

***

To define an HTTP route and start a server with Elysia:

```ts server.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { Elysia } from "elysia";

const app = new Elysia().get("/", () => "Hello Elysia").listen(8080);

console.log(`🦊 Elysia is running on port ${app.server?.port}...`);
```

***

Elysia is a server framework with Express-like syntax, type inference, middleware, file uploads, and plugins for JWT authentication and OpenAPI documentation. It's one of the [fastest Bun web frameworks](https://github.com/SaltyAom/bun-http-framework-benchmark).

See the Elysia [documentation](https://elysiajs.com/quick-start.html).


# Build an HTTP server using Express and Bun
Source: https://bun.com/docs/guides/ecosystem/express



Express and other major Node.js HTTP libraries should work in Bun without changes. Bun implements the [`node:http`](https://nodejs.org/api/http.html) and [`node:https`](https://nodejs.org/api/https.html) modules that these libraries rely on.

<Note>See [Node.js compatibility](/docs/runtime/nodejs-compat#node-http) for details.</Note>

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun add express
```

***

To define an HTTP route and start a server with Express:

```ts server.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import express from "express";

const app = express();
const port = 8080;

app.get("/", (req, res) => {
  res.send("Hello World!");
});

app.listen(port, () => {
  console.log(`Listening on port ${port}...`);
});
```

***

To start the server on `localhost`:

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun server.ts
```


# Use Gel with Bun
Source: https://bun.com/docs/guides/ecosystem/gel



Gel (formerly EdgeDB) is a graph-relational database built on Postgres. It provides a declarative schema language, migrations system, and object-oriented query language. It also supports raw SQL queries. It solves object-relational mapping at the database layer, so your application code doesn't need an ORM library.

***

First, [install Gel](https://docs.geldata.com/learn/installation) if you haven't already.

<CodeGroup>
  ```sh Linux/macOS terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
  curl https://www.geldata.com/sh --proto "=https" -sSf1 | sh
  ```

  ```sh Windows terminal icon="windows" theme={"theme":{"light":"github-light","dark":"dracula"}}
  irm https://www.geldata.com/ps1 | iex
  ```

  ```sh Homebrew terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
  brew install geldata/tap/gel-cli
  ```
</CodeGroup>

***

Use `bun init` to create a fresh project.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
mkdir my-gel-app
cd my-gel-app
bun init -y
```

***

Initialize a Gel instance for the project with the Gel CLI. The `gel project init` command creates a `gel.toml` file in the project root.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
gel project init
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
No `gel.toml` (or `edgedb.toml`) found in `/Users/colinmcd94/Documents/bun/fun/examples/my-gel-app` or above
Initializing new project...
Checking Gel versions...
┌─────────────────────┬──────────────────────────────────────────────────────────────────┐
│ Project directory   │ /Users/colinmcd94/Documents/bun/fun/examples/my-gel-app          │
│ Project config      │ /Users/colinmcd94/Documents/bun/fun/examples/my-gel-app/gel.toml │
│ Schema dir (empty)  │ /Users/colinmcd94/Documents/bun/fun/examples/my-gel-app/dbschema │
│ Installation method │ portable package                                                 │
│ Version             │ x.y+6d5921b                                                      │
│ Instance name       │ my_gel_app                                                       │
│ Branch              │ main                                                             │
└─────────────────────┴──────────────────────────────────────────────────────────────────┘
Version x.y+6d5921b is already downloaded
Initializing Gel instance 'my_gel_app'...
Applying migrations...
Everything is up to date. Revision initial
Writing gel.local.toml for configuration
Project initialized.
To connect to my_gel_app, run `gel`
```

***

To check that the database is running, open a REPL and run a query.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
gel
my_gel_app:main> select 1 + 1;
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
{2}
```

Then run `\quit` to exit the REPL.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
my_gel_app:main> \quit
```

***

Next, define a schema. The `gel project init` command already created a `dbschema/default.gel` file to hold it.

```txt File Tree icon="folder-tree" theme={"theme":{"light":"github-light","dark":"dracula"}}
dbschema
├── default.gel
├── extensions.gel
├── futures.gel
└── migrations
```

***

Open that file and paste the following contents.

```ts default.gel icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
module default {
  type Movie {
    required title: str;
    releaseYear: int64;
  }
};
```

***

Then generate and apply an initial migration.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
gel migration create
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
Created dbschema/migrations/00001-m1uwekr.edgeql, id: m1uwekrn4ni4qs7ul7hfar4xemm5kkxlpswolcoyqj3xdhweomwjrq
```

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
gel migrate
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
Applying m1uwekrn4ni4qs7ul7hfar4xemm5kkxlpswolcoyqj3xdhweomwjrq (00001-m1uwekr.edgeql)
... parsed
... applied
```

***

With the schema applied, query the database with Gel's JavaScript client library. Install the client library and Gel's codegen CLI, then create a `seed.ts` file.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun add gel
bun add -D @gel/generate
touch seed.ts
```

***

Paste the following code into `seed.ts`.

The client auto-connects to the database. The script inserts a few movies with the `.execute()` method, using EdgeQL's `for` expression to turn the bulk insert into a single query.

```ts seed.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { createClient } from "gel";

const client = createClient();

const INSERT_MOVIE = `
  with movies := <array<tuple<title: str, year: int64>>>$movies
  for movie in array_unpack(movies) union (
    insert Movie {
      title := movie.title,
      releaseYear := movie.year,
    }
  )
`;

const movies = [
  { title: "The Matrix", year: 1999 },
  { title: "The Matrix Reloaded", year: 2003 },
  { title: "The Matrix Revolutions", year: 2003 },
];

await client.execute(INSERT_MOVIE, { movies });

console.log(`Seeding complete.`);
process.exit();
```

***

Then run this file with Bun.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun run seed.ts
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
Seeding complete.
```

***

Gel implements several code generation tools for TypeScript. To write typesafe queries against the seeded database, generate the EdgeQL query builder with `@gel/generate`.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bunx @gel/generate edgeql-js
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
Generating query builder...
Detected tsconfig.json, generating TypeScript files.
   To override this, use the --target flag.
   Run `npx @gel/generate --help` for full options.
Introspecting database schema...
Writing files to ./dbschema/edgeql-js
Generation complete! 🤘
Checking the generated query builder into version control
is not recommended. Would you like to update .gitignore to ignore
the query builder directory? The following line will be added:

   dbschema/edgeql-js

[y/n] (leave blank for "y")
> y
```

***

In `index.ts`, import the generated query builder from `./dbschema/edgeql-js` and write a select query.

```ts index.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { createClient } from "gel";
import e from "./dbschema/edgeql-js";

const client = createClient();

const query = e.select(e.Movie, () => ({
  title: true,
  releaseYear: true,
}));

const results = await query.run(client);
console.log(results);

results; // { title: string, releaseYear: number | null }[]
```

***

Run the file with Bun to see the movies you inserted.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun run index.ts
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
[
  {
    title: "The Matrix",
    releaseYear: 1999,
  }, {
    title: "The Matrix Reloaded",
    releaseYear: 2003,
  }, {
    title: "The Matrix Revolutions",
    releaseYear: 2003,
  }
]
```

***

See the [Gel docs](https://docs.geldata.com/).


# Build an HTTP server using Hono and Bun
Source: https://bun.com/docs/guides/ecosystem/hono



[Hono](https://github.com/honojs/hono) is a lightweight web framework designed for the edge.

```ts server.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { Hono } from "hono";
const app = new Hono();

app.get("/", c => c.text("Hono!"));

export default app;
```

***

Use `create-hono` to get started with one of Hono's project templates. Select `bun` when prompted for a template.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun create hono myapp
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
create-hono version 0.19.4
✔ Using target directory … myapp
✔ Which template do you want to use? bun
✔ Do you want to install project dependencies? Yes
✔ Which package manager do you want to use? bun
✔ Cloning the template
✔ Installing project dependencies
🎉 Copied project files
Get started with: cd myapp
```

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
cd myapp
bun install
```

***

Then start the dev server and visit [localhost:3000](http://localhost:3000).

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun run dev
```

***

Refer to Hono's [getting started with Bun](https://hono.dev/docs/getting-started/bun) guide.


# Read and write data to MongoDB using Mongoose and Bun
Source: https://bun.com/docs/guides/ecosystem/mongoose



MongoDB and Mongoose work with Bun with no extra configuration. This guide assumes you've already installed MongoDB and are running it as a background process or service on your development machine. See the [MongoDB installation guide](https://www.mongodb.com/docs/manual/installation/) for details.

***

Once MongoDB is running, create a directory and initialize it with `bun init`.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
mkdir mongoose-app
cd mongoose-app
bun init
```

***

Then add Mongoose as a dependency.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun add mongoose
```

***

In `schema.ts`, declare and export an `Animal` model.

```ts schema.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import * as mongoose from "mongoose";

const animalSchema = new mongoose.Schema(
  {
    name: { type: String, required: true },
    sound: { type: String, required: true },
  },
  {
    methods: {
      speak() {
        console.log(`${this.sound}!`);
      },
    },
  },
);

export type Animal = mongoose.InferSchemaType<typeof animalSchema>;
export const Animal = mongoose.model("Animal", animalSchema);
```

***

In `index.ts`, import `Animal`, connect to MongoDB, and add some data to the database.

```ts index.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import * as mongoose from "mongoose";
import { Animal } from "./schema";

// connect to database
await mongoose.connect("mongodb://127.0.0.1:27017/mongoose-app");

// create new Animal
const cow = new Animal({
  name: "Cow",
  sound: "Moo",
});
await cow.save(); // saves to the database

// read all Animals
const animals = await Animal.find();
animals[0]!.speak(); // logs "Moo!"

// disconnect
await mongoose.disconnect();
```

***

Run the file with `bun run`.

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun run index.ts
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
Moo!
```

***

As you build your application, refer to the official [MongoDB](https://www.mongodb.com/docs) and [Mongoose](https://mongoosejs.com/docs/) docs.


# Use Neon Postgres through Drizzle ORM
Source: https://bun.com/docs/guides/ecosystem/neon-drizzle



[Neon](https://neon.com/) is a fully managed serverless Postgres. Neon separates compute and storage to offer features like autoscaling, branching and bottomless storage. You can use Neon from Bun directly with the `@neondatabase/serverless` driver or through an ORM like Drizzle.

Drizzle ORM supports both a SQL-like "query builder" API and an ORM-like [Queries API](https://orm.drizzle.team/docs/rqb). Get started by creating a project directory, initializing it with `bun init`, and installing Drizzle and the [Neon serverless driver](https://github.com/neondatabase/serverless/).

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
mkdir bun-drizzle-neon
cd bun-drizzle-neon
bun init -y
bun add drizzle-orm @neondatabase/serverless
bun add -D drizzle-kit
```

***

Create a `.env.local` file and add your [Neon Postgres connection string](https://neon.com/docs/connect/connect-from-any-app) to it.

```ini .env.local icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
DATABASE_URL=postgresql://username:password@ep-adj-noun-guid.us-east-1.aws.neon.tech/neondb?sslmode=require
```

***

In `db.ts`, connect to the Neon database with the Neon serverless driver, wrapped in a Drizzle database instance.

```ts db.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { neon } from "@neondatabase/serverless";
import { drizzle } from "drizzle-orm/neon-http";

// Bun automatically loads the DATABASE_URL from .env.local
// Refer to: https://bun.com/docs/runtime/environment-variables for more information
const sql = neon(process.env.DATABASE_URL!);

export const db = drizzle({ client: sql });
```

***

To see the database in action, add these lines to `index.ts`.

```ts index.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { db } from "./db";
import { sql } from "drizzle-orm";

const query = sql`select 'hello world' as text`;
const result = await db.execute(query);
console.log(result.rows);
```

***

Then run `index.ts` with Bun.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun run index.ts
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
[
  {
    text: "hello world",
  }
]
```

***

Define a schema for the database with Drizzle ORM primitives. Create a `schema.ts` file and add this code.

```ts schema.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { pgTable, integer, serial, text, timestamp } from "drizzle-orm/pg-core";

export const authors = pgTable("authors", {
  id: serial("id").primaryKey(),
  name: text("name").notNull(),
  bio: text("bio"),
  createdAt: timestamp("created_at").notNull().defaultNow(),
});
```

***

Then use the `drizzle-kit` CLI to generate an initial SQL migration.

```sh theme={"theme":{"light":"github-light","dark":"dracula"}}
bunx drizzle-kit generate --dialect postgresql --schema ./schema.ts --out ./drizzle
```

***

The command creates a `drizzle` directory containing a `.sql` migration file and a `meta` directory.

```txt File Tree icon="folder-tree" theme={"theme":{"light":"github-light","dark":"dracula"}}
drizzle
├── 0000_aspiring_post.sql
└── meta
    ├── 0000_snapshot.json
    └── _journal.json
```

***

Execute these migrations with a `migrate.ts` script. The script opens a new connection to the Neon database and executes all unexecuted migrations in the `drizzle` directory.

```ts migrate.ts theme={"theme":{"light":"github-light","dark":"dracula"}}
import { db } from "./db";
import { migrate } from "drizzle-orm/neon-http/migrator";

const main = async () => {
  try {
    await migrate(db, { migrationsFolder: "drizzle" });
    console.log("Migration completed");
  } catch (error) {
    console.error("Error during migration:", error);
    process.exit(1);
  }
};

main();
```

***

Run the script with `bun`.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun run migrate.ts
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
Migration completed
```

***

Now add some data to the database. Create a `seed.ts` file with the following contents.

```ts seed.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { db } from "./db";
import * as schema from "./schema";

async function seed() {
  await db.insert(schema.authors).values([
    {
      name: "J.R.R. Tolkien",
      bio: "The creator of Middle-earth and author of The Lord of the Rings.",
    },
    {
      name: "George R.R. Martin",
      bio: "The author of the epic fantasy series A Song of Ice and Fire.",
    },
    {
      name: "J.K. Rowling",
      bio: "The creator of the Harry Potter series.",
    },
  ]);
}

async function main() {
  try {
    await seed();
    console.log("Seeding completed");
  } catch (error) {
    console.error("Error during seeding:", error);
    process.exit(1);
  }
}

main();
```

***

Then run this file.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun run seed.ts
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
Seeding completed
```

***

The database now has a schema and sample data. Query it with Drizzle by replacing the contents of `index.ts` with the following.

```ts index.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import * as schema from "./schema";
import { db } from "./db";

const result = await db.select().from(schema.authors);
console.log(result);
```

***

Then run the file. It prints the three authors you inserted.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun run index.ts
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
[
  {
    id: 1,
    name: "J.R.R. Tolkien",
    bio: "The creator of Middle-earth and author of The Lord of the Rings.",
    createdAt: 2024-05-11T10:28:46.029Z,
  }, {
    id: 2,
    name: "George R.R. Martin",
    bio: "The author of the epic fantasy series A Song of Ice and Fire.",
    createdAt: 2024-05-11T10:28:46.029Z,
  }, {
    id: 3,
    name: "J.K. Rowling",
    bio: "The creator of the Harry Potter series.",
    createdAt: 2024-05-11T10:28:46.029Z,
  }
]
```

***

This example used the Neon serverless driver's SQL-over-HTTP functionality. Neon's serverless driver also exposes `Client` and `Pool` constructors to enable sessions, interactive transactions, and node-postgres compatibility. Refer to [Neon's documentation](https://neon.com/docs/serverless/serverless-driver) for a complete overview.

Refer to the [Drizzle website](https://orm.drizzle.team/docs/overview) for complete documentation.


# Use Neon's Serverless Postgres with Bun
Source: https://bun.com/docs/guides/ecosystem/neon-serverless-postgres



[Neon](https://neon.com/) is a fully managed serverless Postgres. Neon separates compute and storage to offer features such as autoscaling, branching, and bottomless storage.

***

Get started by creating a project directory, initializing the directory using `bun init`, and adding the [Neon serverless driver](https://github.com/neondatabase/serverless/) as a project dependency.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
mkdir bun-neon-postgres
cd bun-neon-postgres
bun init -y
bun add @neondatabase/serverless
```

***

Create a `.env.local` file and add your [Neon Postgres connection string](https://neon.com/docs/connect/connect-from-any-app) to it.

```ini .env.local icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
DATABASE_URL=postgresql://username:password@ep-adj-noun-guid.us-east-1.aws.neon.tech/neondb?sslmode=require
```

***

Paste the following code into your project's `index.ts` file.

```ts index.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { neon } from "@neondatabase/serverless";

// Bun automatically loads the DATABASE_URL from .env.local
// Refer to: https://bun.com/docs/runtime/environment-variables for more information
const sql = neon(process.env.DATABASE_URL!);

const rows = await sql`SELECT version()`;

console.log(rows[0].version);
```

***

Start the program with `bun ./index.ts`. It prints the Postgres version to the console.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun ./index.ts
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
PostgreSQL 16.2 on x86_64-pc-linux-gnu, compiled by gcc (Debian 10.2.1-6) 10.2.1 20210110, 64-bit
```

***

This example used the Neon serverless driver's SQL-over-HTTP functionality. Neon's serverless driver also exposes `Client` and `Pool` constructors to enable sessions, interactive transactions, and node-postgres compatibility.

Refer to [Neon's documentation](https://neon.com/docs/serverless/serverless-driver) for a complete overview of the serverless driver.


# Build an app with Next.js and Bun
Source: https://bun.com/docs/guides/ecosystem/nextjs



[Next.js](https://nextjs.org/) is a React framework for building full-stack web applications. It supports server-side rendering, static site generation, and API routes. Bun installs packages fast and can run Next.js development and production servers.

***

<Steps>
  <Step title="Create a new Next.js app">
    Use the interactive CLI to scaffold a new Next.js project and install its dependencies.

    ```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
    bun create next-app@latest my-bun-app
    ```
  </Step>

  <Step title="Start the dev server">
    Change to the project directory and run the dev server with Bun.

    ```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
    cd my-bun-app
    bun --bun run dev
    ```

    This starts the Next.js dev server with Bun's runtime.

    Open [`http://localhost:3000`](http://localhost:3000) in your browser to see the result. Changes you make to `app/page.tsx` are hot-reloaded in the browser.
  </Step>

  <Step title="Update scripts in package.json">
    Prefix the Next.js CLI commands in your `package.json` scripts with `bun --bun` so that Bun executes the Next.js CLI for `dev`, `build`, and `start`.

    ```json package.json icon="file-json" theme={"theme":{"light":"github-light","dark":"dracula"}}
    {
      "scripts": {
        "dev": "bun --bun next dev", // [!code ++]
        "build": "bun --bun next build", // [!code ++]
        "start": "bun --bun next start" // [!code ++]
      }
    }
    ```
  </Step>
</Steps>

***

## Hosting

<Columns>
  <Card title="Vercel" href="/guides/deployment/vercel" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/ecosystem/vercel.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=165bc9841eef2a62f3951be494dbc89a">
    Deploy on Vercel
  </Card>

  <Card title="Railway" href="/guides/deployment/railway" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/ecosystem/railway.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=029a2b4b7c3a9f8dfab8d1bebbbfa054">
    Deploy on Railway
  </Card>

  <Card title="DigitalOcean" href="/guides/deployment/digital-ocean" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/ecosystem/digitalocean.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=aead95e8fef32d0ec83e63292f21f80c">
    Deploy on DigitalOcean
  </Card>

  <Card title="AWS Lambda" href="/guides/deployment/aws-lambda" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/ecosystem/aws.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=f747e7b59435e87b2e379be04eabda8f">
    Deploy on AWS Lambda
  </Card>

  <Card title="Google Cloud Run" href="/guides/deployment/google-cloud-run" icon="https://mintcdn.com/bun-1dd33a4e/cfVIaCNGtFU88Wgc/icons/ecosystem/gcp.svg?fit=max&auto=format&n=cfVIaCNGtFU88Wgc&q=85&s=a99e6cb0cfadfeb9ea3b6451de38cfd6">
    Deploy on Google Cloud Run
  </Card>

  <Card title="Render" href="/guides/deployment/render" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/ecosystem/render.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=b632a0b982a579fa5a2d0b96f33bace8">
    Deploy on Render
  </Card>
</Columns>

***

## Templates

<Columns>
  <Card title="Bun + Next.js Basic Starter" href="https://github.com/bun-templates/bun-nextjs-basic">
    A basic App Router starter with Bun, Next.js, and Tailwind CSS.
  </Card>

  <Card title="Todo App with Next.js + Bun" href="https://github.com/bun-templates/bun-nextjs-todo">
    A full-stack todo application built with Bun, Next.js, and PostgreSQL.
  </Card>
</Columns>

***

Refer to the [Next.js documentation](https://nextjs.org/docs) for more on building and deploying Next.js applications.


# Build an app with Nuxt and Bun
Source: https://bun.com/docs/guides/ecosystem/nuxt



Bun supports [Nuxt](https://nuxt.com) with no extra configuration. Initialize a Nuxt app with the official `create-nuxt` CLI.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun create nuxt@latest my-nuxt-app
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
┌  Welcome to Nuxt!
│
◇  Templates loaded
│
◇  Which template would you like to use?
│  minimal – Minimal starter with a single app.vue.
│
◇  Creating project in my-nuxt-app
│
◇  Downloaded minimal template
│
◇  Which package manager would you like to use?
│  bun
│
◇  Initialize git repository?
│  Yes
│
◇  Dependencies installed
│
◇  Git repository initialized
│
◇  Would you like to browse and install modules?
│  No
│
└  ✨ Nuxt project has been created with the minimal template.

╭── 👉 Next steps ─────╮
│                      │
│   › cd my-nuxt-app   │
│   › bun run dev      │
│                      │
╰──────────────────────╯
```

***

To start the dev server, run `bun --bun run dev` from the project root. This executes the `nuxt dev` command defined in the `"dev"` script in `package.json`.

<Note>
  The `nuxt` CLI uses Node.js by default; passing the `--bun` flag forces the dev server to use the Bun runtime instead.
</Note>

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
cd my-nuxt-app
bun --bun run dev
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
$ nuxt dev
│
●  Nuxt 4.5.2 (with Nitro 2.13.4, Vite 8.2.1 and Vue 3.5.41)

  ➜ Local:    http://localhost:3000/
  ➜ Network:  use --host to expose

  ➜ DevTools: press Shift + Alt + D in the browser (v3.4.1)

✔ Vite client built in 95ms
✔ Vite server built in 33ms
[nitro] ✔ Nuxt Nitro server built in 948ms
ℹ Vite server warmed up in 3ms
ℹ Vite client warmed up in 9ms
```

***

Once the dev server starts, open [http://localhost:3000](http://localhost:3000) to see the app. It renders Nuxt's built-in `NuxtWelcome` template component.

To start developing your app, replace `<NuxtWelcome />` in `app/app.vue` with your own UI.

<Frame>
  ![Demo Nuxt app running on
  localhost](https://github.com/oven-sh/bun/assets/3084745/2c683ecc-3298-4bb0-b8c0-cf4cfaea1daa)
</Frame>

***

For production builds, the default preset is compatible with Bun, but the [Bun preset](https://nitro.build/deploy/runtimes/bun) generates better optimized builds.

```ts nuxt.config.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
export default defineNuxtConfig({
  nitro: {
    preset: "bun", // [!code ++]
  },
});
```

Alternatively, set the preset with an environment variable:

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
NITRO_PRESET=bun bun run build
```

<Note>
  Some packages provide Bun-specific exports that Nitro does not bundle correctly with the default preset. Use the Bun
  preset so those packages work in production builds.
</Note>

After building, start the server:

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun run ./.output/server/index.mjs
```

***

Refer to the [Nuxt website](https://nuxt.com/docs) for complete documentation.


# Run Bun as a daemon with PM2
Source: https://bun.com/docs/guides/ecosystem/pm2



[PM2](https://pm2.keymetrics.io/) is a process manager that runs your applications as daemons (background processes).

PM2 offers process monitoring, automatic restarts, and scaling. It keeps your application running when you deploy it to a cloud-hosted virtual private server (VPS).

***

You can use PM2 with Bun in two ways: as a CLI option or in a configuration file.

### With `--interpreter`

To start your application with PM2 and Bun as the interpreter, run:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
pm2 start --interpreter ~/.bun/bin/bun index.ts
```

***

### With a configuration file

Alternatively, create a file named `pm2.config.cjs` in your project directory and add the following content.

```js pm2.config.cjs icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
module.exports = {
  name: "app", // Name of your application
  script: "index.ts", // Entry point of your application
  interpreter: "bun", // Bun interpreter
  env: {
    PATH: `${process.env.HOME}/.bun/bin:${process.env.PATH}`, // Add "~/.bun/bin/bun" to PATH
  },
};
```

***

After saving the file, start your application with PM2.

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
pm2 start pm2.config.cjs
```

***

Your JavaScript/TypeScript web server now runs as a daemon with PM2, using Bun as the interpreter.


# Use Prisma with Bun
Source: https://bun.com/docs/guides/ecosystem/prisma



<Note>
  Prisma's dynamically loaded subcommands, such as `prisma dev`, require npm to be installed alongside Bun. The commands
  used in this guide (`prisma init`, `prisma migrate`, and `prisma generate`) are built into the CLI. Generated code
  works with Bun using the `prisma-client` generator.
</Note>

<Steps>
  <Step title="Create a new project">
    Create a directory and initialize it with `bun init`.

    ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
    mkdir prisma-app
    cd prisma-app
    bun init
    ```
  </Step>

  <Step title="Install Prisma dependencies">
    Then install the Prisma CLI (`prisma`), Prisma Client (`@prisma/client`), and the LibSQL adapter as dependencies.

    ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
    bun add -d prisma
    bun add @prisma/client @prisma/adapter-libsql
    ```
  </Step>

  <Step title="Initialize Prisma with SQLite">
    Use the Prisma CLI with `bunx` to initialize the schema and migration directory. This guide uses a local SQLite database file. `prisma init` writes its connection string, `DATABASE_URL="file:./dev.db"`, to `.env`.

    ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
    bunx --bun prisma init --datasource-provider sqlite
    ```

    This creates a basic schema. Open `prisma/schema.prisma`, update the generator block to use the Rust-free client with the `bun` runtime, and add a `User` model.

    ```prisma prisma/schema.prisma icon="https://mintcdn.com/bun-1dd33a4e/nIz6GtMH5K-dfXeV/icons/ecosystem/prisma.svg?fit=max&auto=format&n=nIz6GtMH5K-dfXeV&q=85&s=c37203455320f85a20a7b29ce374661c" theme={"theme":{"light":"github-light","dark":"dracula"}}
      generator client {
        provider = "prisma-client"
        output = "../generated/prisma" // [!code --]
        output = "./generated" // [!code ++]
        engineType = "client" // [!code ++]
        runtime = "bun" // [!code ++]
      }

      datasource db {
        provider = "sqlite"
      }

      model User { // [!code ++]
        id    Int     @id @default(autoincrement()) // [!code ++]
        email String  @unique // [!code ++]
        name  String? // [!code ++]
      } // [!code ++]
    ```
  </Step>

  <Step title="Create and run database migration">
    Generate and run the initial migration. The command writes a `.sql` migration file to `prisma/migrations`, creates a new SQLite database, and runs the migration against it. Bun [does not load `.env` automatically](/docs/runtime/environment-variables) when it runs a CLI with `--bun`. The `prisma.config.ts` generated by `prisma init` reads `DATABASE_URL` from the environment, so pass `--env-file=.env` explicitly.

    ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
     bun run --bun --env-file=.env prisma migrate dev --name init
    ```

    ```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
    Loaded Prisma config from prisma.config.ts.

    Prisma schema loaded from prisma/schema.prisma.
    Datasource "db": SQLite database "dev.db" at "file:./dev.db"

    SQLite database dev.db created at file:./dev.db

    Applying migration `20251014141233_init`

    The following migration(s) have been created and applied from new schema changes:

    prisma/migrations/
     └─ 20251014141233_init/
       └─ migration.sql

    Your database is now in sync with your schema.
    ```
  </Step>

  <Step title="Generate Prisma Client">
    `prisma migrate dev` does not generate the *Prisma client*, so generate it with the Prisma CLI. The client provides a fully typed API for reading and writing to your database.

    ```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
    bun run --bun --env-file=.env prisma generate
    ```
  </Step>

  <Step title="Initialize Prisma Client with LibSQL">
    Create a new file `prisma/db.ts` that initializes the PrismaClient with the LibSQL adapter.

    ```ts prisma/db.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
    import { PrismaClient } from "./generated/client";
    import { PrismaLibSql } from "@prisma/adapter-libsql";

    const adapter = new PrismaLibSql({ url: process.env.DATABASE_URL || "" });
    export const prisma = new PrismaClient({ adapter });
    ```
  </Step>

  <Step title="Create a test script">
    Write a script that creates a new user, then counts the users in the database.

    ```ts index.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
    import { prisma } from "./prisma/db";

    // create a new user
    await prisma.user.create({
      data: {
        name: "John Dough",
        email: `john-${Math.random()}@example.com`,
      },
    });

    // count the number of users
    const count = await prisma.user.count();
    console.log(`There are ${count} users in the database.`);
    ```
  </Step>

  <Step title="Run and test the application">
    Run the script with `bun run`. Each run creates a new user.

    ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
    bun run index.ts
    ```

    ```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
    There are 1 users in the database.
    ```

    ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
    bun run index.ts
    ```

    ```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
    There are 2 users in the database.
    ```

    ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
    bun run index.ts
    ```

    ```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
    There are 3 users in the database.
    ```
  </Step>
</Steps>

***

Prisma is now set up with Bun. See the [Prisma docs](https://www.prisma.io/docs/orm/prisma-client) as you build out your application.


# Use Prisma Postgres with Bun
Source: https://bun.com/docs/guides/ecosystem/prisma-postgres



<Steps>
  <Step title="Create a new project">
    First, create a directory and initialize it with `bun init`.

    ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
    mkdir prisma-postgres-app
    cd prisma-postgres-app
    bun init
    ```
  </Step>

  <Step title="Install Prisma dependencies">
    Then install the Prisma CLI (`prisma`), Prisma Client (`@prisma/client`), and the Postgres driver adapter (`@prisma/adapter-pg`) as dependencies.

    ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
    bun add -d prisma
    bun add @prisma/client @prisma/adapter-pg
    ```
  </Step>

  <Step title="Initialize Prisma with PostgreSQL">
    Use the Prisma CLI with `bunx` to initialize the schema and migration directory, with PostgreSQL as the database.

    ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
    bunx --bun prisma init --db
    ```

    This creates a basic schema. Update it to use the Rust-free client optimized for Bun: open `prisma/schema.prisma` and modify the generator block, then add a `User` model.

    ```prisma prisma/schema.prisma icon="https://mintcdn.com/bun-1dd33a4e/nIz6GtMH5K-dfXeV/icons/ecosystem/prisma.svg?fit=max&auto=format&n=nIz6GtMH5K-dfXeV&q=85&s=c37203455320f85a20a7b29ce374661c" theme={"theme":{"light":"github-light","dark":"dracula"}}
    generator client {
    	provider = "prisma-client"
    	output = "../generated/prisma" // [!code --]
    	output = "./generated" // [!code ++]
    	engineType = "client" // [!code ++]
    	runtime = "bun" // [!code ++]
    }

    datasource db {
    	provider = "postgresql"
    }

    model User { // [!code ++]
    	id    Int     @id @default(autoincrement()) // [!code ++]
    	email String  @unique // [!code ++]
    	name  String? // [!code ++]
    } // [!code ++]
    ```
  </Step>

  <Step title="Configure database connection">
    Set up your Postgres database URL in the `.env` file.

    ```ini .env icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
    DATABASE_URL="postgresql://username:password@localhost:5432/mydb?schema=public"
    ```
  </Step>

  <Step title="Create and run database migration">
    Then generate and run the initial migration.

    The command writes a `.sql` migration file to `prisma/migrations` and executes it against your Postgres database. Bun [does not load `.env` automatically](/docs/runtime/environment-variables) when it runs a CLI with `--bun`. The `prisma.config.ts` generated by `prisma init` reads `DATABASE_URL` from the environment, so pass `--env-file=.env` explicitly.

    ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
    bun run --bun --env-file=.env prisma migrate dev --name init
    ```

    ```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
    Loaded Prisma config from prisma.config.ts.

    Prisma schema loaded from prisma/schema.prisma.
    Datasource "db": PostgreSQL database "mydb", schema "public" at "localhost:5432"

    Applying migration `20250114141233_init`

    The following migration(s) have been created and applied from new schema changes:

    prisma/migrations/
      └─ 20250114141233_init/
        └─ migration.sql

    Your database is now in sync with your schema.
    ```
  </Step>

  <Step title="Generate Prisma Client">
    `prisma migrate dev` does not generate the *Prisma client*, so generate it with the Prisma CLI. The client provides a fully typed API for reading and writing to the database.

    ```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
    bun run --bun --env-file=.env prisma generate
    ```
  </Step>

  <Step title="Initialize Prisma Client with the Postgres adapter">
    Create a new file `prisma/db.ts` that initializes the PrismaClient with the Postgres adapter.

    ```ts prisma/db.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
    import { PrismaClient } from "./generated/client";
    import { PrismaPg } from "@prisma/adapter-pg";

    const adapter = new PrismaPg({ connectionString: process.env.DATABASE_URL! });
    export const prisma = new PrismaClient({ adapter });
    ```
  </Step>

  <Step title="Create a test script">
    Write a script that creates a new user, then counts the users in the database.

    ```ts index.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
    import { prisma } from "./prisma/db";

    // create a new user
    await prisma.user.create({
      data: {
        name: "John Dough",
        email: `john-${Math.random()}@example.com`,
      },
    });

    // count the number of users
    const count = await prisma.user.count();
    console.log(`There are ${count} users in the database.`);
    ```
  </Step>

  <Step title="Run and test the application">
    Run the script with `bun run`. Each run creates a new user.

    ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
    bun run index.ts
    ```

    ```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
    There are 1 users in the database.
    ```

    ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
    bun run index.ts
    ```

    ```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
    There are 2 users in the database.
    ```

    ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
    bun run index.ts
    ```

    ```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
    There are 3 users in the database.
    ```
  </Step>
</Steps>

***

Prisma Postgres is now set up with Bun. Refer to the [official Prisma Postgres docs](https://www.prisma.io/docs/postgres) as you continue to develop your application.


# Build an app with Qwik and Bun
Source: https://bun.com/docs/guides/ecosystem/qwik



Initialize a new Qwik app with `bunx create-qwik`.

The `create-qwik` package detects when you are using `bunx` and installs dependencies with `bun`.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun create qwik
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
      ............
    .::: :--------:.
   .::::  .:-------:.
  .:::::.   .:-------.
  ::::::.     .:------.
 ::::::.        :-----:
 ::::::.       .:-----.
  :::::::.     .-----.
   ::::::::..   ---:.
    .:::::::::. :-:.
     ..::::::::::::
             ...::::


┌  Let's create a  Qwik App  ✨ (v1.20.0)
│
◇  Where would you like to create your new project? (Use '.' or './' for current directory)
│  ./my-app
│
●  Creating new project in  /path/to/my-app  ... 🐇
│
◇  Select a starter
│  Playground App (Qwik City + Qwik)
│
◇  Would you like to install bun dependencies?
│  Yes
│
◇  Initialize a new git repository?
│  No
│
◇  Finishing the install. Wanna hear a joke?
│  Yes
│
○  ────────────────────────────────────────────────────────╮
│                                                          │
│  How do you know if there’s an elephant under your bed?  │
│  Your head hits the ceiling!                             │
│                                                          │
├──────────────────────────────────────────────────────────╯
│
◇  App Created 🐰
│
◇  Installed bun dependencies 📋
│
○  Result ─────────────────────────────────────────────╮
│                                                      │
│  Success!  Project created in my-app directory       │
│                                                      │
│  Integrations? Add Netlify, Cloudflare, Tailwind...  │
│  bun qwik add                                        │
│                                                      │
│  Relevant docs:                                      │
│  https://qwik.dev/docs/getting-started/              │
│                                                      │
│  Questions? Start the conversation at:               │
│  https://qwik.dev/chat                               │
│  https://twitter.com/QwikDev                         │
│                                                      │
│  Presentations, Podcasts and Videos:                 │
│  https://qwik.dev/media/                             │
│                                                      │
│  Next steps:                                         │
│  cd my-app                                           │
│  bun start                                           │
│                                                      │
│                                                      │
├──────────────────────────────────────────────────────╯
│
└  Happy coding! 🎉

```

***

Run `bun run dev` to start the development server.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun run dev
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
$ vite --mode ssr

VITE v7.3.1   ssr   ready in 433 ms

➜  Local:   http://localhost:5173/
➜  Network: use --host to expose
➜  press h + enter to show help
```

***

Open [http://localhost:5173](http://localhost:5173) in your browser to see the result. Qwik hot-reloads your app as you edit your source files.

<Frame>![Qwik screenshot](https://github.com/oven-sh/bun/assets/3084745/ec35f2f7-03dd-4c90-851e-fb4ad150bb28)</Frame>

***

See the [Qwik docs](https://qwik.dev/docs/getting-started/) to learn more.


# Build a React app with Bun
Source: https://bun.com/docs/guides/ecosystem/react



Bun has built-in support for `.jsx` and `.tsx` files. React works with Bun.

Create a new React app with `bun init --react`. This gives you a template with a React app and an API server together in one full-stack app.

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
# Create a new React app
bun init --react

# Run the app in development mode
bun dev

# Build as a static site for production
bun run build

# Run the server in production
bun start
```

***

### Hot Reloading

Run `bun dev` to start the app in development mode. This starts the API server and the React app with hot reloading.

### Full-Stack App

Run `bun start` to start the API server and frontend together in one process.

### Static Site

Run `bun run build` to build the app as a static site. This creates a `dist` directory with the built app and its assets.

```txt File Tree icon="folder-tree" theme={"theme":{"light":"github-light","dark":"dracula"}}
├── src/
│   ├── index.ts        # Server entry point with API routes
│   ├── frontend.tsx    # React app entry point with HMR
│   ├── App.tsx         # Main React component
│   ├── APITester.tsx   # Component for testing API endpoints
│   ├── index.html      # HTML template
│   ├── index.css       # Styles
│   └── *.svg           # Static assets
├── package.json        # Dependencies and scripts
├── tsconfig.json       # TypeScript configuration
├── bun-env.d.ts        # Type declarations for .svg and .css imports
├── bunfig.toml         # Bun configuration
└── bun.lock            # Lock file
```


# Build an app with Remix and Bun
Source: https://bun.com/docs/guides/ecosystem/remix



[Remix 3](https://github.com/remix-run/remix) is a web framework built from standalone packages: a router, HTML rendering, sessions, form parsing, and more. These packages are distributed together as the `remix` package. Remix 3 is published under the `next` tag on npm while it is in beta. Its server runs on Bun as-is.

<Note>This guide covers Remix 3. To create a Remix 2 project, use `create-remix` instead.</Note>

***

Scaffold a new project with the Remix CLI.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bunx remix@next new my-remix-app
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
• Prepare target directory...
✓ Prepare target directory
• Generate scaffold files...
✓ Generate scaffold files
• Finalize package.json...
✓ Finalize package.json

Created My Remix App at my-remix-app
```

Then install its dependencies.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
cd my-remix-app
bun install
```

***

The generated `server.ts` creates a `node:http` server that hands every request to the app's router. Bun runs it directly; pass `--watch` to restart the server whenever a file it imports changes.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun --watch server.ts
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
Server listening on http://localhost:44100
```

Open [http://localhost:44100](http://localhost:44100) to see the starter page. The routes live in `app/routes.ts` and `app/router.ts`. `app/actions/home-page.tsx` renders the starter home page.

***

The `scripts` generated in `package.json` run the server with Node.js and the `remix/node-tsx` TypeScript loader. Bun runs TypeScript itself, so point the scripts at `bun` instead.

```json package.json icon="file-json" theme={"theme":{"light":"github-light","dark":"dracula"}}
{
  "scripts": {
    "dev": "NODE_ENV=development node --watch --import remix/node-tsx server.ts", // [!code --]
    "dev": "bun --watch server.ts", // [!code ++]
    "start": "NODE_ENV=production node --import remix/node-tsx server.ts", // [!code --]
    "start": "NODE_ENV=production bun server.ts" // [!code ++]
  }
}
```

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun run dev
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
$ bun --watch server.ts
Server listening on http://localhost:44100
```

<Note>
  The generated `hmr` script (`hmr.ts`) also loads `remix/node-tsx`, which relies on `module.registerHooks()`. Bun does
  not implement that API yet, so the script fails under Bun. See [Node.js
  compatibility](/docs/runtime/nodejs-compat#node-module). `bun --watch` restarts the server on changes instead.
</Note>

***

The router is a `fetch` handler, so you can also serve the app with [`Bun.serve()`](/docs/runtime/http/server) instead of `node:http`.

```ts server.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { router } from "./app/router.ts";

const server = Bun.serve({
  port: 44100,
  fetch: request => router.fetch(request),
});

console.log(`Server listening on ${server.url}`);
```

***

See the Remix [documentation](https://github.com/remix-run/remix/tree/main/docs) to learn more.


# Add Sentry to a Bun app
Source: https://bun.com/docs/guides/ecosystem/sentry



[Sentry](https://sentry.io) is an error tracking and performance monitoring platform. Its Bun SDK, `@sentry/bun`, instruments your application to automatically collect error and performance data.

If you don't have a Sentry account and project yet, create one at [sentry.io](https://sentry.io/signup/), then return to this page.

***

First, install the Sentry Bun SDK.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun add @sentry/bun
```

***

Then initialize the SDK with your Sentry DSN in its own file. You can find your DSN in your Sentry project settings.

```ts sentry.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import * as Sentry from "@sentry/bun";

// Ensure to call this before importing any other modules!
Sentry.init({
  dsn: "__SENTRY_DSN__",

  // Add Performance Monitoring by setting tracesSampleRate
  // We recommend adjusting this value in production
  tracesSampleRate: 1.0,
});
```

***

Start your app with [`--preload`](/docs/runtime) so this file runs before any of your app's modules. Bun evaluates a file's `import`s before its own code, so calling `Sentry.init()` at the top of your entry file would still run after everything that file imports.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun --preload ./sentry.ts index.ts
```

***

Verify that Sentry is working by capturing a test error:

```ts sentry.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
setTimeout(() => {
  try {
    foo();
  } catch (e) {
    Sentry.captureException(e);
  }
}, 99);
```

To view and resolve the recorded error, log into [sentry.io](https://sentry.io/) and open your project. Clicking the error's title opens a page with details, where you can mark it as resolved.

***

To learn more about the Sentry Bun SDK, see the [Sentry documentation](https://docs.sentry.io/platforms/javascript/guides/bun).


# Build an app with SolidStart and Bun
Source: https://bun.com/docs/guides/ecosystem/solidstart



Initialize a SolidStart app with `create-solid`. Pass the `--solidstart` flag to create a SolidStart project and `--ts` for TypeScript support. When prompted for a SolidStart version, select `2 (Stable)`. When prompted for a template, select `basic` for a minimal starter app.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun create solid my-app --solidstart --ts
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
┌
 Create-Solid v0.9.0
│
◇  Which version of SolidStart?
│  2 (Stable)
│
◇  Which template would you like to use?
│  basic
│
◇  Project created 🎉
│
◇  To get started, run: ─╮
│                        │
│  cd my-app             │
│  bun install           │
│  bun dev               │
│                        │
├────────────────────────╯
```

***

Install the dependencies.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
cd my-app
bun install
```

Then run the development server with `bun dev`.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun dev
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
$ vite dev

  VITE v8.1.4  ready in 818 ms

  ➜  Local:   http://localhost:3000/
  ➜  Network: use --host to expose
```

Open [localhost:3000](http://localhost:3000). The development server automatically hot-reloads changes you make to `src/routes/index.tsx`.

***

See the [SolidStart docs](https://docs.solidjs.com/solid-start) to learn more.


# Server-side render (SSR) a React component
Source: https://bun.com/docs/guides/ecosystem/ssr-react



Install `react` and `react-dom`:

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
# Any package manager can be used
bun add react react-dom
```

***

To render a React component to an HTML stream server-side (SSR):

```tsx ssr-react.tsx icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { renderToReadableStream } from "react-dom/server";

function Component(props: { message: string }) {
  return (
    <body>
      <h1>{props.message}</h1>
    </body>
  );
}

const stream = await renderToReadableStream(<Component message="Hello from server!" />);
```

***

Combine this with `Bun.serve()` to get an SSR HTTP server:

```tsx server.tsx icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
Bun.serve({
  async fetch() {
    const stream = await renderToReadableStream(<Component message="Hello from server!" />);
    return new Response(stream, {
      headers: { "Content-Type": "text/html" },
    });
  },
});
```

***

React `19` and later include an [SSR optimization](https://github.com/react/react/pull/25597) that takes advantage of Bun's "direct" `ReadableStream` implementation. If you run into an error like `export named 'renderToReadableStream' not found`, install version `19` of `react` and `react-dom`, or import from `react-dom/server.browser` instead of `react-dom/server`. See [react/react#28941](https://github.com/react/react/issues/28941) for details.


# Build an app with SvelteKit and Bun
Source: https://bun.com/docs/guides/ecosystem/sveltekit



Use `sv create my-app` to create a SvelteKit project with the Svelte CLI. Answer the prompts to select a template and set up your development environment.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bunx sv create my-app
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
┌  Welcome to the Svelte CLI! (v0.5.7)
│
◇  Which template would you like?
│  SvelteKit demo
│
◇  Add type checking with Typescript?
│  Yes, using Typescript syntax
│
◆  Project created
│
◇  What would you like to add to your project?
│  none
│
◇  Which package manager do you want to install dependencies with?
│  bun
│
◇  Successfully installed dependencies
│
◇  Project next steps ─────────────────────────────────────────────────────╮
│                                                                          │
│  1: cd my-app                                                            │
│  2: git init && git add -A && git commit -m "Initial commit" (optional)  │
│  3: bun run dev -- --open                                                │
│                                                                          │
│  To close the dev server, hit Ctrl-C                                     │
│                                                                          │
│  Stuck? Visit us at https://svelte.dev/chat                              │
│                                                                          │
├──────────────────────────────────────────────────────────────────────────╯
│
└  You're all set!
```

***

Once the project is initialized, `cd` into the new project. The dependencies are already installed, so you don't need to run `bun install`.

Then start the development server with `bun --bun run dev`.

To run the dev server with Node.js instead of Bun, omit the `--bun` flag.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
cd my-app
bun --bun run dev
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
  $ vite dev
  Forced re-optimization of dependencies

    VITE v5.4.10  ready in 424 ms

    ➜  Local:   http://localhost:5173/
    ➜  Network: use --host to expose
    ➜  press h + enter to show help
```

***

Visit [http://localhost:5173](http://localhost:5173/) in a browser to see the template app.

<Frame>
  ![SvelteKit app running](https://github.com/oven-sh/bun/assets/3084745/7c76eae8-78f9-44fa-9f15-1bd3ca1a47c0)
</Frame>

***

Edit and save `src/routes/+page.svelte` and the dev server hot-reloads your changes in the browser.

***

To build for production, you need a SvelteKit adapter. We recommend `svelte-adapter-bun`; install it with `bun add -D svelte-adapter-bun`.

Then make the following changes to your `vite.config.ts` (or `vite.config.js`). If your project configures SvelteKit in a `svelte.config.js` instead, swap the adapter import there.

```ts vite.config.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import adapter from "@sveltejs/adapter-auto"; // [!code --]
import adapter from "svelte-adapter-bun"; // [!code ++]
import { sveltekit } from "@sveltejs/kit/vite";
import { defineConfig } from "vite";

export default defineConfig({
  plugins: [
    sveltekit({
      compilerOptions: {
        // Force runes mode for the project, except for libraries. Can be removed in svelte 6.
        runes: ({ filename }) => (filename.split(/[/\\]/).includes("node_modules") ? undefined : true),
      },

      // adapter-auto only supports some environments, see https://svelte.dev/docs/kit/adapter-auto for a list.
      // If your environment is not supported, or you settled on a specific environment, switch out the adapter.
      // See https://svelte.dev/docs/kit/adapters for more information about adapters.
      adapter: adapter(),
    }),
  ],
});
```

***

To build a production bundle:

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun --bun run build
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
  $ vite build
  vite v5.4.10 building SSR bundle for production...
  "confetti" is imported from external module "@neoconfetti/svelte" but never used in "src/routes/sverdle/+page.svelte".
  ✓ 130 modules transformed.
  vite v5.4.10 building for production...
  ✓ 148 modules transformed.
  ...
  ✓ built in 231ms
  ...
  ✓ built in 899ms

  Run npm run preview to preview your production build locally.

  > Using svelte-adapter-bun
    ✔ done
```

***

Then start the production server with `bun ./build/index.js`. It listens on port `3000` by default; set the `PORT` environment variable to change it.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun ./build/index.js
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
Listening on http://0.0.0.0:3000/
```


# Run Bun as a daemon with systemd
Source: https://bun.com/docs/guides/ecosystem/systemd



[systemd](https://systemd.io) is an init system and service manager for Linux. It manages the startup and control of system processes and services.

***

To run a Bun application as a daemon with **systemd**, create a *service file* in `/etc/systemd/system/`.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
cd /etc/systemd/system
touch my-app.service
```

***

Here is a typical service file that runs an application on system start. Use it as a template for your own service. Replace `YOUR_USER` with the name of the user to run the application as. To run as `root`, replace `YOUR_USER` with `root` and `/home/YOUR_USER` with `/root` (root's home directory). For security reasons, we don't recommend running as `root`.

Refer to the [systemd documentation](https://www.freedesktop.org/software/systemd/man/systemd.service.html) for details on each setting.

```ini my-app.service icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
[Unit]
# describe the app
Description=My App
# start the app after the network management stack has started
# (this does not wait for the network to be up, see https://systemd.io/NETWORK_ONLINE)
After=network.target

[Service]
# usually you'll use 'simple'
# one of https://www.freedesktop.org/software/systemd/man/systemd.service.html#Type=
Type=simple
# which user to use when starting the app
User=YOUR_USER
# path to your application's root directory
WorkingDirectory=/home/YOUR_USER/path/to/my-app
# the command to start the app
# requires absolute paths
ExecStart=/home/YOUR_USER/.bun/bin/bun run index.ts
# restart policy
# one of {no|on-success|on-failure|on-abnormal|on-watchdog|on-abort|always}
Restart=always

[Install]
# start the app automatically
WantedBy=multi-user.target
```

***

If your application starts a webserver, non-`root` users cannot listen on ports 80 or 443 by default. To allow Bun to listen on these ports when run by a non-`root` user, use the following command. The command requires `sudo` permissions. This step isn't necessary when running as `root`.

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
setcap CAP_NET_BIND_SERVICE=+eip /home/YOUR_USER/.bun/bin/bun
```

The command attaches the capability to the `bun` binary itself. Replacing the binary, for example with `bun upgrade`, removes the capability. Re-run the command after upgrading. Alternatively, add `AmbientCapabilities=CAP_NET_BIND_SERVICE` to the `[Service]` section of the service file instead.

***

With the service file configured, *enable* the service. Once enabled, it starts automatically on reboot. Enabling the service requires `sudo` permissions.

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
systemctl enable my-app
```

***

To start the service without rebooting, *start* it manually.

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
systemctl start my-app
```

***

Check the status of your application with `systemctl status`. If the app started successfully, the output looks like this:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
systemctl status my-app
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
● my-app.service - My App
     Loaded: loaded (/etc/systemd/system/my-app.service; enabled; preset: enabled)
     Active: active (running) since Thu 2023-10-12 11:34:08 UTC; 1h 8min ago
   Main PID: 309641 (bun)
      Tasks: 3 (limit: 503)
     Memory: 40.9M
        CPU: 1.093s
     CGroup: /system.slice/my-app.service
             └─309641 /home/YOUR_USER/.bun/bin/bun run index.ts
```

***

To update the service, edit the service file, then reload the daemon.

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
systemctl daemon-reload
```

***

For a complete guide to service unit configuration, see the [systemd.service documentation](https://www.freedesktop.org/software/systemd/man/systemd.service.html). Or use this cheatsheet of common commands:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
systemctl daemon-reload # tell systemd that some files got changed
systemctl enable my-app # enable the app (to allow auto-start)
systemctl disable my-app # disable the app (turns off auto-start)
systemctl start my-app # start the app if is stopped
systemctl stop my-app # stop the app
systemctl restart my-app # restart the app
```


# Use TanStack Start with Bun
Source: https://bun.com/docs/guides/ecosystem/tanstack-start



[TanStack Start](https://tanstack.com/start/latest) is a full-stack framework powered by TanStack Router and [Vite](https://vite.dev/). It supports full-document SSR, streaming, server functions, and bundling.

***

<Steps>
  <Step title="Create a new TanStack Start app">
    Use the interactive CLI to create a new TanStack Start app.

    ```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
    bunx @tanstack/cli create my-tanstack-app
    ```
  </Step>

  <Step title="Start the dev server">
    Change to the project directory and start the Vite dev server with Bun.

    ```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
    cd my-tanstack-app
    bun --bun run dev
    ```
  </Step>

  <Step title="Update scripts in package.json">
    In the scripts field of your `package.json`, prefix the Vite CLI commands with `bun --bun` so that Bun runs the Vite CLI for `dev`, `build`, and `preview`.

    ```json package.json icon="file-json" theme={"theme":{"light":"github-light","dark":"dracula"}}
    {
      "scripts": {
        "dev": "bun --bun vite dev", // [!code ++]
        "build": "bun --bun vite build", // [!code ++]
        "preview": "bun --bun vite preview" // [!code ++]
      }
    }
    ```
  </Step>
</Steps>

***

## Hosting

To host your TanStack Start app in production, use [Nitro](https://nitro.build/) or a custom Bun server.

<Tabs>
  <Tab title="Nitro">
    <Steps>
      <Step title="Add Nitro to your project">
        Add [Nitro](https://nitro.build/) to your project to deploy your TanStack Start app to different platforms.

        ```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
        bun add nitro
        ```
      </Step>

      <Step title={<span>Update your <code>vite.config.ts</code> file</span>}>
        Add the Nitro plugin to your `vite.config.ts` file.

        ```ts vite.config.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
        // other imports...
        import { nitro } from "nitro/vite"; // [!code ++]

        const config = defineConfig({
          plugins: [
            tanstackStart(),
            nitro({ preset: "bun" }), // [!code ++]
            // other plugins...
          ],
        });

        export default config;
        ```

        <Note>
          The `bun` preset is optional, but it configures the build output specifically for Bun's runtime.
        </Note>
      </Step>

      <Step title="Update the start command">
        Make sure `build` and `start` scripts are present in your `package.json` file:

        ```json package.json icon="file-json" theme={"theme":{"light":"github-light","dark":"dracula"}}
          {
            "scripts": {
              "build": "bun --bun vite build", // [!code ++]
              // The .output files are created by Nitro when you run `bun run build`.
              // Not necessary when deploying to Vercel.
              "start": "bun run .output/server/index.mjs" // [!code ++]
            }
          }
        ```

        <Note>
          You do **not** need the custom `start` script when deploying to Vercel.
        </Note>
      </Step>

      <Step title="Deploy your app">
        Use one of the following guides to deploy your app to a hosting provider.

        <Note>
          When deploying to Vercel, either add `"bunVersion": "1.x"` to your `vercel.json` file, or set the Bun version in the `nitro` config in your `vite.config.ts` file:

          <Warning>
            Do **not** use the `bun` Nitro preset when deploying to Vercel.
          </Warning>

          ```ts vite.config.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
          export default defineConfig({
            plugins: [
              tanstackStart(),
              nitro({
                preset: "bun", // [!code --]
                vercel: { // [!code ++]
                  functions: { // [!code ++]
                    runtime: "bun1.x", // [!code ++]
                  }, // [!code ++]
              }, // [!code ++]
              }),
            ],
          });
          ```
        </Note>
      </Step>
    </Steps>
  </Tab>

  <Tab title="Custom Server">
    <Note>
      This custom server is based on [TanStack's Bun template](https://github.com/TanStack/router/blob/main/examples/react/start-bun/server.ts). It gives you fine-grained control over static asset serving: the server preloads small files into memory and serves larger files on-demand. You can configure the limits on what the server preloads.
    </Note>

    <Steps>
      <Step title="Create the production server">
        Create a `server.ts` file in your project root:

        ```ts server.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" expandable theme={"theme":{"light":"github-light","dark":"dracula"}}
        /**
        * TanStack Start Production Server with Bun
        *
        * A high-performance production server for TanStack Start applications that
        * implements intelligent static asset loading with configurable memory management.
        *
        * Features:
        * - Hybrid loading strategy (preload small files, serve large files on-demand)
        * - Configurable file filtering with include/exclude patterns
        * - Memory-efficient response generation
        * - Production-ready caching headers
        *
        * Environment Variables:
        *
        * PORT (number)
        *   - Server port number
        *   - Default: 3000
        *
        * ASSET_PRELOAD_MAX_SIZE (number)
        *   - Maximum file size in bytes to preload into memory
        *   - Files larger than this will be served on-demand from disk
        *   - Default: 5242880 (5MB)
        *   - Example: ASSET_PRELOAD_MAX_SIZE=5242880 (5MB)
        *
        * ASSET_PRELOAD_INCLUDE_PATTERNS (string)
        *   - Comma-separated list of glob patterns for files to include
        *   - If specified, only matching files are eligible for preloading
        *   - Patterns are matched against filenames only, not full paths
        *   - Example: ASSET_PRELOAD_INCLUDE_PATTERNS="*.js,*.css,*.woff2"
        *
        * ASSET_PRELOAD_EXCLUDE_PATTERNS (string)
        *   - Comma-separated list of glob patterns for files to exclude
        *   - Applied after include patterns
        *   - Patterns are matched against filenames only, not full paths
        *   - Example: ASSET_PRELOAD_EXCLUDE_PATTERNS="*.map,*.txt"
        *
        * ASSET_PRELOAD_VERBOSE_LOGGING (boolean)
        *   - Enable detailed logging of loaded and skipped files
        *   - Default: false
        *   - Set to "true" to enable verbose output
        *
        * ASSET_PRELOAD_ENABLE_ETAG (boolean)
        *   - Enable ETag generation for preloaded assets
        *   - Default: true
        *   - Set to "false" to disable ETag support
        *
        * ASSET_PRELOAD_ENABLE_GZIP (boolean)
        *   - Enable Gzip compression for eligible assets
        *   - Default: true
        *   - Set to "false" to disable Gzip compression
        *
        * ASSET_PRELOAD_GZIP_MIN_SIZE (number)
        *   - Minimum file size in bytes required for Gzip compression
        *   - Files smaller than this will not be compressed
        *   - Default: 1024 (1KB)
        *
        * ASSET_PRELOAD_GZIP_MIME_TYPES (string)
        *   - Comma-separated list of MIME types eligible for Gzip compression
        *   - Supports partial matching for types ending with "/"
        *   - Default: text/,application/javascript,application/json,application/xml,image/svg+xml
        *
        * Usage:
        *   bun run server.ts
        */

        import path from 'node:path'

        // Configuration
        const SERVER_PORT = Number(process.env.PORT ?? 3000)
        const CLIENT_DIRECTORY = './dist/client'
        const SERVER_ENTRY_POINT = './dist/server/server.js'

        // Logging utilities for professional output
        const log = {
          info: (message: string) => {
            console.log(`[INFO] ${message}`)
          },
          success: (message: string) => {
            console.log(`[SUCCESS] ${message}`)
          },
          warning: (message: string) => {
            console.log(`[WARNING] ${message}`)
          },
          error: (message: string) => {
            console.log(`[ERROR] ${message}`)
          },
          header: (message: string) => {
            console.log(`\n${message}\n`)
          },
        }

        // Preloading configuration from environment variables
        const MAX_PRELOAD_BYTES = Number(
          process.env.ASSET_PRELOAD_MAX_SIZE ?? 5 * 1024 * 1024, // 5MB default
        )

        // Parse comma-separated include patterns (no defaults)
        const INCLUDE_PATTERNS = (process.env.ASSET_PRELOAD_INCLUDE_PATTERNS ?? '')
          .split(',')
          .map((s) => s.trim())
          .filter(Boolean)
          .map((pattern: string) => convertGlobToRegExp(pattern))

        // Parse comma-separated exclude patterns (no defaults)
        const EXCLUDE_PATTERNS = (process.env.ASSET_PRELOAD_EXCLUDE_PATTERNS ?? '')
          .split(',')
          .map((s) => s.trim())
          .filter(Boolean)
          .map((pattern: string) => convertGlobToRegExp(pattern))

        // Verbose logging flag
        const VERBOSE = process.env.ASSET_PRELOAD_VERBOSE_LOGGING === 'true'

        // Optional ETag feature
        const ENABLE_ETAG = (process.env.ASSET_PRELOAD_ENABLE_ETAG ?? 'true') === 'true'

        // Optional Gzip feature
        const ENABLE_GZIP = (process.env.ASSET_PRELOAD_ENABLE_GZIP ?? 'true') === 'true'
        const GZIP_MIN_BYTES = Number(process.env.ASSET_PRELOAD_GZIP_MIN_SIZE ?? 1024) // 1KB
        const GZIP_TYPES = (
          process.env.ASSET_PRELOAD_GZIP_MIME_TYPES ??
          'text/,application/javascript,application/json,application/xml,image/svg+xml'
        )
          .split(',')
          .map((v) => v.trim())
          .filter(Boolean)

        /**
        * Convert a simple glob pattern to a regular expression
        * Supports * wildcard for matching any characters
        */
        function convertGlobToRegExp(globPattern: string): RegExp {
          // Escape regex special chars except *, then replace * with .*
          const escapedPattern = globPattern
            .replace(/[-/\\^$+?.()|[\]{}]/g, '\\$&')
            .replace(/\*/g, '.*')
          return new RegExp(`^${escapedPattern}$`, 'i')
        }

        /**
        * Compute ETag for a given data buffer
        */
        function computeEtag(data: Uint8Array): string {
          const hash = Bun.hash(data)
          return `W/"${hash.toString(16)}-${data.byteLength.toString()}"`
        }

        /**
        * Metadata for preloaded static assets
        */
        interface AssetMetadata {
          route: string
          size: number
          type: string
        }

        /**
        * In-memory asset with ETag and Gzip support
        */
        interface InMemoryAsset {
          raw: Uint8Array
          gz?: Uint8Array
          etag?: string
          type: string
          immutable: boolean
          size: number
        }

        /**
        * Result of static asset preloading process
        */
        interface PreloadResult {
          routes: Record<string, (req: Request) => Response | Promise<Response>>
          loaded: AssetMetadata[]
          skipped: AssetMetadata[]
        }

        /**
        * Check if a file is eligible for preloading based on configured patterns
        */
        function isFileEligibleForPreloading(relativePath: string): boolean {
          const fileName = relativePath.split(/[/\\]/).pop() ?? relativePath

          // If include patterns are specified, file must match at least one
          if (INCLUDE_PATTERNS.length > 0) {
            if (!INCLUDE_PATTERNS.some((pattern) => pattern.test(fileName))) {
              return false
            }
          }

          // If exclude patterns are specified, file must not match any
          if (EXCLUDE_PATTERNS.some((pattern) => pattern.test(fileName))) {
            return false
          }

          return true
        }

        /**
        * Check if a MIME type is compressible
        */
        function isMimeTypeCompressible(mimeType: string): boolean {
          return GZIP_TYPES.some((type) =>
            type.endsWith('/') ? mimeType.startsWith(type) : mimeType === type,
          )
        }

        /**
        * Conditionally compress data based on size and MIME type
        */
        function compressDataIfAppropriate(
          data: Uint8Array,
          mimeType: string,
        ): Uint8Array | undefined {
          if (!ENABLE_GZIP) return undefined
          if (data.byteLength < GZIP_MIN_BYTES) return undefined
          if (!isMimeTypeCompressible(mimeType)) return undefined
          try {
            return Bun.gzipSync(data.buffer as ArrayBuffer)
          } catch {
            return undefined
          }
        }

        /**
        * Create response handler function with ETag and Gzip support
        */
        function createResponseHandler(
          asset: InMemoryAsset,
        ): (req: Request) => Response {
          return (req: Request) => {
            const headers: Record<string, string> = {
              'Content-Type': asset.type,
              'Cache-Control': asset.immutable
                ? 'public, max-age=31536000, immutable'
                : 'public, max-age=3600',
            }

            if (ENABLE_ETAG && asset.etag) {
              const ifNone = req.headers.get('if-none-match')
              if (ifNone && ifNone === asset.etag) {
                return new Response(null, {
                  status: 304,
                  headers: { ETag: asset.etag },
                })
              }
              headers.ETag = asset.etag
            }

            if (
              ENABLE_GZIP &&
              asset.gz &&
              req.headers.get('accept-encoding')?.includes('gzip')
            ) {
              headers['Content-Encoding'] = 'gzip'
              headers['Content-Length'] = String(asset.gz.byteLength)
              const gzCopy = new Uint8Array(asset.gz)
              return new Response(gzCopy, { status: 200, headers })
            }

            headers['Content-Length'] = String(asset.raw.byteLength)
            const rawCopy = new Uint8Array(asset.raw)
            return new Response(rawCopy, { status: 200, headers })
          }
        }

        /**
        * Create composite glob pattern from include patterns
        */
        function createCompositeGlobPattern(): Bun.Glob {
          const raw = (process.env.ASSET_PRELOAD_INCLUDE_PATTERNS ?? '')
            .split(',')
            .map((s) => s.trim())
            .filter(Boolean)
          if (raw.length === 0) return new Bun.Glob('**/*')
          if (raw.length === 1) return new Bun.Glob(raw[0])
          return new Bun.Glob(`{${raw.join(',')}}`)
        }

        /**
        * Initialize static routes with intelligent preloading strategy
        * Small files are loaded into memory, large files are served on-demand
        */
        async function initializeStaticRoutes(
          clientDirectory: string,
        ): Promise<PreloadResult> {
          const routes: Record<string, (req: Request) => Response | Promise<Response>> =
            {}
          const loaded: AssetMetadata[] = []
          const skipped: AssetMetadata[] = []

          log.info(`Loading static assets from ${clientDirectory}...`)
          if (VERBOSE) {
            console.log(
              `Max preload size: ${(MAX_PRELOAD_BYTES / 1024 / 1024).toFixed(2)} MB`,
            )
            if (INCLUDE_PATTERNS.length > 0) {
              console.log(
                `Include patterns: ${process.env.ASSET_PRELOAD_INCLUDE_PATTERNS ?? ''}`,
              )
            }
            if (EXCLUDE_PATTERNS.length > 0) {
              console.log(
                `Exclude patterns: ${process.env.ASSET_PRELOAD_EXCLUDE_PATTERNS ?? ''}`,
              )
            }
          }

          let totalPreloadedBytes = 0

          try {
            const glob = createCompositeGlobPattern()
            for await (const relativePath of glob.scan({ cwd: clientDirectory })) {
              const filepath = path.join(clientDirectory, relativePath)
              const route = `/${relativePath.split(path.sep).join(path.posix.sep)}`

              try {
                // Get file metadata
                const file = Bun.file(filepath)

                // Skip if file doesn't exist or is empty
                if (!(await file.exists()) || file.size === 0) {
                  continue
                }

                const metadata: AssetMetadata = {
                  route,
                  size: file.size,
                  type: file.type || 'application/octet-stream',
                }

                // Determine if file should be preloaded
                const matchesPattern = isFileEligibleForPreloading(relativePath)
                const withinSizeLimit = file.size <= MAX_PRELOAD_BYTES

                if (matchesPattern && withinSizeLimit) {
                  // Preload small files into memory with ETag and Gzip support
                  const bytes = new Uint8Array(await file.arrayBuffer())
                  const gz = compressDataIfAppropriate(bytes, metadata.type)
                  const etag = ENABLE_ETAG ? computeEtag(bytes) : undefined
                  const asset: InMemoryAsset = {
                    raw: bytes,
                    gz,
                    etag,
                    type: metadata.type,
                    immutable: true,
                    size: bytes.byteLength,
                  }
                  routes[route] = createResponseHandler(asset)

                  loaded.push({ ...metadata, size: bytes.byteLength })
                  totalPreloadedBytes += bytes.byteLength
                } else {
                  // Serve large or filtered files on-demand
                  routes[route] = () => {
                    const fileOnDemand = Bun.file(filepath)
                    return new Response(fileOnDemand, {
                      headers: {
                        'Content-Type': metadata.type,
                        'Cache-Control': 'public, max-age=3600',
                      },
                    })
                  }

                  skipped.push(metadata)
                }
              } catch (error: unknown) {
                if (error instanceof Error && error.name !== 'EISDIR') {
                  log.error(`Failed to load ${filepath}: ${error.message}`)
                }
              }
            }

            // Show detailed file overview only when verbose mode is enabled
            if (VERBOSE && (loaded.length > 0 || skipped.length > 0)) {
              const allFiles = [...loaded, ...skipped].sort((a, b) =>
                a.route.localeCompare(b.route),
              )

              // Calculate max path length for alignment
              const maxPathLength = Math.min(
                Math.max(...allFiles.map((f) => f.route.length)),
                60,
              )

              // Format file size with KB and actual gzip size
              const formatFileSize = (bytes: number, gzBytes?: number) => {
                const kb = bytes / 1024
                const sizeStr = kb < 100 ? kb.toFixed(2) : kb.toFixed(1)

                if (gzBytes !== undefined) {
                  const gzKb = gzBytes / 1024
                  const gzStr = gzKb < 100 ? gzKb.toFixed(2) : gzKb.toFixed(1)
                  return {
                    size: sizeStr,
                    gzip: gzStr,
                  }
                }

                // Rough gzip estimation (typically 30-70% compression) if no actual gzip data
                const gzipKb = kb * 0.35
                return {
                  size: sizeStr,
                  gzip: gzipKb < 100 ? gzipKb.toFixed(2) : gzipKb.toFixed(1),
                }
              }

              if (loaded.length > 0) {
                console.log('\n📁 Preloaded into memory:')
                console.log(
                  'Path                                          │    Size │ Gzip Size',
                )
                loaded
                  .sort((a, b) => a.route.localeCompare(b.route))
                  .forEach((file) => {
                    const { size, gzip } = formatFileSize(file.size)
                    const paddedPath = file.route.padEnd(maxPathLength)
                    const sizeStr = `${size.padStart(7)} kB`
                    const gzipStr = `${gzip.padStart(7)} kB`
                    console.log(`${paddedPath} │ ${sizeStr} │  ${gzipStr}`)
                  })
              }

              if (skipped.length > 0) {
                console.log('\n💾 Served on-demand:')
                console.log(
                  'Path                                          │    Size │ Gzip Size',
                )
                skipped
                  .sort((a, b) => a.route.localeCompare(b.route))
                  .forEach((file) => {
                    const { size, gzip } = formatFileSize(file.size)
                    const paddedPath = file.route.padEnd(maxPathLength)
                    const sizeStr = `${size.padStart(7)} kB`
                    const gzipStr = `${gzip.padStart(7)} kB`
                    console.log(`${paddedPath} │ ${sizeStr} │  ${gzipStr}`)
                  })
              }
            }

            // Show detailed verbose info if enabled
            if (VERBOSE) {
              if (loaded.length > 0 || skipped.length > 0) {
                const allFiles = [...loaded, ...skipped].sort((a, b) =>
                  a.route.localeCompare(b.route),
                )
                console.log('\n📊 Detailed file information:')
                console.log(
                  'Status       │ Path                            │ MIME Type                    │ Reason',
                )
                allFiles.forEach((file) => {
                  const isPreloaded = loaded.includes(file)
                  const status = isPreloaded ? 'MEMORY' : 'ON-DEMAND'
                  const reason =
                    !isPreloaded && file.size > MAX_PRELOAD_BYTES
                      ? 'too large'
                      : !isPreloaded
                        ? 'filtered'
                        : 'preloaded'
                  const route =
                    file.route.length > 30
                      ? file.route.substring(0, 27) + '...'
                      : file.route
                  console.log(
                    `${status.padEnd(12)} │ ${route.padEnd(30)} │ ${file.type.padEnd(28)} │ ${reason.padEnd(10)}`,
                  )
                })
              } else {
                console.log('\n📊 No files found to display')
              }
            }

            // Log summary after the file list
            console.log() // Empty line for separation
            if (loaded.length > 0) {
              log.success(
                `Preloaded ${String(loaded.length)} files (${(totalPreloadedBytes / 1024 / 1024).toFixed(2)} MB) into memory`,
              )
            } else {
              log.info('No files preloaded into memory')
            }

            if (skipped.length > 0) {
              const tooLarge = skipped.filter((f) => f.size > MAX_PRELOAD_BYTES).length
              const filtered = skipped.length - tooLarge
              log.info(
                `${String(skipped.length)} files will be served on-demand (${String(tooLarge)} too large, ${String(filtered)} filtered)`,
              )
            }
          } catch (error) {
            log.error(
              `Failed to load static files from ${clientDirectory}: ${String(error)}`,
            )
          }

          return { routes, loaded, skipped }
        }

        /**
        * Initialize the server
        */
        async function initializeServer() {
          log.header('Starting Production Server')

          // Load TanStack Start server handler
          let handler: { fetch: (request: Request) => Response | Promise<Response> }
          try {
            const serverModule = (await import(SERVER_ENTRY_POINT)) as {
              default: { fetch: (request: Request) => Response | Promise<Response> }
            }
            handler = serverModule.default
            log.success('TanStack Start application handler initialized')
          } catch (error) {
            log.error(`Failed to load server handler: ${String(error)}`)
            process.exit(1)
          }

          // Build static routes with intelligent preloading
          const { routes } = await initializeStaticRoutes(CLIENT_DIRECTORY)

          // Create Bun server
          const server = Bun.serve({
            port: SERVER_PORT,

            routes: {
              // Serve static assets (preloaded or on-demand)
              ...routes,

              // Fallback to TanStack Start handler for all other routes
              '/*': (req: Request) => {
                try {
                  return handler.fetch(req)
                } catch (error) {
                  log.error(`Server handler error: ${String(error)}`)
                  return new Response('Internal Server Error', { status: 500 })
                }
              },
            },

            // Global error handler
            error(error) {
              log.error(
                `Uncaught server error: ${error instanceof Error ? error.message : String(error)}`,
              )
              return new Response('Internal Server Error', { status: 500 })
            },
          })

          log.success(`Server listening on http://localhost:${String(server.port)}`)
        }

        // Initialize the server
        initializeServer().catch((error: unknown) => {
          log.error(`Failed to start server: ${String(error)}`)
          process.exit(1)
        })
        ```
      </Step>

      <Step title="Update package.json scripts">
        Add a `start` script to run the custom server:

        ```json package.json icon="file-json" theme={"theme":{"light":"github-light","dark":"dracula"}}
        {
          "scripts": {
            "build": "bun --bun vite build",
            "start": "bun run server.ts" // [!code ++]
          }
        }
        ```
      </Step>

      <Step title="Build and run">
        Build your application and start the server:

        ```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
        bun run build
        bun run start
        ```

        The server listens on port 3000 by default; set the `PORT` environment variable to change it.
      </Step>
    </Steps>
  </Tab>
</Tabs>

<Columns>
  <Card title="Vercel" href="/guides/deployment/vercel" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/ecosystem/vercel.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=165bc9841eef2a62f3951be494dbc89a">
    Deploy on Vercel
  </Card>

  <Card title="Render" href="/guides/deployment/render" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/ecosystem/render.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=b632a0b982a579fa5a2d0b96f33bace8">
    Deploy on Render
  </Card>

  <Card title="Railway" href="/guides/deployment/railway" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/ecosystem/railway.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=029a2b4b7c3a9f8dfab8d1bebbbfa054">
    Deploy on Railway
  </Card>

  <Card title="DigitalOcean" href="/guides/deployment/digital-ocean" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/ecosystem/digitalocean.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=aead95e8fef32d0ec83e63292f21f80c">
    Deploy on DigitalOcean
  </Card>

  <Card title="AWS Lambda" href="/guides/deployment/aws-lambda" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/ecosystem/aws.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=f747e7b59435e87b2e379be04eabda8f">
    Deploy on AWS Lambda
  </Card>

  <Card title="Google Cloud Run" href="/guides/deployment/google-cloud-run" icon="https://mintcdn.com/bun-1dd33a4e/cfVIaCNGtFU88Wgc/icons/ecosystem/gcp.svg?fit=max&auto=format&n=cfVIaCNGtFU88Wgc&q=85&s=a99e6cb0cfadfeb9ea3b6451de38cfd6">
    Deploy on Google Cloud Run
  </Card>
</Columns>

***

## Templates

<Columns>
  <Card title="Todo App with Tanstack + Bun" href="https://github.com/bun-templates/bun-tanstack-todo">
    A Todo application built with Bun, TanStack Start, and PostgreSQL.
  </Card>

  <Card title="Bun + TanStack Start Application" href="https://github.com/bun-templates/bun-tanstack-basic">
    A TanStack Start template using Bun with SSR and file-based routing.
  </Card>

  <Card title="Basic Bun + Tanstack Starter" href="https://github.com/bun-templates/bun-tanstack-start">
    The basic TanStack starter using the Bun runtime and Bun's file APIs.
  </Card>
</Columns>

***

[→ See TanStack Start's hosting documentation](https://tanstack.com/start/latest/docs/framework/react/guide/hosting)


# Bun Redis with Upstash
Source: https://bun.com/docs/guides/ecosystem/upstash



[Upstash](https://upstash.com/) is a fully managed Redis database as a service. It works with the Redis® API, so you can connect with Bun's native Redis client.

<Note>TLS is enabled by default for all Upstash Redis databases.</Note>

***

<Steps>
  <Step title="Create a new project">
    Create a new project with `bun init`:

    ```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
    bun init bun-upstash-redis
    cd bun-upstash-redis
    ```
  </Step>

  <Step title="Create an Upstash Redis database">
    Go to the [Upstash dashboard](https://console.upstash.com/) and create a new Redis database. After completing the [getting started guide](https://upstash.com/docs/redis/overall/getstarted), you'll see your database page with connection information.

    The database page displays two connection methods: HTTP and TLS. For Bun's Redis client, you need the **TLS** connection details; the URL starts with `rediss://`.

    <Frame>
      <img alt="Upstash Redis database page" />
    </Frame>
  </Step>

  <Step title="Connect using Bun's Redis client">
    Set the `REDIS_URL` environment variable in your `.env` file using the Redis endpoint (not the REST URL):

    ```ini .env icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
    REDIS_URL=rediss://********@********.upstash.io:6379
    ```

    Bun's Redis client reads connection information from `REDIS_URL` by default:

    ```ts index.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
    import { redis } from "bun";

    // Reads from process.env.REDIS_URL automatically
    await redis.set("counter", "0"); // [!code ++]
    ```

    Alternatively, create a custom client with `RedisClient`:

    ```ts index.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
    import { RedisClient } from "bun";

    const redis = new RedisClient(process.env.REDIS_URL); // [!code ++]
    ```
  </Step>

  <Step title="Use the Redis client">
    Use the Redis client to read and write keys in your Upstash database:

    ```ts index.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
    import { redis } from "bun";

    // Get a value
    let counter = await redis.get("counter");

    // Set a value if it doesn't exist
    if (!counter) {
    	await redis.set("counter", "0");
    }

    // Increment the counter
    await redis.incr("counter");

    // Get the updated value
    counter = await redis.get("counter");
    console.log(counter);
    ```

    ```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
    1
    ```

    The Redis client handles connections automatically. You don't need to connect or disconnect manually for basic operations.
  </Step>
</Steps>


# Build a frontend using Vite and Bun
Source: https://bun.com/docs/guides/ecosystem/vite



<Note>
  You can use Vite with Bun, but many projects get faster builds & drop hundreds of dependencies by switching to [HTML
  imports](/docs/bundler/fullstack).
</Note>

***

Vite works with Bun with no extra configuration. Get started with one of Vite's templates.

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun create vite my-app
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
◇  Select a framework:
│  React
│
◇  Select a variant:
│  TypeScript
│
◇  Which linter to use?
│  Oxlint
│
◇  Install with bun and start now?
│  No
│
◇  Scaffolding project in /path/to/my-app...
```

***

Then `cd` into the project directory and install dependencies.

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
cd my-app
bun install
```

***

Start the development server with the `vite` CLI using `bunx`.

The `--bun` flag tells Bun to run Vite's CLI using `bun` instead of `node`. By default, Bun respects Vite's `#!/usr/bin/env node` [shebang line](https://en.wikipedia.org/wiki/Shebang_\(Unix\)).

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bunx --bun vite
```

***

To simplify this command, update the `"dev"` script in `package.json` to the following.

```json package.json icon="file-json" theme={"theme":{"light":"github-light","dark":"dracula"}}
  "scripts": {
    "dev": "vite", // [!code --]
    "dev": "bunx --bun vite", // [!code ++]
    "build": "tsc -b && vite build",
    "lint": "oxlint",
    "preview": "vite preview"
  },
  // ...
```

***

Now you can start the development server with `bun run dev`.

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun run dev
```

***

Build your app for production.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bunx --bun vite build
```

***

For more information, see the [Vite documentation](https://vite.dev/guide/).


# Extract links from a webpage using HTMLRewriter
Source: https://bun.com/docs/guides/html-rewriter/extract-links



## Extract links from a webpage

Bun's [HTMLRewriter](/docs/runtime/html-rewriter) API extracts links from HTML. Chain CSS selectors to match the elements, text, and attributes you want to process. Then pass `.transform` a `Response`, `ArrayBuffer`, or `string`.

```ts extract-links.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
async function extractLinks(url: string) {
  const links = new Set<string>();
  const response = await fetch(url);

  const rewriter = new HTMLRewriter().on("a[href]", {
    element(el) {
      const href = el.getAttribute("href");
      if (href) {
        links.add(href);
      }
    },
  });

  // Wait for the response to be processed
  await rewriter.transform(response).blob();
  console.log([...links]); // ["https://bun.com", "/docs", ...]
}

// Extract all links from the Bun website
await extractLinks("https://bun.com");
```

***

## Convert relative URLs to absolute

When scraping websites, you often want to convert relative URLs (like `/docs`) to absolute URLs:

```ts extract-links.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
async function extractLinksFromURL(url: string) {
  const response = await fetch(url);
  const links = new Set<string>();

  const rewriter = new HTMLRewriter().on("a[href]", {
    element(el) {
      const href = el.getAttribute("href");
      if (href) {
        // Convert relative URLs to absolute // [!code ++]
        try { // [!code ++]
          const absoluteURL = new URL(href, url).href; // [!code ++]
          links.add(absoluteURL);
        } catch { // [!code ++]
          links.add(href); // [!code ++]
        } // [!code ++]
      }
    },
  });

  // Wait for the response to be processed
  await rewriter.transform(response).blob();
  return [...links];
}

const websiteLinks = await extractLinksFromURL("https://example.com");
```

***

See [`HTMLRewriter`](/docs/runtime/html-rewriter).


# Extract social share images and Open Graph tags
Source: https://bun.com/docs/guides/html-rewriter/extract-social-meta



## Extract social share images and Open Graph tags

Bun's [HTMLRewriter](/docs/runtime/html-rewriter) API extracts social share images and Open Graph metadata from HTML by matching CSS selectors against the elements, text, and attributes you want to process. Use it to build link previews, social media cards, or web scrapers.

```ts extract-social-meta.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
interface SocialMetadata {
  title?: string;
  description?: string;
  image?: string;
  url?: string;
  site_name?: string;
  type?: string;
}

async function extractSocialMetadata(url: string): Promise<SocialMetadata> {
  const metadata: SocialMetadata = {};
  const response = await fetch(url);

  const rewriter = new HTMLRewriter()
    // Extract Open Graph meta tags
    .on('meta[property^="og:"]', {
      element(el) {
        const property = el.getAttribute("property");
        const content = el.getAttribute("content");
        if (property && content) {
          // Convert "og:image" to "image" etc.
          const key = property.replace("og:", "") as keyof SocialMetadata;
          metadata[key] = content;
        }
      },
    })
    // Extract Twitter Card meta tags as fallback
    .on('meta[name^="twitter:"]', {
      element(el) {
        const name = el.getAttribute("name");
        const content = el.getAttribute("content");
        if (name && content) {
          const key = name.replace("twitter:", "") as keyof SocialMetadata;
          // Only use Twitter Card data if nothing has set this key yet (OG tags always overwrite it)
          if (!metadata[key]) {
            metadata[key] = content;
          }
        }
      },
    })
    // Fallback to regular meta tags
    .on('meta[name="description"]', {
      element(el) {
        const content = el.getAttribute("content");
        if (content && !metadata.description) {
          metadata.description = content;
        }
      },
    })
    // Fallback to title tag
    .on("title", {
      text(text) {
        if (!metadata.title) {
          metadata.title = text.text;
        }
      },
    });

  // Process the response
  await rewriter.transform(response).blob();

  // Convert relative image URLs to absolute
  if (metadata.image && !metadata.image.startsWith("http")) {
    try {
      metadata.image = new URL(metadata.image, url).href;
    } catch {
      // Keep the original URL if parsing fails
    }
  }

  return metadata;
}
```

```ts Example Usage icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
// Example usage
const metadata = await extractSocialMetadata("https://bun.com");
console.log(metadata);
// {
//   title: "Bun — A fast all-in-one JavaScript runtime",
//   description: "Bundle, install, and run JavaScript &amp; TypeScript — all in Bun. Bun is a fast JavaScript runtime &amp; toolkit with a bundler, test runner, and npm-compatible package manager built in.",
//   image: "https://bun.com/share_v4.png",
//   type: "website",
//   ...
// }
```


# Start a cluster of HTTP servers
Source: https://bun.com/docs/guides/http/cluster

Run multiple HTTP servers concurrently with the "reusePort" option to share the same port across multiple processes

To run multiple HTTP servers concurrently, use the `reusePort` option in `Bun.serve()`. It shares one port across multiple processes, and incoming requests are load balanced across them.

```ts server.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { serve } from "bun";

const id = Math.random().toString(36).slice(2);

serve({
  port: process.env.PORT || 8080,
  development: false,

  // Share the same port across multiple processes
  // This is the important part!
  reusePort: true,

  async fetch(request) {
    return new Response("Hello from Bun #" + id + "!\n");
  },
});
```

***

<Note>
  **Linux only** — Windows and macOS ignore the `reusePort` option. This is an operating system limitation with
  `SO_REUSEPORT`.
</Note>

After saving the file, start your servers on the same port.

`reusePort` uses the Linux `SO_REUSEPORT` and `SO_REUSEADDR` socket options to ensure fair load balancing across processes. [Learn more about `SO_REUSEPORT` and `SO_REUSEADDR`](https://lwn.net/Articles/542629/)

```ts cluster.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { spawn } from "bun";

const cpus = navigator.hardwareConcurrency; // Number of CPU cores
const buns = new Array(cpus);

for (let i = 0; i < cpus; i++) {
  buns[i] = spawn({
    cmd: ["bun", "./server.ts"],
    stdout: "inherit",
    stderr: "inherit",
    stdin: "inherit",
  });
}

function kill() {
  for (const bun of buns) {
    bun.kill();
  }
}

process.on("SIGINT", kill);
process.on("exit", kill);
```

***

Bun also implements the `node:cluster` module; `reusePort` is a faster but more limited alternative.


# Send an HTTP request using fetch
Source: https://bun.com/docs/guides/http/fetch



Bun implements the Web-standard [`fetch`](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) API for sending HTTP requests. To send a `GET` request to a URL:

```ts fetch.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
const response = await fetch("https://bun.com");
const html = await response.text(); // HTML string
```

***

To send a `POST` request to an API endpoint:

```ts fetch.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
const response = await fetch("https://bun.com/api", {
  method: "POST",
  body: JSON.stringify({ message: "Hello from Bun!" }),
  headers: { "Content-Type": "application/json" },
});

const body = await response.json();
```


# fetch with unix domain sockets in Bun
Source: https://bun.com/docs/guides/http/fetch-unix



In Bun, `fetch()` can send HTTP requests over a [unix domain socket](https://en.wikipedia.org/wiki/Unix_domain_socket) with the `unix` option.

```ts fetch-unix.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
const unix = "/var/run/docker.sock";

const response = await fetch("http://localhost/info", { unix });

const body = await response.json();
console.log(body); // { ... }
```

***

The `unix` option is the local file path to a unix domain socket. `fetch()` sends the request over that socket instead of a TCP connection. HTTPS is also supported: use the `https://` protocol in the URL instead of `http://`.

To send a `POST` request to an API endpoint over a unix domain socket:

```ts fetch-unix.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
const response = await fetch("https://hostname/a/path", {
  unix: "/var/run/path/to/unix.sock",
  method: "POST",
  body: JSON.stringify({ message: "Hello from Bun!" }),
  headers: {
    "Content-Type": "application/json",
  },
});

const body = await response.json();
```


# Upload files via HTTP using FormData
Source: https://bun.com/docs/guides/http/file-uploads



To upload files over HTTP with Bun, use the [`FormData`](https://developer.mozilla.org/en-US/docs/Web/API/FormData) API. Start with an HTTP server that serves an HTML form.

```ts index.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
const server = Bun.serve({
  port: 4000,
  async fetch(req) {
    const url = new URL(req.url);

    // return index.html for root path
    if (url.pathname === "/")
      return new Response(Bun.file("index.html"), {
        headers: {
          "Content-Type": "text/html",
        },
      });

    return new Response("Not Found", { status: 404 });
  },
});

console.log(`Listening on http://localhost:${server.port}`);
```

***

Define the HTML form in another file, `index.html`.

```html index.html icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>Form</title>
  </head>
  <body>
    <form action="/action" method="post" enctype="multipart/form-data">
      <input type="text" name="name" placeholder="Name" />
      <input type="file" name="profilePicture" />
      <input type="submit" value="Submit" />
    </form>
  </body>
</html>
```

***

Run the server and visit [`localhost:4000`](http://localhost:4000) to see the form.

```bash theme={"theme":{"light":"github-light","dark":"dracula"}}
bun run index.ts
Listening on http://localhost:4000
```

***

The form sends a `POST` request with the form data to the `/action` endpoint. Handle that request in the server.

First, call [`.formData()`](https://developer.mozilla.org/en-US/docs/Web/API/Request/formData) on the incoming `Request` to asynchronously parse its contents into a `FormData` instance. Then use [`.get()`](https://developer.mozilla.org/en-US/docs/Web/API/FormData/get) to extract the `name` and `profilePicture` fields; `name` is a `string` and `profilePicture` is a `Blob`.

Finally, write the `Blob` to disk with [`Bun.write()`](/docs/runtime/file-io#writing-files-bun-write).

```ts index.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
const server = Bun.serve({
  port: 4000,
  async fetch(req) {
    const url = new URL(req.url);

    // return index.html for root path
    if (url.pathname === "/")
      return new Response(Bun.file("index.html"), {
        headers: {
          "Content-Type": "text/html",
        },
      });

    // parse formdata at /action // [!code ++]
    if (url.pathname === "/action") { // [!code ++]
      const formdata = await req.formData(); // [!code ++]
      const name = formdata.get("name"); // [!code ++]
      const profilePicture = formdata.get("profilePicture"); // [!code ++]
      if (!profilePicture) throw new Error("Must upload a profile picture."); // [!code ++]
      // write profilePicture to disk // [!code ++]
      await Bun.write("profilePicture.png", profilePicture); // [!code ++]
      return new Response("Success"); // [!code ++]
    } // [!code ++]

    return new Response("Not Found", { status: 404 });
  },
});
```


# Hot reload an HTTP server
Source: https://bun.com/docs/guides/http/hot



The [`--hot`](/docs/runtime/watch-mode#hot-mode) flag runs a file with hot reloading enabled. When any module or file changes, Bun re-runs the file.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun --hot run index.ts
```

***

Bun detects when you are running an HTTP server with `Bun.serve()`. It reloads your fetch handler when source files change, *without* restarting the `bun` process. This makes hot reloads nearly instantaneous.

<Note>
  Hot reloading doesn't reload the page in your browser.
</Note>

```ts index.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
Bun.serve({
  port: 3000,
  fetch(req) {
    return new Response("Hello world");
  },
});
```


# Proxy HTTP requests using fetch()
Source: https://bun.com/docs/guides/http/proxy



In Bun, `fetch` supports sending requests through an HTTP or HTTPS proxy. Use it on corporate networks or when a request must come from a specific IP address.

```ts proxy.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
await fetch("https://example.com", {
  // The URL of the proxy server
  proxy: "https://username:password@proxy.example.com:8080",
});
```

***

The `proxy` option can be a URL string, a `URL` instance, or an object with `url` (a string or a `URL`) and optional `headers`. The URL can include the username and password if the proxy requires authentication. It can be `http://` or `https://`.

***

## Custom proxy headers

To send custom headers to the proxy server (for proxy authentication tokens or custom routing), use the object format:

```ts proxy-headers.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
await fetch("https://example.com", {
  proxy: {
    url: "https://proxy.example.com:8080",
    headers: {
      "Proxy-Authorization": "Bearer my-token",
      "X-Proxy-Region": "us-east-1",
    },
  },
});
```

The `headers` property accepts a plain object or a `Headers` instance. Bun sends these headers directly to the proxy server in `CONNECT` requests (for HTTPS targets) or in the proxy request (for HTTP targets).

If you provide a `Proxy-Authorization` header, it overrides any credentials in the proxy URL.

***

## Environment variables

To use the same proxy for all requests, set the `$HTTP_PROXY` and `$HTTPS_PROXY` environment variables to the proxy URL. Bun uses `$HTTP_PROXY` only for requests to `http://` URLs and `$HTTPS_PROXY` only for requests to `https://` URLs, so set both to proxy every request.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
HTTP_PROXY=https://username:password@proxy.example.com:8080 HTTPS_PROXY=https://username:password@proxy.example.com:8080 bun run index.ts
```


# Common HTTP server usage
Source: https://bun.com/docs/guides/http/server



This code starts an HTTP server listening on port `3000`. It demonstrates basic routing with common responses and handles POST data from standard forms or as JSON.

See [`Bun.serve`](/docs/runtime/http/server) for details.

```ts server.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
const server = Bun.serve({
  async fetch(req) {
    const path = new URL(req.url).pathname;

    // respond with text/plain
    if (path === "/") return new Response("Welcome to Bun!");

    // redirect
    if (path === "/abc") return Response.redirect("/source", 301);

    // send back a file (in this case, *this* file)
    if (path === "/source") return new Response(Bun.file(import.meta.path));

    // respond with JSON
    if (path === "/api") return Response.json({ some: "buns", for: "you" });

    // receive JSON data to a POST request
    if (req.method === "POST" && path === "/api/post") {
      const data = await req.json();
      console.log("Received JSON:", data);
      return Response.json({ success: true, data });
    }

    // receive POST data from a form
    if (req.method === "POST" && path === "/form") {
      const data = await req.formData();
      console.log(data.get("someField"));
      return new Response("Success");
    }

    // 404s
    return new Response("Page not found", { status: 404 });
  },
});

console.log(`Listening on ${server.url}`);
```


# Write a simple HTTP server
Source: https://bun.com/docs/guides/http/simple



This code starts an HTTP server listening on port `3000`. It responds to every request with a `200` status and the body `"Welcome to Bun!"`.

See [`Bun.serve`](/docs/runtime/http/server) for details.

```ts server.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
const server = Bun.serve({
  port: 3000,
  fetch(request) {
    return new Response("Welcome to Bun!");
  },
});

console.log(`Listening on ${server.url}`);
```


# Server-Sent Events (SSE) with Bun
Source: https://bun.com/docs/guides/http/sse



[Server-Sent Events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events) let you push a stream of text events to the browser over a single HTTP response. The client consumes them with [`EventSource`](https://developer.mozilla.org/en-US/docs/Web/API/EventSource).

To implement an SSE endpoint in Bun, return a `Response` whose body is a streaming source and set the `Content-Type` header to `text/event-stream`.

<Note>
  `Bun.serve` closes idle connections after **10 seconds** by default. A quiet SSE stream counts as idle, so the
  examples below call `server.timeout(req, 0)` to disable the timeout for the stream. See
  [`idleTimeout`](/docs/runtime/http/server#idletimeout) for details.
</Note>

## Using an async generator

In Bun, `new Response` accepts an async generator function directly. An async generator is usually the simplest way to write an SSE endpoint. Each `yield` flushes a chunk to the client. If the client disconnects, the generator's `finally` block runs so you can clean up.

```ts server.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
Bun.serve({
  port: 3000,
  routes: {
    "/events": (req, server) => {
      // SSE streams are often quiet between events. By default,
      // Bun.serve closes connections after 10 seconds of inactivity.
      // Disable the idle timeout for this request so the stream
      // stays open indefinitely.
      server.timeout(req, 0);

      return new Response(
        async function* () {
          yield `data: connected at ${Date.now()}\n\n`;

          // Emit a tick every 5 seconds until the client disconnects.
          // When the client goes away, the generator is returned
          // (cancelled) and this loop stops automatically.
          while (true) {
            await Bun.sleep(5000);
            yield `data: tick ${Date.now()}\n\n`;
          }
        },
        {
          headers: {
            "Content-Type": "text/event-stream",
            "Cache-Control": "no-cache",
          },
        },
      );
    },
  },
});
```

## Using a `ReadableStream`

If your events originate from callbacks (message brokers, timers, external pushes) rather than a linear `await` flow, a `ReadableStream` often fits better. When the client disconnects, Bun calls the stream's `cancel()` method automatically, so you can release any resources you set up in `start()`.

```ts server.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
Bun.serve({
  port: 3000,
  routes: {
    "/events": (req, server) => {
      server.timeout(req, 0);

      let timer: Timer;
      const stream = new ReadableStream({
        start(controller) {
          controller.enqueue(`data: connected at ${Date.now()}\n\n`);

          timer = setInterval(() => {
            controller.enqueue(`data: tick ${Date.now()}\n\n`);
          }, 5000);
        },
        cancel() {
          // Called automatically when the client disconnects.
          clearInterval(timer);
        },
      });

      return new Response(stream, {
        headers: {
          "Content-Type": "text/event-stream",
          "Cache-Control": "no-cache",
        },
      });
    },
  },
});
```


# Stream a file as an HTTP Response
Source: https://bun.com/docs/guides/http/stream-file



[`Bun.file()`](/docs/runtime/file-io#reading-files-bun-file) accepts a path and returns a lazily-loaded `BunFile` instance, which you can pass directly to the `new Response` constructor.

```ts server.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
const path = "/path/to/file.txt";
const file = Bun.file(path);
const resp = new Response(file);
```

***

Bun determines the `Content-Type` from the file extension and sets it on the `Response`.

```ts server.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
new Response(Bun.file("./package.json")).headers.get("Content-Type");
// => application/json;charset=utf-8

new Response(Bun.file("./test.txt")).headers.get("Content-Type");
// => text/plain;charset=utf-8

new Response(Bun.file("./index.tsx")).headers.get("Content-Type");
// => text/javascript;charset=utf-8

new Response(Bun.file("./img.png")).headers.get("Content-Type");
// => image/png
```

***

Putting it all together with [`Bun.serve()`](/docs/runtime/http/server).

```ts server.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
// static file server
Bun.serve({
  async fetch(req) {
    const path = new URL(req.url).pathname;
    const file = Bun.file(path);
    return new Response(file);
  },
});
```

***

See [`Bun.write()`](/docs/runtime/file-io#writing-files-bun-write).


# Streaming HTTP Server with Async Iterators
Source: https://bun.com/docs/guides/http/stream-iterator



In Bun, a [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response) accepts an async generator function as its body, so you can stream data to the client as it becomes available rather than waiting for the entire response to be ready.

```ts stream-iterator.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
Bun.serve({
  port: 3000,
  fetch(req) {
    return new Response(
      // An async generator function
      async function* () {
        yield "Hello, ";
        await Bun.sleep(100);
        yield "world!";

        // you can also yield a TypedArray or Buffer
        yield new Uint8Array(["\n".charCodeAt(0)]);
      },
      { headers: { "Content-Type": "text/plain" } },
    );
  },
});
```

***

You can pass any async iterable directly to `Response`:

```ts stream-iterator.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
Bun.serve({
  port: 3000,
  fetch(req) {
    return new Response(
      {
        [Symbol.asyncIterator]: async function* () {
          yield "Hello, ";
          await Bun.sleep(100);
          yield "world!";
        },
      },
      { headers: { "Content-Type": "text/plain" } },
    );
  },
});
```


# Streaming HTTP Server with Node.js Streams
Source: https://bun.com/docs/guides/http/stream-node-streams-in-bun



In Bun, a [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response) accepts a Node.js [`Readable`](https://nodejs.org/api/stream.html#stream_readable_streams) as its body.

This works because Bun's `Response` accepts any async iterable as its body, and Node.js streams are async iterables.

```ts server.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { Readable } from "stream";
import { serve } from "bun";
serve({
  port: 3000,
  fetch(req) {
    return new Response(Readable.from(["Hello, ", "world!"]), {
      headers: { "Content-Type": "text/plain" },
    });
  },
});
```


# Configure TLS on an HTTP server
Source: https://bun.com/docs/guides/http/tls



Set the `tls` key to configure TLS. Both `key` and `cert` are required: `key` is the contents of your private key and `cert` is the contents of your issued certificate. Use [`Bun.file()`](/docs/runtime/file-io#reading-files-bun-file) to read them.

```ts server.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
const server = Bun.serve({
  fetch: request => new Response("Welcome to Bun!"),
  tls: {
    cert: Bun.file("cert.pem"),
    key: Bun.file("key.pem"),
  },
});
```

***

By default, Bun trusts the Mozilla-curated list of well-known root CAs. To override this list, pass an array of certificates as `ca`. On a server, Bun uses this list to verify *client* certificates, so also set `requestCert: true`.

```ts server.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
const server = Bun.serve({
  fetch: request => new Response("Welcome to Bun!"),
  tls: {
    cert: Bun.file("cert.pem"),
    key: Bun.file("key.pem"),
    ca: [Bun.file("ca1.pem"), Bun.file("ca2.pem")],
    requestCert: true,
  },
});
```


# Guides
Source: https://bun.com/docs/guides/index

Code samples and walkthroughs for common tasks with Bun

<GuidesList />


# Add a dependency
Source: https://bun.com/docs/guides/install/add



To add an npm package as a dependency, use `bun add`.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun add zod
```

***

This adds the package to `dependencies` in `package.json`. By default, Bun uses the `^` range specifier, which accepts future minor and patch versions.

```json package.json icon="file-json" theme={"theme":{"light":"github-light","dark":"dracula"}}
{
  "dependencies": {
    "zod": "^4.0.0" // [!code ++]
  }
}
```

***

To pin your project to the exact version you installed, use `--exact`. This adds the package to `dependencies` without the `^`.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun add zod --exact
```

***

To specify an exact version or a tag:

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun add zod@3.0.0
bun add zod@next
```

***

See [`bun install`](/docs/pm/cli/install).


# Add a development dependency
Source: https://bun.com/docs/guides/install/add-dev



To add an npm package as a development dependency, use `bun add --development`.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun add zod --dev
bun add zod -d # shorthand
```

***

This adds the package to `devDependencies` in `package.json`.

```json theme={"theme":{"light":"github-light","dark":"dracula"}}
{
  "devDependencies": {
    "zod": "^4.0.0" // [!code ++]
  }
}
```

***

See [`bun install`](/docs/pm/cli/install).


# Add a Git dependency
Source: https://bun.com/docs/guides/install/add-git



Bun supports directly adding GitHub repositories as dependencies of your project.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun add github:lodash/lodash
```

***

This adds the following line to your `package.json`:

```json package.json icon="file-json" theme={"theme":{"light":"github-light","dark":"dracula"}}
{
  "dependencies": {
    "lodash": "github:lodash/lodash"
  }
}
```

***

Bun supports several protocols for specifying Git dependencies.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun add git+https://github.com/lodash/lodash.git
bun add git+ssh://github.com/lodash/lodash.git#4.17.21
bun add git@github.com:lodash/lodash.git
bun add github:lodash/lodash#4.17.21
```

When possible, Bun downloads GitHub dependencies as HTTP tarballs, which is faster.

***

See [`bun install`](/docs/pm/cli/install).


# Add an optional dependency
Source: https://bun.com/docs/guides/install/add-optional



To add an npm package as an optional dependency, use the `--optional` flag.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun add zod --optional
```

***

This adds the package to `optionalDependencies` in `package.json`.

```json package.json icon="file-json" theme={"theme":{"light":"github-light","dark":"dracula"}}
{
  "optionalDependencies": {
    "zod": "^4.0.0" // [!code ++]
  }
}
```

***

See [`bun install`](/docs/pm/cli/install).


# Add a peer dependency
Source: https://bun.com/docs/guides/install/add-peer



To add an npm package as a peer dependency, use the `--peer` flag.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun add @types/bun --peer
```

***

This adds the package to `peerDependencies` in `package.json`.

```json package.json icon="file-json" theme={"theme":{"light":"github-light","dark":"dracula"}}
{
  "peerDependencies": {
    "@types/bun": "^1.3.3" // [!code ++]
  }
}
```

***

`bun install` installs peer dependencies by default, unless they are marked optional in `peerDependenciesMeta`.

```json package.json icon="file-json" theme={"theme":{"light":"github-light","dark":"dracula"}}
{
  "peerDependencies": {
    "@types/bun": "^1.3.3"
  },
  "peerDependenciesMeta": {
    "@types/bun": { // [!code ++]
      "optional": true // [!code ++]
    } // [!code ++]
  }
}
```

***

See [`bun install`](/docs/pm/cli/install).


# Add a tarball dependency
Source: https://bun.com/docs/guides/install/add-tarball



Bun's package manager can install any publicly available tarball URL as a dependency of your project.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun add zod@https://registry.npmjs.org/zod/-/zod-3.21.4.tgz
```

***

This command downloads, extracts, and installs the tarball into your project's `node_modules` directory, and adds the following line to your `package.json`:

```json package.json icon="file-json" theme={"theme":{"light":"github-light","dark":"dracula"}}
{
  "dependencies": {
    "zod": "https://registry.npmjs.org/zod/-/zod-3.21.4.tgz" // [!code ++]
  }
}
```

***

You can now import `zod` as usual.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
import { z } from "zod";
```

***

See [`bun install`](/docs/pm/cli/install).


# Using bun install with an Azure Artifacts npm registry
Source: https://bun.com/docs/guides/install/azure-artifacts



<Note>
  [Azure
  Artifacts'](https://learn.microsoft.com/en-us/azure/devops/artifacts/npm/npmrc?view=azure-devops\&tabs=windows%2Cclassic)
  instructions for `.npmrc` say to base64 encode the password. Do not do this in `bunfig.toml` or `NPM_CONFIG_REGISTRY`
  as shown below; Bun base64 encodes the password for you. Bun also reads [`.npmrc`](/docs/pm/npmrc) files, and there
  `_password` must stay base64 encoded, as in Azure's instructions.
</Note>

[Azure Artifacts](https://azure.microsoft.com/en-us/products/devops/artifacts) is a package management system for Azure DevOps. You can use it to host your own private npm registry, along with other types of packages.

***

### Configure with bunfig.toml

***

To use Azure Artifacts with `bun install`, add a `bunfig.toml` file to your project with the following contents. Replace `my-azure-devops-org` with the name of your Azure DevOps organization and `my-feed` with the name of your feed. If the feed is project-scoped, the URL also includes the project name: `https://pkgs.dev.azure.com/my-azure-devops-org/my-project/_packaging/my-feed/npm/registry/`. The `username` can be any non-empty string.

```toml bunfig.toml icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[install.registry]
url = "https://pkgs.dev.azure.com/my-azure-devops-org/_packaging/my-feed/npm/registry/"
username = "my-azure-artifacts-user"
# You can use an environment variable here
password = "$NPM_PASSWORD"
```

***

Then assign your Azure Personal Access Token to the `NPM_PASSWORD` environment variable. Bun [automatically reads](/docs/runtime/environment-variables) `.env` files, so create a file called `.env` in your project root. Don't base64 encode the token; Bun does that for you.

```ini .env icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
NPM_PASSWORD=<paste token here>
```

***

### Configure with environment variables

***

To configure Azure Artifacts without `bunfig.toml`, set the `NPM_CONFIG_REGISTRY` environment variable. Append `:username=<USERNAME>` and `:_password=<PASSWORD>` to the URL, as shown below. Replace `<USERNAME>` and `<PASSWORD>` with your own values.

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
NPM_CONFIG_REGISTRY=https://pkgs.dev.azure.com/my-azure-devops-org/_packaging/my-feed/npm/registry/:username=<USERNAME>:_password=<PASSWORD>
```

***

### Don't base64 encode the password

***

[Azure Artifacts'](https://learn.microsoft.com/en-us/azure/devops/artifacts/npm/npmrc?view=azure-devops\&tabs=windows%2Cclassic) instructions for `.npmrc` say to base64 encode the password. Do not do this in `bunfig.toml` or `NPM_CONFIG_REGISTRY`; Bun base64 encodes the password for you. Bun also reads [`.npmrc`](/docs/pm/npmrc) files, and there `_password` must stay base64 encoded, as in Azure's instructions.

<Note>
  Azure DevOps personal access tokens are 84 characters long. A base64-encoded one is 112 characters long and does not
  end with `=`.
</Note>

***

To decode a base64-encoded password, open your browser console and run:

```js browser icon="computer" theme={"theme":{"light":"github-light","dark":"dracula"}}
atob("<base64-encoded password>");
```

***

Alternatively, use the `base64` command line tool, though the password may end up in your shell history:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
echo "base64-encoded-password" | base64 --decode
```


# Install dependencies with Bun in GitHub Actions
Source: https://bun.com/docs/guides/install/cicd



Use the official [`setup-bun`](https://github.com/oven-sh/setup-bun) GitHub Action to install `bun` in your GitHub Actions runner.

```yaml workflow.yml icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
name: my-workflow
jobs:
  my-job:
    name: my-job
    runs-on: ubuntu-latest
    steps:
      # ...
      - uses: actions/checkout@v4
      - uses: oven-sh/setup-bun@v2 # [!code ++]

      # run any `bun` or `bunx` command
      - run: bun install # [!code ++]
```

***

To specify a version of Bun to install:

```yaml workflow.yml icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
name: my-workflow
jobs:
  my-job:
    name: my-job
    runs-on: ubuntu-latest
    steps:
      # ...
      - uses: oven-sh/setup-bun@v2
        with: # [!code ++]
          bun-version: "latest" # or "canary" # [!code ++]
```

***

See the [`setup-bun` README](https://github.com/oven-sh/setup-bun).


# Override the default npm registry for bun install
Source: https://bun.com/docs/guides/install/custom-registry



The default registry is `registry.npmjs.org`. Override it globally in `bunfig.toml`.

```toml bunfig.toml icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[install]
# set default registry as a string
registry = "https://registry.npmjs.org"

# if needed, set a token
# registry = { url = "https://registry.npmjs.org", token = "123456" }

# if needed, set a username/password
# registry = "https://username:password@registry.npmjs.org"
```

***

Your `bunfig.toml` can reference environment variables. `bun install` automatically loads environment variables from `.env.production.local`, `.env.local`, `.env.production`, and `.env`, regardless of `NODE_ENV`. It does not read `.env.development` or `.env.test`. See [Environment variables](/docs/runtime/environment-variables).

```toml bunfig.toml icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[install]
registry = { url = "https://registry.npmjs.org", token = "$npm_token" }
```

***

See [`bun install`](/docs/pm/cli/install).


# Migrate from npm install to bun install
Source: https://bun.com/docs/guides/install/from-npm-install-to-bun-install



`bun install` is a fast, Node.js-compatible npm client.

To migrate from `npm install` to `bun install`, run `bun install` instead of `npm install`.

* **Designed for Node.js & Bun**: `bun install` installs a Node.js compatible `node_modules` folder. You can use it in place of `npm install` for Node.js projects without any code changes and without using Bun's runtime.
* **Automatically converts `package-lock.json`** to Bun's `bun.lock` lockfile format, preserving your existing resolved dependency versions. You can secretly use `bun install` in place of `npm install` at work without anyone noticing.
* **`.npmrc` compatible**: `bun install` reads npm registry configuration from npm's `.npmrc`, so you can use the same configuration for both npm and Bun.
* **Hardlinks**: On Windows and Linux, `bun install` uses hardlinks to save disk space and speed up installs.

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
# It only takes one command to migrate
bun i

# To add dependencies:
bun i @types/bun

# To add devDependencies:
bun i -d @types/bun

# To remove a dependency:
bun rm @types/bun
```

***

## Run package.json scripts faster

Run scripts from `package.json`, executables from `node_modules/.bin` (like `npx`), and JavaScript/TypeScript files (like `node`), all with a single command.

| NPM                | Bun              |
| ------------------ | ---------------- |
| `npm run <script>` | `bun <script>`   |
| `npm exec <bin>`   | `bun <bin>`      |
| `node <file>`      | `bun <file>`     |
| `npx <package>`    | `bunx <package>` |

`bun run <executable>` uses the locally-installed executable.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
# Run a package.json script:
bun my-script
bun run my-script

# Run an executable in node_modules/.bin:
bun my-executable # such as tsc, esbuild, etc.
bun run my-executable

# Run a JavaScript/TypeScript file:
bun ./index.ts
```

***

## Workspaces? Yes.

`bun install` supports workspaces similarly to npm, with more features.

In `package.json`, set `"workspaces"` to an array of relative paths.

```json package.json icon="file-json" theme={"theme":{"light":"github-light","dark":"dracula"}}
{
  "name": "my-app",
  "workspaces": ["packages/*", "apps/*"]
}
```

***

### Filter scripts by workspace name

In Bun, the `--filter` flag accepts a glob pattern and runs the command concurrently for every workspace package whose `name` matches it, respecting dependency order.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun --filter 'lib-*' my-script
# instead of:
# npm run --workspace lib-foo --workspace lib-bar my-script
```

***

## Update dependencies

`bun update <package>` updates a dependency to the latest version that satisfies the semver range in `package.json`.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
# Update a single dependency
bun update @types/bun

# Update all dependencies
bun update

# Ignore semver, update to the latest version
bun update @types/bun --latest

# Update a dependency to a specific version
bun update @types/bun@1.3.3

# Update all dependencies to the latest versions
bun update --latest
```

***

### View outdated dependencies

To view outdated dependencies, run `bun outdated`. It works like `npm outdated`, with more compact output.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun outdated
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
┌────────────────────────────────────────┬─────────┬────────┬────────┐
│ Package                                │ Current │ Update │ Latest │
├────────────────────────────────────────┼─────────┼────────┼────────┤
│ @types/bun (dev)                       │ 1.1.6   │ 1.1.10 │ 1.1.10 │
├────────────────────────────────────────┼─────────┼────────┼────────┤
│ @types/react (dev)                     │ 18.3.3  │ 18.3.8 │ 18.3.8 │
├────────────────────────────────────────┼─────────┼────────┼────────┤
│ @typescript-eslint/eslint-plugin (dev) │ 7.16.1  │ 7.18.0 │ 8.6.0  │
├────────────────────────────────────────┼─────────┼────────┼────────┤
│ @typescript-eslint/parser (dev)        │ 7.16.1  │ 7.18.0 │ 8.6.0  │
├────────────────────────────────────────┼─────────┼────────┼────────┤
│ @vscode/debugadapter (dev)             │ 1.66.0  │ 1.67.0 │ 1.67.0 │
├────────────────────────────────────────┼─────────┼────────┼────────┤
│ esbuild (dev)                          │ 0.21.5  │ 0.21.5 │ 0.24.0 │
├────────────────────────────────────────┼─────────┼────────┼────────┤
│ eslint (dev)                           │ 9.7.0   │ 9.11.0 │ 9.11.0 │
├────────────────────────────────────────┼─────────┼────────┼────────┤
│ mitata (dev)                           │ 0.1.11  │ 0.1.14 │ 1.0.2  │
├────────────────────────────────────────┼─────────┼────────┼────────┤
│ prettier-plugin-organize-imports (dev) │ 4.0.0   │ 4.1.0  │ 4.1.0  │
├────────────────────────────────────────┼─────────┼────────┼────────┤
│ source-map-js (dev)                    │ 1.2.0   │ 1.2.1  │ 1.2.1  │
├────────────────────────────────────────┼─────────┼────────┼────────┤
│ typescript (dev)                       │ 5.5.3   │ 5.6.2  │ 5.6.2  │
└────────────────────────────────────────┴─────────┴────────┴────────┘
```

***

## List installed packages

`bun pm ls` lists the packages installed in the `node_modules` folder, using Bun's lockfile as the source of truth. Pass the `-a` flag to also list transitive dependencies.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
# List top-level installed packages:
bun pm ls
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
my-pkg node_modules (781)
├── @types/node@20.16.5
├── @types/react@18.3.8
├── @types/react-dom@18.3.0
├── eslint@8.57.1
├── eslint-config-next@14.2.8
...
```

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
# List all installed packages:
bun pm ls -a
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
my-pkg node_modules
├── @alloc/quick-lru@5.2.0
├── @isaacs/cliui@8.0.2
│   └── strip-ansi@7.1.0
│       └── ansi-regex@6.1.0
├── @jridgewell/gen-mapping@0.3.5
├── @jridgewell/resolve-uri@3.1.2
...
```

***

## Create a package tarball

`bun pm pack` creates a tarball of the package in the current directory.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
# Create a tarball
bun pm pack
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
Total files: 46
Shasum: 2ee19b6f0c6b001358449ca0eadead703f326216
Integrity: sha512-ZV0lzWTEkGAMz[...]Gl4f8lA9sl97g==
Unpacked size: 0.41MB
Packed size: 117.50KB
```

***

## Shebang

If the package references `node` in the `#!/usr/bin/env node` shebang, `bun run` respects it by default and uses the system's `node` executable. To force it to use Bun instead, pass `--bun` to `bun run`.

When you pass `--bun`, Bun creates a symlink to the locally-installed Bun executable named `"node"` in a temporary directory and adds it to your `PATH` for the duration of the script.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
# Force using Bun's runtime instead of node
bun --bun my-script

# This also works:
bun run --bun my-script
```

***

## Global installs

Install packages globally with `bun i -g <package>`. By default, they go into a `.bun/install/global/node_modules` folder inside your home directory.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
# Install a package globally
bun i -g eslint

# Run a globally-installed package without the `bun run` prefix
eslint --init
```


# Configure git to diff Bun's lockb lockfile
Source: https://bun.com/docs/guides/install/git-diff-bun-lockfile



<Note>
  Bun v1.1.39 introduced `bun.lock`, a JSONC formatted lockfile. `bun.lock` is human-readable and git-diffable without
  configuration, at no cost to performance. In 1.2.0+ it is the default format for new projects. See [the lockfile
  docs](/docs/pm/lockfile#text-based-lockfile).
</Note>

***

To teach `git` how to generate a human-readable diff of Bun's binary lockfile format (`.lockb`), add the following to your local or global `.gitattributes` file:

```js gitattributes icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
*.lockb binary diff=lockb
```

***

Then add the following to your local git config:

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
git config diff.lockb.textconv bun
git config diff.lockb.binary true
```

***

To configure this for every repository, add the following to your global git config:

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
git config --global diff.lockb.textconv bun
git config --global diff.lockb.binary true
```

***

## How this works

* `textconv` tells git to run bun on the file before diffing
* `binary` tells git to treat the file as binary (so it doesn't try to diff it line-by-line)

Running Bun on the lockfile (`bun ./bun.lockb`) prints a human-readable version of it, which `git diff` then diffs.


# Using bun install with Artifactory
Source: https://bun.com/docs/guides/install/jfrog-artifactory



[JFrog Artifactory](https://jfrog.com/artifactory/) is a package management system for npm, Docker, Maven, NuGet, Ruby, Helm, and more. You can use it to host your own private npm registry, along with other types of packages.

To use it with `bun install`, add a `bunfig.toml` file to your project with the following contents:

***

### Configure with bunfig.toml

Replace `MY_SUBDOMAIN` with your JFrog Artifactory subdomain, such as `jarred1234`. Replace `MY_TOKEN` with your base64-encoded `username:password`. Bun sends this value as-is in a `Basic` `Authorization` header. To authenticate with a JFrog access token instead, use `_authToken=` in place of `_auth=`. Bun sends that value as a `Bearer` token.

```toml bunfig.toml icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[install.registry]
url = "https://MY_SUBDOMAIN.jfrog.io/artifactory/api/npm/npm/_auth=MY_TOKEN"
# To authenticate with an access token instead
# url = "https://MY_SUBDOMAIN.jfrog.io/artifactory/api/npm/npm/_authToken=MY_TOKEN"
# You can use an environment variable here
# url = "$NPM_CONFIG_REGISTRY"
```

***

### Configure with `$NPM_CONFIG_REGISTRY`

As with npm, you can set the `NPM_CONFIG_REGISTRY` environment variable to configure JFrog Artifactory for `bun install`.


# Install a package under a different name
Source: https://bun.com/docs/guides/install/npm-alias



To install an npm package under an alias:

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun add my-custom-name@npm:zod
```

***

You can now import the `zod` package as `my-custom-name`.

```ts index.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { z } from "my-custom-name";

z.string();
```

***

See [`bun install`](/docs/pm/cli/install).


# Configure a private registry for an organization scope with bun install
Source: https://bun.com/docs/guides/install/registry-scope



You can configure private registries in [`.npmrc`](/docs/pm/npmrc) or [`bunfig.toml`](/docs/runtime/bunfig#install-registry). Both work; we recommend `bunfig.toml` for its Bun-specific options.

To configure a registry for a particular npm scope:

```toml bunfig.toml icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[install.scopes]
# as a string
"@myorg1" = "https://username:password@registry.myorg.com/"

# as an object with username/password
# you can reference environment variables
"@myorg2" = {
  username = "myusername",
  password = "$npm_pass",
  url = "https://registry.myorg.com/"
}

# as an object with token
"@myorg3" = { token = "$npm_token", url = "https://registry.myorg.com/" }

```

***

Your `bunfig.toml` can reference environment variables. `bun install` automatically loads environment variables from `.env.production.local`, `.env.local`, `.env.production`, and `.env`, regardless of `NODE_ENV`. It does not read `.env.development` or `.env.test`. See [Environment variables](/docs/runtime/environment-variables).

```toml bunfig.toml icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[install.scopes]
"@myorg3" = { token = "$npm_token", url = "https://registry.myorg.com/" }
```

***

See [`bun install`](/docs/pm/cli/install).


# Add a trusted dependency
Source: https://bun.com/docs/guides/install/trusted



By default, Bun does not execute arbitrary lifecycle scripts for installed dependencies, such as `postinstall` and `node-gyp` builds. These scripts represent a potential security risk, as they can execute arbitrary code on your machine.

<Note>
  Bun includes a default allowlist of popular packages whose `postinstall` scripts are known to be safe. See [the full
  list](https://github.com/oven-sh/bun/blob/main/src/install/default-trusted-dependencies.txt). The allowlist 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`. Defining `trustedDependencies` in your
  `package.json` [*replaces* this default list](/docs/pm/lifecycle#behavior-of-the-trusteddependencies-field) rather than
  extending it, so also list any packages from the default list whose lifecycle scripts you still need.
</Note>

***

If you see one of the following errors, you are probably using a package that needs its `postinstall` script to work:

* `error: could not determine executable to run for package`
* `ENOEXEC` (`Exec format error`)

***

To allow Bun to execute lifecycle scripts for a specific package, add the package to `trustedDependencies` in your `package.json`. You can do this automatically by running `bun pm trust <pkg>`.

<Note>
  Listing a package in `trustedDependencies` only allows lifecycle scripts for that specific package, *not* the
  dependencies of that dependency.
</Note>

```json package.json icon="file-json" theme={"theme":{"light":"github-light","dark":"dracula"}}
{
  "name": "my-app",
  "version": "1.0.0",
  "trustedDependencies": ["my-trusted-package"] // [!code ++]
}
```

***

Once you add the package to `trustedDependencies`, run a fresh install. Bun re-installs your dependencies and runs the package's lifecycle scripts. (`bun pm trust` runs them immediately, so you can skip the extra install.)

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
rm -rf node_modules
bun install
```

***

See [trusted dependencies](/docs/pm/lifecycle).


# Configuring a monorepo using workspaces
Source: https://bun.com/docs/guides/install/workspaces



Bun's package manager supports npm `"workspaces"`. Workspaces split a codebase into distinct packages that live in the same repository, can depend on each other, and (when possible) share a `node_modules` directory.

Clone [this sample project](https://github.com/colinhacks/bun-workspaces) to experiment with workspaces.

***

The root `package.json` should not contain `"dependencies"`, `"devDependencies"`, or other dependency fields. Each package should be self-contained and declare its own dependencies. It's conventional to declare `"private": true` to avoid accidentally publishing the root package to `npm`.

```json package.json icon="file-json" theme={"theme":{"light":"github-light","dark":"dracula"}}
{
  "name": "my-monorepo",
  "private": true,
  "workspaces": ["packages/*"]
}
```

***

It's common to place all packages in a `packages` directory. The `"workspaces"` field in `package.json` supports glob patterns, so `packages/*` treats each subdirectory of `packages` as a separate *package* (also known as a workspace).

```txt File Tree icon="folder-tree" theme={"theme":{"light":"github-light","dark":"dracula"}}
.
├── package.json
├── node_modules
└── packages
    ├── stuff-a
    │   └── package.json
    └── stuff-b
        └── package.json
```

***

To add dependencies between workspaces, use the `"workspace:*"` syntax. The following adds `stuff-a` as a dependency of `stuff-b`.

```json packages/stuff-b/package.json icon="file-json" theme={"theme":{"light":"github-light","dark":"dracula"}}
{
  "name": "stuff-b",
  "dependencies": {
    "stuff-a": "workspace:*" // [!code ++]
  }
}
```

***

Once you add the dependency, run `bun install` from the project root to install dependencies for all workspaces.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun install
```

***

To add npm dependencies to a particular workspace, `cd` to that directory and run `bun add` as you normally would. Bun detects that you are in a workspace, adds the dependency to that workspace's `package.json`, and updates the root lockfile. New workspaces use [isolated installs](/docs/pm/isolated-installs) by default, so Bun installs the package into the root `node_modules/.bun` store and symlinks it from the workspace's own `node_modules`. With `--linker hoisted`, Bun hoists the package into the root `node_modules` instead.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
cd packages/stuff-a
bun add zod
```

***

See [`bun install`](/docs/pm/cli/install).


# Generate a yarn-compatible lockfile
Source: https://bun.com/docs/guides/install/yarnlock



<Note>
  Bun v1.1.39 introduced `bun.lock`, a JSONC-formatted lockfile. It is human-readable and git-diffable without
  configuration, at no cost to performance. In 1.2.0+ it is the default format for new projects. See [the lockfile
  docs](/docs/pm/lockfile#text-based-lockfile).
</Note>

***

Use the `--yarn` flag to generate a Yarn-compatible `yarn.lock` file (in addition to `bun.lock{b}`).

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun install --yarn
```

***

To set this as the default behavior, add the following to your `bunfig.toml` file.

```toml bunfig.toml icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[install.lockfile]
print = "yarn"
```

***

To print a Yarn lockfile to your console without writing it to disk, "run" your `bun.lockb` with `bun`.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun bun.lockb
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
# bun ./bun.lockb --hash: 9BFBF11D86084AAB-9418b03ff880c569-390CE6459EACEC9A...

abab@^2.0.6:
  version "2.0.6"
  resolved "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz"
  integrity sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvH...
```

***

See [`bun install`](/docs/pm/cli/install).


# Parse command-line arguments
Source: https://bun.com/docs/guides/process/argv



The *argument vector* is the list of arguments passed to the program when it is run. It is available as `Bun.argv`.

```ts cli.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
console.log(Bun.argv);
```

***

Running this file with arguments results in the following:

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun run cli.ts --flag1 --flag2 value
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
[ "/path/to/bun", "/path/to/cli.ts", "--flag1", "--flag2", "value" ]
```

***

To parse `argv` into a more useful format, use `util.parseArgs`.

```ts cli.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { parseArgs } from "util";

const { values, positionals } = parseArgs({
  args: Bun.argv,
  options: {
    flag1: {
      type: "boolean",
    },
    flag2: {
      type: "string",
    },
  },
  strict: true,
  allowPositionals: true,
});

console.log(values);
console.log(positionals);
```

***

Running `cli.ts` with the same arguments prints the parsed values.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun run cli.ts --flag1 --flag2 value
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
[Object: null prototype] {
  flag1: true,
  flag2: "value",
}
[ "/path/to/bun", "/path/to/cli.ts" ]
```


# Listen for CTRL+C
Source: https://bun.com/docs/guides/process/ctrl-c



The `ctrl+c` shortcut sends an *interrupt signal* to the running process. Intercept it by listening for the `SIGINT` event. To close the process, you must explicitly call `process.exit()`.

```ts process.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
process.on("SIGINT", () => {
  console.log("Ctrl-C was pressed");
  process.exit();
});
```

***

See [Utils](/docs/runtime/utils) for more utilities.


# Spawn a child process and communicate using IPC
Source: https://bun.com/docs/guides/process/ipc



Use [`Bun.spawn()`](/docs/runtime/child-process) to spawn a child process. When spawning a second `bun` process, you can open a direct inter-process communication (IPC) channel between the two processes.

<Note>
  To communicate with a Node.js process, set `serialization: "json"` in `Bun.spawn`. Use `process.execPath` to get a
  path to the currently running `bun` executable.
</Note>

```ts parent.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
const child = Bun.spawn(["bun", "child.ts"], {
  ipc(message) {
    /**
     * The message received from the sub process
     **/
  },
});
```

***

The parent process sends messages to the subprocess with the `.send()` method on the returned `Subprocess` instance. The `ipc` handler also receives the subprocess as its second argument.

```ts parent.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
const childProc = Bun.spawn(["bun", "child.ts"], {
  ipc(message, childProc) {
    /**
     * The message received from the sub process
     **/
    childProc.send("Respond to child");
  },
});

childProc.send("I am your father"); // The parent can send messages to the child as well
```

***

The child process sends messages to its parent with `process.send()` and receives messages with `process.on("message")`. This is the same API used for `child_process.fork()` in Node.js.

```ts child.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
process.send("Hello from child as string");
process.send({ message: "Hello from child as object" });

process.on("message", message => {
  // print message from parent
  console.log(message);
});
```

***

By default, Bun serializes messages with the JSC `serialize` API. This API supports everything [`structuredClone` supports](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm), including strings, typed arrays, and objects. The API does not support transferring ownership of objects.

```ts child.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
// send a string
process.send("Hello from child as string");

// send an object
process.send({ message: "Hello from child as object" });
```

***

See [Child processes](/docs/runtime/child-process).


# Get the process uptime in nanoseconds
Source: https://bun.com/docs/guides/process/nanoseconds



Use `Bun.nanoseconds()` to get the total number of nanoseconds the `bun` process has been alive.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
Bun.nanoseconds();
```

***

See [Utils](/docs/runtime/utils) for more utilities.


# Listen to OS signals
Source: https://bun.com/docs/guides/process/os-signals



Bun supports the Node.js `process` global, including the `process.on()` method for listening to OS signals.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
process.on("SIGINT", () => {
  console.log("Received SIGINT");
});
```

***

To run code when the process exits, listen for these events:

* [`"beforeExit"`](https://nodejs.org/api/process.html#event-beforeexit): Bun emits this event when the event loop empties.
* [`"exit"`](https://nodejs.org/api/process.html#event-exit): Bun emits this event when the event loop empties or when `process.exit()` is called.

Neither event is emitted when the process is killed by a signal it has no listener for. To run cleanup on a signal, listen for that signal and call `process.exit()` from the listener.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
process.on("beforeExit", code => {
  console.log(`Event loop is empty!`);
});

process.on("exit", code => {
  console.log(`Process is exiting with code ${code}`);
});
```

***

See [Utils](/docs/runtime/utils) for more utilities.


# Spawn a child process
Source: https://bun.com/docs/guides/process/spawn



Use [`Bun.spawn()`](/docs/runtime/child-process) to spawn a child process.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const proc = Bun.spawn(["echo", "hello"]);

// await completion
await proc.exited;
```

***

The second argument is a configuration object.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const proc = Bun.spawn(["echo", "Hello, world!"], {
  cwd: "/tmp",
  env: { FOO: "bar" },
  onExit(proc, exitCode, signalCode, error) {
    // exit handler
  },
});
```

***

By default, `proc.stdout` is a `ReadableStream` of the child process's `stdout`.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const proc = Bun.spawn(["echo", "hello"]);

const output = await proc.stdout.text();
output; // => "hello\n"
```

***

See [Child processes](/docs/runtime/child-process).


# Read stderr from a child process
Source: https://bun.com/docs/guides/process/spawn-stderr



When you spawn a child process with [`Bun.spawn()`](/docs/runtime/child-process), it inherits the `stderr` of the spawning process. To read and handle `stderr` instead, set the `stderr` option to `"pipe"`.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const proc = Bun.spawn(["echo", "hello"], {
  stderr: "pipe",
});

proc.stderr; // => ReadableStream
```

***

To read `stderr` until the child process exits, use `.text()`.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const proc = Bun.spawn(["echo", "hello"], {
  stderr: "pipe",
});

const errors: string = await proc.stderr.text();
if (errors) {
  // handle errors
}
```

***

See [Child processes](/docs/runtime/child-process).


# Read stdout from a child process
Source: https://bun.com/docs/guides/process/spawn-stdout



When you spawn a child process with [`Bun.spawn()`](/docs/runtime/child-process), `proc.stdout` is a `ReadableStream` of the child's `stdout`.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const proc = Bun.spawn(["echo", "hello"]);

const output = await proc.stdout.text();
output; // => "hello\n"
```

***

To pipe the child process's `stdout` to the parent's `stdout` instead, set the `stdout` option to `"inherit"`.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const proc = Bun.spawn(["echo", "hello"], {
  stdout: "inherit",
});
```

***

See [Child processes](/docs/runtime/child-process).


# Read from stdin
Source: https://bun.com/docs/guides/process/stdin



In Bun, the `console` object is an `AsyncIterable` that yields lines from `stdin`.

```ts index.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
const prompt = "Type something: ";
process.stdout.write(prompt);
for await (const line of console) {
  console.log(`You typed: ${line}`);
  process.stdout.write(prompt);
}
```

***

Running this file starts a never-ending interactive prompt that echoes whatever you type.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun run index.ts
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
Type something: hello
You typed: hello
Type something: hello again
You typed: hello again
```

***

Bun also exposes `stdin` as a `BunFile`, `Bun.stdin`. Use it to incrementally read large inputs piped into the `bun` process.

Chunks aren't guaranteed to be split line-by-line.

```ts stdin.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
for await (const chunk of Bun.stdin.stream()) {
  // chunk is Uint8Array
  // this converts it to text (assumes UTF-8 encoding)
  const chunkText = Buffer.from(chunk).toString();
  console.log(`Chunk: ${chunkText}`);
}
```

***

Running `stdin.ts` prints whatever is piped into it.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
echo "hello" | bun run stdin.ts
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
Chunk: hello
```

***

See [Utils](/docs/runtime/utils) for more utilities.


# Read a file to an ArrayBuffer
Source: https://bun.com/docs/guides/read-file/arraybuffer



The `Bun.file()` function accepts a path and returns a `BunFile` instance. `BunFile` extends `Blob`, so you can read the file lazily in a variety of formats. Use `.arrayBuffer()` to read the file as an `ArrayBuffer`.

```ts index.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
const path = "/path/to/package.json";
const file = Bun.file(path);

const buffer = await file.arrayBuffer();
```

***

Read the binary content of the `ArrayBuffer` with a typed array, such as `Int8Array`. For `Uint8Array`, use [`.bytes()`](/docs/guides/read-file/uint8array).

```ts index.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
const buffer = await file.arrayBuffer();
const bytes = new Int8Array(buffer);

bytes[0];
bytes.length;
```

***

See [Typed arrays](/docs/runtime/binary-data#typedarray) for more on working with typed arrays in Bun.


# Read a file to a Buffer
Source: https://bun.com/docs/guides/read-file/buffer



The `Bun.file()` function accepts a path and returns a `BunFile` instance. `BunFile` extends `Blob`, so you can read the file lazily in a variety of formats.

To read the file into a `Buffer`, read it as an `ArrayBuffer` with `.arrayBuffer()`, then pass the result to `Buffer.from()`.

```ts index.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
const path = "/path/to/package.json";
const file = Bun.file(path);

const arrbuf = await file.arrayBuffer();
const buffer = Buffer.from(arrbuf);
```

***

See [Buffer](/docs/runtime/binary-data#buffer) for more on working with `Buffer` and other binary data formats in Bun.


# Check if a file exists
Source: https://bun.com/docs/guides/read-file/exists



The `Bun.file()` function accepts a path and returns a `BunFile` instance. Use the `.exists()` method to check if a file exists at the given path.

```ts index.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
const path = "/path/to/package.json";
const file = Bun.file(path);

await file.exists(); // boolean;
```

***

See [File I/O](/docs/runtime/file-io) for more on working with `BunFile`.


# Read a JSON file
Source: https://bun.com/docs/guides/read-file/json



The `Bun.file()` function accepts a path and returns a `BunFile` instance. `BunFile` extends `Blob`, so you can read the file lazily in a variety of formats. Use `.json()` to read and parse the contents of a `.json` file as a plain object.

Bun sets the MIME type of the `BunFile` accordingly.

```ts index.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
const path = "/path/to/package.json";
const file = Bun.file(path);

const contents = await file.json();
// { name: "my-package" }

file.type; // => "application/json;charset=utf-8";
```


# Get the MIME type of a file
Source: https://bun.com/docs/guides/read-file/mime



The `Bun.file()` function accepts a path and returns a `BunFile` instance. The `BunFile` class extends `Blob`, so use the `.type` property to read the MIME type.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const file = Bun.file("./package.json");
file.type; // application/json;charset=utf-8

const html = Bun.file("./index.html");
html.type; // text/html;charset=utf-8

const image = Bun.file("./image.png");
image.type; // image/png
```

***

See [File I/O](/docs/runtime/file-io) for more on working with `BunFile`.


# Read a file as a ReadableStream
Source: https://bun.com/docs/guides/read-file/stream



The `Bun.file()` function accepts a path and returns a `BunFile` instance. `BunFile` extends `Blob`, so you can read the file lazily in a variety of formats. Use `.stream()` to consume the file incrementally as a `ReadableStream`.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const path = "/path/to/package.json";
const file = Bun.file(path);

const stream = file.stream();
```

***

The stream is an [async iterable](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#the_async_iterator_and_async_iterable_protocols), so you can read its chunks with `for await`.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
for await (const chunk of stream) {
  chunk; // => Uint8Array
}
```

***

See [Streams](/docs/runtime/streams) for more on working with streams in Bun.


# Read a file as a string
Source: https://bun.com/docs/guides/read-file/string



The `Bun.file()` function accepts a path and returns a `BunFile` instance. `BunFile` extends `Blob`, so you can read the file lazily in a variety of formats. Use `.text()` to read the contents as a string.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const path = "/path/to/file.txt";
const file = Bun.file(path);

const text = await file.text();
// string
```

***

Bun resolves relative paths from the current working directory.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const path = "./file.txt";
const file = Bun.file(path);
```


# Read a file to a Uint8Array
Source: https://bun.com/docs/guides/read-file/uint8array



The `Bun.file()` function accepts a path and returns a `BunFile` instance. `BunFile` extends `Blob`, so you can read the file lazily in a variety of formats.

To read the file into a `Uint8Array`, use `.bytes()`.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const path = "/path/to/package.json";
const file = Bun.file(path);

const byteArray = await file.bytes();

byteArray[0]; // first byte
byteArray.length; // length of byteArray
```

***

See [Typed arrays](/docs/runtime/binary-data#typedarray) for more on working with `Uint8Array` and other binary data formats in Bun.


# Watch a directory for changes
Source: https://bun.com/docs/guides/read-file/watch



Bun implements the `node:fs` module, including the `fs.watch` function for listening for file system changes.

The following code listens for changes to files in the current directory. By default the watch is *shallow*: it does not detect changes to files in subdirectories.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
import { watch } from "fs";

const watcher = watch(import.meta.dir, (event, filename) => {
  console.log(`Detected ${event} in ${filename}`);
});
```

***

To listen for changes in subdirectories, pass the `recursive: true` option to `fs.watch`.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
import { watch } from "fs";

const watcher = watch(import.meta.dir, { recursive: true }, (event, relativePath) => {
  console.log(`Detected ${event} in ${relativePath}`);
});
```

***

With the `node:fs/promises` module, you can listen for changes with `for await...of` instead of a callback.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
import { watch } from "fs/promises";

const watcher = watch(import.meta.dir);
for await (const event of watcher) {
  console.log(`Detected ${event.eventType} in ${event.filename}`);
}
```

***

To stop listening for changes, call `watcher.close()`. It's common to do this when the process receives a `SIGINT` signal, such as when the user presses Ctrl-C.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
import { watch } from "fs";

const watcher = watch(import.meta.dir, (event, filename) => {
  console.log(`Detected ${event} in ${filename}`);
});

process.on("SIGINT", () => {
  // close watcher when Ctrl-C is pressed
  console.log("Closing watcher...");
  watcher.close();

  process.exit(0);
});
```

***

See the [Node.js documentation](https://nodejs.org/api/fs.html#fswatchfilename-options-listener) for `fs.watch`.


# Build-time constants with --define
Source: https://bun.com/docs/guides/runtime/build-time-constants



Pass `--define` to `bun build` or `bun build --compile` to inject build-time constants into your application. Use it to embed metadata like build versions, timestamps, or configuration flags directly into your compiled executables.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun build --compile --define BUILD_VERSION='"1.2.3"' --define BUILD_TIME='"2024-01-15T10:30:00Z"' src/index.ts --outfile myapp
```

***

## Why use build-time constants?

Bun embeds build-time constants directly into your compiled code, making them:

* **Zero runtime overhead** - No environment variable lookups or file reads
* **Immutable** - Values are baked into the binary at compile time
* **Optimizable** - Dead code elimination can remove unused branches
* **Secure** - No external dependencies or configuration files to manage

Build-time constants are similar to `gcc -D` or `#define` in C/C++, but for JavaScript/TypeScript.

***

## Basic usage

### With `bun build`

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
# Bundle with build-time constants
bun build --define BUILD_VERSION='"1.0.0"' --define NODE_ENV='"production"' src/index.ts --outdir ./dist
```

### With `bun build --compile`

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
# Compile to executable with build-time constants
bun build --compile --define BUILD_VERSION='"1.0.0"' --define BUILD_TIME='"2024-01-15T10:30:00Z"' src/cli.ts --outfile mycli
```

### JavaScript API

```ts build.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
await Bun.build({
  entrypoints: ["./src/index.ts"],
  outdir: "./dist",
  define: {
    BUILD_VERSION: '"1.0.0"',
    BUILD_TIME: '"2024-01-15T10:30:00Z"',
    DEBUG: "false",
  },
});
```

***

## Common use cases

### Version information

Embed version and build metadata directly into your executable:

<CodeGroup>
  ```ts src/version.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
  // These constants are replaced at build time
  declare const BUILD_VERSION: string;
  declare const BUILD_TIME: string;
  declare const GIT_COMMIT: string;

  export function getVersion() {
    return {
      version: BUILD_VERSION,
      buildTime: BUILD_TIME,
      commit: GIT_COMMIT,
    };
  }
  ```

  ```sh Build command theme={"theme":{"light":"github-light","dark":"dracula"}}
  bun build --compile \
    --define BUILD_VERSION='"1.2.3"' \
    --define BUILD_TIME='"2024-01-15T10:30:00Z"' \
    --define GIT_COMMIT='"abc123"' \
    src/cli.ts --outfile mycli
  ```
</CodeGroup>

### Feature flags

Use build-time constants to enable/disable features:

```ts src/app.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
// Replaced at build time
declare const ENABLE_ANALYTICS: boolean;
declare const ENABLE_DEBUG: boolean;

function trackEvent(event: string) {
  if (ENABLE_ANALYTICS) {
    // This entire block is removed if ENABLE_ANALYTICS is false
    console.log("Tracking:", event);
  }
}

if (ENABLE_DEBUG) {
  console.log("Debug mode enabled");
}
```

```sh theme={"theme":{"light":"github-light","dark":"dracula"}}
# Production build - analytics enabled, debug disabled
bun build --compile --define ENABLE_ANALYTICS=true --define ENABLE_DEBUG=false src/app.ts --outfile app-prod

# Development build - analytics disabled, debug enabled
bun build --compile --define ENABLE_ANALYTICS=false --define ENABLE_DEBUG=true src/app.ts --outfile app-dev
```

### Configuration

Replace configuration objects at build time:

```ts src/app.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
declare const CONFIG: {
  apiUrl: string;
  timeout: number;
  retries: number;
};

// CONFIG is replaced with the actual object at build time
const response = await fetch(CONFIG.apiUrl, {
  timeout: CONFIG.timeout,
});
```

```sh theme={"theme":{"light":"github-light","dark":"dracula"}}
bun build --compile --define 'CONFIG={"apiUrl":"https://api.example.com","timeout":5000,"retries":3}' src/app.ts --outfile app
```

***

## Advanced patterns

### Environment-specific builds

Create different executables for different environments:

```json theme={"theme":{"light":"github-light","dark":"dracula"}}
{
  "scripts": {
    "build:dev": "bun build --compile --define NODE_ENV='\"development\"' --define API_URL='\"http://localhost:3000\"' src/app.ts --outfile app-dev",
    "build:staging": "bun build --compile --define NODE_ENV='\"staging\"' --define API_URL='\"https://staging.example.com\"' src/app.ts --outfile app-staging",
    "build:prod": "bun build --compile --define NODE_ENV='\"production\"' --define API_URL='\"https://api.example.com\"' src/app.ts --outfile app-prod"
  }
}
```

### Using shell commands for dynamic values

Generate build-time constants from shell commands:

```sh theme={"theme":{"light":"github-light","dark":"dracula"}}
# Use git to get current commit and timestamp
bun build --compile \
  --define BUILD_VERSION="\"$(git describe --tags --always)\"" \
  --define BUILD_TIME="\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"" \
  --define GIT_COMMIT="\"$(git rev-parse HEAD)\"" \
  src/cli.ts --outfile mycli
```

### Build automation script

Create a build script that injects build metadata:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
// build.ts
import { $ } from "bun";

const version = await $`git describe --tags --always`.text();
const buildTime = new Date().toISOString();
const gitCommit = await $`git rev-parse HEAD`.text();

await Bun.build({
  entrypoints: ["./src/cli.ts"],
  outdir: "./dist",
  define: {
    BUILD_VERSION: JSON.stringify(version.trim()),
    BUILD_TIME: JSON.stringify(buildTime),
    GIT_COMMIT: JSON.stringify(gitCommit.trim()),
  },
});

console.log(`Built with version ${version.trim()}`);
```

***

## Important considerations

### Value format

Values can be JSON, identifiers, or property paths such as `globalThis` or `console.log`. Bun parses each value and inlines it as a JavaScript expression:

```sh theme={"theme":{"light":"github-light","dark":"dracula"}}
# ✅ Strings must be JSON-quoted
--define VERSION='"1.0.0"'

# ✅ Numbers are JSON literals
--define PORT=3000

# ✅ Booleans are JSON literals
--define DEBUG=true

# ✅ Objects and arrays (use single quotes to wrap the JSON)
--define 'CONFIG={"host":"localhost","port":3000}'

# ✅ Arrays work too
--define 'FEATURES=["auth","billing","analytics"]'

# ❌ This won't work - missing quotes around string
--define VERSION=1.0.0
```

### Property keys

Keys can be property access patterns as well as plain identifiers:

```sh theme={"theme":{"light":"github-light","dark":"dracula"}}
# ✅ Replace process.env.NODE_ENV with "production"
--define 'process.env.NODE_ENV="production"'

# ✅ Replace process.env.API_KEY with the actual key
--define 'process.env.API_KEY="abc123"'

# ✅ Replace nested properties
--define 'window.myApp.version="1.0.0"'
```

Use this to inline environment variables at build time:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
// Before compilation
if (process.env.NODE_ENV === "production") {
  console.log("Production mode");
}

// After compilation with --define 'process.env.NODE_ENV="production"'
if ("production" === "production") {
  console.log("Production mode");
}

// After optimization
console.log("Production mode");
```

### TypeScript declarations

For TypeScript projects, declare your constants to avoid type errors:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
// types/build-constants.d.ts
declare const BUILD_VERSION: string;
declare const BUILD_TIME: string;
declare const NODE_ENV: "development" | "staging" | "production";
declare const DEBUG: boolean;
```

### Cross-platform compatibility

When building for multiple platforms, constants work the same way:

```sh theme={"theme":{"light":"github-light","dark":"dracula"}}
# Linux
bun build --compile --target=bun-linux-x64 --define PLATFORM='"linux"' src/app.ts --outfile app-linux

# macOS
bun build --compile --target=bun-darwin-x64 --define PLATFORM='"darwin"' src/app.ts --outfile app-macos

# Windows
bun build --compile --target=bun-windows-x64 --define PLATFORM='"windows"' src/app.ts --outfile app-windows.exe
```

***

## Related

* [Define constants at runtime](/docs/guides/runtime/define-constant) - Using `--define` with `bun run`
* [Building executables](/docs/bundler/executables) - Complete guide to `bun build --compile`
* [Bundler API](/docs/bundler) - Full bundler documentation including `define` option


# Install and run Bun in GitHub Actions
Source: https://bun.com/docs/guides/runtime/cicd



Use the official [`setup-bun`](https://github.com/oven-sh/setup-bun) GitHub Action to install `bun` in your GitHub Actions runner.

```yaml workflow.yml icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
name: my-workflow
jobs:
  my-job:
    name: my-job
    runs-on: ubuntu-latest
    steps:
      # ...
      - uses: actions/checkout@v4
      - uses: oven-sh/setup-bun@v2 # [!code ++]

      # run any `bun` or `bunx` command
      - run: bun install # [!code ++]
      - run: bun index.ts # [!code ++]
      - run: bun run build # [!code ++]
```

***

To specify a version of Bun to install:

```yaml workflow.yml icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
name: my-workflow
jobs:
  my-job:
    name: my-job
    runs-on: ubuntu-latest
    steps:
      # ...
      - uses: oven-sh/setup-bun@v2
        with: # [!code ++]
          bun-version: 1.3.3 # or "latest", "canary", "1.3.x" # [!code ++]
```

***

See the [`setup-bun` README](https://github.com/oven-sh/setup-bun) for all options.


# Codesign a single-file JavaScript executable on macOS
Source: https://bun.com/docs/guides/runtime/codesign-macos-executable

Fix the "can't be opened because it is from an unidentified developer" Gatekeeper warning when running your JavaScript executable.

Compile your executable using the `--compile` flag.

```sh theme={"theme":{"light":"github-light","dark":"dracula"}}
bun build --compile ./path/to/entry.ts --outfile myapp
```

***

List your available signing identities. You'll pass one of these to `codesign` in a later step. This command requires macOS.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
security find-identity -v -p codesigning
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
1. XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX "Developer ID Application: Your Name (ZZZZZZZZZZ)"
   1 valid identities found
```

***

Optional, but recommended: create an `entitlements.plist` file with the necessary permissions for the JavaScript engine to work correctly.

```xml entitlements.plist icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>com.apple.security.cs.allow-jit</key>
    <true/>
    <key>com.apple.security.cs.allow-unsigned-executable-memory</key>
    <true/>
    <key>com.apple.security.cs.disable-executable-page-protection</key>
    <true/>
    <key>com.apple.security.cs.allow-dyld-environment-variables</key>
    <true/>
    <key>com.apple.security.cs.disable-library-validation</key>
    <true/>
</dict>
</plist>
```

***

Sign your executable using the `codesign` command and verify it works.

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
codesign --entitlements entitlements.plist -vvvv --deep --sign "XXXXXXXXXX" ./myapp --force
codesign -vvv --verify ./myapp
```

***

For more information on macOS codesigning, refer to [Apple's Code Signing documentation](https://developer.apple.com/documentation/security/code_signing_services). For details about creating single-file executables with Bun, see [Standalone Executables](/docs/bundler/executables). This guide requires Bun v1.2.4 or newer.


# Define and replace static globals & constants
Source: https://bun.com/docs/guides/runtime/define-constant



The `--define` flag declares statically-analyzable constants and globals. It replaces all usages of an identifier or property in a JavaScript or TypeScript file with a constant value, and works both at runtime and in `bun build`. It's similar to `#define` in C/C++, but for JavaScript.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun --define process.env.NODE_ENV="'production'" src/index.ts # Runtime
bun build --define process.env.NODE_ENV="'production'" src/index.ts # Build
```

***

Bun uses these statically-known values for dead code elimination and other optimizations.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
if (process.env.NODE_ENV === "production") {
  console.log("Production mode");
} else {
  console.log("Development mode");
}
```

***

Before the code reaches the JavaScript engine, Bun replaces `process.env.NODE_ENV` with `"production"`.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
if ("production" === "production") { // [!code ++]
  console.log("Production mode");
} else {
  console.log("Development mode");
}
```

***

Bun's optimizing transpiler also does basic constant folding.

Since `"production" === "production"` is always `true`, Bun replaces the entire expression with the `true` value and drops the unreachable `else` branch.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
if (true) { // [!code ++]
  console.log("Production mode");
}
```

***

To also collapse the surrounding `if` scaffolding down to the following output, pass `--minify-syntax` (also enabled by `--minify`):

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun build --define process.env.NODE_ENV="'production'" --minify-syntax src/index.ts
```

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
console.log("Production mode");
```

***

## What types of values are supported?

Values can be strings, identifiers, properties, or JSON.

### Replace global identifiers

To replace all usages of `window` with `undefined`:

```sh theme={"theme":{"light":"github-light","dark":"dracula"}}
bun --define window="undefined" src/index.ts
```

This replacement is useful for server-side rendering (SSR), or to make sure code doesn't depend on the `window` object.

```js theme={"theme":{"light":"github-light","dark":"dracula"}}
if (typeof window !== "undefined") {
  console.log("Client-side code");
} else {
  console.log("Server-side code");
}
```

The value can also be another identifier. For example, to replace all usages of `global` with `globalThis`:

```sh theme={"theme":{"light":"github-light","dark":"dracula"}}
bun --define global="globalThis" src/index.ts
```

`global` is a global object in Node.js, but not in web browsers, so this replacement fixes code that assumes `global` is available.

### Replace values with JSON

`--define` can also replace values with JSON objects and arrays.

To replace all usages of `AWS` with the JSON object `{"ACCESS_KEY":"abc","SECRET_KEY":"def"}`:

```sh theme={"theme":{"light":"github-light","dark":"dracula"}}
# JSON
bun --define AWS='{"ACCESS_KEY":"abc","SECRET_KEY":"def"}' src/index.ts
```

Bun transforms these into the equivalent JavaScript code.

From:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
console.log(AWS.ACCESS_KEY); // => "abc"
```

To:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
console.log({ ACCESS_KEY: "abc", SECRET_KEY: "def" }.ACCESS_KEY);
```

### Replace values with other properties

You can also pass properties to the `--define` flag.

For example, to replace all usages of `console.write` with `console.log`:

```sh theme={"theme":{"light":"github-light","dark":"dracula"}}
bun --define console.write=console.log src/index.ts
```

That transforms the following input:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
console.write("Hello, world!");
```

Into the following output:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
console.log("Hello, world!");
```

## How is this different than setting a variable?

You can also set `process.env.NODE_ENV` to `"production"` in your code, but that doesn't help with dead code elimination. In JavaScript, property accesses can have side effects. Getters & setters can be functions, and even dynamically defined (due to prototype chains and Proxy). Even if you set `process.env.NODE_ENV` to `"production"`, static analysis tools can't assume it is still `"production"` on the next line.

## How is this different than find-and-replace or string replacement?

The `--define` flag operates on the AST (Abstract Syntax Tree), not on text. The replacement happens during transpilation, so it participates in optimizations like dead code elimination.

String replacement tools tend to have escaping issues and replace unintended parts of the code.


# Delete directories
Source: https://bun.com/docs/guides/runtime/delete-directory



To recursively delete a directory and all its contents, use `rm` from `node:fs/promises`. This is like running `rm -rf` in JavaScript.

```ts delete-directory.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { rm } from "node:fs/promises";

// Delete a directory and all its contents
await rm("path/to/directory", { recursive: true, force: true });
```

***

These options configure the deletion behavior:

* `recursive: true` - Delete subdirectories and their contents
* `force: true` - Don't throw errors if the directory doesn't exist

Omit `force` to get an error if the directory doesn't exist:

```ts delete-directory.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
try {
  await rm("path/to/directory", { recursive: true });
} catch (error) {
  if (error.code === "ENOENT") {
    console.log("Directory doesn't exist");
  } else {
    throw error;
  }
}
```

***

See [File I/O](/docs/runtime/file-io) for more filesystem operations.


# Delete files
Source: https://bun.com/docs/guides/runtime/delete-file



To delete a file, use `Bun.file(path).delete()`.

```ts delete-file.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
// Delete a file
const file = Bun.file("path/to/file.txt");
await file.delete();

// Now the file doesn't exist
const exists = await file.exists();
// => false
```

***

See [File I/O](/docs/runtime/file-io) for more filesystem operations.


# Inspect memory usage using V8 heap snapshots
Source: https://bun.com/docs/guides/runtime/heap-snapshot



Bun implements V8's heap snapshot API. Use it to capture the heap at runtime and debug memory leaks in your JavaScript/TypeScript application.

```ts snapshot.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import v8 from "node:v8";

// Creates a heap snapshot file with an auto-generated name
const snapshotPath = v8.writeHeapSnapshot();
console.log(`Heap snapshot written to: ${snapshotPath}`);
```

***

## Inspect memory in Chrome DevTools

To view V8 heap snapshots in Chrome DevTools:

1. Open Chrome DevTools (F12 or right-click and select "Inspect")
2. Go to the "Memory" tab
3. Click the "Load" button (folder icon)
4. Select your `.heapsnapshot` file

<Frame>
  <img alt="Chrome DevTools Memory Tab" />
</Frame>


# Import a HTML file as text
Source: https://bun.com/docs/guides/runtime/import-html



To import a `.html` file in Bun as a text file, use the `type: "text"` attribute in the import statement.

```ts file.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import html from "./file.html" with { type: "text" };

console.log(html); // <!DOCTYPE html><html><head>...
```

With hot module reloading or watch mode, Bun reloads whenever `./file.html` changes.


# Import a JSON file
Source: https://bun.com/docs/guides/runtime/import-json



Bun natively supports `.json` imports.

```json package.json icon="file-json" theme={"theme":{"light":"github-light","dark":"dracula"}}
{
  "name": "bun",
  "version": "1.0.0",
  "author": {
    "name": "John Dough",
    "email": "john@dough.com"
  }
}
```

***

Import the file like any other source file.

```ts data.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import data from "./package.json";

data.name; // => "bun"
data.version; // => "1.0.0"
data.author.name; // => "John Dough"
```

***

Bun also supports [Import Attributes](https://github.com/tc39/proposal-import-attributes/) and [JSON modules](https://github.com/tc39/proposal-json-modules) syntax.

```ts data.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import data from "./package.json" with { type: "json" };

data.name; // => "bun"
data.version; // => "1.0.0"
data.author.name; // => "John Dough"
```

***

See [TypeScript](/docs/runtime/typescript) for more on using TypeScript with Bun.


# Import a JSON5 file
Source: https://bun.com/docs/guides/runtime/import-json5



Bun natively supports `.json5` imports.

```json5 config.json5 icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
{
  // Comments are allowed
  database: {
    host: "localhost",
    port: 5432,
    name: "myapp",
  },

  server: {
    port: 3000,
    timeout: 30,
  },

  features: {
    auth: true,
    rateLimit: true,
  },
}
```

***

Import the file like any other source file.

```ts config.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import config from "./config.json5";

config.database.host; // => "localhost"
config.server.port; // => 3000
config.features.auth; // => true
```

***

You can also use named imports to destructure top-level properties:

```ts config.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { database, server, features } from "./config.json5";

console.log(database.name); // => "myapp"
console.log(server.timeout); // => 30
console.log(features.rateLimit); // => true
```

***

For parsing JSON5 strings at runtime, use `Bun.JSON5.parse()`:

```ts config.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
const data = Bun.JSON5.parse(`{
  name: 'John Doe',
  age: 30,
  hobbies: [
    'reading',
    'coding',
  ],
}`);

console.log(data.name); // => "John Doe"
console.log(data.hobbies); // => ["reading", "coding"]
```

***

See [JSON5](/docs/runtime/json5) for the rest of Bun's JSON5 support.


# Import a TOML file
Source: https://bun.com/docs/guides/runtime/import-toml



Bun natively supports importing `.toml` files.

```toml data.toml icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
name = "bun"
version = "1.0.0"

[author]
name = "John Dough"
email = "john@dough.com"
```

***

Import the file like any other source file.

```ts data.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import data from "./data.toml";

data.name; // => "bun"
data.version; // => "1.0.0"
data.author.name; // => "John Dough"
```

***

See [TypeScript](/docs/runtime/typescript) for more on using TypeScript with Bun.


# Import an XML file
Source: https://bun.com/docs/guides/runtime/import-xml



Bun natively supports `.xml` imports.

```xml config.xml icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
<?xml version="1.0" encoding="UTF-8"?>
<config env="production">
  <database host="localhost" port="5432" name="myapp"/>
  <server port="3000" timeout="30"/>
  <feature name="auth"/>
  <feature name="rateLimit"/>
</config>
```

***

Import the file like any other source file. The module is the parsed document: one key for the root element, `"@name"` keys for attributes, and arrays for repeated elements. Every value is a string.

```ts config.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import doc from "./config.xml";

doc.config["@env"]; // => "production"
doc.config.database["@host"]; // => "localhost"
doc.config.server["@port"]; // => "3000"
doc.config.feature.map(f => f["@name"]); // => ["auth", "rateLimit"]
```

***

The root element is also available as a named import:

```ts config.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { config } from "./config.xml";

console.log(config.database["@name"]); // => "myapp"
console.log(Number(config.server["@timeout"])); // => 30
```

***

For parsing XML strings at runtime, use `Bun.XML.parse()`:

```ts config.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
const data = Bun.XML.parse(`
  <user id="7">
    <name>John Doe</name>
    <hobby>reading</hobby>
    <hobby>coding</hobby>
  </user>
`);

console.log(data.user.name); // => "John Doe"
console.log(data.user.hobby); // => ["reading", "coding"]
console.log(data.user["@id"]); // => "7"
```

***

See [XML](/docs/runtime/xml) for the rest of Bun's XML support, including the ordered `{ compact: false }` node tree and `Bun.XML.stringify()`.


# Import a YAML file
Source: https://bun.com/docs/guides/runtime/import-yaml



Bun natively supports `.yaml` and `.yml` imports.

```yaml config.yaml icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
database:
  host: localhost
  port: 5432
  name: myapp

server:
  port: 3000
  timeout: 30

features:
  auth: true
  rateLimit: true
```

***

Import the file like any other source file.

```ts config.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import config from "./config.yaml";

config.database.host; // => "localhost"
config.server.port; // => 3000
config.features.auth; // => true
```

***

You can also destructure top-level properties with named imports:

```ts config.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { database, server, features } from "./config.yaml";

console.log(database.name); // => "myapp"
console.log(server.timeout); // => 30
console.log(features.rateLimit); // => true
```

***

Bun also supports [Import Attributes](https://github.com/tc39/proposal-import-attributes) syntax:

```ts config.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import config from "./config.yaml" with { type: "yaml" };

config.database.port; // => 5432
```

***

For parsing YAML strings at runtime, use `Bun.YAML.parse()`:

```ts config.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
const yamlString = `
name: John Doe
age: 30
hobbies:
  - reading
  - coding
`;

const data = Bun.YAML.parse(yamlString);
console.log(data.name); // => "John Doe"
console.log(data.hobbies); // => ["reading", "coding"]
```

***

## TypeScript Support

To add TypeScript support for your YAML imports, create a declaration file with `.d.ts` appended to the YAML filename (for example, `config.yaml` → `config.yaml.d.ts`):

```ts config.yaml.d.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
declare const contents: {
  database: {
    host: string;
    port: number;
    name: string;
  };
  server: {
    port: number;
    timeout: number;
  };
  features: {
    auth: boolean;
    rateLimit: boolean;
  };
};

export = contents;
```

***

See [YAML](/docs/runtime/yaml).


# Read environment variables
Source: https://bun.com/docs/guides/runtime/read-env



Access the current environment variables with `process.env`.

```ts index.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
process.env.API_TOKEN; // => "secret"
```

***

Bun also exposes these variables as `Bun.env`, an alias of `process.env`.

```ts index.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
Bun.env.API_TOKEN; // => "secret"
```

***

To print all currently-set environment variables, run `bun --print process.env`.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun --print process.env
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
ProcessEnv {
  BAZ: "stuff",
  FOOBAR: "aaaaaa",
  <lots more lines>
}
```

***

See [Environment variables](/docs/runtime/environment-variables).


# Set environment variables
Source: https://bun.com/docs/guides/runtime/set-env



Access the current environment variables with `process.env` or `Bun.env`.

```ts index.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
Bun.env.API_TOKEN; // => "secret"
process.env.API_TOKEN; // => "secret"
```

***

Set these variables in a `.env` file.

Bun reads the following files automatically (listed in order of increasing precedence).

* `.env`
* `.env.production`, `.env.development`, `.env.test` (depending on value of `NODE_ENV`)
* `.env.local` (not loaded when `NODE_ENV=test`)
* `.env.production.local`, `.env.development.local`, `.env.test.local` (depending on value of `NODE_ENV`)

```ini .env icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
FOO=hello
BAR=world
```

***

You can also set variables on the command line.

<CodeGroup>
  ```sh Linux/macOS icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
  FOO=helloworld bun run dev
  ```

  ```sh Windows icon="windows" theme={"theme":{"light":"github-light","dark":"dracula"}}
  # Using CMD
  set FOO=helloworld && bun run dev

  # Using PowerShell
  $env:FOO="helloworld"; bun run dev
  ```
</CodeGroup>

***

See [Environment variables](/docs/runtime/environment-variables).


# Run a Shell Command
Source: https://bun.com/docs/guides/runtime/shell



Bun Shell is a cross-platform bash-like shell built into Bun.

It runs shell commands from JavaScript and TypeScript. To get started, import the `$` function from the `bun` package.

```ts foo.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { $ } from "bun";

await $`echo Hello, world!`; // => "Hello, world!"
```

***

The `$` function is a tagged template literal that runs the command and returns a promise that resolves with the command's output.

```ts foo.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { $ } from "bun";

const output = await $`ls -l`.text();
console.log(output);
```

***

To iterate over each line of the output, use the `lines` method.

```ts foo.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { $ } from "bun";

for await (const line of $`ls -l`.lines()) {
  console.log(line);
}
```

***

See [Bun Shell](/docs/runtime/shell).


# Set a time zone in Bun
Source: https://bun.com/docs/guides/runtime/timezone



Bun supports programmatically setting a default time zone for the lifetime of the `bun` process. Set the `TZ` environment variable to a [valid time zone identifier](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones).

<Note>
  When running a file with `bun`, the time zone defaults to your system's configured local time zone.

  When running tests with `bun test`, Bun sets the time zone to `UTC` to make tests more deterministic.
</Note>

```ts process.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
process.env.TZ = "America/New_York";
```

***

You can also set `TZ` on the command line when running a Bun command.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
TZ=America/New_York bun run dev
```

***

Once `TZ` is set, every `Date` instance uses that time zone.

```ts process.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
new Date().getHours(); // => 18

process.env.TZ = "America/New_York";

new Date().getHours(); // => 21
```


# Re-map import paths
Source: https://bun.com/docs/guides/runtime/tsconfig-paths



Bun reads the `paths` field in your `tsconfig.json` to re-write import paths. This is useful for aliasing package names or avoiding long relative paths.

```json tsconfig.json icon="file-json" theme={"theme":{"light":"github-light","dark":"dracula"}}
{
  "compilerOptions": {
    "paths": {
      "my-custom-name": ["./node_modules/zod"],
      "@components/*": ["./src/components/*"]
    }
  }
}
```

***

With this `tsconfig.json`, Bun re-writes the following imports:

```ts tsconfig.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { z } from "my-custom-name"; // imports from "zod"
import { Button } from "@components/Button"; // imports from "./src/components/Button"
```

***

See [TypeScript](/docs/runtime/typescript).


# Install TypeScript declarations for Bun
Source: https://bun.com/docs/guides/runtime/typescript



To add TypeScript definitions for Bun's built-in APIs to your project, install `@types/bun`.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun add -d @types/bun # dev dependency
```

***

Here is the full set of recommended `compilerOptions` for a Bun project. With this `tsconfig.json`, you can use top-level await, extensioned or extensionless imports, and JSX.

```json tsconfig.json icon="file-json" theme={"theme":{"light":"github-light","dark":"dracula"}}
{
  "compilerOptions": {
    // Environment setup & latest features
    "lib": ["ESNext"],
    "target": "ESNext",
    "module": "Preserve",
    "moduleDetection": "force",
    "jsx": "react-jsx",
    "allowJs": true,
    "types": ["bun"],

    // Bundler mode
    "moduleResolution": "bundler",
    "allowImportingTsExtensions": true,
    "verbatimModuleSyntax": true,
    "noEmit": true,

    // Best practices
    "strict": true,
    "skipLibCheck": true,
    "noFallthroughCasesInSwitch": true,
    "noUncheckedIndexedAccess": true,
    "noImplicitOverride": true,

    // Some stricter flags (disabled by default)
    "noUnusedLocals": false,
    "noUnusedParameters": false,
    "noPropertyAccessFromIndexSignature": false
  }
}
```

***

If you're using TypeScript 6.0 or later, you also need `"types": ["bun"]` in your `compilerOptions`. See [TypeScript 6 and 7](/docs/typescript-6) for details.

***

See [TypeScript](/docs/runtime/typescript).


# Debugging Bun with the VS Code extension
Source: https://bun.com/docs/guides/runtime/vscode-debugger



<Note>The VS Code extension is buggy. We recommend the [web debugger](/docs/guides/runtime/web-debugger) instead.</Note>

Bun speaks the [WebKit Inspector Protocol](https://github.com/oven-sh/bun/blob/main/packages/bun-inspector-protocol/src/protocol/jsc/index.d.ts), so you can debug your code with an interactive debugger.

***

To install the extension, visit the [Bun for Visual Studio Code](https://marketplace.visualstudio.com/items?itemName=oven.bun-vscode) page on the VS Code marketplace website, then click Install.

<Frame>![VS Code extension](https://github.com/oven-sh/bun/assets/3084745/7c8c80e6-d49e-457a-a45e-45ebed946d56)</Frame>

***

Alternatively, search `bun-vscode` in the Extensions tab of VS Code.

<Frame>![VS Code extension](https://github.com/oven-sh/bun/assets/3084745/664b4c40-944c-4076-a4c2-f812aebd3dc9)</Frame>

***

Make sure you are installing the extension published by the verified Oven organization.

<Frame>![VS Code extension](https://github.com/oven-sh/bun/assets/3084745/73e6b09f-9ff1-4d85-b725-c5eb7215b6ae)</Frame>

***

Once installed, two Bun-specific commands appear in the Command Palette. To open the palette, click View > Command Palette, or type `Ctrl+Shift+P` (Windows, Linux) or `Cmd+Shift+P` (Mac).

***

The `Bun: Run File` command executes your code and prints the output to the Debug Console in VS Code. The command ignores breakpoints; this is similar to executing the file with `bun <file>` from the command line.

<Frame>![VS Code extension](https://github.com/oven-sh/bun/assets/3084745/1b2c7fd9-fbb9-486a-84d0-eb7ec135ded3)</Frame>

***

The `Bun: Debug File` command executes your code and prints the output to the Debug Console in VS Code. You can set breakpoints in your code by clicking to the left of a line number; a red dot appears.

When you run the file with `Bun: Debug File`, execution pauses at the breakpoint. You can inspect the variables in scope and step through the code line-by-line using the VS Code controls.

<Frame>![VS Code extension](https://github.com/oven-sh/bun/assets/3084745/c579a36c-eb21-4a58-bc9c-74612aad82af)</Frame>


# Debugging Bun with the web debugger
Source: https://bun.com/docs/guides/runtime/web-debugger



Bun speaks the [WebKit Inspector Protocol](https://github.com/oven-sh/bun/blob/main/packages/bun-inspector-protocol/src/protocol/jsc/index.d.ts). To enable debugging when running code with Bun, use the `--inspect` flag. Consider the following web server.

```ts server.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
Bun.serve({
  fetch(req) {
    console.log(req.url);
    return new Response("Hello, world!");
  },
});
```

***

Run the file with the `--inspect` flag.

This starts a WebSocket server on port `6499` (pass `--inspect=<port>` to use a different port). Debugging tools connect to this server to introspect the running Bun process.

Bun hosts a web-based debugger at [debug.bun.sh](https://debug.bun.sh). It is a modified version of WebKit's [Web Inspector Interface](https://webkit.org/web-inspector/web-inspector-interface/), which looks familiar to Safari users.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun --inspect server.ts
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
--------------------- Bun Inspector ---------------------
Listening:
  ws://localhost:6499/204e4940-4a8b-4ebc-8fee-1c66926bbc21
Inspect in browser:
  https://debug.bun.sh/#localhost:6499/204e4940-4a8b-4ebc-8fee-1c66926bbc21
--------------------- Bun Inspector ---------------------
```

***

Open the provided `debug.bun.sh` URL in your browser to start a debugging session. From this interface, you can view the source code of the running file, view and set breakpoints, and execute code with the built-in console.

<Frame>
  ![Screenshot of Bun debugger, Console
  tab](https://github.com/oven-sh/bun/assets/3084745/e6a976a8-80cc-4394-8925-539025cc025d)
</Frame>

***

Next, set a breakpoint. Open the Sources tab, which shows the code from earlier, and click line number `3` to set a breakpoint on the `console.log(req.url)` statement.

<Frame>
  ![screenshot of Bun debugger](https://github.com/oven-sh/bun/assets/3084745/3b69c7e9-25ff-4f9d-acc4-caa736862935)
</Frame>

***

Then visit [`http://localhost:3000`](http://localhost:3000) in your browser to send an HTTP request to the server. The page hangs because the program is paused at the breakpoint you set.

Note how the UI has changed.

<Frame>
  ![screenshot of Bun debugger](https://github.com/oven-sh/bun/assets/3084745/8b565e58-5445-4061-9bc4-f41090dfe769)
</Frame>

***

Use the console at the bottom to run arbitrary code in the context of the program, with full access to the variables in scope at the breakpoint.

<Frame>
  ![Bun debugger console](https://github.com/oven-sh/bun/assets/3084745/f4312b76-48ba-4a7d-b3b6-6205968ac681)
</Frame>

***

The right side of the Sources pane lists the local variables in scope; drill down to see their properties and methods. The screenshot shows the `req` variable.

<Frame>
  ![Bun debugger variables panel](https://github.com/oven-sh/bun/assets/3084745/63d7f843-5180-489c-aa94-87c486e68646)
</Frame>

***

The buttons in the upper left of the Sources pane control the program's execution.

<Frame>
  ![Bun debugger controls](https://github.com/oven-sh/bun/assets/3084745/41b76deb-7371-4461-9d5d-81b5a6d2f7a4)
</Frame>

***

Here's what each control flow button does.

* *Continue script execution* — runs the program until the next breakpoint or exception.
* *Step over* — continues to the next line.
* *Step into* — if the current statement contains a function call, steps into the called function.
* *Step out* — if the current statement is a function call, finishes executing it, then steps out of the function to the location where it was called.

<Frame>
  ![Debugger control buttons cheat
  sheet](https://github-production-user-asset-6210df.s3.amazonaws.com/3084745/261510346-6a94441c-75d3-413a-99a7-efa62365f83d.png)
</Frame>


# Convert a Node.js Readable to an ArrayBuffer
Source: https://bun.com/docs/guides/streams/node-readable-to-arraybuffer



To convert a Node.js `Readable` stream to an `ArrayBuffer` in Bun, create a `Response` with the stream as the body, then call `arrayBuffer()`.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
import { Readable } from "stream";
const stream = Readable.from(["Hello, ", "world!"]);
const buf = await new Response(stream).arrayBuffer();
```


# Convert a Node.js Readable to a Blob
Source: https://bun.com/docs/guides/streams/node-readable-to-blob



To convert a Node.js `Readable` stream to a [`Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Blob) in Bun, create a [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response) with the stream as the body, then call [`response.blob()`](https://developer.mozilla.org/en-US/docs/Web/API/Response/blob).

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
import { Readable } from "stream";
const stream = Readable.from(["Hello, ", "world!"]);
const blob = await new Response(stream).blob();
```


# Convert a Node.js Readable to JSON
Source: https://bun.com/docs/guides/streams/node-readable-to-json



To convert a Node.js `Readable` stream to a JSON object in Bun, create a [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response) with the stream as the body, then call [`response.json()`](https://developer.mozilla.org/en-US/docs/Web/API/Response/json).

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
import { Readable } from "stream";
const stream = Readable.from([JSON.stringify({ hello: "world" })]);
const json = await new Response(stream).json();
console.log(json); // { hello: "world" }
```


# Convert a Node.js Readable to a string
Source: https://bun.com/docs/guides/streams/node-readable-to-string



To convert a Node.js `Readable` stream to a string in Bun, create a [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response) with the stream as the body, then call [`response.text()`](https://developer.mozilla.org/en-US/docs/Web/API/Response/text).

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
import { Readable } from "stream";
const stream = Readable.from([Buffer.from("Hello, world!")]);
const text = await new Response(stream).text();
console.log(text); // "Hello, world!"
```


# Convert a Node.js Readable to an Uint8Array
Source: https://bun.com/docs/guides/streams/node-readable-to-uint8array



To convert a Node.js `Readable` stream to a `Uint8Array` in Bun, create a `Response` with the stream as the body, then call `bytes()`.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
import { Readable } from "stream";
const stream = Readable.from(["Hello, ", "world!"]);
const buf = await new Response(stream).bytes();
```


# Convert a ReadableStream to an array of chunks
Source: https://bun.com/docs/guides/streams/to-array



`Bun.readableStreamToArray` reads the contents of a [`ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream) into an array of chunks.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const stream = new ReadableStream();
const chunks = await Bun.readableStreamToArray(stream);
```

***

See [Bun's other `ReadableStream` conversion functions](/docs/runtime/utils#bun-readablestreamto).


# Convert a ReadableStream to an ArrayBuffer
Source: https://bun.com/docs/guides/streams/to-arraybuffer



`Bun.readableStreamToArrayBuffer` reads the contents of a [`ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream) into an `ArrayBuffer`.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const stream = new ReadableStream();
const buf = await Bun.readableStreamToArrayBuffer(stream);
```

***

See [Bun's other `ReadableStream` conversion functions](/docs/runtime/utils#bun-readablestreamto).


# Convert a ReadableStream to a Blob
Source: https://bun.com/docs/guides/streams/to-blob



Bun provides several conveniences for reading the contents of a [`ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream) into other formats. To read a stream into a `Blob`, call its `blob()` method.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const stream = new ReadableStream();
const blob = await stream.blob();
```

`Bun.readableStreamToBlob(stream)` does the same thing, but is deprecated in favor of `stream.blob()`.

***

See [Bun's other `ReadableStream` conversion functions](/docs/runtime/utils#bun-readablestreamto).


# Convert a ReadableStream to a Buffer
Source: https://bun.com/docs/guides/streams/to-buffer



To convert a [`ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream) to a `Buffer`, read its contents into an [`ArrayBuffer`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer) with `Bun.readableStreamToArrayBuffer`, then create a [`Buffer`](https://nodejs.org/api/buffer.html) that points to it.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const stream = new ReadableStream();
const arrBuf = await Bun.readableStreamToArrayBuffer(stream);
const nodeBuf = Buffer.from(arrBuf);
```

***

See [Bun's other `ReadableStream` conversion functions](/docs/runtime/utils#bun-readablestreamto).


# Convert a ReadableStream to JSON
Source: https://bun.com/docs/guides/streams/to-json



Bun provides several conveniences for reading the contents of a [`ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream) into other formats. To read a stream and parse it as JSON, call its `json()` method.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const stream = new ReadableStream();
const json = await stream.json();
```

***

See [Bun's other `ReadableStream` conversion functions](/docs/runtime/utils#bun-readablestreamto).


# Convert a ReadableStream to a string
Source: https://bun.com/docs/guides/streams/to-string



Bun provides several conveniences for reading the contents of a [`ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream) into other formats. To read a stream into a string, call its `text()` method.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const stream = new ReadableStream();
const str = await stream.text();
```

`Bun.readableStreamToText(stream)` does the same thing, but is deprecated in favor of `stream.text()`.

***

See [Bun's other `ReadableStream` conversion functions](/docs/runtime/utils#bun-readablestreamto).


# Convert a ReadableStream to a Uint8Array
Source: https://bun.com/docs/guides/streams/to-typedarray



To convert a [`ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream) to a `Uint8Array`, read its contents into an [`ArrayBuffer`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer) with `Bun.readableStreamToArrayBuffer`, then create a [`Uint8Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array) that points to the buffer.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const stream = new ReadableStream();
const buf = await Bun.readableStreamToArrayBuffer(stream);
const uint8 = new Uint8Array(buf);
```

The stream's `bytes()` method converts to a `Uint8Array` directly.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const stream = new ReadableStream();
const uint8 = await stream.bytes();
```

`Bun.readableStreamToBytes(stream)` does the same thing, but is deprecated in favor of `stream.bytes()`.

***

See [Bun's other `ReadableStream` conversion functions](/docs/runtime/utils#bun-readablestreamto).


# Bail early with the Bun test runner
Source: https://bun.com/docs/guides/test/bail



Use the `--bail` flag to abort a test run after the first failure, so a continuous integration run fails as early as possible.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun test --bail
```

***

To bail after a certain number of failures, pass a number after the flag.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
# bail after 10 failures
bun test --bail=10
```

***

See [`bun test`](/docs/test).


# Selectively run tests concurrently with glob patterns
Source: https://bun.com/docs/guides/test/concurrent-test-glob

Set a glob pattern to decide which tests from which files run in parallel

The `concurrentTestGlob` option in `bunfig.toml` runs tests concurrently in files whose names match a glob pattern.

## Project Structure

```sh title="Project Structure" icon="folder-tree" theme={"theme":{"light":"github-light","dark":"dracula"}}
my-project/
├── bunfig.toml
├── tests/
│   ├── unit/
│   │   ├── math.test.ts          # Sequential
│   │   └── utils.test.ts         # Sequential
│   └── integration/
│       ├── concurrent-api.test.ts     # Concurrent
│       └── concurrent-database.test.ts # Concurrent
```

## Configuration

Configure your `bunfig.toml` to run test files with the "concurrent-" prefix concurrently:

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[test]
# Run all test files with "concurrent-" prefix concurrently
concurrentTestGlob = "**/concurrent-*.test.ts"
```

## Test Files

### Unit Test (Sequential)

Tests that share state or depend on ordering should stay sequential:

```ts title="tests/unit/math.test.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { test, expect } from "bun:test";

// These tests run sequentially by default
let sharedState = 0;

test("addition", () => {
  sharedState = 5 + 3;
  expect(sharedState).toBe(8);
});

test("uses previous state", () => {
  // This test depends on the previous test's state
  expect(sharedState).toBe(8);
});
```

### Integration Test (Concurrent)

Tests in files matching the glob pattern automatically run concurrently:

```ts title="tests/integration/concurrent-api.test.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { test, expect } from "bun:test";

// These tests automatically run concurrently due to filename matching the glob pattern.
// Using test() is equivalent to test.concurrent() when the file matches concurrentTestGlob.
// Each test is independent and can run in parallel.

test("fetch user data", async () => {
  const response = await fetch("/api/user/1");
  expect(response.ok).toBe(true);
});

// can also use test.concurrent() for explicitly marking it as concurrent
test.concurrent("fetch posts", async () => {
  const response = await fetch("/api/posts");
  expect(response.ok).toBe(true);
});

// can also use test.serial() for explicitly marking it as sequential
test.serial("fetch comments", async () => {
  const response = await fetch("/api/comments");
  expect(response.ok).toBe(true);
});
```

## Running Tests

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
# Run all tests - concurrent-*.test.ts files will run concurrently
bun test

# Override: Force ALL tests to run concurrently
# Note: This overrides bunfig.toml and runs all tests concurrently, regardless of glob
bun test --concurrent

# Run only unit tests (sequential)
bun test tests/unit

# Run only integration tests (concurrent due to glob pattern)
bun test tests/integration
```

## Benefits

1. **Gradual migration**: rename files one at a time to move them to concurrent execution
2. **Clear organization**: the filename tells you how a file's tests run
3. **Performance**: independent integration tests finish faster in parallel
4. **Safety**: unit tests stay sequential where they need to

## Migration Strategy

To migrate existing tests to concurrent execution:

1. **Start with independent integration tests** - these typically don't share state
2. **Rename files to match the glob pattern**: `mv api.test.ts concurrent-api.test.ts`
3. **Run `bun test`** - check for race conditions and flaky or unexpected failures
4. **Continue migrating stable tests** file by file

## Tips

* **Use descriptive prefixes**: `concurrent-`, `parallel-`, `async-`
* **Keep related sequential tests together** in the same directory
* **Document why certain tests must remain sequential** with comments
* **Use `test.concurrent()` for fine-grained control** in sequential files
  (in files matched by `concurrentTestGlob`, plain `test()` already runs concurrently)

## Multiple Patterns

`concurrentTestGlob` also accepts multiple patterns:

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[test]
concurrentTestGlob = [
  "**/integration/*.test.ts",
  "**/e2e/*.test.ts",
  "**/concurrent-*.test.ts"
]
```

Tests in files matching any of these patterns run concurrently:

* All tests in `integration/` directories
* All tests in `e2e/` directories
* All tests with `concurrent-` prefix anywhere in the project


# Generate code coverage reports with the Bun test runner
Source: https://bun.com/docs/guides/test/coverage



Bun's test runner has built-in *code coverage reporting*. Use it to see how much of your codebase your tests cover, and where the gaps are.

***

Pass the `--coverage` flag to `bun test` to print a coverage report after the test run.

The report lists the source files the tests executed, the percentage of functions and lines that ran, and the line ranges that never ran.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun test --coverage
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}

test.test.ts:
✓ math > add [0.71ms]
✓ math > multiply [0.03ms]
✓ random [0.13ms]
-------------|---------|---------|-------------------
File         | % Funcs | % Lines | Uncovered Line #s
-------------|---------|---------|-------------------
All files    |   50.00 |   66.67 |
 math.ts     |   50.00 |   66.67 |
 random.ts   |   50.00 |   66.67 |
-------------|---------|---------|-------------------

 3 pass
 0 fail
 3 expect() calls
```

***

To enable coverage reporting by default, add the following to your `bunfig.toml`:

```toml bunfig.toml icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[test]
coverage = true # always enable coverage
```

***

See [Code coverage](/docs/test/code-coverage).


# Set a code coverage threshold with the Bun test runner
Source: https://bun.com/docs/guides/test/coverage-threshold



Bun's test runner has built-in code coverage reporting. Enable it with the `--coverage` flag.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun test --coverage
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
test.test.ts:
✓ math > add [0.71ms]
✓ math > multiply [0.03ms]
✓ random [0.13ms]
-------------|---------|---------|-------------------
File         | % Funcs | % Lines | Uncovered Line #s
-------------|---------|---------|-------------------
All files    |   50.00 |   66.67 |
 math.ts     |   50.00 |   66.67 |
 random.ts   |   50.00 |   66.67 |
-------------|---------|---------|-------------------

 3 pass
 0 fail
 3 expect() calls
```

***

To set a minimum coverage threshold, add the following to your `bunfig.toml`. A threshold of `0.9` requires that tests cover 90% of the lines and 90% of the functions of every file in the coverage report. Bun checks the threshold against each file, not against the `All files` average.

```toml bunfig.toml icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[test]
# to require 90% line-level and function-level coverage
coverageThreshold = 0.9
```

***

If your test suite does not meet this threshold, `bun test` exits with a non-zero exit code to signal a failure.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun test --coverage
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
<test output>
$ echo $?
1 # this is the exit code of the previous command
```

***

You can set different thresholds for line-level and function-level coverage.

```toml bunfig.toml icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[test]
# to set different thresholds for lines and functions
coverageThreshold = { lines = 0.5, functions = 0.7 }
```

***

See [Code coverage](/docs/test/code-coverage).


# Write browser DOM tests with Bun and happy-dom
Source: https://bun.com/docs/guides/test/happy-dom



Use [Happy DOM](https://github.com/capricorn86/happy-dom) to write browser tests with Bun's test runner. Happy DOM implements mocked versions of browser APIs like `document` and `location`.

***

Install `@happy-dom/global-registrator`.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun add -d @happy-dom/global-registrator
```

***

This module exports a "registrator" that injects the mocked browser APIs into the global scope.

```ts happydom.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { GlobalRegistrator } from "@happy-dom/global-registrator";

GlobalRegistrator.register();
```

***

This file needs to run before any of your test files. That's a job for Bun's built-in [*preload*](/docs/runtime/bunfig#test-preload) option. Create a `bunfig.toml` file in the root of your project (if it doesn't already exist) and add the following lines.

The `./happydom.ts` file should contain the registration code from the previous step.

```toml bunfig.toml icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[test]
preload = "./happydom.ts"
```

***

Now `bun test` executes `happydom.ts` before your test files, so you can write tests that use browser APIs.

```ts dom.test.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { test, expect } from "bun:test";

test("set button text", () => {
  document.body.innerHTML = `<button>My button</button>`;
  const button = document.querySelector("button");
  expect(button?.innerText).toEqual("My button");
});
```

***

With Happy DOM registered, the test passes.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun test
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}

dom.test.ts:
✓ set button text [0.82ms]

 1 pass
 0 fail
 1 expect() calls
Ran 1 test across 1 file. [125.00ms]
```

***

See the [Happy DOM repo](https://github.com/capricorn86/happy-dom) and [DOM testing](/docs/test/dom).


# Migrate from Jest to Bun's test runner
Source: https://bun.com/docs/guides/test/migrate-from-jest



In many cases, Bun's test runner can run Jest test suites with no code changes. Run `bun test` instead of `npx jest` or `yarn test`.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
npx jest # [!code --]
yarn test # [!code --]
bun test # [!code ++]
```

***

Your test files usually work as-is.

* Bun internally rewrites imports from `@jest/globals` to their `bun:test` equivalents.
* If you rely on Jest to inject globals like `test` and `expect`, Bun does that too.

If you'd rather import from `bun:test` directly, update the imports.

```ts title="test.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { test, expect } from "@jest/globals"; // [!code --]
import { test, expect } from "bun:test"; // [!code ++]
```

***

Since Bun v1.2.19, a triple-slash directive enables **TypeScript support** for global test functions. Add it to *one file* in your project, such as:

* A `global.d.ts` file in your project root
* Your test `preload.ts` setup file (if using `preload` in bunfig.toml)
* Any single `.ts` file that TypeScript includes in your compilation

```ts title="global.d.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
/// <reference types="bun-types/test-globals" />
```

***

Once added, every test file in your project gets TypeScript support for the Jest globals:

```ts math.test.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
describe("my test suite", () => {
  test("should work", () => {
    expect(1 + 1).toBe(2);
  });

  beforeAll(() => {
    // setup code
  });

  afterEach(() => {
    // cleanup code
  });
});
```

***

Bun implements most of Jest's matchers, but compatibility isn't 100%. See the compatibility table in [Writing tests](/docs/test/writing-tests#matchers).

***

If you use `testEnvironment: "jsdom"` to run your tests in a browser-like environment, follow the [DOM testing with Bun and happy-dom](/docs/guides/test/happy-dom) guide to inject browser APIs into the global scope. That guide uses [`happy-dom`](https://github.com/capricorn86/happy-dom), a leaner and faster alternative to [`jsdom`](https://github.com/jsdom/jsdom).

```toml bunfig.toml icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[test]
preload = ["./happydom.ts"]
```

***

Replace `bail` in your Jest config with the `--bail` CLI flag.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun test --bail=3
```

***

Replace `collectCoverage` with the `--coverage` CLI flag.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun test --coverage
```

***

Replace `testTimeout` with the `--timeout` CLI flag.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun test --timeout 10000
```

***

Many other Jest settings are irrelevant in `bun test`.

* `transform` — Bun supports TypeScript & JSX. Configure other file types with [plugins](/docs/runtime/plugins).
* `extensionsToTreatAsEsm`
* `haste` — Bun uses its own [module resolver](/docs/runtime/module-resolution)
* `watchman`, `watchPlugins`, `watchPathIgnorePatterns` — use `--watch` to run tests in watch mode
* `verbose` — `bun test` reports each test by default. Use `--only-failures` or `--dots` for less output (see [Test reporters](/docs/test/reporters)).

***

Many other settings have an equivalent in the `[test]` section of `bunfig.toml`. For example:

* `setupFiles`/`setupFilesAfterEnv` → `preload`
* `testPathIgnorePatterns` → `pathIgnorePatterns`
* `rootDir` → `root`
* `coverageDirectory` → `coverageDir`
* `coverageReporters` → `coverageReporter`
* `coverageThreshold` → `coverageThreshold` (as a fraction like `0.9`, not a percentage)

See [Test configuration](/docs/test/configuration). Settings without an equivalent are not supported. [File a feature request](https://github.com/oven-sh/bun/issues/new?template=4-feature-request.yml) if something you need is missing.

***

See also:

* [Mark a test as a todo](/docs/guides/test/todo-tests)
* [Writing tests](/docs/test/writing-tests)


# Set the system time in Bun's test runner
Source: https://bun.com/docs/guides/test/mock-clock



Set the system time in tests with the `setSystemTime` function from `bun:test`.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
import { test, expect, setSystemTime } from "bun:test";

test("party like it's 1999", () => {
  const date = new Date("1999-01-01T00:00:00.000Z");
  setSystemTime(date); // it's now January 1, 1999

  const now = new Date();
  expect(now.getFullYear()).toBe(1999);
  expect(now.getMonth()).toBe(0);
  expect(now.getDate()).toBe(1);
});
```

***

Call `setSystemTime` in a [lifecycle hook](/docs/test/lifecycle) like `beforeAll` to give your tests a deterministic "fake clock".

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
import { test, expect, beforeAll, setSystemTime } from "bun:test";

beforeAll(() => {
  const date = new Date("1999-01-01T00:00:00.000Z");
  setSystemTime(date); // it's now January 1, 1999
});

// tests...
```

***

To reset the system clock to the actual time, call `setSystemTime` with no arguments.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
import { test, expect, beforeAll, setSystemTime } from "bun:test";

setSystemTime(); // reset to actual time
```

***

See [Dates and times](/docs/test/dates-times).


# Mock functions in `bun test`
Source: https://bun.com/docs/guides/test/mock-functions



Create mocks with the `mock` function from `bun:test`.

```ts test.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { test, expect, mock } from "bun:test";

const random = mock(() => Math.random());
```

***

The mock function can accept arguments.

```ts test.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { test, expect, mock } from "bun:test";

const random = mock((multiplier: number) => multiplier * Math.random());
```

***

The result of `mock()` is a new function decorated with extra properties.

```ts test.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { mock } from "bun:test";

const random = mock((multiplier: number) => multiplier * Math.random());

random(2);
random(10);

random.mock.calls;
// [[ 2 ], [ 10 ]]

random.mock.results;
//  [
//    { type: "return", value: 0.6533907460954099 },
//    { type: "return", value: 0.6452713933037312 }
//  ]
```

***

Use these properties to write `expect` assertions about how the mock was used: how many times it was called, with which arguments, and what it returned.

```ts test.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { test, expect, mock } from "bun:test";

const random = mock((multiplier: number) => multiplier * Math.random());

test("random", async () => {
  const a = random(1);
  const b = random(2);
  const c = random(3);

  expect(random).toHaveBeenCalled();
  expect(random).toHaveBeenCalledTimes(3);
  expect(random.mock.calls).toEqual([[1], [2], [3]]);
  expect(random.mock.results[0]).toEqual({ type: "return", value: a });
});
```

***

See [Mocks](/docs/test/mocks).


# Re-run tests multiple times with the Bun test runner
Source: https://bun.com/docs/guides/test/rerun-each



The `--rerun-each` flag runs every test multiple times. Use it to find flaky or non-deterministic tests.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
# re-run each test 10 times
bun test --rerun-each 10
```

***

See [`bun test`](/docs/test).


# Run your tests with the Bun test runner
Source: https://bun.com/docs/guides/test/run-tests



Bun has a built-in [test runner](/docs/test) with a Jest-like `expect` API.

***

To use it, run `bun test` from your project directory. The test runner recursively searches for files matching the following patterns and runs the tests they contain.

```txt File Tree icon="folder-tree" theme={"theme":{"light":"github-light","dark":"dracula"}}
*.test.{js|jsx|ts|tsx|mjs|cjs|mts|cts}
*_test.{js|jsx|ts|tsx|mjs|cjs|mts|cts}
*.spec.{js|jsx|ts|tsx|mjs|cjs|mts|cts}
*_spec.{js|jsx|ts|tsx|mjs|cjs|mts|cts}
```

***

Here's the output of a typical run: three test files (`test.test.js`, `test2.test.js`, and `test3.test.js`), each containing two tests (`add` and `multiply`).

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun test
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
test.test.js:
✓ add [0.87ms]
✓ multiply [0.02ms]

test2.test.js:
✓ add [0.72ms]
✓ multiply [0.01ms]

test3.test.js:
✓ add [0.54ms]
✓ multiply [0.01ms]

 6 pass
 0 fail
 6 expect() calls
Ran 6 tests across 3 files. [9.00ms]
```

***

To only run certain test files, pass a positional argument to `bun test`. The runner only executes files with that argument in their path.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun test test3
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
test3.test.js:
✓ add [1.40ms]
✓ multiply [0.03ms]

 2 pass
 0 fail
 2 expect() calls
Ran 2 tests across 1 file. [15.00ms]
```

***

Every test has a name: the first argument to the `test` function. You can also group tests into suites with `describe`.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
import { test, expect, describe } from "bun:test";

describe("math", () => {
  test("add", () => {
    expect(2 + 2).toEqual(4);
  });

  test("multiply", () => {
    expect(2 * 2).toEqual(4);
  });
});
```

***

To filter tests by name, use the `-t`/`--test-name-pattern` flag.

`-t add` only runs tests with "add" in the name. The pattern matches test names defined with `test` and suite names defined with `describe`.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun test -t add
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
test.test.js:
✓ add [1.79ms]

test2.test.js:
✓ add [2.30ms]

test3.test.js:
✓ add [0.32ms]

 3 pass
 3 filtered out
 0 fail
 3 expect() calls
Ran 3 tests across 3 files. [59.00ms]
```

***

See [`bun test`](/docs/test).


# Skip tests with the Bun test runner
Source: https://bun.com/docs/guides/test/skip-tests



To skip a test with the Bun test runner, use the `test.skip` function.

```ts test.test.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { test, expect } from "bun:test";

test.skip("unimplemented feature", () => {
  expect(Bun.isAwesome()).toBe(true);
});
```

***

Running `bun test` doesn't execute this test. The terminal output marks it as skipped.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun test
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
test.test.ts:
» unimplemented feature

 0 pass
 1 skip
 0 fail
Ran 1 test across 1 file. [74.00ms]
```

***

See also:

* [Mark a test as a todo](/docs/guides/test/todo-tests)
* [Writing tests](/docs/test/writing-tests)


# Use snapshot testing in `bun test`
Source: https://bun.com/docs/guides/test/snapshot



Bun's test runner supports Jest-style snapshot testing with `.toMatchSnapshot()`.

```ts snap.test.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { test, expect } from "bun:test";

test("snapshot", () => {
  expect({ foo: "bar" }).toMatchSnapshot();
});
```

***

The first time this test runs, Bun evaluates the value passed into `expect()` and writes it to a `__snapshots__` directory alongside the test file. (Note the `snapshots: +1 added` line in the output.)

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun test test/snap
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
test/snap.test.ts:
✓ snapshot [1.48ms]

 1 pass
 0 fail
 snapshots: +1 added
 1 expect() calls
Ran 1 test across 1 file. [82.00ms]
```

***

The `__snapshots__` directory contains a `.snap` file for each test file in the directory.

```txt File Tree icon="folder-tree" theme={"theme":{"light":"github-light","dark":"dracula"}}
test
├── __snapshots__
│   └── snap.test.ts.snap
└── snap.test.ts
```

***

The `snap.test.ts.snap` file is a JavaScript file that exports a serialized version of the value passed into `expect()`. Bun pretty-prints the `{foo: "bar"}` object in Jest's snapshot format, which is not strict JSON (note the trailing comma).

```js snap.test.ts.snap icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
// Bun Snapshot v1, https://bun.sh/docs/test/snapshots

exports[`snapshot 1`] = `
{
  "foo": "bar",
}
`;
```

***

On later runs, Bun reads the snapshot file and compares it to the value passed into `expect()`. If they differ, the test fails.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun test
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
bun test v1.3.3 (9c68abdb)
test/snap.test.ts:
✓ snapshot [1.05ms]

 1 pass
 0 fail
 1 snapshots, 1 expect() calls
Ran 1 test across 1 file. [101.00ms]
```

***

To update snapshots, use the `--update-snapshots` flag.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun test --update-snapshots
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
bun test v1.3.3 (9c68abdb)
test/snap.test.ts:
✓ snapshot [0.86ms]

 1 pass
 0 fail
 snapshots: +1 added  # the snapshot was regenerated
 1 expect() calls
Ran 1 test across 1 file. [102.00ms]
```

***

See [Snapshots](/docs/test/snapshots).


# Spy on methods in `bun test`
Source: https://bun.com/docs/guides/test/spy-on



Use the `spyOn` utility to track method calls with Bun's test runner.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
import { test, expect, spyOn } from "bun:test";

const leo = {
  name: "Leonardo",
  sayHi(thing: string) {
    console.log(`Sup I'm ${this.name} and I like ${thing}`);
  },
};

const spy = spyOn(leo, "sayHi");
```

***

Once you've created the spy, use it in `expect` assertions about method calls.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
import { test, expect, spyOn } from "bun:test";

const leo = {
  name: "Leonardo",
  sayHi(thing: string) {
    console.log(`Sup I'm ${this.name} and I like ${thing}`);
  },
};

const spy = spyOn(leo, "sayHi");

test("turtles", () => { // [!code ++]
  expect(spy).toHaveBeenCalledTimes(0); // [!code ++]
  leo.sayHi("pizza"); // [!code ++]
  expect(spy).toHaveBeenCalledTimes(1); // [!code ++]
  expect(spy.mock.calls).toEqual([["pizza"]]); // [!code ++]
}); // [!code ++]
```

***

See [Mocks](/docs/test/mocks).


# import, require, and test Svelte components with bun test
Source: https://bun.com/docs/guides/test/svelte-test



Use Bun's [Plugin API](/docs/runtime/plugins) to add a custom loader for `.svelte` files, and the `test.preload` option in `bunfig.toml` to load it before your tests run.

First, install `@testing-library/svelte`, `svelte`, and `@happy-dom/global-registrator`.

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun add @testing-library/svelte svelte@4 @happy-dom/global-registrator
```

Then, save this plugin in your project.

```ts svelte-loader.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { plugin } from "bun";
import { compile } from "svelte/compiler";
import { readFileSync } from "fs";
import { beforeEach, afterEach } from "bun:test";
import { GlobalRegistrator } from "@happy-dom/global-registrator";

beforeEach(async () => {
  await GlobalRegistrator.register();
});

afterEach(async () => {
  await GlobalRegistrator.unregister();
});

plugin({
  name: "svelte loader",
  setup(builder) {
    builder.onLoad({ filter: /\.svelte(\?[^.]+)?$/ }, ({ path }) => {
      try {
        const source = readFileSync(path.substring(0, path.includes("?") ? path.indexOf("?") : path.length), "utf-8");

        const result = compile(source, {
          filename: path,
          generate: "dom",
          dev: false,
        });

        return {
          contents: result.js.code,
          loader: "js",
        };
      } catch (err) {
        throw new Error(`Failed to compile Svelte component: ${err.message}`);
      }
    });
  },
});
```

***

Add this to `bunfig.toml` so Bun preloads the plugin before your tests run.

```toml bunfig.toml icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[test]
# Tell Bun to load this plugin before your tests run
preload = ["./svelte-loader.ts"]

# This also works, at the top level of bunfig.toml instead of under [test]:
# test.preload = ["./svelte-loader.ts"]
```

***

Add an example `.svelte` file in your project.

```html Counter.svelte icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
<script>
  export let initialCount = 0;
  let count = initialCount;
</script>

<button on:click={() => (count += 1)}>+1</button>
```

***

Now you can `import` or `require` `*.svelte` files in your tests. Bun loads each Svelte component as a JavaScript module.

```ts hello-svelte.test.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { test, expect } from "bun:test";
import { render, fireEvent } from "@testing-library/svelte";
import Counter from "./Counter.svelte";

test("Counter increments when clicked", async () => {
  const { getByText, component } = render(Counter);
  const button = getByText("+1");

  // Initial state
  expect(component.$$.ctx[0]).toBe(0); // ctx[0] is count; ctx[1] is initialCount

  // Click the increment button
  await fireEvent.click(button);

  // Check the new state
  expect(component.$$.ctx[0]).toBe(1);
});
```

***

Use `bun test` to run your tests.

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun test
```


# Using Testing Library with Bun
Source: https://bun.com/docs/guides/test/testing-library



You can use [Testing Library](https://testing-library.com/) with Bun's test runner.

***

First, install [Happy DOM](https://github.com/capricorn86/happy-dom) (see [Bun's Happy DOM guide](/docs/guides/test/happy-dom)).

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun add -D @happy-dom/global-registrator
```

***

Next, install the Testing Library packages you plan to use, plus `@testing-library/jest-dom` for the matchers used later. For React:

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun add -D @testing-library/react @testing-library/dom @testing-library/jest-dom
```

***

Next, create preload scripts for Happy DOM and for Testing Library.

```ts happydom.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { GlobalRegistrator } from "@happy-dom/global-registrator";

GlobalRegistrator.register();
```

***

For Testing Library, extend Bun's `expect` function with Testing Library's matchers. Optionally, run cleanup after each test to better match the behavior of test runners like Jest.

```ts testing-library.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { afterEach, expect } from "bun:test";
import { cleanup } from "@testing-library/react";
import * as matchers from "@testing-library/jest-dom/matchers";

expect.extend(matchers);

// Optional: cleans up `render` after each test
afterEach(() => {
  cleanup();
});
```

***

Next, add these preload scripts to your `bunfig.toml`. If you combine them into a single preload script, load the `@testing-library/*` packages with `await import()` after `GlobalRegistrator.register()`. Bun evaluates a static `import` before `register()` runs, and `screen` queries then throw.

```toml bunfig.toml icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[test]
preload = ["./happydom.ts", "./testing-library.ts"]
```

***

If you use TypeScript, you also need declaration merging so the new matcher types show up in your editor. Create a type declaration file that extends `Matchers`.

```ts matchers.d.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { TestingLibraryMatchers } from "@testing-library/jest-dom/matchers";
import { Matchers, AsymmetricMatchers } from "bun:test";

declare module "bun:test" {
  interface Matchers<T> extends TestingLibraryMatchers<typeof expect.stringContaining, void> {}
  interface AsymmetricMatchers extends TestingLibraryMatchers<any, any> {}
}
```

***

Now you can use Testing Library in your tests.

```tsx myComponent.test.tsx icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { test, expect } from "bun:test";
import { screen, render } from "@testing-library/react";
import { MyComponent } from "./myComponent";

test("Can use Testing Library", () => {
  render(<MyComponent />);
  const myComponent = screen.getByTestId("my-component");
  expect(myComponent).toBeInTheDocument();
});
```

***

See the [Testing Library docs](https://testing-library.com/), the [Happy DOM repo](https://github.com/capricorn86/happy-dom), and [DOM testing](/docs/test/dom).


# Set a per-test timeout with the Bun test runner
Source: https://bun.com/docs/guides/test/timeout



Use the `--timeout` flag to set a timeout for each test in milliseconds. Bun marks a test that exceeds this timeout as failed.

The default timeout is `5000` (5 seconds).

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun test --timeout 3000 # 3 seconds
```

***

See [`bun test`](/docs/test).


# Mark a test as a "todo" with the Bun test runner
Source: https://bun.com/docs/guides/test/todo-tests



To remind yourself to write a test later, use the `test.todo` function. An implementation isn't required.

```ts test.test.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { test, expect } from "bun:test";

// write this later
test.todo("unimplemented feature");
```

***

The `bun test` output reports the number of `todo` tests.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun test
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
test.test.ts:
✎ unimplemented feature

 0 pass
 1 todo
 0 fail
Ran 1 test across 1 file. [74.00ms]
```

***

You can provide a test implementation.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
import { test, expect } from "bun:test";

test.todo("unimplemented feature", () => {
  expect(Bun.isAwesome()).toBe(true);
});
```

***

Bun doesn't run the implementation unless you pass the `--todo` flag. With `--todo`, the test runs and is *expected to fail*. If a todo test passes, `bun test` returns a non-zero exit code.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun test --todo
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
test.test.ts:
✗ unimplemented feature
  ^ this test is marked as todo but passes. Remove `.todo` if tested behavior now works

 0 pass
 1 fail
 1 expect() calls
$ echo $?
1 # this is the exit code of the previous command
```

***

See also:

* [Skip a test](/docs/guides/test/skip-tests)
* [Writing tests](/docs/test/writing-tests)


# Update snapshots in `bun test`
Source: https://bun.com/docs/guides/test/update-snapshots



Bun's test runner supports Jest-style snapshot testing with `.toMatchSnapshot()`.

```ts snap.test.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { test, expect } from "bun:test";

test("snapshot", () => {
  expect({ foo: "bar" }).toMatchSnapshot();
});
```

***

The first time this test runs, Bun writes a snapshot file to a `__snapshots__` directory alongside the test file.

```txt File Tree icon="folder-tree" theme={"theme":{"light":"github-light","dark":"dracula"}}
test
├── __snapshots__
│   └── snap.test.ts.snap
└── snap.test.ts
```

***

To regenerate snapshots, use the `--update-snapshots` flag.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun test --update-snapshots
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
test/snap.test.ts:
✓ snapshot [0.86ms]

 1 pass
 0 fail
 snapshots: +1 added # the snapshot was regenerated
 1 expect() calls
Ran 1 test across 1 file. [102.00ms]
```

***

See [Snapshots](/docs/test/snapshots).


# Run tests in watch mode with Bun
Source: https://bun.com/docs/guides/test/watch-mode



Use the `--watch` flag to run your tests in watch mode.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun test --watch
```

***

Watch mode restarts the running Bun process whenever a file changes. It's fast. In this example, the editor saves the file on every keystroke.

<Frame>
  ![Running tests in watch mode in
  Bun](https://github.com/oven-sh/bun/assets/3084745/dc49a36e-ba82-416f-b960-1c883a924248)
</Frame>

***

See [`bun test`](/docs/test).


# Encode and decode base64 data
Source: https://bun.com/docs/guides/util/base64



Prefer [`Uint8Array.prototype.toBase64()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array/toBase64) and [`Uint8Array.fromBase64()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array/fromBase64) for encoding and decoding base64 data in Bun. These APIs work directly with bytes, so they are a better fit for binary data than the older `btoa()` and `atob()` globals.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const bytes = new Uint8Array([98, 117, 110]);
const encoded = bytes.toBase64(); // => "YnVu"

const decoded = Uint8Array.fromBase64(encoded);
// => Uint8Array(3) [ 98, 117, 110 ]
```

For strings, convert to and from UTF-8 bytes with [`TextEncoder`](https://developer.mozilla.org/en-US/docs/Web/API/TextEncoder) and [`TextDecoder`](https://developer.mozilla.org/en-US/docs/Web/API/TextDecoder).

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const bytes = new TextEncoder().encode("hello world");
const encoded = bytes.toBase64(); // => "aGVsbG8gd29ybGQ="

const decoded = Uint8Array.fromBase64(encoded);
const text = new TextDecoder().decode(decoded); // => "hello world"
```

Node.js `Buffer` extends `Uint8Array`, so you can encode buffers with `toBase64()` and pass them to APIs that accept `Uint8Array`. `Buffer` also provides Node.js-compatible base64 decoding with `Buffer.from(encoded, "base64")`.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const encoded = Buffer.from("hello world").toBase64();
// => "aGVsbG8gd29ybGQ="

const bytes = Buffer.from(encoded, "base64");
bytes instanceof Uint8Array; // => true

const text = bytes.toString("utf8");
// => "hello world"
```

<Warning>
  The older [`btoa()`](https://developer.mozilla.org/en-US/docs/Web/API/Window/btoa) and [`atob()`](https://developer.mozilla.org/en-US/docs/Web/API/Window/atob) APIs are still available for compatibility, but they operate on binary strings instead of byte arrays. Avoid them in new code, especially when handling arbitrary binary data or non-ASCII text.

  ```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
  const encoded = btoa("bun"); // => "YnVu"
  const decoded = atob(encoded); // => "bun"
  ```
</Warning>

***

See [Web APIs](/docs/runtime/web-apis).


# Check if two objects are deeply equal
Source: https://bun.com/docs/guides/util/deep-equals



`Bun.deepEquals()` checks if two objects are deeply equal. `expect().toEqual()` in Bun's [test runner](/docs/test/writing-tests) uses it internally.

```ts index.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
const a = { a: 1, b: 2, c: { d: 3 } };
const b = { a: 1, b: 2, c: { d: 3 } };

Bun.deepEquals(a, b); // true
```

***

Pass `true` as a third argument to enable strict mode. `expect().toStrictEqual()` uses strict mode.

The following examples return `true` in non-strict mode but `false` in strict mode.

```ts index.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
// undefined values
Bun.deepEquals({}, { a: undefined }, true); // false

// undefined in arrays
Bun.deepEquals(["asdf"], ["asdf", undefined], true); // false

// sparse arrays
Bun.deepEquals([, 1], [undefined, 1], true); // false

// object literals vs instances w/ same properties
class Foo {
  a = 1;
}
Bun.deepEquals(new Foo(), { a: 1 }, true); // false
```

***

See [Utils](/docs/runtime/utils).


# Compress and decompress data with DEFLATE
Source: https://bun.com/docs/guides/util/deflate



Use `Bun.deflateSync()` to compress a `Uint8Array` with DEFLATE.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const data = Buffer.from("Hello, world!");
const compressed = Bun.deflateSync(data);
// => Uint8Array

const decompressed = Bun.inflateSync(compressed);
// => Uint8Array
```

***

See [Utils](/docs/runtime/utils).


# Detect when code is executed with Bun
Source: https://bun.com/docs/guides/util/detect-bun



Check `process.versions.bun` to detect whether code is running in Bun. This works in both JavaScript and TypeScript without extra type definitions.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
if (process.versions.bun) {
  // this code will only run when the file is run with Bun
}
```

***

Alternatively, check for the `Bun` global, the same way you'd check for `window` to detect a browser.

<Note>
  In TypeScript, this check is a type error unless `@types/bun` is installed. Install it with `bun add -d @types/bun`.
  If you're using TypeScript 6.0 or later, you also need `"types": ["bun"]` in your `compilerOptions`. See [TypeScript 6
  and 7](/docs/typescript-6) for details.
</Note>

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
if (typeof Bun !== "undefined") {
  // this code will only run when the file is run with Bun
}
```


# Check if the current file is the entrypoint
Source: https://bun.com/docs/guides/util/entrypoint



Bun provides a handful of module-specific utilities on the [`import.meta`](/docs/runtime/module-resolution#import-meta) object. Use `import.meta.main` to check if the current file is the entrypoint of the current process.

```ts index.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
if (import.meta.main) {
  // this file is directly executed with `bun run`
} else {
  // this file is being imported by another file
}
```

***

See [`import.meta`](/docs/runtime/module-resolution#import-meta).


# Escape an HTML string
Source: https://bun.com/docs/guides/util/escape-html



`Bun.escapeHTML()` escapes HTML characters in a string. It makes the following replacements.

* `"` becomes `"&quot;"`
* `&` becomes `"&amp;"`
* `'` becomes `"&#x27;"`
* `<` becomes `"&lt;"`
* `>` becomes `"&gt;"`

This function is optimized for large input. It converts non-string values to a string before escaping them.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
Bun.escapeHTML("<script>alert('Hello World!')</script>");
// &lt;script&gt;alert(&#x27;Hello World!&#x27;)&lt;/script&gt;
```

***

See [Utils](/docs/runtime/utils).


# Convert a file URL to an absolute path
Source: https://bun.com/docs/guides/util/file-url-to-path



Use `Bun.fileURLToPath()` to convert a `file://` URL to an absolute path.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
Bun.fileURLToPath("file:///path/to/file.txt");
// => "/path/to/file.txt"
```

***

See [Utils](/docs/runtime/utils).


# Compress and decompress data with gzip
Source: https://bun.com/docs/guides/util/gzip



Use `Bun.gzipSync()` to compress a `Uint8Array` with gzip.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const data = Buffer.from("Hello, world!");
const compressed = Bun.gzipSync(data);
// => Uint8Array

const decompressed = Bun.gunzipSync(compressed);
// => Uint8Array
```

***

See [Utils](/docs/runtime/utils).


# Hash a password
Source: https://bun.com/docs/guides/util/hash-a-password



Use `Bun.password.hash()` to securely hash passwords. It's built into Bun, with no third-party dependencies.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const password = "super-secure-pa$$word";

const hash = await Bun.password.hash(password);
// => $argon2id$v=19$m=65536,t=2,p=1$tFq+9AVr1bfPxQdh6E8DQRhEXg/M/...
```

***

By default, `Bun.password.hash()` uses the [Argon2id](https://en.wikipedia.org/wiki/Argon2) algorithm. Pass a second argument to use a different algorithm or configure the hashing parameters.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const password = "super-secure-pa$$word";

// use argon2 (default)
const argonHash = await Bun.password.hash(password, {
  algorithm: "argon2id",
  memoryCost: 8, // memory usage in kibibytes (minimum 8)
  timeCost: 3, // the number of iterations
});
```

***

Bun also implements the [bcrypt](https://en.wikipedia.org/wiki/Bcrypt) algorithm. Specify `algorithm: "bcrypt"` to use it.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
// use bcrypt
const bcryptHash = await Bun.password.hash(password, {
  algorithm: "bcrypt",
  cost: 4, // number between 4-31
});
```

***

Use `Bun.password.verify()` to verify a password. The hash stores the algorithm and its parameters, so you don't need to specify them again.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const password = "super-secure-pa$$word";
const hash = await Bun.password.hash(password);

const isMatch = await Bun.password.verify(password, hash);
// => true
```

***

See [`Bun.password`](/docs/runtime/hashing#bun-password).


# Get the directory of the current file
Source: https://bun.com/docs/guides/util/import-meta-dir



Bun provides a handful of module-specific utilities on the [`import.meta`](/docs/runtime/module-resolution#import-meta) object. Use `import.meta.dir` to retrieve the absolute path to the directory containing the current file.

```ts /a/b/c.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import.meta.dir; // => "/a/b"
```

***

See [`import.meta`](/docs/runtime/module-resolution#import-meta).


# Get the file name of the current file
Source: https://bun.com/docs/guides/util/import-meta-file



Bun provides a handful of module-specific utilities on the [`import.meta`](/docs/runtime/module-resolution#import-meta) object. Use `import.meta.file` to retrieve the name of the current file.

```ts /a/b/c.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import.meta.file; // => "c.ts"
```

***

See [`import.meta`](/docs/runtime/module-resolution#import-meta).


# Get the absolute path of the current file
Source: https://bun.com/docs/guides/util/import-meta-path



Bun provides a handful of module-specific utilities on the [`import.meta`](/docs/runtime/module-resolution#import-meta) object. Use `import.meta.path` to retrieve the absolute path of the current file.

```ts /a/b/c.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import.meta.path; // => "/a/b/c.ts"
```

***

See [`import.meta`](/docs/runtime/module-resolution#import-meta).


# Generate a UUID
Source: https://bun.com/docs/guides/util/javascript-uuid



Use `crypto.randomUUID()` to generate a UUID v4. It works in Bun, Node.js, and browsers, with no dependencies.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
crypto.randomUUID();
// => "123e4567-e89b-42d3-a456-426614174000"
```

***

Bun also provides `Bun.randomUUIDv7()`, which generates a [UUID v7](https://www.rfc-editor.org/rfc/rfc9562.html#name-uuid-version-7).

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
Bun.randomUUIDv7();
// => "0196a000-bb12-7000-905e-8039f5d5b206"
```

***

See [Utils](/docs/runtime/utils).


# Get the absolute path to the current entrypoint
Source: https://bun.com/docs/guides/util/main



The `Bun.main` property contains the absolute path to the current entrypoint.

<CodeGroup>
  ```ts foo.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
  console.log(Bun.main);
  ```

  ```ts index.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
  import "./foo.ts";
  ```
</CodeGroup>

***

The printed path is the file executed with `bun run`.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun run index.ts
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
/path/to/index.ts
```

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun run foo.ts
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
/path/to/foo.ts
```

***

See [Utils](/docs/runtime/utils).


# Convert an absolute path to a file URL
Source: https://bun.com/docs/guides/util/path-to-file-url



Use `Bun.pathToFileURL()` to convert an absolute path to a `file://` URL.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
Bun.pathToFileURL("/path/to/file.txt").href;
// => "file:///path/to/file.txt"
```

***

See [Utils](/docs/runtime/utils).


# Sleep for a fixed number of milliseconds
Source: https://bun.com/docs/guides/util/sleep



`Bun.sleep()` returns a void `Promise` that resolves after a given number of milliseconds.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
// sleep for 1 second
await Bun.sleep(1000);
```

***

Internally, `Bun.sleep()` is equivalent to the following [`setTimeout`](https://developer.mozilla.org/en-US/docs/Web/API/Window/setTimeout) snippet.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
await new Promise(resolve => setTimeout(resolve, ms));
```

***

See [Utils](/docs/runtime/utils).


# Upgrade Bun to the latest version
Source: https://bun.com/docs/guides/util/upgrade



Bun upgrades itself with the built-in `bun upgrade` command.

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun upgrade
```

`bun upgrade` downloads and installs the latest stable version of Bun, replacing the currently installed version. If you're on a canary build, `bun upgrade` installs the latest canary build instead.

<Note>To see the current version of Bun, run `bun --version`.</Note>

***

## Verify the upgrade

After upgrading, verify the new version:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun --version
# Output: 1.x.y

# See the exact commit of the Bun binary
bun --revision
# Output: 1.x.y+abc123def
```

***

## Upgrade to canary builds

Canary builds are published automatically from the `main` branch once a commit's CI tests pass. They're useful for trying new features or verifying bug fixes before a release.

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun upgrade --canary
```

<Warning>Canary builds are not recommended for production use. They may contain bugs or breaking changes.</Warning>

***

## Switch back to stable

If you're on a canary build and want to return to the latest stable release:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun upgrade --stable
```

***

## Install a specific version

To install a specific version of Bun, use the install script with a version tag:

<Tabs>
  <Tab title="macOS & Linux">
    ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
    curl -fsSL https://bun.sh/install | bash -s "bun-v1.3.3"
    ```
  </Tab>

  <Tab title="Windows">
    ```powershell PowerShell icon="windows" theme={"theme":{"light":"github-light","dark":"dracula"}}
    iex "& {$(irm https://bun.sh/install.ps1)} -Version 1.3.3"
    ```
  </Tab>
</Tabs>

***

## Package manager users

If you installed Bun with a package manager, upgrade with that package manager instead of `bun upgrade` to avoid conflicts.

<Tip>
  **Homebrew users** <br />
  Use `brew upgrade bun` instead.

  **Scoop users** <br />
  Use `scoop update bun` instead.
</Tip>

***

## See also

* [Installation](/docs/installation) — Install Bun for the first time
* [Update packages](/docs/pm/cli/update) — Update dependencies to latest versions


# Get the current Bun version
Source: https://bun.com/docs/guides/util/version



`Bun.version` contains the current version of Bun in semver format.

```ts index.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
Bun.version; // => "1.3.3"
```

***

`Bun.revision` contains the exact `git` commit of [`oven-sh/bun`](https://github.com/oven-sh/bun) that was compiled to produce this Bun binary.

```ts index.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
Bun.revision; // => "49231b2cb9aa48497ab966fc0bb6b742dacc4994"
```

***

See [Utils](/docs/runtime/utils).


# Get the path to an executable bin file
Source: https://bun.com/docs/guides/util/which-path-to-executable-bin



`Bun.which` finds the absolute path of an executable file, like the `which` command on Unix-like systems.

```ts foo.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
Bun.which("sh"); // => "/bin/sh"
Bun.which("notfound"); // => null
Bun.which("bun"); // => "/home/user/.bun/bin/bun"
```

***

See [`Bun.which`](/docs/runtime/utils#bun-which).


# Enable compression for WebSocket messages
Source: https://bun.com/docs/guides/websocket/compression



Set the `perMessageDeflate` parameter to enable the [permessage-deflate](https://tools.ietf.org/html/rfc7692) WebSocket extension. Bun then negotiates compression with clients that support it. Messages sent with `ws.send()` are still uncompressed unless you opt in per message (see below).

```ts server.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
Bun.serve({
  // ...
  websocket: {
    // enable compression
    perMessageDeflate: true,
  },
});
```

***

To enable compression for individual messages, pass `true` as the second parameter to `ws.send()`. This requires `perMessageDeflate` to be enabled; otherwise Bun sends the message uncompressed.

```ts server.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
Bun.serve({
  // ...
  websocket: {
    perMessageDeflate: true,
    async message(ws, message) {
      // send a compressed message
      ws.send(message, true);
    },
  },
});
```


# Set per-socket contextual data on a WebSocket
Source: https://bun.com/docs/guides/websocket/context



A WebSocket server typically needs to store some identifying information or context for each connected client.

With [Bun.serve()](/docs/runtime/http/websockets#contextual-data), set this "contextual data" by passing a `data` parameter to `server.upgrade()` when upgrading the connection.

```ts server.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
Bun.serve({
  fetch(req, server) {
    const success = server.upgrade(req, {
      data: {
        socketId: Math.random(),
      },
    });
    if (success) return undefined;

    // handle HTTP request normally
    // ...
  },
  websocket: {
    // TypeScript: specify the type of ws.data like this
    data: {} as { socketId: number },

    // define websocket handlers
    async message(ws, message) {
      // the contextual data is available as the `data` property
      // on the WebSocket instance
      console.log(`Received ${message} from ${ws.data.socketId}`);
    },
  },
});
```

***

It's common to read cookies/headers from the incoming request to identify the connecting client.

```ts server.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
type WebSocketData = {
  createdAt: number;
  token: string;
  userId: string;
};

Bun.serve({
  async fetch(req, server) {
    // use a library to parse cookies
    const cookies = parseCookies(req.headers.get("Cookie"));
    const token = cookies["X-Token"];
    const user = await getUserFromToken(token);

    const upgraded = server.upgrade(req, {
      data: {
        createdAt: Date.now(),
        token: cookies["X-Token"],
        userId: user.id,
      },
    });

    if (upgraded) return undefined;
  },
  websocket: {
    // TypeScript: specify the type of ws.data like this
    data: {} as WebSocketData,

    async message(ws, message) {
      // save the message to a database
      await saveMessageToDatabase({
        message: String(message),
        userId: ws.data.userId,
      });
    },
  },
});
```


# Build a publish-subscribe WebSocket server
Source: https://bun.com/docs/guides/websocket/pubsub



Bun's server-side `WebSocket` API includes native pub-sub. Subscribe a socket to a set of named channels with `socket.subscribe(<name>)`; publish a message to a channel with `server.publish(<name>, <message>)`.

This code snippet implements a single-channel chat server.

```ts server.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
const server = Bun.serve({
  fetch(req, server) {
    const cookies = req.headers.get("cookie");
    const username = getUsernameFromCookies(cookies);
    const success = server.upgrade(req, { data: { username } });
    if (success) return undefined;

    return new Response("Hello world");
  },
  websocket: {
    // TypeScript: specify the type of ws.data like this
    data: {} as { username: string },

    open(ws) {
      const msg = `${ws.data.username} has entered the chat`;
      ws.subscribe("the-group-chat");
      server.publish("the-group-chat", msg);
    },
    message(ws, message) {
      // the server re-broadcasts incoming messages to everyone
      server.publish("the-group-chat", `${ws.data.username}: ${message}`);
    },
    close(ws) {
      const msg = `${ws.data.username} has left the chat`;
      server.publish("the-group-chat", msg);
      ws.unsubscribe("the-group-chat");
    },
  },
});

console.log(`Listening on ${server.hostname}:${server.port}`);
```


# Build a simple WebSocket server
Source: https://bun.com/docs/guides/websocket/simple



Start a WebSocket server with [`Bun.serve`](/docs/runtime/http/server).

Inside `fetch`, `server.upgrade()` attempts to upgrade incoming `ws:` or `wss:` requests to WebSocket connections.

```ts server.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
const server = Bun.serve({
  fetch(req, server) {
    const success = server.upgrade(req);
    if (success) {
      // Bun automatically returns a 101 Switching Protocols
      // if the upgrade succeeds
      return undefined;
    }

    // handle HTTP request normally
    return new Response("Hello world!");
  },
  websocket: {
    // TypeScript: specify the type of ws.data like this
    data: {} as { authToken: string },

    // this is called when a message is received
    async message(ws, message) {
      console.log(`Received ${message}`);
      // send back a message
      ws.send(`You said: ${message}`);
    },
  },
});

console.log(`Listening on ${server.hostname}:${server.port}`);
```


# Append content to a file
Source: https://bun.com/docs/guides/write-file/append



Bun implements the `node:fs` module, which includes the `fs.appendFile` and `fs.appendFileSync` functions for appending content to files.

***

`fs.appendFile` asynchronously appends data to a file, creating the file if it does not yet exist. The content can be a string or a `Buffer`.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
import { appendFile } from "node:fs/promises";

await appendFile("message.txt", "data to append");
```

***

To use the non-`Promise` API:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
import { appendFile } from "node:fs";

appendFile("message.txt", "data to append", err => {
  if (err) throw err;
  console.log('The "data to append" was appended to file!');
});
```

***

To specify the encoding of the content:

```js theme={"theme":{"light":"github-light","dark":"dracula"}}
import { appendFile } from "node:fs";

appendFile("message.txt", "data to append", "utf8", callback);
```

***

To append the data synchronously, use `fs.appendFileSync`:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
import { appendFileSync } from "node:fs";

appendFileSync("message.txt", "data to append", "utf8");
```

***

See the [Node.js documentation](https://nodejs.org/api/fs.html#fspromisesappendfilepath-data-options) for `fs.appendFile`.


# Write a string to a file
Source: https://bun.com/docs/guides/write-file/basic



Use [`Bun.write()`](/docs/runtime/file-io#writing-files-bun-write) to write a string to disk at an *absolute path*. The first argument is a *destination*; the second is the *data* to write.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const path = "/path/to/file.txt";
await Bun.write(path, "Lorem ipsum");
```

***

Bun resolves relative paths from the current working directory.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const path = "./file.txt";
await Bun.write(path, "Lorem ipsum");
```

***

You can pass a `BunFile` as the destination. `Bun.write()` writes the data to its associated path.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const path = Bun.file("./file.txt");
await Bun.write(path, "Lorem ipsum");
```

***

`Bun.write()` returns the number of bytes written to disk.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const path = "./file.txt";
const bytes = await Bun.write(path, "Lorem ipsum");
// => 11
```

***

See [`Bun.write()`](/docs/runtime/file-io#writing-files-bun-write).


# Write a Blob to a file
Source: https://bun.com/docs/guides/write-file/blob



Use [`Bun.write()`](/docs/runtime/file-io#writing-files-bun-write) to write a `Blob` to disk. The first argument is a *destination*, like an absolute path or `BunFile` instance. The second argument is the *data* to write.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const path = "/path/to/file.txt";
const data = new Blob(["Lorem ipsum"]);
await Bun.write(path, data);
```

***

The `BunFile` class extends `Blob`, so you can pass a `BunFile` directly into `Bun.write()` as well.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const path = "./out.txt";
const data = Bun.file("./in.txt");

// write the contents of ./in.txt to ./out.txt
await Bun.write(path, data);
```

***

See [`Bun.write()`](/docs/runtime/file-io#writing-files-bun-write).


# Write a file to stdout
Source: https://bun.com/docs/guides/write-file/cat



Bun exposes `stdout` as a `BunFile` with the `Bun.stdout` property. Pass it as the destination to [`Bun.write()`](/docs/runtime/file-io#writing-files-bun-write).

The following code writes a file to `stdout`, like the Unix `cat` command.

```ts cat.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
const path = "/path/to/file.txt";
const file = Bun.file(path);
await Bun.write(Bun.stdout, file);
```

***

See [`Bun.write()`](/docs/runtime/file-io#writing-files-bun-write).


# Copy a file to another location
Source: https://bun.com/docs/guides/write-file/file-cp



Use [`Bun.write()`](/docs/runtime/file-io#writing-files-bun-write) to copy a file to another location on disk. The first argument is a *destination*, like an absolute path or `BunFile` instance. The second argument is the *data* to write.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const file = Bun.file("/path/to/original.txt");
await Bun.write("/path/to/copy.txt", file);
```

***

See [`Bun.write()`](/docs/runtime/file-io#writing-files-bun-write).


# Write a file incrementally
Source: https://bun.com/docs/guides/write-file/filesink



Bun provides an API for incrementally writing to a file. Use it for large files, or when writing to a file over a long period of time.

Call `.writer()` on a `BunFile` to retrieve a `FileSink` instance. The `FileSink` buffers data. Call `.flush()` to write the buffer to disk. You can write & flush many times.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const file = Bun.file("/path/to/file.txt");
const writer = file.writer();

writer.write("lorem");
writer.write("ipsum");
writer.write("dolor");

writer.flush();

// continue writing & flushing
```

***

The `.write()` method accepts strings or binary data.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
writer.write("hello");
writer.write(Buffer.from("there"));
writer.write(new Uint8Array([0, 255, 128]));
writer.flush();
```

***

The `FileSink` also auto-flushes when its internal buffer is full.

***

When you're done writing, call `.end()` to flush the buffer and close the file.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
writer.end();
```

***

Full documentation: [FileSink](/docs/runtime/file-io#incremental-writing-with-filesink).


# Write a Response to a file
Source: https://bun.com/docs/guides/write-file/response



Use [`Bun.write()`](/docs/runtime/file-io#writing-files-bun-write) to write a `Response` to disk. Bun writes the body of the `Response` to the destination.

The first argument is a *destination*, like an absolute path or `BunFile` instance. The second argument is the *data* to write.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const result = await fetch("https://bun.com");
const path = "./file.txt";
await Bun.write(path, result);
```

***

See [`Bun.write()`](/docs/runtime/file-io#writing-files-bun-write).


# Write to stdout
Source: https://bun.com/docs/guides/write-file/stdout



The `console.log` function writes to `stdout` and appends a line break to the printed data.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
console.log("Lorem ipsum");
```

***

Bun also exposes `stdout` as a `BunFile` with the `Bun.stdout` property. Pass it as the destination to [`Bun.write()`](/docs/runtime/file-io#writing-files-bun-write).

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
await Bun.write(Bun.stdout, "Lorem ipsum");
```

***

See [`Bun.write()`](/docs/runtime/file-io#writing-files-bun-write).


# Write a ReadableStream to a file
Source: https://bun.com/docs/guides/write-file/stream



To write a `ReadableStream` to disk, call `.writer()` on a `BunFile` to get a [`FileSink`](/docs/runtime/file-io#incremental-writing-with-filesink). The stream is an async iterable, so write each of its chunks to the `FileSink` with `for await`. Then call `.end()` to flush the buffer and close the file.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const stream: ReadableStream = ...;
const path = "./file.txt";
const writer = Bun.file(path).writer();

for await (const chunk of stream) {
  writer.write(chunk);
}

await writer.end();
```

***

`.writer()` creates the file if it doesn't exist, but does not truncate an existing file. If the file may already exist, delete it first.

***

See [`FileSink`](/docs/runtime/file-io#incremental-writing-with-filesink).


# Delete a file
Source: https://bun.com/docs/guides/write-file/unlink



The `Bun.file()` function accepts a path and returns a `BunFile` instance. Use the `.delete()` method to delete the file.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const path = "/path/to/file.txt";
const file = Bun.file(path);

await file.delete();
```

***

See [`Bun.file()`](/docs/runtime/file-io#reading-files-bun-file).


# Welcome to Bun
Source: https://bun.com/docs/index

Bun is an all-in-one toolkit for developing modern JavaScript/TypeScript applications.

<CardGroup>
  <Card icon="cog" title="Bun Runtime" href="/runtime">
    A fast JavaScript runtime designed as a drop-in replacement for Node.js
  </Card>

  <Card icon="box" title="Bun Package Manager" href="/pm/cli/install">
    Install packages up to 30x faster than npm with a global cache and workspaces
  </Card>

  <Card icon="flask-conical" title="Bun Test Runner" href="/test">
    Jest-compatible, TypeScript-first tests with snapshots, DOM, and watch mode
  </Card>

  <Card icon="combine" title="Bun Bundler" href="/bundler">
    Bundle TypeScript, JSX, React & CSS for both browsers and servers
  </Card>
</CardGroup>

***

## Get Started

Bun ships as a single, dependency-free binary and includes a runtime, package manager, test runner, and bundler. New to Bun?

<CardGroup>
  <Card icon="download" title="Install Bun" href="/installation">
    Supported platforms and all install methods.
  </Card>

  <Card icon="zap" title="Quickstart" href="/quickstart">
    Hello world in minutes with Bun.serve.
  </Card>
</CardGroup>

***

## What's Inside

* Runtime: Execute JavaScript/TypeScript files and package scripts with near-zero overhead.
* Package Manager: Fast installs, workspaces, overrides, and audits with `bun install`.
* Test Runner: Jest-compatible, TypeScript-first tests with snapshots, DOM, and watch mode.
* Bundler: Native bundling for JS/TS/JSX with splitting, plugins, and HTML imports.

Explore each area using the cards above. Each section is structured with an overview, short examples, reference, and best practices for fast scanning and deep dives.

***

## What is Bun?

Bun is an all-in-one toolkit for JavaScript and TypeScript apps. It ships as a single executable called `bun`.

At its core is the *Bun runtime*, a fast JavaScript runtime designed as **a drop-in replacement for Node.js**. It's written in Rust and powered by JavaScriptCore, reducing startup time and memory usage.

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun run index.tsx  # TS and JSX supported by default
```

The `bun` command-line tool also implements a test runner, script runner, and Node.js-compatible package manager. All three are significantly faster than existing tools, and you can use them in existing Node.js projects with little to no changes.

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun run start                 # run the `start` script
bun install <pkg>             # install a package
bun build ./index.tsx         # bundle a project for browsers
bun test                      # run tests
bunx cowsay 'Hello, world!'   # execute a package
```

## What is a runtime?

JavaScript (or, more formally, ECMAScript) is only a *specification* for a programming language. Anyone can write a JavaScript *engine* that ingests a valid JavaScript program and executes it. The two most popular engines in use today are V8 (developed by Google)
and JavaScriptCore (developed by Apple). Both are open source.

But most JavaScript programs don't run in a vacuum. They need a way to access the outside world to perform useful tasks. This is where *runtimes* come in. They implement additional APIs and make them available to the JavaScript programs they execute.

### Browsers

Browsers ship with JavaScript runtimes that implement a set of Web-specific APIs, exposed on the global `window` object. Any JavaScript code executed by the browser can use these APIs to add interactive or dynamic behavior to the current webpage.

### Node.js

Node.js is a JavaScript runtime for non-browser environments, like servers. JavaScript programs executed by Node.js have access to a set of Node.js-specific [globals](https://nodejs.org/api/globals.html) like `Buffer`, `process`, and `__dirname` in addition to built-in modules for OS-level tasks like reading/writing files (`node:fs`) and networking (`node:net`, `node:http`). Node.js also implements a CommonJS-based module system and resolution algorithm that pre-dates JavaScript's native module system.

Bun is designed as a faster, leaner, more modern replacement for Node.js.

## Design goals

Bun is designed from the ground-up with today's JavaScript ecosystem in mind.

* **Speed**. Bun processes start [4x faster than Node.js](https://twitter.com/jarredsumner/status/1499225725492076544).
* **TypeScript & JSX support**. You can directly execute `.jsx`, `.ts`, and `.tsx` files; Bun's transpiler converts these to vanilla JavaScript before execution.
* **ESM & CommonJS compatibility**. The world is moving towards ES modules (ESM), but millions of packages on npm still require CommonJS. Bun recommends ES modules, but supports CommonJS.
* **Web-standard APIs**. Bun implements standard Web APIs like `fetch`, `WebSocket`, and `ReadableStream`. Bun is powered by the JavaScriptCore engine, which is developed by Apple for Safari, so some APIs like [`Headers`](https://developer.mozilla.org/en-US/docs/Web/API/Headers) and [`URL`](https://developer.mozilla.org/en-US/docs/Web/API/URL) directly use [Safari's implementation](https://github.com/oven-sh/bun/blob/HEAD/src/jsc/bindings/webcore/JSFetchHeaders.cpp).
* **Node.js compatibility**. In addition to supporting Node-style module resolution, Bun aims for full compatibility with built-in Node.js globals (`process`, `Buffer`) and modules (`path`, `fs`, `http`). *This is an ongoing effort.* See [Node.js compatibility](/docs/runtime/nodejs-compat) for the current status.

Bun is more than a runtime. The long-term goal is a cohesive, infrastructural toolkit for building apps with JavaScript/TypeScript: a package manager, transpiler, bundler, script runner, and test runner.


# Installation
Source: https://bun.com/docs/installation

Install Bun with npm, Homebrew, Docker, or the official script.

## Overview

Bun ships as a single, dependency-free executable. Install it with the install script, a package manager, or Docker on macOS, Linux, and Windows.

<Tip>After installation, verify with `bun --version` and `bun --revision`.</Tip>

## Installation

<Tabs>
  <Tab title="macOS & Linux">
    <CodeGroup>
      ```bash curl icon="globe" theme={"theme":{"light":"github-light","dark":"dracula"}}
      curl -fsSL https://bun.com/install | bash
      ```
    </CodeGroup>

    <Note>
      **Linux users:** You need the `unzip` package to install Bun (`sudo apt install unzip`). We recommend kernel version 5.6 or higher. Bun runs on kernels as old as 3.10 (RHEL 7) with graceful degradation of newer syscalls. Use `uname -r` to check your kernel version.
    </Note>
  </Tab>

  <Tab title="Windows">
    <CodeGroup>
      ```powershell PowerShell icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
      powershell -c "irm bun.sh/install.ps1|iex"
      ```
    </CodeGroup>

    <Warning>
      Bun requires Windows 10 version 1809 or later.
    </Warning>

    For support and discussion, join the **#windows** channel on the [Discord](https://bun.com/discord).
  </Tab>

  <Tab title="Package Managers">
    <CodeGroup>
      ```bash npm icon="npm" theme={"theme":{"light":"github-light","dark":"dracula"}}
      npm install -g bun # the last `npm` command you'll ever need
      ```

      ```bash Homebrew icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/homebrew.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=72e2bae1e891694752fa3352050dc96f" theme={"theme":{"light":"github-light","dark":"dracula"}}
      brew install oven-sh/bun/bun
      ```

      ```bash Scoop icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
      scoop install bun
      ```
    </CodeGroup>
  </Tab>

  <Tab title="Docker">
    Bun provides a Docker image that supports both Linux x64 and arm64.

    ```bash Docker icon="docker" theme={"theme":{"light":"github-light","dark":"dracula"}}
    docker pull oven/bun
    docker run --rm --init --ulimit memlock=-1:-1 oven/bun
    ```

    ### Image Variants

    Bun also publishes image variants for different operating systems:

    ```bash Docker icon="docker" theme={"theme":{"light":"github-light","dark":"dracula"}}
    docker pull oven/bun:debian
    docker pull oven/bun:slim
    docker pull oven/bun:distroless
    docker pull oven/bun:alpine
    ```
  </Tab>
</Tabs>

To check that Bun was installed successfully, open a new terminal window and run:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun --version
# Output: 1.x.y

# See the precise commit of `oven-sh/bun` that you're using
bun --revision
# Output: 1.x.y+b7982ac13189
```

<Warning>
  If you've installed Bun but are seeing a `command not found` error, you may have to manually add the installation
  directory (`~/.bun/bin`) to your `PATH`.
</Warning>

<Accordion title="Add Bun to your PATH">
  <Tabs>
    <Tab title="macOS & Linux">
      <Steps>
        <Step title="Determine which shell you're using">
          ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
          echo $SHELL
          # /bin/zsh  or /bin/bash or /bin/fish
          ```
        </Step>

        <Step title="Open your shell configuration file">
          * For bash: `~/.bashrc`
          * For zsh: `~/.zshrc`
          * For fish: `~/.config/fish/config.fish`
        </Step>

        <Step title="Add the Bun directory to PATH">
          Add these lines to your configuration file:

          ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
          export BUN_INSTALL="$HOME/.bun"
          export PATH="$BUN_INSTALL/bin:$PATH"
          ```
        </Step>

        <Step title="Reload your shell configuration">
          ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
          source ~/.bashrc  # or ~/.zshrc
          ```
        </Step>
      </Steps>
    </Tab>

    <Tab title="Windows">
      <Steps>
        <Step title="Determine if the bun binary is properly installed">
          ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
          & "$env:USERPROFILE\.bun\bin\bun" --version
          ```

          If the command runs successfully but `bun --version` is not recognized, bun is not in your system's PATH. To fix this, open a PowerShell terminal and run the following command:

          ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
          [System.Environment]::SetEnvironmentVariable(
            "Path",
            [System.Environment]::GetEnvironmentVariable("Path", "User") + ";$env:USERPROFILE\.bun\bin",
            [System.EnvironmentVariableTarget]::User
          )
          ```
        </Step>

        <Step title="Restart your terminal">
          Restart your terminal and test with `bun --version`.

          ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
          bun --version
          ```
        </Step>
      </Steps>
    </Tab>
  </Tabs>
</Accordion>

***

## Upgrading

Once installed, the binary can upgrade itself:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun upgrade
```

<Tip>
  **Homebrew users** <br />
  To avoid conflicts with Homebrew, use `brew upgrade bun` instead.

  **Scoop users** <br />
  To avoid conflicts with Scoop, use `scoop update bun` instead.
</Tip>

***

## Canary Builds

[-> View canary build](https://github.com/oven-sh/bun/releases/tag/canary)

Bun automatically releases an (untested) canary build on every commit to main. To upgrade to the latest canary build:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
# Upgrade to latest canary
bun upgrade --canary

# Switch back to stable
bun upgrade --stable
```

Use a canary build to test new features and bug fixes before they reach a stable release. To help the Bun team fix bugs faster, canary builds automatically upload crash reports.

***

## Installing Older Versions

Since Bun is a single binary, you can install older versions by re-running the installer script with a specific version.

<Tabs>
  <Tab title="Linux & macOS">
    To install a specific version, pass the git tag to the install script:

    ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
    curl -fsSL https://bun.com/install | bash -s "bun-v1.3.3"
    ```
  </Tab>

  <Tab title="Windows">
    On Windows, pass the version number to the PowerShell install script:

    ```powershell PowerShell icon="windows" theme={"theme":{"light":"github-light","dark":"dracula"}}
    iex "& {$(irm https://bun.com/install.ps1)} -Version 1.3.3"
    ```
  </Tab>
</Tabs>

***

## Direct Downloads

To download Bun binaries directly, visit the [releases page on GitHub](https://github.com/oven-sh/bun/releases).

### Latest Version Downloads

<CardGroup>
  <Card icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/linux.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=f45928d13c067bfd53a41fb7c0860fff" title="Linux x64" href="https://github.com/oven-sh/bun/releases/latest/download/bun-linux-x64.zip">
    Standard Linux x64 binary
  </Card>

  <Card icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/linux.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=f45928d13c067bfd53a41fb7c0860fff" title="Linux x64 Baseline" href="https://github.com/oven-sh/bun/releases/latest/download/bun-linux-x64-baseline.zip">
    For older CPUs without AVX2
  </Card>

  <Card icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/windows.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=c1e6c9fd5641fcc457e95413202af191" title="Windows x64" href="https://github.com/oven-sh/bun/releases/latest/download/bun-windows-x64.zip">
    Standard Windows binary
  </Card>

  <Card icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/windows.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=c1e6c9fd5641fcc457e95413202af191" title="Windows x64 Baseline" href="https://github.com/oven-sh/bun/releases/latest/download/bun-windows-x64-baseline.zip">
    For older CPUs without AVX2
  </Card>

  <Card icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/windows.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=c1e6c9fd5641fcc457e95413202af191" title="Windows ARM64" href="https://github.com/oven-sh/bun/releases/latest/download/bun-windows-aarch64.zip">
    Windows on ARM (Snapdragon, etc.)
  </Card>

  <Card icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/apple.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=eeb6d9360a2dc50a9df88bdb002fd768" title="macOS ARM64" href="https://github.com/oven-sh/bun/releases/latest/download/bun-darwin-aarch64.zip">
    Apple Silicon (M1/M2/M3)
  </Card>

  <Card icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/apple.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=eeb6d9360a2dc50a9df88bdb002fd768" title="macOS x64" href="https://github.com/oven-sh/bun/releases/latest/download/bun-darwin-x64.zip">
    Intel Macs
  </Card>

  <Card icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/linux.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=f45928d13c067bfd53a41fb7c0860fff" title="Linux ARM64" href="https://github.com/oven-sh/bun/releases/latest/download/bun-linux-aarch64.zip">
    ARM64 Linux systems
  </Card>
</CardGroup>

### Musl Binaries

For distributions without `glibc` (Alpine Linux, Void Linux):

* [Linux x64 musl](https://github.com/oven-sh/bun/releases/latest/download/bun-linux-x64-musl.zip)
* [Linux x64 musl baseline](https://github.com/oven-sh/bun/releases/latest/download/bun-linux-x64-musl-baseline.zip)
* [Linux ARM64 musl](https://github.com/oven-sh/bun/releases/latest/download/bun-linux-aarch64-musl.zip)

<Note>
  Bun's glibc binaries require glibc 2.17 or newer. If you encounter an error like `bun:
      /lib/x86_64-linux-gnu/libc.so.6: version GLIBC_... not found`, try using the musl binary. Bun's install script
  automatically chooses the correct binary for your system.
</Note>

***

## CPU Requirements

CPU requirements depend on which binary you're using:

<Tabs>
  <Tab title="Standard Builds">
    **x64 binaries** target the Haswell CPU architecture (AVX and AVX2 instructions required)

    | Platform | Intel Requirement               | AMD Requirement    |
    | -------- | ------------------------------- | ------------------ |
    | x64      | Haswell (4th gen Core) or newer | Excavator or newer |
  </Tab>

  <Tab title="Baseline Builds">
    **x64-baseline binaries** target the Nehalem architecture for older CPUs

    | Platform     | Intel Requirement               | AMD Requirement    |
    | ------------ | ------------------------------- | ------------------ |
    | x64-baseline | Nehalem (1st gen Core) or newer | Bulldozer or newer |

    <Warning>
      Baseline builds are slower than regular builds. Use them only if you encounter an "Illegal
      Instruction" error.
    </Warning>
  </Tab>
</Tabs>

<Note>
  Bun does not support CPUs older than the baseline target, which requires the SSE4.2 extension. Bun requires macOS 13.0
  or later.
</Note>

***

## Uninstall

To remove Bun from your system:

<Tabs>
  <Tab title="macOS & Linux">
    ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
    rm -rf ~/.bun
    ```
  </Tab>

  <Tab title="Windows">
    ```powershell PowerShell icon="windows" theme={"theme":{"light":"github-light","dark":"dracula"}}
    powershell -c ~\.bun\uninstall.ps1
    ```
  </Tab>

  <Tab title="Package Managers">
    <CodeGroup>
      ```bash npm icon="npm" theme={"theme":{"light":"github-light","dark":"dracula"}}
      npm uninstall -g bun
      ```

      ```bash Homebrew icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/homebrew.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=72e2bae1e891694752fa3352050dc96f" theme={"theme":{"light":"github-light","dark":"dracula"}}
      brew uninstall bun
      ```

      ```bash Scoop icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
      scoop uninstall bun
      ```
    </CodeGroup>
  </Tab>
</Tabs>


# bunx
Source: https://bun.com/docs/pm/bunx

Run packages from npm

<Note>`bunx` is an alias for `bun x`. The `bunx` CLI is auto-installed when you install `bun`.</Note>

Use `bunx` to auto-install and run packages from `npm`. It's Bun's equivalent of `npx` or `yarn dlx`.

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bunx cowsay "Hello world!"
```

<Note>
  ⚡️ **Speed** — With Bun's fast startup times, `bunx` is [roughly 100x
  faster](https://twitter.com/jarredsumner/status/1606163655527059458) than `npx` for locally installed packages.
</Note>

Packages can declare executables in the `"bin"` field of their `package.json`. These are known as *package executables* or *package binaries*.

```json package.json icon="file-json" theme={"theme":{"light":"github-light","dark":"dracula"}}
{
  // ... other fields
  "name": "my-cli",
  "bin": {
    "my-cli": "dist/index.js"
  }
}
```

These executables are commonly plain JavaScript files marked with a [shebang line](https://en.wikipedia.org/wiki/Shebang_\(Unix\)) naming the program that should run them. The following file runs with `node`.

```js dist/index.js icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/javascript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=5148f41bbc784f9828f1363dab67340f" theme={"theme":{"light":"github-light","dark":"dracula"}}
#!/usr/bin/env node

console.log("Hello world!");
```

Run these executables with `bunx`:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bunx my-cli
```

As with `npx`, `bunx` checks for a locally installed package first, then falls back to auto-installing it from `npm`. `bunx` stores installed packages in Bun's [global cache](/docs/pm/global-cache) for future use.

## Arguments and flags

To pass additional command-line flags and arguments through to the executable, place them after the executable name.

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bunx my-cli --foo bar
```

***

## Shebangs

By default, Bun respects shebangs. If an executable is marked with `#!/usr/bin/env node`, Bun spins up a `node` process to execute the file. To run the executable with Bun's runtime instead, pass the `--bun` flag.

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bunx --bun my-cli
```

The `--bun` flag must occur *before* the executable name. `bunx` passes flags that appear *after* the name through to the executable.

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bunx --bun my-cli # good
bunx my-cli --bun # bad
```

## Package flag

**`--package <pkg>` or `-p <pkg>`** - Run a binary from a specific package. Useful when the binary name differs from the package name:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bunx -p renovate renovate-config-validator
bunx --package @angular/cli ng
```

To force a script to always run with Bun, give it a `bun` shebang.

```js dist/index.js icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/javascript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=5148f41bbc784f9828f1363dab67340f" theme={"theme":{"light":"github-light","dark":"dracula"}}
#!/usr/bin/env bun
```

***

## Usage

```bash theme={"theme":{"light":"github-light","dark":"dracula"}}
bunx [flags] <package>[@version] [flags and arguments for the package]
```

Execute an npm package executable (CLI). If the package isn't installed in `node_modules`, Bun installs it into a global shared cache.

### Flags

<ParamField type="boolean">
  Force the command to run with Bun instead of Node.js, even if the executable contains a Node shebang (`#!/usr/bin/env
      node`)
</ParamField>

<ParamField type="string">
  Specify package to install when binary name differs from package name
</ParamField>

<ParamField type="boolean">
  Skip installation if package is not already installed
</ParamField>

<ParamField type="boolean">
  Enable verbose output during installation
</ParamField>

<ParamField type="boolean">
  Suppress output during installation
</ParamField>

### Examples

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
# Run Prisma migrations
bunx prisma migrate

# Format a file with Prettier
bunx prettier foo.js

# Run a specific version of a package
bunx uglify-js@3.14.0 app.js

# Use --package when binary name differs from package name
bunx -p @angular/cli ng new my-app

# Force running with Bun instead of Node.js, even if the executable contains a Node shebang
bunx --bun vite dev foo.js
```


# Catalogs
Source: https://bun.com/docs/pm/catalogs

Share common dependency versions across multiple packages in a monorepo

**Catalogs** share dependency versions across the packages in a monorepo. Rather than repeating the same versions in each workspace package, you define them once in the root `package.json` and reference them throughout your project.

## Overview

Instead of each workspace package specifying its own versions, you:

1. Define version catalogs in the root `package.json`
2. Reference those versions with the `catalog:` protocol
3. Update every package at once by changing the version in one place

Catalogs matter most in large monorepos where dozens of packages depend on the same versions of key dependencies.

## How to Use Catalogs

### Directory Structure Example

Consider a monorepo with the following structure:

```
my-monorepo/
├── package.json
├── bun.lock
└── packages/
    ├── app/
    │   └── package.json
    ├── ui/
    │   └── package.json
    └── utils/
        └── package.json
```

### 1. Define Catalogs in Root package.json

In your root-level `package.json`, add a `catalog` or `catalogs` field within the `workspaces` object:

```json package.json icon="file-json" theme={"theme":{"light":"github-light","dark":"dracula"}}
{
  "name": "my-monorepo",
  "workspaces": {
    "packages": ["packages/*"],
    "catalog": {
      "react": "^19.0.0",
      "react-dom": "^19.0.0"
    },
    "catalogs": {
      "testing": {
        "jest": "30.0.0",
        "testing-library": "14.0.0"
      }
    }
  }
}
```

`catalog` and `catalogs` also work at the top level of `package.json`.

### 2. Reference Catalog Versions in Workspace Packages

In your workspace packages, use the `catalog:` protocol to reference versions:

```json packages/app/package.json icon="file-json" theme={"theme":{"light":"github-light","dark":"dracula"}}
{
  "name": "app",
  "dependencies": {
    "react": "catalog:",
    "react-dom": "catalog:",
    "jest": "catalog:testing"
  }
}
```

```json packages/ui/package.json icon="file-json" theme={"theme":{"light":"github-light","dark":"dracula"}}
{
  "name": "ui",
  "dependencies": {
    "react": "catalog:",
    "react-dom": "catalog:"
  },
  "devDependencies": {
    "jest": "catalog:testing",
    "testing-library": "catalog:testing"
  },
  "peerDependencies": {
    "react": "catalog:"
  }
}
```

`catalog:` references work in `dependencies`, `devDependencies`, `optionalDependencies`, `peerDependencies`, and as the value of a root [`overrides`](/docs/pm/overrides) rule. A catalog reference behaves exactly as if the catalog's range were written inline.

### 3. Run Bun Install

Run `bun install` to install all dependencies according to the catalog versions.

## Catalog vs Catalogs

Bun supports two ways to define catalogs:

1. **`catalog`** (singular): A single default catalog for commonly used dependencies

   ```json package.json icon="file-json" theme={"theme":{"light":"github-light","dark":"dracula"}}
   "catalog": {
     "react": "^19.0.0",
     "react-dom": "^19.0.0"
   }
   ```

   Reference with `catalog:`:

   ```json packages/app/package.json icon="file-json" theme={"theme":{"light":"github-light","dark":"dracula"}}
   "dependencies": {
     "react": "catalog:"
   }
   ```

2. **`catalogs`** (plural): Multiple named catalogs for grouping dependencies

   ```json package.json icon="file-json" theme={"theme":{"light":"github-light","dark":"dracula"}}
   "catalogs": {
     "testing": {
       "jest": "30.0.0"
     },
     "ui": {
       "tailwind": "4.0.0"
     }
   }
   ```

   Reference with `catalog:<name>`:

   ```json packages/app/package.json icon="file-json" theme={"theme":{"light":"github-light","dark":"dracula"}}
   "dependencies": {
     "jest": "catalog:testing",
     "tailwind": "catalog:ui"
   }
   ```

## Benefits of Using Catalogs

* **Consistency**: All packages use the same version of critical dependencies
* **Maintenance**: Update a dependency version in one place instead of across multiple `package.json` files
* **Clarity**: Makes it obvious which dependencies are standardized across your monorepo
* **Simplicity**: No extra version resolution strategies or external tools

## Real-World Example

A larger example, for a React application:

**Root package.json**

```json package.json icon="file-json" theme={"theme":{"light":"github-light","dark":"dracula"}}
{
  "name": "react-monorepo",
  "workspaces": {
    "packages": ["packages/*"],
    "catalog": {
      "react": "^19.0.0",
      "react-dom": "^19.0.0",
      "react-router-dom": "^6.15.0"
    },
    "catalogs": {
      "build": {
        "webpack": "5.88.2",
        "babel": "7.22.10"
      },
      "testing": {
        "jest": "29.6.2",
        "react-testing-library": "14.0.0"
      }
    }
  },
  "devDependencies": {
    "typescript": "5.1.6"
  }
}
```

```json packages/app/package.json icon="file-json" theme={"theme":{"light":"github-light","dark":"dracula"}}
{
  "name": "app",
  "dependencies": {
    "react": "catalog:",
    "react-dom": "catalog:",
    "react-router-dom": "catalog:",
    "@monorepo/ui": "workspace:*",
    "@monorepo/utils": "workspace:*"
  },
  "devDependencies": {
    "webpack": "catalog:build",
    "babel": "catalog:build",
    "jest": "catalog:testing",
    "react-testing-library": "catalog:testing"
  }
}
```

```json packages/ui/package.json icon="file-json" theme={"theme":{"light":"github-light","dark":"dracula"}}
{
  "name": "@monorepo/ui",
  "dependencies": {
    "react": "catalog:",
    "react-dom": "catalog:"
  },
  "devDependencies": {
    "jest": "catalog:testing",
    "react-testing-library": "catalog:testing"
  }
}
```

```json packages/utils/package.json icon="file-json" theme={"theme":{"light":"github-light","dark":"dracula"}}
{
  "name": "@monorepo/utils",
  "dependencies": {
    "react": "catalog:"
  },
  "devDependencies": {
    "jest": "catalog:testing"
  }
}
```

## Updating Versions

To update versions across all packages, change the version in the root package.json:

```json package.json icon="file-json" theme={"theme":{"light":"github-light","dark":"dracula"}}
"catalog": {
  "react": "^19.1.0",  // Updated from ^19.0.0
  "react-dom": "^19.1.0"  // Updated from ^19.0.0
}
```

Then run `bun install` to update all packages.

## Adding to the catalog with `bun add`

`bun add --catalog` adds the entry to the root catalog and writes `"catalog:"` to the current package; `--catalog=<name>` adds it to `catalogs.<name>` and writes `"catalog:<name>"`. Bun reuses an existing catalog entry unless you pass an explicit version. See [`bun add --catalog`](/docs/pm/cli/add#--catalog).

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun add react --catalog
```

Even without the flag, `bun add react` (no version) writes `"catalog:"` when the default catalog already lists `react`. Pass a version to write a concrete range instead.

## Lockfile Integration

Bun's lockfile tracks catalog versions, so installs are consistent across environments. The lockfile includes:

* The catalog definitions from your package.json
* The resolution of each cataloged dependency

```json bun.lock(excerpt) icon="file-json" theme={"theme":{"light":"github-light","dark":"dracula"}}
{
  "lockfileVersion": 2,
  "workspaces": {
    "": {
      "name": "react-monorepo",
    },
    "packages/app": {
      "name": "app",
      "dependencies": {
        "react": "catalog:",
        "react-dom": "catalog:",
        ...
      },
    },
    ...
  },
  "catalog": {
    "react": "^19.0.0",
    "react-dom": "^19.0.0",
    ...
  },
  "catalogs": {
    "build": {
      "webpack": "5.88.2",
      ...
    },
    ...
  },
  "packages": {
    ...
  }
}
```

## Limitations and Edge Cases

* Catalog references must match a dependency defined in either `catalog` or one of the named `catalogs`
* Bun ignores empty strings and whitespace in catalog names and treats them as the default catalog
* `catalog:default` is the same as `catalog:`. You can define the default catalog as either `catalog` or `catalogs.default`, but a package listed in both is an error
* Invalid dependency versions in catalogs fail to resolve during `bun install`
* `catalog:` only works in the root and workspace `package.json` files. Inside a published package it fails to resolve. Publish with `bun publish` or `bun pm pack`, which replace it with the real range (see [Publishing](#publishing))

## Publishing

When you run `bun publish` or `bun pm pack`, Bun replaces `catalog:` references
in your `package.json` with the resolved version numbers. The published package
includes regular semver strings and no longer depends on your catalog
definitions.


# bun add
Source: https://bun.com/docs/pm/cli/add

Add packages to your project with Bun's fast package manager

To add a particular package:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun add preact
```

To specify a version, version range, or tag:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun add zod@3.20.0
bun add zod@^3.0.0
bun add zod@latest
```

Bun writes the package to `dependencies` unless you pass `--dev`, `--optional`, or `--peer`. If `package.json` already lists it in another group, Bun updates that entry in place.

## `--dev`

<Note>**Alias** — `--development`, `-d`, `-D`</Note>

To add a package as a dev dependency (`"devDependencies"`):

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun add --dev @types/react
bun add -d @types/react
```

## `--optional`

To add a package as an optional dependency (`"optionalDependencies"`):

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun add --optional lodash
```

## `--peer`

To add a package as a peer dependency (`"peerDependencies"`):

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun add --peer @types/bun
```

Bun installs peer dependencies by default, so no additional `devDependencies` entry is needed.

## `--exact`

<Note>**Alias** — `-E`</Note>

To pin a package to the resolved version, use `--exact`. Bun writes the exact version number to your `package.json` instead of a version range.

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun add react --exact
bun add react -E
```

The difference in `package.json`:

```json package.json icon="file-json" theme={"theme":{"light":"github-light","dark":"dracula"}}
{
  "dependencies": {
    // without --exact
    "react": "^18.2.0", // this matches >= 18.2.0 < 19.0.0

    // with --exact
    "react": "18.2.0" // this matches only 18.2.0 exactly
  }
}
```

To view a complete list of options for this command:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun add --help
```

## `--catalog`

In a workspace, `--catalog` writes the version to the root `package.json` [catalog](/docs/pm/catalogs) and adds `"catalog:"` to the current package. `--catalog=<name>` uses a named catalog (`workspaces.catalogs.<name>`) and writes `"catalog:<name>"`.

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun add react --catalog
bun add vitest --catalog=testing
```

```json package.json icon="file-json" theme={"theme":{"light":"github-light","dark":"dracula"}}
// root package.json
{
  "workspaces": {
    "packages": ["packages/*"],
    "catalog": {
      "react": "^18.2.0" // [!code ++]
    }
  }
}
```

```json packages/app/package.json icon="file-json" theme={"theme":{"light":"github-light","dark":"dracula"}}
{
  "dependencies": {
    "react": "catalog:" // [!code ++]
  }
}
```

* If the catalog already has an entry, Bun reuses it and writes only `"catalog:"` to the current package. Pass an explicit version (`bun add react@19 --catalog`) to replace the entry — this affects every package that references it.
* If you omit the version and the current `package.json` already has a range (`"react": "^18.2.0"`), Bun catalogs that range.
* A package that already references `"catalog:<name>"` keeps using that catalog.
* Attach the name with `=`: `--catalog=testing`, not `--catalog testing`.
* Bun catalogs tarball and git specifiers under the package's real name. It rejects relative paths and workspace packages.

Even without the flag, `bun add react` (no version) writes `"catalog:"` if the default catalog already lists `react`. Pass a version to write a concrete range instead.

## `--filter`

<Note>**Alias** — `-F`</Note>

In a monorepo, add the package to the matching workspace(s) instead of the current directory's package. See [filtering](/docs/pm/filter) for the pattern syntax. Repeat the flag to combine patterns; `!pattern` excludes.

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun add zod --filter api
bun add -d typescript --filter './packages/*'
bun add ./vendor/logger --filter '*'
bun remove zod --filter '*' --filter '!api'
```

* `*` matches every workspace package but not the root. To include the root, name it: `--filter '*' --filter '<root-name>'`.
* If no workspace matches, Bun writes nothing and the command fails.
* Bun resolves local paths from the current directory and rewrites them relative to each selected package.
* Bun updates `bun.lock` for the whole repo but links only the selected workspaces into `node_modules`, as with `bun install --filter`.
* Cannot be combined with `--global`.

## `--global`

<Note>**Alias** — `bun add --global`, `bun add -g`, `bun install --global` and `bun install -g`</Note>

To install a package globally, use the `-g`/`--global` flag. This does not modify the `package.json` of your current project. Use it to install command-line tools.

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun add --global cowsay # or `bun add -g cowsay`
cowsay "Bun!"
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
 ______
< Bun! >
 ------
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||
```

<Accordion title="Configuring global installation behavior">
  ```toml bunfig.toml icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
  [install]
  # where `bun add --global` installs packages
  globalDir = "~/.bun/install/global"

  # where globally-installed package bins are linked
  globalBinDir = "~/.bun/bin"
  ```
</Accordion>

## Trusted dependencies

Unlike other npm clients, Bun does not execute arbitrary lifecycle scripts for installed dependencies, such as `postinstall`. These scripts represent a potential security risk, as they can execute arbitrary code on your machine.

To tell Bun to allow lifecycle scripts for a particular package, add the package to `trustedDependencies` in your package.json.

```json package.json icon="file-json" theme={"theme":{"light":"github-light","dark":"dracula"}}
{
  "name": "my-app",
  "version": "1.0.0",
  "trustedDependencies": ["my-trusted-package"] // [!code ++]
}
```

Bun reads this field and runs lifecycle scripts for `my-trusted-package`.

## Git dependencies

To add a dependency from a public or private git repository:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun add git@github.com:moment/moment.git
```

<Note>
  To install private repositories, your system needs the appropriate SSH credentials to access the repository.
</Note>

Bun supports a variety of protocols, including [`github`](https://docs.npmjs.com/cli/v9/configuring-npm/package-json#github-urls), [`git`](https://docs.npmjs.com/cli/v9/configuring-npm/package-json#git-urls-as-dependencies), `git+ssh`, and `git+https`.

```json package.json icon="file-json" theme={"theme":{"light":"github-light","dark":"dracula"}}
{
  "dependencies": {
    "dayjs": "git+https://github.com/iamkun/dayjs.git",
    "lodash": "git+ssh://github.com/lodash/lodash.git#4.17.21",
    "moment": "git@github.com:moment/moment.git",
    "zod": "github:colinhacks/zod"
  }
}
```

## Tarball dependencies

A package name can correspond to a publicly hosted `.tgz` file. Bun downloads and installs the package from that tarball URL rather than from the package registry.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun add zod@https://registry.npmjs.org/zod/-/zod-3.21.4.tgz
```

`bun add` writes the URL to your `package.json`:

```json package.json icon="file-json" theme={"theme":{"light":"github-light","dark":"dracula"}}
{
  "dependencies": {
    "zod": "https://registry.npmjs.org/zod/-/zod-3.21.4.tgz"
  }
}
```

***

## CLI Usage

```bash theme={"theme":{"light":"github-light","dark":"dracula"}}
bun add <package> <@version>
```

### Dependency Management

<ParamField type="boolean">
  Don't install devDependencies. Alias: <code>-p</code>
</ParamField>

<ParamField type="string">
  Exclude <code>dev</code>, <code>optional</code>, or <code>peer</code> dependencies from install
</ParamField>

<ParamField type="boolean">
  Install globally. Alias: <code>-g</code>
</ParamField>

<ParamField type="boolean">
  Add dependency to <code>devDependencies</code>. Alias: <code>-d</code>
</ParamField>

<ParamField type="boolean">
  Add dependency to <code>optionalDependencies</code>
</ParamField>

<ParamField type="boolean">
  Add dependency to <code>peerDependencies</code>
</ParamField>

<ParamField type="boolean">
  Add the exact version instead of the <code>^</code> range. Alias: <code>-E</code>
</ParamField>

<ParamField type="boolean">
  Only add dependencies to <code>package.json</code> if they are not already present
</ParamField>

<ParamField type="string">
  Add the resolved version to the root <code>package.json</code> catalog and depend on it as <code>catalog:</code>;
  <code>--catalog=NAME</code> targets <code>catalogs.NAME</code>
</ParamField>

<ParamField type="string">
  Add the package(s) to the matching workspaces instead of the current package. Alias: <code>-F</code>
</ParamField>

### Project Files & Lockfiles

<ParamField type="boolean">
  Write a <code>yarn.lock</code> file (yarn v1). Alias: <code>-y</code>
</ParamField>

<ParamField type="boolean">
  Don't update <code>package.json</code> or save a lockfile
</ParamField>

<ParamField type="boolean">
  Save to <code>package.json</code>
</ParamField>

<ParamField type="boolean">
  Disallow changes to lockfile
</ParamField>

<ParamField type="boolean">
  Add to <code>trustedDependencies</code> in the project's <code>package.json</code> and install the package(s)
</ParamField>

<ParamField type="boolean">
  Save a text-based lockfile
</ParamField>

<ParamField type="boolean">
  Generate a lockfile without installing dependencies
</ParamField>

### Installation Control

<ParamField type="boolean">
  Perform a dry run without making changes
</ParamField>

<ParamField type="boolean">
  Always request the latest versions from the registry & reinstall all dependencies. Alias: <code>-f</code>
</ParamField>

<ParamField type="boolean">
  Skip verifying integrity of newly downloaded packages
</ParamField>

<ParamField type="boolean">
  Skip lifecycle scripts for all packages, including the project's <code>package.json</code> and trusted dependencies
</ParamField>

<ParamField type="boolean">
  Recursively analyze & install dependencies of files passed as arguments (using Bun's bundler). Alias:
  <code>-a</code>
</ParamField>

### Network & Registry

<ParamField type="string">
  Provide a Certificate Authority signing certificate
</ParamField>

<ParamField type="string">
  Same as <code>--ca</code>, but as a file path to the certificate
</ParamField>

<ParamField type="string">
  Use a specific registry by default, overriding <code>.npmrc</code>, <code>bunfig.toml</code>, and environment
  variables
</ParamField>

<ParamField type="number">
  Maximum number of concurrent network requests
</ParamField>

### Performance & Resource

<ParamField type="string">
  Platform-specific optimizations for installing dependencies. Possible values: <code>clonefile</code> (default on
  macOS), <code>hardlink</code> (default on Linux and Windows), <code>symlink</code>, <code>copyfile</code>
</ParamField>

<ParamField type="number">
  Maximum number of concurrent jobs for lifecycle scripts (default: 2x CPU cores)
</ParamField>

### Caching

<ParamField type="string">
  Store & load cached data from a specific directory path
</ParamField>

<ParamField type="boolean">
  Ignore manifest cache entirely
</ParamField>

### Output & Logging

<ParamField type="boolean">
  Don't log anything
</ParamField>

<ParamField type="boolean">
  Excessively verbose logging
</ParamField>

<ParamField type="boolean">
  Disable the progress bar
</ParamField>

<ParamField type="boolean">
  Don't print a summary
</ParamField>

### Global Configuration & Context

<ParamField type="string">
  Specify path to config file (<code>bunfig.toml</code>). Alias: <code>-c</code>
</ParamField>

<ParamField type="string">
  Set a specific current working directory
</ParamField>

### Help

<ParamField type="boolean">
  Print this help menu. Alias: <code>-h</code>
</ParamField>


# bun audit
Source: https://bun.com/docs/pm/cli/audit

Check your installed packages for known security vulnerabilities

Run the command in a project with a `bun.lock` file:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun audit
```

Bun reads the package list from `bun.lock` (no `node_modules` required), sends it to the npm advisory endpoint, and prints a report. Bun sends packages from a scoped registry to that registry instead. If that registry has no advisory endpoint, Bun lists those packages as skipped and they don't affect the exit code.

`bun audit` never modifies `package.json`, `bun.lock`, or `node_modules`. To apply fixes, use [`bun audit fix`](#bun-audit-fix).

If Bun finds no vulnerabilities, the command prints:

```
No vulnerabilities found
```

Otherwise, Bun lists each affected package with its severity, a short description, and a link to the advisory, followed by a summary:

```
3 vulnerabilities (1 high, 2 moderate)

  bun audit fix           upgrade the vulnerable packages within their ranges
  bun audit fix --latest  also cross major versions
```

### Filtering options

**`--audit-level=<low|moderate|high|critical>`** - Only report vulnerabilities at this severity or higher:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun audit --audit-level=high
```

**`--prod`** (`-p`, `-P`, `--production`) - Only audit packages reachable through `dependencies`, `optionalDependencies`, or `peerDependencies`:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun audit --prod
```

**`--omit=<dev|optional|peer>`** - Skip packages only reachable through the given dependency type. Repeatable. `--omit=dev` is the same as `--prod`:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun audit --omit=optional --omit=peer
```

**`--ignore <id>`** - Ignore an advisory by GHSA ID or numeric ID. Repeatable. (CVE IDs are not in the registry data and don't match.)

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun audit --ignore GHSA-c2qf-rxjj-qqgw --ignore 1112918
```

These options are CLI-only; to apply them on every run, put them in a `package.json` script.

### `--json`

Print the raw JSON response from the registry instead of the formatted report:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun audit --json
```

The JSON is unfiltered — `--audit-level` and `--ignore` only affect the exit code.

### `bun audit fix`

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun audit fix
```

Runs the audit, then upgrades each vulnerable package to the lowest non-vulnerable version that every dependent's range allows, and installs. Only `bun.lock` and `node_modules` change, with one exception: Bun treats a direct dependency pinned to an exact version as `^version`. If Bun finds a fix, it rewrites the pin in `package.json` (or the catalog entry).

```
fixing:
  ms@0.7.0 → 0.7.1
  lodash@4.17.20 → 4.17.21
    package.json: 4.17.20 → 4.17.21

blocked by a dependent's range:
  minimatch@0.3.0 → 3.0.2
    express@3.21.2 depends on minimatch@0.3.0
  semver@5.7.1 → 6.3.1
    my-app depends on semver@^5.0.0
    bun audit fix --latest

no published version fixes:
  left-pad@1.3.0  GHSA-xxxx-xxxx-xxxx
    bun audit fix --ignore GHSA-xxxx-xxxx-xxxx

Fixed 2 vulnerabilities in 2 packages
5 vulnerabilities remaining
```

* **blocked by a dependent's range** — no safe version fits a dependent's declared range. If the range is in your own `package.json` or catalog, `bun audit fix --latest` gets past it. Otherwise, update the dependent or add an [`overrides`](/docs/pm/overrides) entry.
* **no published version fixes** — every published version is vulnerable. Replace the package, or silence the advisory with the printed `--ignore` command.
* If no newer version is safe but an older one is, Bun downgrades and marks the row `(downgrade)`.
* Bun still installs a safe version newer than `--minimum-release-age` and marks the row `(newer than --minimum-release-age)`.
* Bun upgrades patched dependencies (`patchedDependencies`) like any other package. Re-create the patch afterwards with `bun patch`.
* After installing, Bun re-audits the new lockfile. The `remaining` count and exit code reflect that second audit, so they match what a follow-up `bun audit` would report.
* `--dry-run` prints the plan without installing.
* `--json` prints a single JSON object describing the plan and result (`fixes`, `blocked`, `unfixable`, `unmatched`, `unaudited`, `vulnerableAfterInstall`, `fixed`, `remaining`, `dryRun`). Pass `--ignore-scripts` if lifecycle scripts might write to stdout.
* A [security scanner](/docs/pm/security-scanner-api) configured in `bunfig.toml` runs on the packages about to be installed, as with `bun update`.
* Bun rejects `--prod`, `--frozen-lockfile`, and `--no-save` since they prevent writing `bun.lock`.

### `bun audit fix --latest`

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun audit fix --latest
```

Same as `bun audit fix`, but ranges in your own `package.json` files and catalogs no longer block a fix. Bun rewrites them to accept the new version, keeping their style (`^5.0.0` → `^6.3.1`, `~5.7.1` → `~6.3.1`, exact stays exact). Ranges declared by third-party packages still block; use `overrides` for those.

### Exit code

`0` if no vulnerabilities remain after Bun applies `--audit-level` and `--ignore`, `1` otherwise. For `bun audit fix`, this is based on the re-audit after installing (or the plan, with `--dry-run`).

If the registry request fails, both commands print `audit request failed` to stderr and exit `1`.


# bun dedupe
Source: https://bun.com/docs/pm/cli/dedupe

Remove duplicate versions of packages from bun.lock

Over time, `bun.lock` can accumulate several versions of the same package even though one of them satisfies every range. For example, it can contain both `esbuild@0.15.10` and `esbuild@0.15.11` when the ranges are `^0.15.7` and `^0.15.8`. `bun dedupe` collapses these onto the smallest set of already-locked versions (preferring newer ones), saves `bun.lock`, and installs.

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun dedupe
```

```
bun dedupe v1.4.0 (abc12345)

↳ esbuild 0.15.10 → 0.15.11
↳ react 18.2.0 → 18.3.1

2 duplicate versions removed, 3 packages installed (checked 5 packages) [12.00ms]
```

Each row is a version Bun removed and the version its dependents now use.

`bun dedupe` only chooses between versions already in the lockfile and never modifies `package.json`. It never fetches new versions from the registry and never moves a dependency outside its range. Use [`bun update`](/docs/pm/cli/update) for that.

### `--check` and `--dry-run`

`--check` reports what Bun would remove without changing anything, and exits `1` if there are duplicates. Use it in CI:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun dedupe --check
```

```
bun dedupe v1.4.0 (abc12345)

↳ esbuild 0.15.10 → 0.15.11
↳ react 18.2.0 → 18.3.1

2 duplicate versions can be removed (checked 5 packages) [9.00ms]
  bun dedupe
```

`--dry-run` prints the same output but always exits `0`.

`--lockfile-only` rewrites `bun.lock` without installing.

### Notes

* Bun respects overrides and catalogs. It re-points each dependency using its effective range.
* Bun may move a direct dependency to an *older* locked version if that's the only way to remove a duplicate (e.g. a transitive dependency pins it exactly). Use `bun update` or an [override](/docs/pm/overrides) if you want the newer one to win.
* Bun never removes versions in `patchedDependencies`. If that forces another version to be kept too, Bun prints a `kept …` line explaining why.
* Dependencies on a dist-tag, git URL, or tarball keep their resolved version.
* Requires a lockfile that matches `package.json`. If dependencies changed since the last install, it exits with `bun.lock does not match package.json`. Run `bun install` first. Bun migrates a `package-lock.json`, `yarn.lock`, or `pnpm-lock.yaml` automatically.
* Cannot be combined with `--frozen-lockfile`, `--production`, or `--no-save`; use `--check` instead.
* With the isolated linker, several copies of the *same* version that differ only in peer dependencies are not duplicates, and Bun does not report them. [`bun prune`](/docs/pm/cli/prune) cleans up stale store entries.


# bun info
Source: https://bun.com/docs/pm/cli/info

Display package metadata from the npm registry

`bun info` displays package metadata from the npm registry.

## Usage

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun info react
```

`bun info react` prints the package's latest version, description, homepage, dependencies, and other metadata.

## Viewing specific versions

To view information about a specific version:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun info react@18.0.0
```

## Viewing specific properties

To print specific properties from the package metadata:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun info react version
bun info react dependencies
bun info react repository.url
```

## JSON output

To get the output in JSON format, use the `--json` flag:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun info react --json
```

## Alias

`bun pm view` is an alias for `bun info`:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun pm view react  # equivalent to: bun info react
```

## Examples

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
# View basic package information
bun info is-number

# View a specific version
bun info is-number@7.0.0

# View all available versions
bun info is-number versions

# View package dependencies
bun info express dependencies

# View package homepage
bun info lodash homepage

# Get JSON output
bun info react --json
```


# bun install
Source: https://bun.com/docs/pm/cli/install

Install packages with Bun's fast package manager

## Basic Usage

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun install react
bun install react@19.1.1 # specific version
bun install react@latest # specific tag
```

The `bun` CLI contains a Node.js-compatible package manager designed to be a dramatically faster replacement for `npm`, `yarn`, and `pnpm`. It's a standalone tool that works in existing Node.js projects; if your project has a `package.json`, you can use `bun install`.

<Note>
  **⚡️ 25x faster** — Switch from `npm install` to `bun install` in any Node.js project to make your installations up to 25x faster.

  <Frame>
    ![Bun installation speed
    comparison](https://user-images.githubusercontent.com/709451/147004342-571b6123-17a9-49a2-8bfd-dcfc5204047e.png)
  </Frame>
</Note>

To install all dependencies of a project:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun install
```

`bun install`:

* **Installs** all `dependencies`, `devDependencies`, and `optionalDependencies`. Bun installs `peerDependencies` by default.
* **Runs** your project's `{pre|post}install` and `{pre|post}prepare` scripts at the appropriate time. For security reasons Bun *does not execute* lifecycle scripts of installed dependencies unless they are [trusted](/docs/pm/lifecycle).
* **Writes** a `bun.lock` lockfile to the project root.

***

## Logging

To modify logging verbosity:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun install --verbose # debug logging
bun install --silent  # no logging
```

***

## Lifecycle scripts

Unlike other npm clients, Bun does not execute arbitrary lifecycle scripts like `postinstall` for installed dependencies. Executing arbitrary scripts represents a potential security risk.

To tell Bun to allow lifecycle scripts for a particular package, add the package to `trustedDependencies` in your package.json.

```json package.json icon="file-json" theme={"theme":{"light":"github-light","dark":"dracula"}}
{
  "name": "my-app",
  "version": "1.0.0",
  "trustedDependencies": ["my-trusted-package"] // [!code ++]
}
```

Then re-install the package. Bun reads this field and runs lifecycle scripts for `my-trusted-package`.

Lifecycle scripts run in parallel during installation. To adjust the maximum number of concurrent scripts, use the `--concurrent-scripts` flag. The default is two times the reported cpu count or GOMAXPROCS.

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun install --concurrent-scripts 5
```

Bun automatically optimizes postinstall scripts for popular packages (like `esbuild` and `sharp`) by determining which scripts need to run. To disable these optimizations:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
BUN_FEATURE_FLAG_DISABLE_NATIVE_DEPENDENCY_LINKER=1 bun install
BUN_FEATURE_FLAG_DISABLE_IGNORE_SCRIPTS=1 bun install
```

***

## Workspaces

Bun supports `"workspaces"` in package.json. See [workspaces](/docs/pm/workspaces).

```json package.json icon="file-json" theme={"theme":{"light":"github-light","dark":"dracula"}}
{
  "name": "my-app",
  "version": "1.0.0",
  "workspaces": ["packages/*"], // [!code ++]
  "dependencies": {
    "preact": "^10.5.13"
  }
}
```

***

## Installing dependencies for specific packages

In a monorepo, you can install the dependencies for a subset of packages using the `--filter` flag.

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
# Install dependencies for all workspaces except `pkg-c`
bun install --filter '!pkg-c'

# Install dependencies for only `pkg-a` in `./packages/pkg-a`
bun install --filter './packages/pkg-a'
```

See [filtering](/docs/pm/filter#bun-install-and-bun-outdated).

***

## Overrides and resolutions

Bun supports npm's `"overrides"` and Yarn's `"resolutions"` in `package.json`. Both specify a version range for *metadependencies*, the dependencies of your dependencies. See [overrides and resolutions](/docs/pm/overrides).

```json package.json icon="file-json" theme={"theme":{"light":"github-light","dark":"dracula"}}
{
  "name": "my-app",
  "dependencies": {
    "foo": "^2.0.0"
  },
  "overrides": { // [!code ++]
    "bar": "~4.4.0" // [!code ++]
  } // [!code ++]
}
```

***

## Global packages

To install a package globally, use the `-g`/`--global` flag. Use it to install command-line tools.

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun install --global cowsay # or `bun install -g cowsay`
cowsay "Bun!"
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
 ______
< Bun! >
 ------
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||
```

***

## Production mode

To install in production mode (without `devDependencies`):

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun install --production
```

`--production` implies `--frozen-lockfile`. It only controls what gets installed. `devDependencies` already in `node_modules` from an earlier install stay there. Use [`bun prune --production`](/docs/pm/cli/prune) to remove them.

For reproducible installs, use `--frozen-lockfile`. Bun installs the exact versions specified in the lockfile and does not update it. If your `package.json` disagrees with `bun.lock`, Bun exits with an error.

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun install --frozen-lockfile
```

Bun does not enable `--frozen-lockfile` automatically in CI; pass the flag or use `bun ci`. If there is no lockfile at all, `--frozen-lockfile` installs from `package.json` without writing one.

`--frozen-lockfile` works on a pruned monorepo checkout (e.g. `turbo prune` output, or a Docker context with only some workspace folders copied in). If a workspace listed in `bun.lock` is missing its `package.json` on disk, Bun skips it with a `note:` and does not install its exclusive dependencies. If a remaining workspace depends on a skipped one, the install fails.

To validate the lockfile without installing, use `bun install --frozen-lockfile --dry-run`.

See [lockfile](/docs/pm/lockfile) for more on `bun.lock`.

***

## Omitting dependencies

To omit dev, peer, or optional dependencies, use the `--omit` flag.

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
# Exclude "devDependencies" from the installation. This will apply to the
# root package and workspaces if they exist. Transitive dependencies will
# not have "devDependencies".
bun install --omit dev

# Install only dependencies from "dependencies"
bun install --omit=dev --omit=peer --omit=optional
```

***

## Dry run

To perform a dry run, without installing anything:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun install --dry-run
```

***

## Non-npm dependencies

Bun supports installing dependencies from Git, GitHub, and local or remotely-hosted tarballs. See [`bun add`](/docs/pm/cli/add).

```json package.json icon="file-json" theme={"theme":{"light":"github-light","dark":"dracula"}}
{
  "dependencies": {
    "dayjs": "git+https://github.com/iamkun/dayjs.git",
    "lodash": "git+ssh://github.com/lodash/lodash.git#4.17.21",
    "moment": "git@github.com:moment/moment.git",
    "zod": "github:colinhacks/zod",
    "react": "https://registry.npmjs.org/react/-/react-18.2.0.tgz",
    "bun-types": "npm:@types/bun"
  }
}
```

***

## Installation strategies

Bun supports two package installation strategies that determine how dependencies are organized in `node_modules`:

### Hoisted installs

The traditional npm/Yarn approach that flattens dependencies into a shared `node_modules` directory:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun install --linker hoisted
```

### Isolated installs

A pnpm-like approach that creates strict dependency isolation to prevent [phantom dependencies](/docs/pm/isolated-installs), packages you can import without declaring them in `package.json`:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun install --linker isolated
```

Isolated installs create a central package store in `node_modules/.bun/` with symlinks in the top-level `node_modules`. This ensures packages can only access their declared dependencies.

### Default strategy

The default linker strategy depends on whether you're starting fresh or have an existing project:

* **New workspaces/monorepos**: `isolated` (prevents phantom dependencies)
* **New single-package projects**: `hoisted` (traditional npm behavior)
* **Existing projects (made pre-v1.3.2)**: `hoisted` (preserves backward compatibility)

A `configVersion` field in your lockfile controls the default. For a detailed explanation, see [isolated installs](/docs/pm/isolated-installs).

***

## Minimum release age

To protect against supply chain attacks where malicious packages are quickly published, you can configure a minimum age requirement for npm packages. Bun filters out package versions published more recently than the specified threshold (in seconds) during installation.

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
# Only install package versions published at least 3 days ago
bun add @types/bun --minimum-release-age 259200 # seconds
```

You can also configure this in `bunfig.toml`:

```toml bunfig.toml icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[install]
# Only install package versions published at least 3 days ago
minimumReleaseAge = 259200 # seconds

# Exclude trusted packages from the age gate
minimumReleaseAgeExcludes = ["@types/node", "typescript"]
```

When the minimum age filter is active:

* It 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 resolving them
* When the age gate blocks versions, a stability check detects rapid bugfix patterns
  * If multiple versions were published close together just outside your age gate, Bun extends the filter to skip those potentially unstable versions and selects an older, more mature version
  * The check searches up to 7 days past the age gate; if releases are still rapid beyond that, Bun ignores the stability check
  * Exact version requests (like `package@1.1.1`) still respect the age gate but bypass the stability check
* Bun treats versions without a `time` field as passing the age check (the npm registry should always provide timestamps)

For more advanced security scanning, including integration with services and custom filtering, see the [Security Scanner API](/docs/pm/security-scanner-api).

***

## Configuration

### Configuring `bun install` with `bunfig.toml`

On `bun install`, `bun remove`, and `bun add`, Bun looks for `bunfig.toml` in:

1. `$XDG_CONFIG_HOME/.bunfig.toml` or `$HOME/.bunfig.toml`
2. `./bunfig.toml`

If Bun finds both, it loads both. Keys set in the project's `bunfig.toml` override the same keys in the global file.

Configuring with `bunfig.toml` is optional. These are the default values:

```toml bunfig.toml icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[install]

# whether to install optionalDependencies
optional = true

# whether to install devDependencies
dev = true

# whether to install peerDependencies
peer = true

# equivalent to `--production` flag
production = false

# equivalent to `--save-text-lockfile` flag
saveTextLockfile = true

# equivalent to `--frozen-lockfile` flag
frozenLockfile = false

# equivalent to `--dry-run` flag
dryRun = false

# equivalent to `--concurrent-scripts` flag
concurrentScripts = 16 # (cpu count or GOMAXPROCS) x2

# installation strategy: "hoisted" or "isolated"
# default depends on lockfile configVersion and workspaces:
# - configVersion = 1: "isolated" if using workspaces, otherwise "hoisted"
# - configVersion = 0: "hoisted"
linker = "hoisted"


# minimum age config
minimumReleaseAge = 259200 # seconds
minimumReleaseAgeExcludes = ["@types/node", "typescript"]
```

### Configuring with environment variables

Environment variables take priority over `bunfig.toml`.

| Name                               | Description                                                                             |
| ---------------------------------- | --------------------------------------------------------------------------------------- |
| `BUN_CONFIG_REGISTRY`              | Set an npm registry (default: [https://registry.npmjs.org](https://registry.npmjs.org)) |
| `BUN_CONFIG_TOKEN`                 | Set an auth token for the default registry                                              |
| `BUN_CONFIG_YARN_LOCKFILE`         | Save a Yarn v1-style yarn.lock                                                          |
| `BUN_CONFIG_SKIP_SAVE_LOCKFILE`    | Don’t save a lockfile                                                                   |
| `BUN_CONFIG_SKIP_LOAD_LOCKFILE`    | Don’t load a lockfile                                                                   |
| `BUN_CONFIG_SKIP_INSTALL_PACKAGES` | Don’t install any packages                                                              |

Bun uses the fastest installation method available on the target platform: `clonefile` on macOS and `hardlink` on Linux and Windows. You can change the installation method with the `--backend` flag. When unavailable or on error, `clonefile` and `hardlink` fall back to a platform-specific implementation of copying files.

Bun stores installed packages from npm in `~/.bun/install/cache/${name}@${version}`. If the semver version has a `build` or a `pre` tag, Bun replaces it with a hash of that value. This reduces the chances of errors from long file paths, but complicates figuring out where a package was installed on disk.

When the `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"`.

When a `bun.lock` doesn’t exist or `package.json` has changed dependencies, Bun downloads and extracts tarballs eagerly while resolving.

When a `bun.lock` exists and `package.json` hasn’t changed, Bun downloads missing dependencies lazily. If the package with a matching `name` and `version` already exists in the expected location within `node_modules`, Bun doesn’t attempt to download the tarball.

## CI/CD

Use the official [`oven-sh/setup-bun`](https://github.com/oven-sh/setup-bun) action to install `bun` in a GitHub Actions pipeline:

```yaml .github/workflows/release.yml icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
name: bun-types
jobs:
  build:
    name: build-app
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repo
        uses: actions/checkout@v4
      - name: Install bun
        uses: oven-sh/setup-bun@v2
      - name: Install dependencies
        run: bun install
      - name: Build app
        run: bun run build
```

For CI/CD environments that want to enforce reproducible builds, use `bun ci` to fail the build if the package.json is out of sync with the lockfile:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun ci
```

`bun ci` is equivalent to `bun install --frozen-lockfile`. It installs exact versions from `bun.lock` and fails if `package.json` doesn't match the lockfile. To use `bun ci` or `bun install --frozen-lockfile`, you must commit `bun.lock` to version control.

In your workflow, run `bun ci` instead of `bun install`:

```yaml .github/workflows/release.yml icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
name: bun-types
jobs:
  build:
    name: build-app
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repo
        uses: actions/checkout@v4
      - name: Install bun
        uses: oven-sh/setup-bun@v2
      - name: Install dependencies
        run: bun ci
      - name: Build app
        run: bun run build
```

## Platform-specific dependencies?

Bun stores normalized `cpu` and `os` values from npm in the lockfile, along with the resolved packages. It skips downloading, extracting, and installing packages disabled for the current target at runtime. This means the lockfile doesn't change between platforms/architectures even if the packages ultimately installed do change.

### `--cpu` and `--os` flags

You can override the target platform for package selection:

```bash theme={"theme":{"light":"github-light","dark":"dracula"}}
bun install --cpu=x64 --os=linux
```

These flags install packages for the specified platform instead of the current system. Use them for cross-platform builds or when preparing deployments for different environments.

**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`

## Peer dependencies?

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.

## Lockfile

`bun.lock` is Bun’s lockfile format. See [our blog post about the text lockfile](https://bun.com/blog/bun-lock-text-lockfile).

Before Bun 1.2, the lockfile was binary and called `bun.lockb`. To upgrade an old lockfile to the new format, run `bun install --save-text-lockfile --frozen-lockfile --lockfile-only`, then delete `bun.lockb`.

## Cache

To delete the cache:

```bash theme={"theme":{"light":"github-light","dark":"dracula"}}
bun pm cache rm
# or
rm -rf ~/.bun/install/cache
```

## Platform-specific backends

For performance, `bun install` uses different system calls to install dependencies depending on the platform. You can force a specific backend with the `--backend` flag.

**`hardlink`** is the default backend on Linux and Windows. Benchmarking showed it to be the fastest on Linux.

```bash theme={"theme":{"light":"github-light","dark":"dracula"}}
rm -rf node_modules
bun install --backend hardlink
```

**`clonefile`** is the default backend on macOS. Benchmarking showed it to be the fastest on macOS. It is only available on macOS.

```bash theme={"theme":{"light":"github-light","dark":"dracula"}}
rm -rf node_modules
bun install --backend clonefile
```

**`clonefile_each_dir`** is similar to `clonefile`, except it clones each file individually per directory. It is only available on macOS and tends to perform slower than `clonefile`. Unlike `clonefile`, `clonefile_each_dir` does not recursively clone subdirectories in one system call.

```bash theme={"theme":{"light":"github-light","dark":"dracula"}}
rm -rf node_modules
bun install --backend clonefile_each_dir
```

**`copyfile`** is the fallback used when any of the above fail, and is the slowest. On macOS, it uses `fcopyfile()`; on Linux, it uses `copy_file_range()`.

```bash theme={"theme":{"light":"github-light","dark":"dracula"}}
rm -rf node_modules
bun install --backend copyfile
```

**`symlink`** is typically only used for `file:` dependencies internally. To prevent infinite loops, it skips symlinking the `node_modules` folder.

If you install with `--backend=symlink`, Node.js won't resolve node\_modules of dependencies unless each dependency has its own node\_modules folder or you pass `--preserve-symlinks` to `node` or `bun`. See [Node.js documentation on `--preserve-symlinks`](https://nodejs.org/api/cli.html#--preserve-symlinks).

```bash theme={"theme":{"light":"github-light","dark":"dracula"}}
rm -rf node_modules
bun install --backend symlink
bun --preserve-symlinks ./my-file.js
node --preserve-symlinks ./my-file.js # https://nodejs.org/api/cli.html#--preserve-symlinks
```

## npm registry metadata

Bun uses a binary format for caching npm registry responses. This loads much faster than JSON and tends to be smaller on disk.
These files live in `~/.bun/install/cache/*.npm`. The filename pattern is `${hash(packageName)}.npm`. It’s a hash so that Bun doesn’t need to create extra directories for scoped packages.

Bun's usage of `Cache-Control` ignores `Age`. This improves performance, but means Bun may be about 5 minutes behind the latest package version metadata from npm.

## pnpm migration

Bun migrates projects from pnpm automatically. When Bun detects a `pnpm-lock.yaml` file and no `bun.lock` file exists, it converts the lockfile to `bun.lock` during installation. The original `pnpm-lock.yaml` file remains unmodified.

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun install
```

Migration only runs when `bun.lock` is absent. There is currently no opt-out flag for pnpm migration.

The migration process handles:

### Lockfile Migration

* Converts `pnpm-lock.yaml` (lockfile versions 7–9, including pnpm 11's multi-document files) to `bun.lock`
* Preserves resolved versions and integrity hashes
* Preserves peer dependency ranges and `peerDependenciesMeta`, so the next `bun install` leaves the migrated lockfile unchanged
* Migrates git, GitHub, tarball URL, `file:`, and `npm:` alias dependencies, including transitive ones
* Resolves pnpm named registries (`name@registry:version`) via `namedRegistries` in `pnpm-workspace.yaml`
* Converts injected workspace packages (`dependenciesMeta.*.injected`) to ordinary workspace dependencies
* Handles patched dependencies, matching pnpm's hash-only `patchedDependencies` entries to the patch files
* Skips `runtime:` entries (pnpm-managed Node.js versions) with a warning

### Workspace Configuration

When a `pnpm-workspace.yaml` file exists, Bun migrates workspace settings to your root `package.json`:

```yaml pnpm-workspace.yaml icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
packages:
  - "apps/*"
  - "packages/*"

catalog:
  react: ^18.0.0
  typescript: ^5.0.0

catalogs:
  build:
    webpack: ^5.0.0
    babel: ^7.0.0
```

Bun moves the workspace packages list and catalogs to the `workspaces` field in `package.json`:

```json package.json icon="file-json" theme={"theme":{"light":"github-light","dark":"dracula"}}
{
  "workspaces": {
    "packages": ["apps/*", "packages/*"],
    "catalog": {
      "react": "^18.0.0",
      "typescript": "^5.0.0"
    },
    "catalogs": {
      "build": {
        "webpack": "^5.0.0",
        "babel": "^7.0.0"
      }
    }
  }
}
```

### Catalog Dependencies

Bun preserves dependencies that use pnpm's `catalog:` protocol:

```json package.json icon="file-json" theme={"theme":{"light":"github-light","dark":"dracula"}}
{
  "dependencies": {
    "react": "catalog:",
    "webpack": "catalog:build"
  }
}
```

### Configuration Migration

Bun migrates the following pnpm configuration from both `pnpm-lock.yaml` and `pnpm-workspace.yaml`:

* **Overrides**: Moved from `pnpm.overrides` to root-level `overrides` in `package.json`
* **Patched Dependencies**: Moved from `pnpm.patchedDependencies` to root-level `patchedDependencies` in `package.json`
* **Workspace Overrides**: Applied from `pnpm-workspace.yaml` to root `package.json`

### Requirements and limitations

* Requires pnpm lockfile version 7 or higher
* Workspace packages must have a `name` field in their `package.json`
* All catalog entries referenced by dependencies must exist in the catalogs definition
* Every workspace in `pnpm-lock.yaml` must have its `package.json` on disk (in Docker, copy them in before `bun install`)
* Relative `link:` dependencies and git dependencies with a sub-directory (`resolution.path`) are not supported
* If migration fails for any of these reasons, Bun prints why and resolves from scratch instead

After migration, you can safely remove `pnpm-lock.yaml` and `pnpm-workspace.yaml` files.

***

## CLI Usage

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun install <name>@<version>
```

### General Configuration

<ParamField type="string">
  Specify path to config file (bunfig.toml)
</ParamField>

<ParamField type="string">
  Set a specific cwd
</ParamField>

### Dependency Scope & Management

<ParamField type="boolean">
  Don't install devDependencies
</ParamField>

<ParamField type="boolean">
  Don't update package.json or save a lockfile
</ParamField>

<ParamField type="boolean">
  Save to package.json
</ParamField>

<ParamField type="string">
  Exclude 'dev', 'optional', or 'peer' dependencies from install
</ParamField>

<ParamField type="boolean">
  Only add dependencies to package.json if they are not already present
</ParamField>

### Dependency Type & Versioning

<ParamField type="boolean">
  Add dependency to "devDependencies"
</ParamField>

<ParamField type="boolean">
  Add dependency to "optionalDependencies"
</ParamField>

<ParamField type="boolean">
  Add dependency to "peerDependencies"
</ParamField>

<ParamField type="boolean">
  Add the exact version instead of the ^ range
</ParamField>

### Lockfile Control

<ParamField type="boolean">
  Write a yarn.lock file (yarn v1)
</ParamField>

<ParamField type="boolean">
  Disallow changes to lockfile
</ParamField>

<ParamField type="boolean">
  Save a text-based lockfile
</ParamField>

<ParamField type="boolean">
  Generate a lockfile without installing dependencies
</ParamField>

### Network & Registry Settings

<ParamField type="string">
  Provide a Certificate Authority signing certificate
</ParamField>

<ParamField type="string">
  File path to Certificate Authority signing certificate
</ParamField>

<ParamField type="string">
  Use a specific registry by default, overriding .npmrc, bunfig.toml and environment variables
</ParamField>

### Installation Process Control

<ParamField type="boolean">
  Perform a dry run without making changes
</ParamField>

<ParamField type="boolean">
  Always request the latest versions from the registry & reinstall all dependencies
</ParamField>

<ParamField type="boolean">
  Install globally
</ParamField>

<ParamField type="string">
  Platform-specific optimizations: "clonefile", "hardlink", "symlink", "copyfile"
</ParamField>

<ParamField type="string">
  Install packages for the matching workspaces
</ParamField>

<ParamField type="boolean">
  Recursively analyze & install all dependencies of files passed as arguments
</ParamField>

### Caching Options

<ParamField type="string">
  Store & load cached data from a specific directory path
</ParamField>

<ParamField type="boolean">
  Ignore manifest cache entirely
</ParamField>

### Output & Logging

<ParamField type="boolean">
  Don't log anything
</ParamField>

<ParamField type="boolean">
  Excessively verbose logging
</ParamField>

<ParamField type="boolean">
  Disable the progress bar
</ParamField>

<ParamField type="boolean">
  Don't print a summary
</ParamField>

### Security & Integrity

<ParamField type="boolean">
  Skip verifying integrity of newly downloaded packages
</ParamField>

<ParamField type="boolean">
  Add to trustedDependencies in the project's package.json and install the package(s)
</ParamField>

### Concurrency & Performance

<ParamField type="number">
  Maximum number of concurrent jobs for lifecycle scripts (default: 2x CPU cores)
</ParamField>

<ParamField type="number">
  Maximum number of concurrent network requests
</ParamField>

### Lifecycle Script Management

<ParamField type="boolean">
  Skip lifecycle scripts for all packages, including the project's package.json and trusted dependencies
</ParamField>

### Help Information

<ParamField type="boolean">
  Print this help menu
</ParamField>


# bun link
Source: https://bun.com/docs/pm/cli/link

Link local packages for development

Use `bun link` in a local directory to register the current package as a "linkable" package.

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
cd /path/to/cool-pkg
cat package.json
bun link
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
bun link v1.3.3 (7416672e)
Success! Registered "cool-pkg"

To use cool-pkg in a project, run:
  bun link cool-pkg

Or add it in dependencies in your package.json file:
  "cool-pkg": "link:cool-pkg"
```

You can now "link" this package into other projects using `bun link cool-pkg`. This command creates a symlink in the target project's `node_modules` directory pointing to the local directory.

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
cd /path/to/my-app
bun link cool-pkg
```

The `--save` flag also adds `cool-pkg` to the `dependencies` field of your app's package.json, with a version specifier that tells Bun to load from the registered local directory instead of installing from `npm`:

```json package.json icon="file-json" theme={"theme":{"light":"github-light","dark":"dracula"}}
{
  "name": "my-app",
  "version": "1.0.0",
  "dependencies": {
    "cool-pkg": "link:cool-pkg" // [!code ++]
  }
}
```

## Unlinking

Use `bun unlink` in the root directory to unregister a local package.

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
cd /path/to/cool-pkg
bun unlink
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
bun unlink v1.3.3 (7416672e)
success: unlinked package "cool-pkg"
```

***

# CLI Usage

```bash theme={"theme":{"light":"github-light","dark":"dracula"}}
bun link <packages>
```

### Installation Scope

<ParamField type="boolean">
  Install globally. Alias: <code>-g</code>
</ParamField>

### Dependency Management

<ParamField type="boolean">
  Don't install devDependencies. Alias: <code>-p</code>
</ParamField>

<ParamField type="string">
  Exclude <code>dev</code>, <code>optional</code>, or <code>peer</code> dependencies from install
</ParamField>

### Project Files & Lockfiles

<ParamField type="boolean">
  Write a <code>yarn.lock</code> file (yarn v1). Alias: <code>-y</code>
</ParamField>

<ParamField type="boolean">
  Disallow changes to lockfile
</ParamField>

<ParamField type="boolean">
  Save a text-based lockfile
</ParamField>

<ParamField type="boolean">
  Generate a lockfile without installing dependencies
</ParamField>

<ParamField type="boolean">
  Don't update <code>package.json</code> or save a lockfile
</ParamField>

<ParamField type="boolean">
  Save to <code>package.json</code>
</ParamField>

<ParamField type="boolean">
  Add to <code>trustedDependencies</code> in the project's <code>package.json</code> and install the package(s)
</ParamField>

### Installation Control

<ParamField type="boolean">
  Always request the latest versions from the registry & reinstall all dependencies. Alias: <code>-f</code>
</ParamField>

<ParamField type="boolean">
  Skip verifying integrity of newly downloaded packages
</ParamField>

<ParamField type="string">
  Platform-specific optimizations for installing dependencies. One of <code>clonefile</code>, <code>hardlink</code>,
  <code>symlink</code>, or <code>copyfile</code>
</ParamField>

<ParamField type="string">
  Linker strategy (one of <code>isolated</code> or <code>hoisted</code>)
</ParamField>

<ParamField type="boolean">
  Perform a dry run without making changes
</ParamField>

<ParamField type="boolean">
  Skip lifecycle scripts for all packages, including the project's <code>package.json</code> and trusted dependencies
</ParamField>

### Network & Registry

<ParamField type="string">
  Provide a Certificate Authority signing certificate
</ParamField>

<ParamField type="string">
  Same as <code>--ca</code>, but as a file path to the certificate
</ParamField>

<ParamField type="string">
  Use a specific registry by default, overriding <code>.npmrc</code>, <code>bunfig.toml</code>, and environment
  variables
</ParamField>

<ParamField type="number">
  Maximum number of concurrent network requests
</ParamField>

### Performance & Resource

<ParamField type="number">
  Maximum number of concurrent jobs for lifecycle scripts (default: 2x CPU cores)
</ParamField>

### Caching

<ParamField type="string">
  Store & load cached data from a specific directory path
</ParamField>

<ParamField type="boolean">
  Ignore manifest cache entirely
</ParamField>

### Output & Logging

<ParamField type="boolean">
  Don't log anything
</ParamField>

<ParamField type="boolean">
  Disable the progress bar
</ParamField>

<ParamField type="boolean">
  Excessively verbose logging
</ParamField>

<ParamField type="boolean">
  Disable the progress bar
</ParamField>

<ParamField type="boolean">
  Don't print a summary
</ParamField>

### Platform Targeting

<ParamField type="string">
  Override CPU architecture for optional dependencies (e.g., <code>x64</code>, <code>arm64</code>, <code>\*</code> for
  all)
</ParamField>

<ParamField type="string">
  Override operating system for optional dependencies (e.g., <code>linux</code>, <code>darwin</code>, <code>\*</code> for
  all)
</ParamField>

### Global Configuration & Context

<ParamField type="string">
  Specify path to config file (<code>bunfig.toml</code>). Alias: <code>-c</code>
</ParamField>

<ParamField type="string">
  Set a specific current working directory
</ParamField>

### Help

<ParamField type="boolean">
  Print this help menu. Alias: <code>-h</code>
</ParamField>


# bun outdated
Source: https://bun.com/docs/pm/cli/outdated

Check for outdated dependencies

`bun outdated` displays a table of the dependencies in your project that have newer versions available.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun outdated
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
| Package                        | Current | Update    | Latest     |
| ------------------------------ | ------- | --------- | ---------- |
| @sinclair/typebox              | 0.34.15 | 0.34.16   | 0.34.16    |
| @types/bun (dev)               | 1.3.0   | 1.3.3     | 1.3.3      |
| eslint (dev)                   | 8.57.1  | 8.57.1    | 9.20.0     |
| eslint-plugin-security (dev)   | 2.1.1   | 2.1.1     | 3.0.1      |
| eslint-plugin-sonarjs (dev)    | 0.23.0  | 0.23.0    | 3.0.1      |
| expect-type (dev)              | 0.16.0  | 0.16.0    | 1.1.0      |
| prettier (dev)                 | 3.4.2   | 3.5.0     | 3.5.0      |
| tsup (dev)                     | 8.3.5   | 8.3.6     | 8.3.6      |
| typescript (dev)               | 5.7.2   | 5.7.3     | 5.7.3      |

```

## Version Information

The output table shows three version columns:

* **Current**: The version currently installed
* **Update**: The latest version that satisfies your package.json version range
* **Latest**: The latest version published to the registry

### Dependency Filters

To check a specific dependency, pass its name as a positional argument:

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun outdated eslint-plugin-security
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
| Package                        | Current | Update | Latest    |
| ------------------------------ | ------- | ------ | --------- |
| eslint-plugin-security (dev)   | 2.1.1   | 2.1.1  | 3.0.1     |

```

Glob patterns work too:

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun outdated 'eslint*'
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
| Package                        | Current | Update | Latest     |
| ------------------------------ | ------- | ------ | ---------- |
| eslint (dev)                   | 8.57.1  | 8.57.1 | 9.20.0     |
| eslint-plugin-security (dev)   | 2.1.1   | 2.1.1  | 3.0.1      |
| eslint-plugin-sonarjs (dev)    | 0.23.0  | 0.23.0 | 3.0.1      |
```

For example, to check for outdated `@types/*` packages:

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun outdated '@types/*'
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
| Package            | Current | Update | Latest |
| ------------------ | ------- | ------ | ------ |
| @types/bun (dev)   | 1.3.0   | 1.3.3  | 1.3.3 |
```

Or to exclude all `@types/*` packages:

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun outdated '!@types/*'
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
| Package                        | Current | Update    | Latest     |
| ------------------------------ | ------- | --------- | ---------- |
| @sinclair/typebox              | 0.34.15 | 0.34.16   | 0.34.16    |
| eslint (dev)                   | 8.57.1  | 8.57.1    | 9.20.0     |
| eslint-plugin-security (dev)   | 2.1.1   | 2.1.1     | 3.0.1      |
| eslint-plugin-sonarjs (dev)    | 0.23.0  | 0.23.0    | 3.0.1      |
| expect-type (dev)              | 0.16.0  | 0.16.0    | 1.1.0      |
| prettier (dev)                 | 3.4.2   | 3.5.0     | 3.5.0      |
| tsup (dev)                     | 8.3.5   | 8.3.6     | 8.3.6      |
| typescript (dev)               | 5.7.2   | 5.7.3     | 5.7.3      |
```

### Workspace Filters

Use the `--filter` flag to check for outdated dependencies in a different workspace package:

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun outdated --filter='@monorepo/types'
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
| Package            | Current | Update | Latest |
| ------------------ | ------- | ------ | ------ |
| tsup (dev)         | 8.3.5   | 8.3.6  | 8.3.6  |
| typescript (dev)   | 5.7.2   | 5.7.3  | 5.7.3  |
```

`--filter` accepts glob patterns to match multiple workspaces:

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun outdated --filter='@monorepo/{types,cli}'
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
| Package                        | Current | Update | Latest     |
| ------------------------------ | ------- | ------ | ---------- |
| eslint (dev)                   | 8.57.1  | 8.57.1 | 9.20.0     |
| eslint-plugin-security (dev)   | 2.1.1   | 2.1.1  | 3.0.1      |
| eslint-plugin-sonarjs (dev)    | 0.23.0  | 0.23.0 | 3.0.1      |
| expect-type (dev)              | 0.16.0  | 0.16.0 | 1.1.0      |
| tsup (dev)                     | 8.3.5   | 8.3.6  | 8.3.6      |
| typescript (dev)               | 5.7.2   | 5.7.3  | 5.7.3      |
```

### Catalog Dependencies

`bun outdated` also checks [catalog](/docs/pm/catalogs) dependencies defined in `package.json`:

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun outdated -r
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
┌────────────────────┬─────────┬─────────┬─────────┬────────────────────────────────┐
│ Package            │ Current │ Update  │ Latest  │ Workspace                      │
├────────────────────┼─────────┼─────────┼─────────┼────────────────────────────────┤
│ body-parser        │ 1.19.0  │ 1.19.0  │ 2.2.0   │ @test/shared                   │
├────────────────────┼─────────┼─────────┼─────────┼────────────────────────────────┤
│ cors               │ 2.8.0   │ 2.8.0   │ 2.8.5   │ @test/shared                   │
├────────────────────┼─────────┼─────────┼─────────┼────────────────────────────────┤
│ chalk              │ 4.0.0   │ 4.0.0   │ 5.6.2   │ @test/utils                    │
├────────────────────┼─────────┼─────────┼─────────┼────────────────────────────────┤
│ uuid               │ 8.0.0   │ 8.0.0   │ 13.0.0  │ @test/utils                    │
├────────────────────┼─────────┼─────────┼─────────┼────────────────────────────────┤
│ axios              │ 0.21.0  │ 0.21.0  │ 1.12.2  │ catalog (@test/app)            │
├────────────────────┼─────────┼─────────┼─────────┼────────────────────────────────┤
│ lodash             │ 4.17.15 │ 4.17.15 │ 4.17.21 │ catalog (@test/app)            │
├────────────────────┼─────────┼─────────┼─────────┼────────────────────────────────┤
│ react              │ 17.0.0  │ 17.0.0  │ 19.1.1  │ catalog (@test/app)            │
├────────────────────┼─────────┼─────────┼─────────┼────────────────────────────────┤
│ react-dom          │ 17.0.0  │ 17.0.0  │ 19.1.1  │ catalog (@test/app)            │
├────────────────────┼─────────┼─────────┼─────────┼────────────────────────────────┤
│ express            │ 4.17.0  │ 4.17.0  │ 5.1.0   │ catalog (@test/shared)         │
├────────────────────┼─────────┼─────────┼─────────┼────────────────────────────────┤
│ moment             │ 2.24.0  │ 2.24.0  │ 2.30.1  │ catalog (@test/utils)          │
├────────────────────┼─────────┼─────────┼─────────┼────────────────────────────────┤
│ @types/node (dev)  │ 14.0.0  │ 14.0.0  │ 24.5.2  │ @test/shared                   │
├────────────────────┼─────────┼─────────┼─────────┼────────────────────────────────┤
│ @types/react (dev) │ 17.0.0  │ 17.0.0  │ 19.1.15 │ catalog:testing (@test/app)    │
├────────────────────┼─────────┼─────────┼─────────┼────────────────────────────────┤
│ eslint (dev)       │ 7.0.0   │ 7.0.0   │ 9.36.0  │ catalog:testing (@test/app)    │
├────────────────────┼─────────┼─────────┼─────────┼────────────────────────────────┤
│ typescript (dev)   │ 4.9.5   │ 4.9.5   │ 5.9.2   │ catalog:build (@test/app)      │
├────────────────────┼─────────┼─────────┼─────────┼────────────────────────────────┤
│ jest (dev)         │ 26.0.0  │ 26.0.0  │ 30.2.0  │ catalog:testing (@test/shared) │
├────────────────────┼─────────┼─────────┼─────────┼────────────────────────────────┤
│ prettier (dev)     │ 2.0.0   │ 2.0.0   │ 3.6.2   │ catalog:build (@test/utils)    │
└────────────────────┴─────────┴─────────┴─────────┴────────────────────────────────┘
```

***

## CLI Usage

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun outdated <filter>
```

### General Options

<ParamField type="string">
  Specify path to config file (<code>bunfig.toml</code>)
</ParamField>

<ParamField type="string">
  Set a specific cwd
</ParamField>

<ParamField type="boolean">
  Print this help menu
</ParamField>

<ParamField type="string">
  Display outdated dependencies for each matching workspace
</ParamField>

<ParamField type="boolean">
  Check outdated packages in all workspaces
</ParamField>

### Output & Logging

<ParamField type="boolean">
  Don't log anything
</ParamField>

<ParamField type="boolean">
  Excessively verbose logging
</ParamField>

<ParamField type="boolean">
  Disable the progress bar
</ParamField>

<ParamField type="boolean">
  Don't print a summary
</ParamField>

### Dependency Scope & Target

<ParamField type="boolean">
  Don't install devDependencies
</ParamField>

<ParamField type="string">
  Exclude <code>dev</code>, <code>optional</code>, or <code>peer</code> dependencies from install
</ParamField>

<ParamField type="boolean">
  Install globally
</ParamField>

### Lockfile & Package.json

<ParamField type="boolean">
  Write a <code>yarn.lock</code> file (yarn v1)
</ParamField>

<ParamField type="boolean">
  Don't update <code>package.json</code> or save a lockfile
</ParamField>

<ParamField type="boolean">
  Save to <code>package.json</code> (true by default)
</ParamField>

<ParamField type="boolean">
  Disallow changes to lockfile
</ParamField>

<ParamField type="boolean">
  Save a text-based lockfile
</ParamField>

<ParamField type="boolean">
  Generate a lockfile without installing dependencies
</ParamField>

<ParamField type="boolean">
  Add to <code>trustedDependencies</code> in the project's <code>package.json</code> and install the package(s)
</ParamField>

### Network & Registry

<ParamField type="string">
  Provide a Certificate Authority signing certificate
</ParamField>

<ParamField type="string">
  Same as <code>--ca</code>, but as a file path to the certificate
</ParamField>

<ParamField type="string">
  Use a specific registry by default, overriding <code>.npmrc</code>, <code>bunfig.toml</code> and environment variables
</ParamField>

<ParamField type="number">
  Maximum number of concurrent network requests (default 48)
</ParamField>

### Caching

<ParamField type="string">
  Store & load cached data from a specific directory path
</ParamField>

<ParamField type="boolean">
  Ignore manifest cache entirely
</ParamField>

### Execution Behavior

<ParamField type="boolean">
  Perform a dry run without making changes
</ParamField>

<ParamField type="boolean">
  Always request the latest versions from the registry & reinstall all dependencies
</ParamField>

<ParamField type="boolean">
  Skip verifying integrity of newly downloaded packages
</ParamField>

<ParamField type="boolean">
  Skip lifecycle scripts for all packages, including the project's <code>package.json</code> and trusted dependencies
</ParamField>

<ParamField type="string">
  Platform-specific optimizations for installing dependencies. Possible values: <code>clonefile</code> (default on
  macOS), <code>hardlink</code> (default on Linux and Windows), <code>symlink</code>, <code>copyfile</code>
</ParamField>

<ParamField type="number">
  Maximum number of concurrent jobs for lifecycle scripts (default: 2x CPU cores)
</ParamField>


# bun patch
Source: https://bun.com/docs/pm/cli/patch

Persistently patch node_modules packages in a git-friendly way

`bun patch` persistently patches packages in `node_modules` in a maintainable, git-friendly way.

Sometimes you need a small change to a package in `node_modules/` to fix a bug or add a feature. `bun patch` lets you do this without vendoring the entire package.

Features:

* Generates `.patch` files that Bun applies to dependencies in `node_modules` on install
* You can commit `.patch` files to your repository and reuse them across installs, projects, and machines
* `"patchedDependencies"` in `package.json` keeps track of patched packages
* Patches packages in `node_modules/` while preserving the integrity of Bun's [Global Cache](/docs/pm/global-cache)
* Test your changes locally before committing them with `bun patch --commit <pkg>`
* To preserve disk space and keep `bun install` fast, Bun commits patched packages to the Global Cache and shares them across projects where possible

#### Step 1. Prepare the package for patching

Use `bun patch <pkg>` to prepare the package for patching:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
# you can supply the package name
bun patch react

# ...and a precise version in case multiple versions are installed
bun patch react@17.0.2

# or the path to the package
bun patch node_modules/react
```

<Note>
  Always run `bun patch <pkg>` first. It ensures the package folder in `node_modules/` contains a fresh copy of the package with no symlinks or hardlinks to Bun's cache.

  If you skip it, you might end up editing the package globally in the cache.
</Note>

#### Step 2. Test your changes locally

`bun patch <pkg>` makes it safe to edit `<pkg>` in `node_modules/` directly, while preserving the integrity of Bun's [Global Cache](/docs/pm/global-cache). It works by re-creating an unlinked clone of the package in `node_modules/`. `bun patch --commit <pkg>` then diffs that clone against the original package in the Global Cache.

#### Step 3. Commit your changes

Once you're happy with your changes, run `bun patch --commit <path or pkg>`.

Bun generates a patch file in `patches/`, updates your `package.json` and lockfile, and starts using the patched package:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
# you can supply the path to the patched package
bun patch --commit node_modules/react

# ... or the package name and optionally the version
bun patch --commit react@17.0.2

# choose the directory to store the patch files
bun patch --commit react --patches-dir=mypatches

# `patch-commit` is available for compatibility with pnpm
bun patch-commit react
```

***

# CLI Usage

```bash theme={"theme":{"light":"github-light","dark":"dracula"}}
bun patch <package>@<version>
```

### Patch Generation

<ParamField type="boolean">
  Install a package containing modifications in <code>dir</code>
</ParamField>

<ParamField type="string">
  The directory to put the patch file in (only if --commit is used)
</ParamField>

### Dependency Management

<ParamField type="boolean">
  Don't install devDependencies. Alias: <code>-p</code>
</ParamField>

<ParamField type="boolean">
  Skip lifecycle scripts for all packages, including the project's <code>package.json</code> and trusted dependencies
</ParamField>

<ParamField type="boolean">
  Add to <code>trustedDependencies</code> in the project's <code>package.json</code> and install the package(s)
</ParamField>

<ParamField type="boolean">
  Install globally. Alias: <code>-g</code>
</ParamField>

<ParamField type="string">
  Exclude <code>dev</code>, <code>optional</code>, or <code>peer</code> dependencies from install
</ParamField>

### Project Files & Lockfiles

<ParamField type="boolean">
  Write a <code>yarn.lock</code> file (yarn v1). Alias: <code>-y</code>
</ParamField>

<ParamField type="boolean">
  Don't update <code>package.json</code> or save a lockfile
</ParamField>

<ParamField type="boolean">
  Save to <code>package.json</code> (true by default)
</ParamField>

<ParamField type="boolean">
  Disallow changes to lockfile
</ParamField>

<ParamField type="boolean">
  Save a text-based lockfile
</ParamField>

<ParamField type="boolean">
  Generate a lockfile without installing dependencies
</ParamField>

### Installation Control

<ParamField type="string">
  Platform-specific optimizations for installing dependencies. Possible values: <code>clonefile</code> (default on
  macOS), <code>hardlink</code> (default on Linux and Windows), <code>symlink</code>, <code>copyfile</code>
</ParamField>

<ParamField type="string">
  Linker strategy (one of <code>isolated</code> or <code>hoisted</code>)
</ParamField>

<ParamField type="number">
  Only install packages published at least N seconds ago (security feature)
</ParamField>

<ParamField type="boolean">
  Perform a dry run without making changes
</ParamField>

<ParamField type="boolean">
  Always request the latest versions from the registry & reinstall all dependencies. Alias: <code>-f</code>
</ParamField>

<ParamField type="boolean">
  Skip verifying integrity of newly downloaded packages
</ParamField>

### Network & Registry

<ParamField type="string">
  Provide a Certificate Authority signing certificate
</ParamField>

<ParamField type="string">
  Same as <code>--ca</code>, but as a file path to the certificate
</ParamField>

<ParamField type="string">
  Use a specific registry by default, overriding <code>.npmrc</code>, <code>bunfig.toml</code>, and environment
  variables
</ParamField>

<ParamField type="number">
  Maximum number of concurrent network requests (default 48)
</ParamField>

### Performance & Resource

<ParamField type="number">
  Maximum number of concurrent jobs for lifecycle scripts (default: 2x CPU cores)
</ParamField>

### Caching

<ParamField type="string">
  Store & load cached data from a specific directory path
</ParamField>

<ParamField type="boolean">
  Ignore manifest cache entirely
</ParamField>

### Output & Logging

<ParamField type="boolean">
  Don't log anything
</ParamField>

<ParamField type="boolean">
  Disable the progress bar
</ParamField>

<ParamField type="boolean">
  Excessively verbose logging
</ParamField>

<ParamField type="boolean">
  Disable the progress bar
</ParamField>

<ParamField type="boolean">
  Don't print a summary
</ParamField>

### Platform Targeting

<ParamField type="string">
  Override CPU architecture for optional dependencies (e.g., <code>x64</code>, <code>arm64</code>, <code>\*</code> for
  all)
</ParamField>

<ParamField type="string">
  Override operating system for optional dependencies (e.g., <code>linux</code>, <code>darwin</code>, <code>\*</code> for
  all)
</ParamField>

### Global Configuration & Context

<ParamField type="string">
  Specify path to config file (<code>bunfig.toml</code>). Alias: <code>-c</code>
</ParamField>

<ParamField type="string">
  Set a specific current working directory
</ParamField>

### Help

<ParamField type="boolean">
  Print this help menu. Alias: <code>-h</code>
</ParamField>


# bun pm
Source: https://bun.com/docs/pm/cli/pm

Package manager utilities

The `bun pm` command group is a set of utilities for working with Bun's package manager.

## pack

To create a tarball of the current workspace:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun pm pack
```

`bun pm pack` creates a `.tgz` file containing all files that would be published to npm, following the same rules as `npm pack`.

## Examples

Basic usage:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun pm pack
# Creates my-package-1.0.0.tgz in current directory
```

Quiet mode for scripting:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
TARBALL=$(bun pm pack --quiet)
echo "Created: $TARBALL"
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
Created: my-package-1.0.0.tgz
```

Custom destination:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun pm pack --destination ./dist
# Saves tarball in ./dist/ directory
```

## Options

* `--dry-run`: Perform all tasks except writing the tarball to disk. Shows what would be included.
* `--destination <dir>`: The directory to save the tarball in.
* `--filename <name>`: An exact file name for the tarball.
* `--ignore-scripts`: Skip running pre/postpack and prepare scripts.
* `--gzip-level <0-9>`: Set the gzip compression level, from 0 to 9 (default 9).
* `--quiet`: Print only the tarball filename, suppressing the rest of the output. Useful in scripts.

> **Note:** `--filename` and `--destination` cannot be used at the same time.

## Output Modes

**Default output:**

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun pm pack
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
bun pack v1.2.19

packed 131B package.json
packed 40B index.js

my-package-1.0.0.tgz

Total files: 2
Shasum: f2451d6eb1e818f500a791d9aace80b394258a90
Unpacked size: 171B
Packed size: 249B
```

**Quiet output:**

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun pm pack --quiet
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
my-package-1.0.0.tgz
```

## bin

To print the path to the `bin` directory for the local project:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun pm bin
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
/path/to/current/project/node_modules/.bin
```

To print the path to the global `bin` directory:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun pm bin -g
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
<$HOME>/.bun/bin
```

## ls

To print a list of installed dependencies in the current project and their resolved versions, excluding their dependencies:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun pm ls
# or
bun list
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
/path/to/project node_modules (135)
├── eslint@8.38.0
├── react@18.2.0
├── react-dom@18.2.0
├── typescript@5.0.4
└── zod@3.21.4
```

To print all installed dependencies, including nth-order dependencies:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun pm ls --all
# or
bun list --all
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
/path/to/project node_modules (135)
├── @eslint-community/eslint-utils@4.4.0
├── @eslint-community/regexpp@4.5.0
├── @eslint/eslintrc@2.0.2
├── @eslint/js@8.38.0
├── @nodelib/fs.scandir@2.1.5
├── @nodelib/fs.stat@2.0.5
├── @nodelib/fs.walk@1.2.8
├── acorn@8.8.2
├── acorn-jsx@5.3.2
├── ajv@6.12.6
├── ansi-regex@5.0.1
├── ...
```

To print only trusted dependencies (those allowed to run [lifecycle scripts](/docs/pm/lifecycle)). When `trustedDependencies` is set in `package.json`, Bun shows the packages from that list; otherwise it shows packages from its [default trusted dependencies](https://github.com/oven-sh/bun/blob/main/src/install/default-trusted-dependencies.txt) list.

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun pm ls --trusted
# or
bun list --trusted
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
/path/to/project node_modules (135)
└── esbuild@0.21.5
```

## licenses

List every installed package grouped by license, as read from each package's `package.json` in `node_modules`. Bun lists packages without a `license` field under `Unknown`. Bun marks packages only reachable through `devDependencies` with `(dev)`.

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun pm licenses
# or
bun pm licenses ls
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
MIT (2)
├── path-parse@1.0.6
└── resolve@1.9.0

Unknown (4)
├── a-dep@1.0.1 (dev)
├── no-deps@1.0.0
├── no-deps@1.0.1
└── one-dep@1.0.0
```

| Flag                           | Description                                                                     |
| ------------------------------ | ------------------------------------------------------------------------------- |
| `--json`                       | Print a JSON object keyed by license                                            |
| `--long`                       | Also print each package's `author`, `description`, and `homepage`               |
| `--prod`, `-p`                 | Skip `devDependencies` (same as `--omit=dev`)                                   |
| `--dev`, `-D`                  | Only list what `devDependencies` pull in                                        |
| `--omit=<dev\|optional\|peer>` | Skip a dependency type, as with `bun install`                                   |
| `--filter <pattern>`, `-F`     | Only list dependencies of the matching workspaces ([filter syntax](/docs/pm/filter)) |

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun pm licenses --json --prod
```

```json theme={"theme":{"light":"github-light","dark":"dracula"}}
{
  "MIT": [
    {
      "name": "path-parse",
      "versions": ["1.0.6"],
      "paths": ["/home/me/app/node_modules/path-parse"],
      "license": "MIT",
      "homepage": "https://github.com/jbgutierrez/path-parse#readme",
      "author": "Javier Blanco <http://jbgutierrez.info>",
      "description": "Node.js path.parse() ponyfill"
    }
  ]
}
```

From a workspace root, Bun lists every workspace's dependencies. From inside a workspace package, Bun lists only that package's dependencies. Use [`bun why`](/docs/pm/cli/why) to find out what pulls in an unexpected package.

Requires both `bun.lock` and `node_modules`. Bun skips packages that are in the lockfile but missing from `node_modules` (e.g. after `bun install --production`) and prints a warning.

## whoami

Print your npm username. Requires you to be logged in (`bunx npm login`) with credentials in either `bunfig.toml` or `.npmrc`:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun pm whoami
```

## hash

To generate and print the hash of the current lockfile:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun pm hash
```

To print the string used to hash the lockfile:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun pm hash-string
```

To print the hash stored in the current lockfile:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun pm hash-print
```

## cache

To print the path to Bun's global module cache:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun pm cache
```

To clear Bun's global module cache:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun pm cache rm
```

## migrate

To migrate another package manager's lockfile without installing anything:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun pm migrate
```

## untrusted

To print current untrusted dependencies with scripts:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun pm untrusted
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
./node_modules/@biomejs/biome @1.8.3
 » [postinstall]: node scripts/postinstall.js

These dependencies had their lifecycle scripts blocked during install.
```

## trust

To run scripts for untrusted dependencies and add to `trustedDependencies`:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun pm trust <names>
```

Options for the `trust` command:

* `--all`: Trust all untrusted dependencies.

## default-trusted

To print the default trusted dependencies list:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun pm default-trusted
```

See the [current list on GitHub](https://github.com/oven-sh/bun/blob/main/src/install/default-trusted-dependencies.txt).

## version

To display current package version and help:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun pm version
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
bun pm version v1.3.3 (ca7428e9)
Current package version: v1.0.0

Increment:
  patch      1.0.0 → 1.0.1
  minor      1.0.0 → 1.1.0
  major      1.0.0 → 2.0.0
  prerelease 1.0.0 → 1.0.1-0
  prepatch   1.0.0 → 1.0.1-0
  preminor   1.0.0 → 1.1.0-0
  premajor   1.0.0 → 2.0.0-0
  from-git   Use version from latest git tag
  1.2.3      Set specific version

Options:
  --no-git-tag-version Skip git operations
  --allow-same-version Prevents throwing error if version is the same
  --message=<val>, -m  Custom commit message, use %s for version substitution
  --preid=<val>        Prerelease identifier (i.e beta → 1.0.1-beta.0)
  --force, -f          Bypass dirty git history check

Examples:
  bun pm version patch
  bun pm version 1.2.3 --no-git-tag-version
  bun pm version prerelease --preid beta --message "Release beta: %s"
```

To bump the version in `package.json`:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun pm version patch
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
v1.0.1
```

Supports `patch`, `minor`, `major`, `premajor`, `preminor`, `prepatch`, `prerelease`, `from-git`, or specific versions like `1.2.3`. By default it creates a git commit and tag; pass `--no-git-tag-version` to skip them.

## pkg

Manage `package.json` data with get, set, delete, and fix operations.

All commands support dot and bracket notation:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
scripts.build              # dot notation
contributors[0]            # array access
workspaces.0               # dot with numeric index
scripts[test:watch]        # bracket for special chars
```

Examples:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
# get
bun pm pkg get name                               # single property
bun pm pkg get name version                       # multiple properties
bun pm pkg get                                    # entire package.json
bun pm pkg get scripts.build                      # nested property

# set
bun pm pkg set name="my-package"                  # simple property
bun pm pkg set scripts.test="jest" version=2.0.0  # multiple properties
bun pm pkg set private=true --json                # JSON values with --json flag

# delete
bun pm pkg delete description                     # single property
bun pm pkg delete scripts.test contributors[0]    # multiple/nested

# fix
bun pm pkg fix                                    # auto-fix common issues
```


# bun prune
Source: https://bun.com/docs/pm/cli/prune

Remove packages that are not in bun.lock from node_modules

`bun prune` deletes everything in `node_modules` that the current `bun.lock` would not install: packages left behind after switching branches, removing a dependency, or installing with another package manager. With the isolated linker, this includes stale entries in `node_modules/.bun`.

`bun prune` never contacts the registry, never runs lifecycle scripts, and never modifies `bun.lock` or `package.json`.

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun prune
```

```
bun prune v1.4.0 (abc12345)

- @types/node@20.11.5
- left-pad@1.3.0
2 packages removed (checked 948) [22.00ms]
```

Packages removed from a workspace or nested `node_modules` folder show the folder in parentheses, e.g. `- typescript@5.4.0 (packages/app/node_modules)`.

### `--production`

Also remove everything `bun install --production` would not install (i.e. `devDependencies`). This lets you build with dev dependencies and ship without them:

```dockerfile theme={"theme":{"light":"github-light","dark":"dracula"}}
COPY package.json bun.lock ./
RUN bun install --frozen-lockfile
COPY . .
RUN bun run build
RUN bun prune --production
```

`--omit=dev`, `--omit=optional`, and `--omit=peer` work the same way they do for `bun install`.

### `--dry-run`

List what would be removed without deleting anything:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun prune --production --dry-run
```

```
bun prune v1.4.0 (abc12345)

- typescript@5.4.0
1 package can be removed (checked 948) [9.00ms]
  bun prune --production
```

### `--filter`

Prune only the selected workspaces' `node_modules` folders (same patterns as [`bun install --filter`](/docs/pm/filter)). Bun also cleans shared locations: the root `node_modules`, or `node_modules/.bun` with the isolated linker. In those locations, Bun keeps anything an unselected workspace still needs.

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun prune --production --filter app
```

### Notes

* Always runs from the workspace root and covers every workspace's `node_modules`, even when invoked inside a workspace package.
* Requires `bun.lock` to match `package.json`. If you edited dependencies since the last install, run `bun install` first.
* Uses the same linker as `bun install` would. If `node_modules` was created with the other linker, `bun prune` refuses to run. Pass the matching `--linker`, or run `bun install`.
* Matches packages by name. If a package is at the wrong version, Bun leaves it for `bun install` to replace. Bun only removes a nested copy (`node_modules/a/node_modules/b`) once the correct version is installed above it; otherwise Bun keeps it and prints a warning.
* Never removes workspace folders, `.bin` entries still in use, dot-directories like `.cache`, plain files, or anything outside `node_modules`.
* Removes packages disabled for the current `os`/`cpu`. Pass `--os`/`--cpu` to prune for another platform.
* Works on a pruned monorepo checkout (e.g. `turbo prune` output) the same way `bun install --frozen-lockfile` does.
* If any entry fails to delete, the command still removes the rest and exits `1`.
* `--global` is not supported.
* To clean the global cache instead, use [`bun pm cache rm`](/docs/pm/cli/pm#cache).


# bun publish
Source: https://bun.com/docs/pm/cli/publish

Use `bun publish` to publish a package to the npm registry

`bun publish` packs your package into a tarball and strips catalog and workspace protocols from the `package.json`, resolving versions if necessary. It then publishes to the registry specified in your configuration files. Both `bunfig.toml` and `.npmrc` files are supported.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
## Publishing the package from the current working directory
bun publish
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
bun publish v1.3.3 (ca7428e9)

packed 203B package.json
packed 224B README.md
packed 30B index.ts
packed 0.64KB tsconfig.json

Total files: 4
Shasum: 79e2b4377b63f4de38dc7ea6e5e9dbee08311a69
Integrity: sha512-6QSNlDdSwyG/+[...]X6wXHriDWr6fA==
Unpacked size: 1.1KB
Packed size: 0.76KB
Tag: latest
Access: default
Registry: http://localhost:4873/

 + publish-1@1.0.0
```

To pack and publish separately, run `bun pm pack`, then `bun publish` with the path to the output tarball.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun pm pack
...
bun publish ./package.tgz
```

<Note>
  `bun publish` does not run lifecycle scripts (`prepublishOnly/prepack/prepare/postpack/publish/postpublish`) if you
  provide a tarball path. Scripts run only when `bun publish` packs the package itself.
</Note>

### `--access`

`--access` sets the access level of the package being published, either `public` or `restricted`. Unscoped packages are always public, and publishing an unscoped package with `--access restricted` is an error.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun publish --access public
```

You can also set `--access` in the `publishConfig` field of your `package.json`.

```json package.json icon="file-json" theme={"theme":{"light":"github-light","dark":"dracula"}}
{
  "publishConfig": {
    "access": "restricted"
  }
}
```

### `--tag`

Set the tag of the package version being published. By default, the tag is `latest`. The initial version of a package is always given the `latest` tag in addition to the specified tag.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun publish --tag alpha
```

You can also set `--tag` in the `publishConfig` field of your `package.json`.

```json package.json icon="file-json" theme={"theme":{"light":"github-light","dark":"dracula"}}
{
  "publishConfig": {
    "tag": "next"
  }
}
```

### `--dry-run`

`--dry-run` runs the publish process without publishing the package, so you can verify what would be published.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun publish --dry-run
```

### `--tolerate-republish`

Exit with code 0 instead of 1 if the package version already exists. Useful in CI/CD where jobs may be re-run.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun publish --tolerate-republish
```

### `--gzip-level`

Set the gzip compression level used when packing the package, from `0` to `9` (default `9`). Only applies to `bun publish` without a tarball path argument.

### `--auth-type`

If you have 2FA enabled for your npm account, `bun publish` prompts you for a one-time password, either through a browser or in the CLI. `--auth-type` tells the npm registry which method you prefer: `web` (the default) or `legacy`.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun publish --auth-type legacy
...
This operation requires a one-time password.
Enter OTP: 123456
...
```

### `--otp`

Provide a one-time password directly to the CLI. If the password is valid, `bun publish` skips the extra one-time password prompt before publishing:

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun publish --otp 123456
```

<Note>
  `bun publish` respects the `NPM_CONFIG_TOKEN` environment variable, useful when publishing from GitHub Actions or
  other automated workflows.
</Note>

***

## CLI Usage

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun publish dist
```

### Publishing Options

<ParamField type="string">
  Set the access level of the package being published, either `public` or `restricted`. Unscoped packages are always public; publishing an unscoped package with `--access restricted` is an error.

  ```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
  bun publish --access public
  ```

  You can also set `--access` in the `publishConfig` field of your `package.json`.

  ```json package.json icon="file-json" theme={"theme":{"light":"github-light","dark":"dracula"}}
  {
    "publishConfig": {
      "access": "restricted" // [!code ++]
    }
  }
  ```
</ParamField>

<ParamField type="string">
  Set the tag of the package version being published. By default, the tag is `latest`. The initial version of a package is always given the `latest` tag in addition to the specified tag.

  ```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
  bun publish --tag alpha
  ```

  You can also set `--tag` in the `publishConfig` field of your `package.json`.

  ```json package.json icon="file-json" theme={"theme":{"light":"github-light","dark":"dracula"}}
  {
    "publishConfig": {
      "tag": "next" // [!code ++]
    }
  }
  ```
</ParamField>

<ParamField type="boolean">
  Simulate the publish process without publishing the package, to verify its contents first.

  ```sh theme={"theme":{"light":"github-light","dark":"dracula"}}
  bun publish --dry-run
  ```
</ParamField>

<ParamField type="boolean">
  `bun publish` exits with code 0 instead of 1 when the version being published already exists in the registry.
</ParamField>

<ParamField type="string">
  Specify the level of gzip compression to use when packing the package. Only applies to `bun publish` without a tarball
  path argument. Values range from `0` to `9` (default is `9`).
</ParamField>

<ParamField type="string">
  If you have 2FA enabled for your npm account, `bun publish` prompts you for a one-time password, either through a browser or the CLI. `--auth-type` tells the npm registry which method you prefer: `web` (the default) or `legacy`.

  ```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
  bun publish --auth-type legacy
  ...
  This operation requires a one-time password.
  Enter OTP: 123456
  ...
  ```
</ParamField>

<ParamField type="string">
  Provide a one-time password directly to the CLI. A valid password skips the extra one-time password prompt before publishing.

  ```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
  bun publish --otp 123456
  ```

  <Note>
    `bun publish` respects the `NPM_CONFIG_TOKEN` environment variable, so you can publish from GitHub Actions or other
    automated workflows.
  </Note>
</ParamField>

### Registry Configuration

#### Custom Registry

<ParamField type="string">
  Specify registry URL, overriding .npmrc, bunfig.toml and environment variables
</ParamField>

```bash theme={"theme":{"light":"github-light","dark":"dracula"}}
bun publish --registry https://my-private-registry.com
```

#### SSL Certificates

<ParamField type="string">
  Provide Certificate Authority signing certificate
</ParamField>

<ParamField type="string">
  Path to Certificate Authority certificate file
</ParamField>

<CodeGroup>
  ```bash Inline Certificate theme={"theme":{"light":"github-light","dark":"dracula"}}
  bun publish --ca "-----BEGIN CERTIFICATE-----..."
  ```

  ```bash Certificate File theme={"theme":{"light":"github-light","dark":"dracula"}}
  bun publish --cafile ./ca-cert.pem
  ```
</CodeGroup>

### General Options

#### Dependency Management

<ParamField type="boolean">
  Don't install devDependencies
</ParamField>

<ParamField type="string">
  Exclude dependency types: `dev`, `optional`, or `peer`
</ParamField>

<ParamField type="boolean">
  Always request the latest versions from the registry & reinstall all dependencies
</ParamField>

#### Script Control

<ParamField type="boolean">
  Skip lifecycle scripts during packing and publishing
</ParamField>

<ParamField type="boolean">
  Add packages to trustedDependencies and run their scripts
</ParamField>

<Note>
  **Lifecycle Scripts** — When you publish a pre-built tarball, Bun does not run lifecycle scripts such as
  `prepublishOnly` and `prepack`; they only run when Bun packs the package itself.
</Note>

#### File Management

<ParamField type="boolean">
  Don't update package.json or lockfile
</ParamField>

<ParamField type="boolean">
  Disallow changes to lockfile
</ParamField>

<ParamField type="boolean">
  Generate yarn.lock file (yarn v1 compatible)
</ParamField>

#### Performance

<ParamField type="string">
  Platform optimizations: `clonefile` (default on macOS), `hardlink` (default on Linux and Windows), `symlink`, or
  `copyfile`
</ParamField>

<ParamField type="number">
  Maximum concurrent network requests
</ParamField>

<ParamField type="number">
  Maximum concurrent lifecycle scripts (default: 2x CPU cores)
</ParamField>

#### Output Control

<ParamField type="boolean">
  Suppress all output
</ParamField>

<ParamField type="boolean">
  Show detailed logging
</ParamField>

<ParamField type="boolean">
  Hide progress bar
</ParamField>

<ParamField type="boolean">
  Don't print publish summary
</ParamField>


# bun remove
Source: https://bun.com/docs/pm/cli/remove

Remove dependencies from your project

## Basic Usage

<Note>**Alias** — `bun rm`, `bun uninstall`, `bun r`</Note>

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun remove ts-node
```

Bun removes the package from every dependency group in `package.json` that lists it and updates `bun.lock`. Once nothing else depends on the package, Bun deletes it from `node_modules`.

## `--filter`

<Note>**Alias** — `-F`</Note>

In a monorepo, remove the package from the matching workspace(s) instead of the current directory's package, using the same patterns as [`bun add --filter`](/docs/pm/cli/add#--filter). Use `--filter '*'` to remove it from every workspace package. Workspaces that don't list the package are left untouched.

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun remove zod --filter api
bun remove zod --filter '*'
```

***

## CLI Usage

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun remove <package>
```

### General Information

<ParamField type="boolean">
  Print this help menu. Alias: <code>-h</code>
</ParamField>

### Configuration

<ParamField type="string">
  Specify path to config file (<code>bunfig.toml</code>). Alias: <code>-c</code>
</ParamField>

### Package.json Interaction

<ParamField type="boolean">
  Don't update <code>package.json</code> or save a lockfile
</ParamField>

<ParamField type="boolean">
  Save to <code>package.json</code> (true by default)
</ParamField>

<ParamField type="boolean">
  Add to <code>trustedDependencies</code> in the project's <code>package.json</code> and install the package(s)
</ParamField>

<ParamField type="string">
  Remove the package(s) from the matching workspaces instead of the current package. Alias: <code>-F</code>
</ParamField>

### Lockfile Behavior

<ParamField type="boolean">
  Write a <code>yarn.lock</code> file (yarn v1). Alias: <code>-y</code>
</ParamField>

<ParamField type="boolean">
  Disallow changes to lockfile
</ParamField>

<ParamField type="boolean">
  Save a text-based lockfile
</ParamField>

<ParamField type="boolean">
  Generate a lockfile without installing dependencies
</ParamField>

### Dependency Filtering

<ParamField type="boolean">
  Don't install devDependencies. Alias: <code>-p</code>
</ParamField>

<ParamField type="string">
  Exclude <code>dev</code>, <code>optional</code>, or <code>peer</code> dependencies from install
</ParamField>

### Network & Registry

<ParamField type="string">
  Provide a Certificate Authority signing certificate
</ParamField>

<ParamField type="string">
  Same as <code>--ca</code>, but as a file path to the certificate
</ParamField>

<ParamField type="string">
  Use a specific registry by default, overriding <code>.npmrc</code>, <code>bunfig.toml</code> and environment variables
</ParamField>

### Execution Control & Validation

<ParamField type="boolean">
  Perform a dry run without making changes
</ParamField>

<ParamField type="boolean">
  Always request the latest versions from the registry & reinstall all dependencies. Alias: <code>-f</code>
</ParamField>

<ParamField type="boolean">
  Skip verifying integrity of newly downloaded packages
</ParamField>

### Output & Logging

<ParamField type="boolean">
  Don't log anything
</ParamField>

<ParamField type="boolean">
  Excessively verbose logging
</ParamField>

<ParamField type="boolean">
  Disable the progress bar
</ParamField>

<ParamField type="boolean">
  Don't print a summary
</ParamField>

### Caching

<ParamField type="string">
  Store & load cached data from a specific directory path
</ParamField>

<ParamField type="boolean">
  Ignore manifest cache entirely
</ParamField>

### Script Execution

<ParamField type="boolean">
  Skip lifecycle scripts for all packages, including the project's <code>package.json</code> and trusted dependencies
</ParamField>

<ParamField type="number">
  Maximum number of concurrent jobs for lifecycle scripts (default: 2x CPU cores)
</ParamField>

### Scope & Path

<ParamField type="boolean">
  Install globally. Alias: <code>-g</code>
</ParamField>

<ParamField type="string">
  Set a specific cwd
</ParamField>

### Advanced & Performance

<ParamField type="string">
  Platform-specific optimizations for installing dependencies. Possible values: <code>clonefile</code> (default on
  macOS), <code>hardlink</code> (default on Linux and Windows), <code>symlink</code>, <code>copyfile</code>
</ParamField>

<ParamField type="number">
  Maximum number of concurrent network requests (default 48)
</ParamField>


# bun update
Source: https://bun.com/docs/pm/cli/update

Update dependencies to the newest versions their ranges allow

<Note>To upgrade your Bun CLI version, see [`bun upgrade`](/docs/installation#upgrading).</Note>

`bun update` (alias `bun up`) updates every dependency, direct and transitive, to the newest version allowed by the ranges that request it. It then rewrites `package.json` and `bun.lock`. To ignore your declared ranges, use [`--latest`](#--latest).

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun update
```

To update specific packages, pass their names. Names can be glob patterns, and `!` excludes:

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun update zod
bun update jquery@3            # move the package.json entry to the newest 3.x
bun update '@types/*'
bun update '@babel/*' '!@babel/core'
```

`bun update <package>` updates `<package>` everywhere it appears in `bun.lock` and leaves everything else alone. This works for transitive dependencies too — `bun update caniuse-lite` picks up a nested fix without adding it to your `package.json`. A name that isn't in `bun.lock` is an error.

Updated packages appear in the install summary as `↑ name old → new`, with `(v3.0.0 available)` when a newer major is out of range. Use `--dry-run` to preview.

### How `package.json` is rewritten

* `^1.1.0` → `^1.2.0`, `~1.1.0` → `~1.1.5`. Bun preserves the operator. With [`install.exact`](/docs/runtime/bunfig#install-exact) or `--exact`, Bun writes an exact version instead.
* Exact pins, dist-tags (`"latest"`, `"next"`), and other range forms (`*`, `1.x`, `>=1.0.0`) are left as written; only `bun.lock` moves. `--latest` rewrites them.
* Bun never rewrites `catalog:` references; it updates the catalog entry in the root `package.json` instead.
* `--no-save` updates `node_modules` only, leaving `package.json` and `bun.lock` untouched.

### What is held back

* Bun never widens ranges. A package that depends on `foo@^1.0.0` never gets `foo@2.x`.
* Versions in `patchedDependencies` stay put as long as their range allows. Bun reports them as `kept name@version (patched, v1.2.3 available)`. `--latest` and [`bun audit fix`](/docs/pm/cli/audit#bun-audit-fix) do move them; re-create the patch with [`bun patch`](/docs/pm/cli/patch) afterwards.
* If a registry request for a transitive package fails, that package keeps its locked version and Bun prints a warning. A failed request for a direct dependency is an error.

## `--interactive`

Use the `--interactive` flag to choose which packages to update:

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun update --interactive
bun update -i
```

`--interactive` opens a terminal interface listing every outdated direct dependency. Bun updates the packages you select as if you had run `bun update <name> ...`; everything else keeps its locked version.

### Interactive Interface

The interface displays packages grouped by dependency type:

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
? Select packages to update - Space to toggle, Enter to confirm, a to select all, n to select none, i to invert, l to toggle latest

  dependencies                Current  Target   Latest
    □ react                   17.0.2   18.2.0   18.3.1
    □ lodash                  4.17.20  4.17.21  4.17.21

  devDependencies             Current  Target   Latest
    □ typescript              4.8.0    5.0.0    5.3.3
    □ @types/node             16.11.7  18.0.0   20.11.5

  optionalDependencies        Current  Target   Latest
    □ some-optional-package   1.0.0    1.1.0    1.2.0
```

**Sections:**

* Packages are grouped under section headers: `dependencies`, `devDependencies`, `peerDependencies`, `optionalDependencies`
* Each section shows column headers aligned with the package data

**Columns:**

* **Package**: Package name (may have a suffix such as ` dev`, ` peer`, or ` optional`)
* **Current**: Currently installed version
* **Target**: Version that would be installed (respects semver constraints)
* **Latest**: Latest available version

### Keyboard Controls

**Selection:**

* **Space**: Toggle package selection
* **Enter**: Confirm selections and update
* **a/A**: Select all packages
* **n/N**: Select none
* **i/I**: Invert selection

**Navigation:**

* **↑/↓ Arrow keys** or **j/k**: Move cursor
* **l/L**: Toggle between target and latest version for current package

**Exit:**

* **Ctrl+C** or **Ctrl+D**: Cancel without updating

### Visual Indicators

* **■** Selected packages (will be updated)
* **□** Unselected packages
* **❯** Current cursor position
* **Colors**: Red (major), yellow (minor), green (patch) version changes
* **Underlined**: Currently selected update target

### Package Grouping

Packages are organized in sections by dependency type:

* **dependencies** - Regular runtime dependencies
* **devDependencies** - Development dependencies
* **peerDependencies** - Peer dependencies
* **optionalDependencies** - Optional dependencies

Within each section, individual packages may have a suffix (` dev`, ` peer`, ` optional`).

## `--recursive` and `--filter`

In a monorepo, `bun update` only rewrites the `package.json` of the workspace you run it in. From the root, it still updates the transitive dependencies of every workspace in `bun.lock`.

* `--recursive` (`-r`) updates every workspace's `package.json`.
* `--filter <pattern>` (`-F`) updates only the matching workspaces, using the [filter syntax](/docs/pm/filter). As with `bun install --filter`, Bun links only the selected workspaces afterwards.

Both combine with package names, `--latest`, `--dry-run`, and `--interactive` (which adds a "Workspace" column).

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun update --recursive
bun update --filter './packages/*'
bun update -i -r
bun update zod -r
bun update zod --filter '...^ui'
```

## `--dev`, `--prod`, `--no-optional`

Restrict which `package.json` entries Bun updates:

* `--dev` (`-D`) updates `devDependencies` only.
* `--prod` (`-P`) updates `dependencies` and `optionalDependencies` only.
* `--no-optional` skips `optionalDependencies`.

They combine with names, patterns, `--latest`, and `--interactive`.

These flags only select what to update — `bun update --prod` still installs `devDependencies`.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun update --dev
bun update --prod --latest
bun update -D '@types/*'
bun update -i --prod
```

## `--global`

`bun update -g` updates packages installed with `bun add -g`:

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun update -g
bun update -g typescript
```

## `--latest`

By default, `bun update` updates each dependency to the latest version that satisfies the version range in your `package.json`.

To update direct dependencies to the latest version regardless of the declared range, use `--latest` (`-L`). Bun rewrites the `package.json` entry to a range of the same style on the new version. Transitive dependencies still respect the ranges their dependents declare. Bun does not downgrade a dependency that is already ahead of `latest` (e.g. a prerelease).

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun update --latest
bun update -L
```

In interactive mode, press **l** to toggle a package between its target version (respecting semver) and the latest version.

For example, with the following `package.json`:

```json package.json icon="file-json" theme={"theme":{"light":"github-light","dark":"dracula"}}
{
  "dependencies": {
    "react": "^17.0.2"
  }
}
```

* `bun update` would update to a version that matches `17.x`.
* `bun update --latest` would update to a version that matches `18.x` or later.

***

## CLI Usage

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun update [<name>[@<version>] | <pattern>]...
bun up
```

### Update Strategy

<ParamField type="boolean">
  Always request the latest versions from the registry & reinstall all dependencies. Alias: <code>-f</code>
</ParamField>

<ParamField type="boolean">
  Update packages to their latest versions. Alias: <code>-L</code>
</ParamField>

### Dependency Scope

<ParamField type="boolean">
  Only update <code>devDependencies</code>. Alias: <code>-D</code>
</ParamField>

<ParamField type="boolean">
  Only update <code>dependencies</code> and <code>optionalDependencies</code>. Aliases: <code>-P</code>,
  <code>--production</code>
</ParamField>

<ParamField type="boolean">
  Don't update <code>optionalDependencies</code>
</ParamField>

<ParamField type="boolean">
  Install globally. Alias: <code>-g</code>
</ParamField>

<ParamField type="string">
  Exclude <code>dev</code>, <code>optional</code>, or <code>peer</code> dependencies from install
</ParamField>

### Project File Management

<ParamField type="boolean">
  Write a <code>yarn.lock</code> file (yarn v1). Alias: <code>-y</code>
</ParamField>

<ParamField type="boolean">
  Don't update <code>package.json</code> or save a lockfile
</ParamField>

<ParamField type="boolean">
  Save to <code>package.json</code> (true by default)
</ParamField>

<ParamField type="boolean">
  Disallow changes to lockfile
</ParamField>

<ParamField type="boolean">
  Save a text-based lockfile
</ParamField>

<ParamField type="boolean">
  Generate a lockfile without installing dependencies
</ParamField>

### Network & Registry

<ParamField type="string">
  Provide a Certificate Authority signing certificate
</ParamField>

<ParamField type="string">
  Same as <code>--ca</code>, but as a file path to the certificate
</ParamField>

<ParamField type="string">
  Use a specific registry by default, overriding <code>.npmrc</code>, <code>bunfig.toml</code> and environment variables
</ParamField>

<ParamField type="number">
  Maximum number of concurrent network requests (default 48)
</ParamField>

### Caching

<ParamField type="string">
  Store & load cached data from a specific directory path
</ParamField>

<ParamField type="boolean">
  Ignore manifest cache entirely
</ParamField>

### Output & Logging

<ParamField type="boolean">
  Don't log anything
</ParamField>

<ParamField type="boolean">
  Excessively verbose logging
</ParamField>

<ParamField type="boolean">
  Disable the progress bar
</ParamField>

<ParamField type="boolean">
  Don't print a summary
</ParamField>

### Script Execution

<ParamField type="boolean">
  Skip lifecycle scripts for all packages, including the project's <code>package.json</code> and trusted dependencies
</ParamField>

<ParamField type="number">
  Maximum number of concurrent jobs for lifecycle scripts (default: 2x CPU cores)
</ParamField>

### Installation Controls

<ParamField type="boolean">
  Skip verifying integrity of newly downloaded packages
</ParamField>

<ParamField type="boolean">
  Add to <code>trustedDependencies</code> in the project's <code>package.json</code> and install the package(s)
</ParamField>

<ParamField type="string">
  Platform-specific optimizations for installing dependencies. Possible values: <code>clonefile</code> (default on
  macOS), <code>hardlink</code> (default on Linux and Windows), <code>symlink</code>, <code>copyfile</code>
</ParamField>

### General & Environment

<ParamField type="string">
  Specify path to config file (<code>bunfig.toml</code>). Alias: <code>-c</code>
</ParamField>

<ParamField type="boolean">
  Perform a dry run without making changes
</ParamField>

<ParamField type="string">
  Set a specific cwd
</ParamField>

<ParamField type="boolean">
  Print this help menu. Alias: <code>-h</code>
</ParamField>


# bun why
Source: https://bun.com/docs/pm/cli/why

Explain why a package is installed

`bun why` explains why a package is installed in your project by showing the dependency chain that led to it.

## Usage

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun why <package>
```

## Arguments

* `<package>`: The name of the package to explain. Supports glob patterns like `@org/*` or `*-lodash`.

## Options

* `--top`: Show only the top-level dependencies instead of the complete dependency tree.
* `--depth <number>`: Maximum depth of the dependency tree to display.

## Examples

Check why a specific package is installed:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun why react
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
react@18.2.0
  └─ my-app@1.0.0 (requires ^18.0.0)
```

Check why all packages matching a pattern are installed:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun why "@types/*"
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
@types/react@18.2.15
  └─ dev my-app@1.0.0 (requires ^18.0.0)

@types/react-dom@18.2.7
  └─ dev my-app@1.0.0 (requires ^18.0.0)
```

Show only top-level dependencies:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun why express --top
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
express@4.18.2
  └─ my-app@1.0.0 (requires ^4.18.2)
```

Limit the dependency tree depth:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun why express --depth 2
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
express@4.18.2
  └─ express-pollyfill@1.20.1 (requires ^4.18.2)
     └─ body-parser@1.20.1 (requires ^1.20.1)
     └─ accepts@1.3.8 (requires ^1.3.8)
        └─ (deeper dependencies hidden)
```

## Understanding the Output

The output shows:

* The package name and version being queried
* The dependency chain that led to its installation
* The type of dependency (dev, peer, optional, or production)
* The version requirement specified in each package's dependencies

For nested dependencies, the command shows the complete dependency tree by default, with indentation indicating the relationship hierarchy.


# bun --filter
Source: https://bun.com/docs/pm/filter

Select packages by pattern in a monorepo using the --filter flag

The `--filter` (or `-F`) flag selects packages in a monorepo by pattern. A pattern is a package name glob, a `./path` glob, a `{dir}` directory selector, or a `...` dependency relation.

The flag works with `bun run`, `bun install`, `bun add`, `bun remove`, `bun update`, `bun outdated`, `bun prune`, and `bun pm licenses`. For package-manager commands, put the flag after the subcommand (`bun install --filter api`), since `bun --filter <pattern> <word>` runs `<word>` as a script.

***

## Matching

### Package Name `--filter <pattern>`

Name patterns select packages by the `name` field in `package.json`. For example, if you have packages `pkg-a`, `pkg-b` and `other`, you can match all of them with `*`, only `pkg-a` and `pkg-b` with `pkg*`, and a specific package with its full name.

### Package Path `--filter ./<glob>`

Path patterns start with `./` and select all packages in directories matching the pattern. For example, to match all packages in subdirectories of `packages`, use `--filter './packages/**'`. To match the package in `packages/foo`, use `--filter ./packages/foo`.

Name patterns match the full package name (`core` does not match `@acme/core`; use `@acme/*`), and `*` does not cross `/`. Path patterns must match the workspace directory itself: `./packages` selects nothing, `./packages/*` selects every workspace directly inside it.

### Directory `--filter '{<dir>}'`

A directory in braces selects every workspace in that directory or anywhere below it, resolved from the current directory. `--filter '{packages}'` selects everything under `packages/`; `--filter '{.}'` selects the current directory's workspace and everything below it.

### Dependency relations `--filter 'foo...'`

Adding `...` to a pattern also selects workspaces related through workspace dependencies:

| Pattern   | Selects                                                              |
| --------- | -------------------------------------------------------------------- |
| `foo...`  | `foo` and the workspaces it depends on, directly or transitively     |
| `foo^...` | only the workspaces `foo` depends on, not `foo` itself               |
| `...foo`  | `foo` and the workspaces that depend on it, directly or transitively |
| `...^foo` | only the workspaces that depend on `foo`, not `foo` itself           |

`foo` can be a name glob or a directory selector (`...{./packages/api}`). To exclude, `!` goes first: `--filter '!...foo'`.

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun install --filter 'web...'
bun add zod --filter '...^ui'
bun outdated --filter '{./packages/apps}'
```

***

## `bun install` and `bun outdated`

By default, `bun install` installs dependencies for every package in the monorepo. To install dependencies for specific packages, use `--filter`.

Multiple `--filter` flags combine: everything matched by a positive pattern, minus everything matched by a `!` pattern. A pattern that matches nothing prints a warning. For `bun add`, `bun remove`, `bun update`, `bun prune`, and `bun pm licenses`, selecting no workspaces at all is an error.

Given a monorepo with workspaces `pkg-a`, `pkg-b`, and `pkg-c` under `./packages`:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
# Install dependencies for all workspaces except `pkg-c`
bun install --filter '!pkg-c'

# Install dependencies for packages in `./packages` (`pkg-a`, `pkg-b`, `pkg-c`)
bun install --filter './packages/*'

# Save as above, but exclude the root package.json
bun install --filter '!./' --filter './packages/*'
```

Similarly, `bun outdated` displays outdated dependencies for all packages in the monorepo, and `--filter` restricts the command to a subset of them:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
# Display outdated dependencies for workspaces starting with `pkg-`
bun outdated --filter 'pkg-*'

# Display outdated dependencies for only the root package.json
bun outdated --filter './'
```

See [`bun install`](/docs/pm/cli/install) and [`bun outdated`](/docs/pm/cli/outdated).

***

## Running scripts with `--filter`

Use the `--filter` flag to execute scripts in multiple packages at once:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun --filter <pattern> <script>
```

Say you have a monorepo with two packages: `packages/api` and `packages/frontend`, both with a `dev` script that starts a local development server. Normally, you would open two terminal tabs, `cd` into each package directory, and run `bun dev`:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
cd packages/api
bun dev

# in another terminal
cd packages/frontend
bun dev
```

Using `--filter`, you can run the `dev` script in both packages at once:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun --filter '*' dev
```

Both scripts run in parallel, and a terminal UI shows their respective outputs:

<Frame>![Terminal Output](https://github.com/oven-sh/bun/assets/48869301/2a103e42-9921-4c33-948f-a1ad6e6bac71)</Frame>

The other pattern forms work the same way:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
# build web and everything it depends on, in dependency order
bun --filter 'web...' build

# test the packages that depend on ui
bun --filter '...^ui' test

# run dev in every package under packages/apps
bun --filter '{./packages/apps}' dev

# lint every package except docs
bun --filter '*' --filter '!docs' lint
```

Only a `./path` pattern can select a `package.json` without a `name`. If no selected package has the script, `bun run` exits with an error (pass `--if-present` to exit 0 instead).

### Running scripts in workspaces

Filters respect your [workspace configuration](/docs/pm/workspaces): if your `package.json` specifies which packages are part of the workspace,
`--filter` only matches those packages. In a workspace, `--filter` can also run scripts in packages located anywhere in the workspace:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
# Packages
# src/foo
# src/bar

# in src/bar: runs myscript in src/foo, no need to cd!
bun run --filter foo myscript
```

### Parallel and sequential mode

Combine `--filter` or `--workspaces` with `--parallel` or `--sequential` to run scripts across workspace packages with Foreman-style prefixed output:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
# Run "build" in all matching packages concurrently
bun run --parallel --filter '*' build

# Run "build" in all workspace packages sequentially
bun run --sequential --workspaces build

# Run glob-matched scripts across all packages
bun run --parallel --filter '*' "build:*"

# Continue running even if one package's script fails
bun run --parallel --no-exit-on-error --filter '*' test

# Run multiple scripts across all packages
bun run --parallel --filter '*' build lint
```

Bun prefixes each line of output with the package and script name (`pkg-a:build | ...`). Without `--filter`/`--workspaces`, the prefix is only the script name (`build | ...`). When a package's `package.json` has no `name` field, Bun uses the relative path from the workspace root instead.

Use `--if-present` with `--workspaces` to skip packages that don't have the requested script instead of erroring.

### Dependency Order

Bun respects package dependency order when running scripts. Say you have a package `foo` that depends on another package `bar` in your workspace, and both have a `build` script. When you run `bun --filter '*' build`, `foo` only starts once `bar` is done.


# Global cache
Source: https://bun.com/docs/pm/global-cache

How Bun stores and manages packages in its global cache

Bun stores every package downloaded from the registry in a global cache at `~/.bun/install/cache`, or the path set by the `BUN_INSTALL_CACHE_DIR` environment variable. Packages live in subdirectories named like `${name}@${version}`, so Bun can cache multiple versions of a package.

<Accordion title="Configuring cache behavior">
  ```toml bunfig.toml icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
  [install.cache]
  # the directory to use for the cache
  dir = "~/.bun/install/cache"

  # when true, don't load from the global cache.
  # Bun may still write to node_modules/.cache
  disable = false

  # when true, always resolve the latest versions from the registry
  disableManifest = false
  ```
</Accordion>

***

## Minimizing re-downloads

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.

<Accordion title="Installation details">
  If the semver version has a pre-release suffix (`1.0.0-beta.0`) or a build suffix (`1.0.0+20220101`), Bun replaces the suffix with a hash of that value instead, to reduce the chance of errors from long file paths.

  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"`.

  If a package is missing or has a version incompatible with the `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.
</Accordion>

***

## Fast copying

Once a package is downloaded into the cache, Bun still needs to copy those files into `node_modules`. It uses the fastest syscalls available for this: hardlinks on Linux and Windows, `clonefile` on macOS.

***

## Saving disk space

On Linux and Windows, Bun uses hardlinks to "copy" a module into a project's `node_modules` directory, so the contents of the package only exist in a single location on disk. This greatly reduces the disk space used by `node_modules`.

The same applies on macOS, with a caveat. There Bun uses `clonefile`, which is copy-on-write: the clone occupies no extra disk space, but it counts towards the drive's limit. Because the copy only happens on write, patching `node_modules/*` in one project can't affect other installations.

<Accordion title="Installation strategies">
  Configure this with the `--backend` flag, which all of Bun's package management commands respect.

  * **`hardlink`**: Default on Linux and Windows.
  * **`clonefile`**: Default on macOS.
  * **`clonefile_each_dir`**: Similar to `clonefile`, except it clones each file individually per directory. It is only available on macOS and tends to perform slower than `clonefile`.
  * **`copyfile`**: The fallback used when any of the above fail. It is the slowest option. On macOS, it uses `fcopyfile()`; on Linux it uses `copy_file_range()`.
  * **`symlink`**: Used only for `file:` dependencies. To prevent infinite loops, it skips symlinking the `node_modules` folder.

  If you install with `--backend=symlink`, Node.js doesn't resolve node\_modules of dependencies unless each dependency has its own `node_modules` folder or you pass `--preserve-symlinks` to `node`. See [Node.js documentation on `--preserve-symlinks`](https://nodejs.org/api/cli.html#--preserve-symlinks).

  ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
  bun install --backend symlink
  node --preserve-symlinks ./foo.js
  ```

  Bun's runtime also supports `--preserve-symlinks`.
</Accordion>


# Global virtual store
Source: https://bun.com/docs/pm/global-store

Install packages once. Every project links to the same copy.

Every project on your machine depends on the same handful of heavy packages — `typescript`, `next`, `webpack`, `@babel/*`, `react-dom`. Without a shared store, every `node_modules` gets its own copy, and every fresh `bun install` writes them all out again.

The global virtual store changes that to **install once, link everywhere**. Package files live in one shared cache; each project's `node_modules` is a thin tree of symlinks into it. A second checkout, a new branch worktree, a CI workspace — they all point at the copy that's already on disk.

The result: warm installs are roughly **7× faster** (one symlink per package instead of copying every file), and `node_modules` shrinks from hundreds of megabytes per project to a few megabytes of links.

## Enabling

The global virtual store is **off by default**. It only applies to the [isolated linker](/docs/pm/isolated-installs); the hoisted linker does not use it.

To enable it for a project:

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[install]
linker = "isolated"
globalStore = true
```

Or per invocation, through the environment:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
BUN_INSTALL_GLOBAL_STORE=1 bun install --linker isolated
```

To explicitly opt out (the default), set `globalStore = false` or `BUN_INSTALL_GLOBAL_STORE=0`.

## Why it's fast

Without the global store, the isolated linker calls `clonefileat()` (macOS) or `link()`/`copyfile()` (other platforms) for every package on every install, even when the package cache is warm and `node_modules` is the only thing missing. Profiling a warm install of a 1,400-package fixture on macOS showed the main thread spending **95.4 % of its time inside `clonefileat`**:

```
Sort by top of stack (bun install --linker isolated, warm cache):
    clonefileat        891 / 934 samples
    __openat            15
    __read_nocancel     10
```

`clonefileat` on APFS holds a volume-wide kernel lock, so spreading the work across more threads barely helps. Eight threads only improved a 2,830-directory clone from 959 ms to 743 ms. The fix is to not call it at all on the warm path.

With the global store, the warm path is one `access()` (does the global entry exist?) plus one `symlink()` (point the project at it) per package.

## Benchmarks

Warm CI install on a 1,400-package React/webpack/Babel/jest fixture, Apple Silicon macOS, `hyperfine --warmup 3 --runs 10`. The lockfile is present, the package cache is warm, and `node_modules` is deleted between runs:

|                                          | wall time    | system time | `clonefileat` | total syscalls |
| ---------------------------------------- | ------------ | ----------- | ------------- | -------------- |
| `--linker hoisted`                       | 823.9 ms     | 477 ms      | 1,387         | 7,857          |
| `--linker isolated`, `globalStore=false` | 840.9 ms     | 1,256 ms    | 1,387         | —              |
| **`--linker isolated`, global store**    | **124.8 ms** | **94 ms**   | **0**         | **4,957**      |

**6.7× faster** than the same linker without the global store, **6.6× faster** than hoisted.

### Disk

`node_modules` sizes for the same fixture (`du -sh node_modules` on APFS; clonefile copies are copy-on-write, so the hoisted/per-project numbers are the *logical* size — on filesystems without CoW that is also the physical size):

|                                          | `node_modules` per project | shared on disk |
| ---------------------------------------- | -------------------------- | -------------- |
| `--linker hoisted`                       | 391 MB                     | —              |
| `--linker isolated`, `globalStore=false` | 391 MB                     | —              |
| **`--linker isolated`, global store**    | **\~5 MB of symlinks**     | 391 MB once    |

Clone the same project five times and the difference is \~2 GB of duplicated package files versus \~400 MB total. The break-even is one project; every additional checkout, branch worktree, or CI workspace after that is free.

### Real-world cold→warm

Cold-to-warm timings on cloned real-world repositories (macOS arm64):

| project                        | packages | cold   | warm       |
| ------------------------------ | -------- | ------ | ---------- |
| cal.com                        | \~3,580  | 37.4 s | **4.7 s**  |
| remix                          | \~1,750  | 23.1 s | **2.0 s**  |
| excalidraw                     | \~1,332  | 5.9 s  | **1.1 s**  |
| hono                           | \~790    | 4.5 s  | **1.3 s**  |
| `next build` (create-next-app) | \~382    | 1.0 s  | **0.35 s** |

## Directory structure

The on-disk layout adds one level of indirection compared to [isolated installs](/docs/pm/isolated-installs#directory-structure):

```bash tree layout icon="list-tree" theme={"theme":{"light":"github-light","dark":"dracula"}}
~/.bun/install/cache/
├── react@18.3.1@@@1/                     # Package cache (unchanged)
│   └── ...package files...
└── links/                                # Global virtual store
    └── react@18.3.1-5664d3cd670b3205/    # <storepath>-<entry hash>
        └── node_modules/
            ├── react/                    # Package files (created once)
            ├── loose-envify -> ../../loose-envify@1.4.0-ea24…/node_modules/loose-envify
            └── .bin/
                └── ...

project/node_modules/
├── .bun/
│   ├── node_modules/                     # Hidden hoisted layer (per project)
│   │   └── react -> ../react@18.3.1/node_modules/react
│   └── react@18.3.1 -> ~/.bun/install/cache/links/react@18.3.1-5664d3cd670b3205
└── react -> .bun/react@18.3.1/node_modules/react
```

The 16-hex `entry_hash` suffix encodes the entry's **resolved dependency closure**: the package's own store path and tarball integrity, plus the hash of every dependency it links to. Two projects that resolve `react@18.3.1` to the same set of transitive versions share one global directory. A project that resolves a transitive dependency to a different version gets a separate global entry whose dep symlinks point at the right siblings. Packages that participate in a dependency cycle share one hash computed over the whole strongly-connected component. The key is therefore independent of which member a given project's dependency graph happened to reach first.

## What stays project-local

An entry only lives in the global store when it can be safely shared. Entries fall back to a per-project `node_modules/.bun/<storepath>/` directory when:

* the package has a **patch** applied via `bun patch` — the patched contents are project-specific;
* the package is listed in **`trustedDependencies`** (or trusted via `bun add --trust`) — its lifecycle script may mutate the install directory, and a script running through the project symlink would mutate the shared copy;
* the package, or **any** dependency it links to, is a `workspace:`, `file:`, or `link:` dependency — those resolve to project-local paths that other projects can't see.

Ineligibility propagates: if `your-app` depends on `internal-utils` which is a workspace package, `internal-utils` is project-local, and so is every entry that links to it. When an entry loses eligibility between installs (newly patched, newly trusted), Bun detaches it from the global store and rebuilds it project-locally on the next install. The shared entry stays untouched.

## Peer dependencies

Bun folds resolved peer dependencies (required and optional) into each global entry as dep symlinks. These peer dependencies contribute to the entry's hash. Bun synthesizes an implicit `"*"` optional peer for packages that list a name only in `peerDependenciesMeta` without a matching `peerDependencies` entry (matching pnpm and yarn). As a result, a package like `webpack` that declares `webpack-cli` only in `peerDependenciesMeta` still gets a `webpack-cli` symlink in its global entry when one is installed in the project.

## Tradeoffs

### Phantom-dependency fallback

When packages live under the project's `node_modules/.bun/`, Node's module resolution walks up through `node_modules/.bun/node_modules/` — the hidden hoisted layer — before reaching the project root. With the global store, packages realpath into `<cache>/links/`, so that layer is no longer on the resolution path from inside a package.

In practice this only affects **true phantom dependencies**: a package doing `require('helper')` for something it never declared in `dependencies`, `peerDependencies`, or `peerDependenciesMeta`. If you hit this, add the helper to the consuming package's dependencies (the right fix) or set `globalStore = false`.

[`publicHoistPattern`](/docs/runtime/bunfig#install-publichoistpattern) and [`hoistPattern`](/docs/runtime/bunfig#install-hoistpattern) hoist into the project's `node_modules`, which packages inside the global store can't reach. They still work for resolving hoisted packages from your own source code.

### `node_modules` is mostly symlinks

Tools that scan `node_modules` without following symlinks, or that compare file paths by string equality, may behave differently. This is the same caveat as any pnpm-style layout.

### Disk usage

Each unique `(package, version, resolved-dependency-set)` triple gets one directory in `<cache>/links/`. Across many projects that's a large net win: one copy on disk instead of one per checkout. But the store does grow over time as new versions and new peer-dependency combinations land. Run `bun pm cache rm` to clear the cache including the global store; the next install repopulates only what that project needs.

### Concurrency

Multiple `bun install` processes (parallel CI jobs, concurrent workspace builds) may race to populate the same global entry. Each process builds the entire entry (package files, dependency symlinks, bin links) under a private `<entry>.tmp-<random>/` staging directory. As the final step, it renames that directory into place. The loser of the rename sees `EEXIST` and discards its identical staging tree. A writer that crashes mid-build leaves only an unreferenced staging directory that the next install ignores. A published entry is therefore always complete; there is no separate completeness sentinel.

## Related documentation

* [Package manager > Isolated installs](/docs/pm/isolated-installs) — The linker the global store builds on
* [Package manager > Global cache](/docs/pm/global-cache) — Where downloaded packages are stored
* [Runtime > bunfig](/docs/runtime/bunfig#install-globalstore) — `bunfig.toml` reference


# Isolated installs
Source: https://bun.com/docs/pm/isolated-installs

Strict dependency isolation similar to pnpm's approach

Bun provides an alternative package installation strategy called **isolated installs** that creates strict dependency isolation similar to pnpm's approach. This mode prevents phantom dependencies (packages importing dependencies they never declared) and makes builds reproducible and deterministic.

Isolated installs are the default for **new** workspace/monorepo projects (with `configVersion = 1` in the lockfile). Existing projects continue using hoisted installs unless explicitly configured.

## What are isolated installs?

Isolated installs create a non-hoisted dependency structure where packages can only access their explicitly declared dependencies. This differs from the traditional "hoisted" installation strategy used by npm and Yarn, where dependencies are flattened into a shared `node_modules` directory.

### Key benefits

* **Prevents phantom dependencies** — Packages cannot accidentally import dependencies they haven't declared
* **Deterministic resolution** — Same dependency tree regardless of what else is installed
* **Better for monorepos** — Workspace isolation prevents cross-contamination between packages
* **Reproducible builds** — More predictable resolution behavior across environments

## Using isolated installs

### Command line

Use the `--linker` flag to specify the installation strategy:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
# Use isolated installs
bun install --linker isolated

# Use traditional hoisted installs
bun install --linker hoisted
```

### Configuration file

Set the default linker strategy in your `bunfig.toml` or globally in `$HOME/.bunfig.toml`:

```toml bunfig.toml icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[install]
linker = "isolated"
```

### Default behavior

The default linker strategy depends on your project's lockfile `configVersion`:

| `configVersion` | Using workspaces? | Default Linker |
| --------------- | ----------------- | -------------- |
| `1`             | ✅                 | `isolated`     |
| `1`             | ❌                 | `hoisted`      |
| `0`             | ✅                 | `hoisted`      |
| `0`             | ❌                 | `hoisted`      |

**New projects**: Default to `configVersion = 1`. In workspaces, v1 uses the isolated linker by default; otherwise it uses hoisted linking.

**Existing Bun projects (made pre-v1.3.2)**: If your existing lockfile doesn't have a version yet, Bun sets `configVersion = 0` when you run `bun install`, preserving the previous hoisted linker default.

**Migrations from other package managers**:

* From pnpm: `configVersion = 1` (using isolated installs in workspaces)
* From npm or yarn: `configVersion = 0` (using hoisted installs)

Override the default by passing the `--linker` flag or setting it in your configuration file.

## How isolated installs work

### Directory structure

Instead of hoisting dependencies, isolated installs create a two-tier structure:

```bash tree layout of node_modules icon="list-tree" theme={"theme":{"light":"github-light","dark":"dracula"}}
node_modules/
├── .bun/                          # Central package store
│   ├── node_modules/              # Hoisted fallback (see install.hoist)
│   │   └── package -> ../package@1.0.0/node_modules/package
│   ├── package@1.0.0/             # Versioned package installations
│   │   └── node_modules/
│   │       └── package/           # Actual package files
│   ├── @scope+package@2.1.0/      # Scoped packages (+ replaces /)
│   │   └── node_modules/
│   │       └── @scope/
│   │           └── package/
│   └── ...
└── package-name -> .bun/package@1.0.0/node_modules/package  # Symlinks
```

### Resolution algorithm

1. **Central store** — Bun installs all packages in `node_modules/.bun/package@version/` directories
2. **Symlinks** — Top-level `node_modules` contains symlinks pointing to the central store
3. **Peer resolution** — Complex peer dependencies create specialized directory names
4. **Deduplication** — Packages with identical package IDs and peer dependency sets are shared
5. **Re-linking** — On later installs, Bun reuses existing store entries and re-points their symlinks if a dependency was re-resolved. Store entries that are no longer referenced stay until you run [`bun prune`](/docs/pm/cli/prune)

### Workspace handling

In monorepos, Bun handles workspace dependencies specially:

* **Workspace packages** — Symlinked directly to their source directories, not the store
* **Workspace dependencies** — Can access other workspace packages in the monorepo
* **External dependencies** — Installed in the isolated store

## Comparison with hoisted installs

| Aspect                    | Hoisted (npm/Yarn)                         | Isolated (pnpm-like)                    |
| ------------------------- | ------------------------------------------ | --------------------------------------- |
| **Dependency access**     | Packages can access any hoisted dependency | Packages only see declared dependencies |
| **Phantom dependencies**  | ❌ Possible                                 | ✅ Prevented                             |
| **Disk usage**            | ✅ Lower (shared installs)                  | ✅ Similar (uses symlinks)               |
| **Determinism**           | ❌ Less deterministic                       | ✅ More deterministic                    |
| **Node.js compatibility** | ✅ Standard behavior                        | ✅ Compatible via symlinks               |
| **Best for**              | Single projects, legacy code               | Monorepos, strict dependency management |

## Advanced features

### Peer dependency handling

Isolated installs encode peer dependencies in the store path:

```bash tree layout of node_modules icon="list-tree" theme={"theme":{"light":"github-light","dark":"dracula"}}
# Package with peer dependencies creates specialized paths
node_modules/.bun/package@1.0.0_react@18.2.0/
```

The directory name includes both the package version and its peer dependency versions, so each unique combination gets its own installation.

### Strict resolution with `install.hoist = false`

By default, Bun creates `node_modules/.bun/node_modules`, a fallback directory with a symlink to every installed package. Because this directory is an ancestor of every store entry, a package in the store can resolve dependencies it never declared. Set [`install.hoist = false`](/docs/runtime/bunfig#install-hoist) (or `hoist=false` in `.npmrc`, matching pnpm) to skip creating this directory. Undeclared imports then fail instead of depending on what else happens to be installed. One caveat, shared with pnpm: the root `node_modules` also sits above the store. As a result, packages linked there (your direct dependencies, `publicHoistPattern` matches, and workspace packages) stay resolvable from any store package:

```toml bunfig.toml icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[install]
linker = "isolated"
hoist = false
```

[`install.hoistPattern`](/docs/runtime/bunfig#install-hoistpattern) and [`install.publicHoistPattern`](/docs/runtime/bunfig#install-publichoistpattern) offer pattern-based control over the same fallback directory and the root `node_modules` respectively.

### Global virtual store

When [`install.globalStore`](/docs/runtime/bunfig#install-globalstore) is enabled, Bun materializes store entries once into a [global virtual store](/docs/pm/global-store) at `<cache>/links/`, and `node_modules/.bun/<pkg>@<ver>` is a symlink into it. Warm installs after `rm -rf node_modules` only create one symlink per package instead of copying every package's files again, which is roughly **7× faster** on a typical mid-size project. The global store is **off by default**; see the [global store docs](/docs/pm/global-store) for how to enable it, the full layout, benchmarks, and tradeoffs.

### Backend strategies

When the global store is disabled (the default) or an entry isn't eligible for it, Bun materializes the entry under the project using one of:

* **Clonefile** (macOS) — Copy-on-write filesystem clones
* **Hardlink** (Linux/Windows) — Hardlinks to save disk space
* **Copyfile** (fallback) — Full file copies when other methods aren't available

### Debugging isolated installs

Enable verbose logging to see what an install is doing:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun install --linker isolated --verbose
```

The verbose output shows:

* Store entry creation
* Symlink operations
* Peer dependency resolution
* Deduplication decisions

## Troubleshooting

### Compatibility issues

Some packages may not work correctly with isolated installs due to:

* **Hardcoded paths** — Packages that assume a flat `node_modules` structure
* **Dynamic imports** — Runtime imports that don't follow Node.js resolution
* **Build tools** — Tools that scan `node_modules` directly

If you encounter issues, you can:

1. **Switch to hoisted mode** for specific projects:

   ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
   bun install --linker hoisted
   ```

2. **Report compatibility issues** to help improve isolated install support

### Performance considerations

* **Install time** — May be slightly slower due to symlink operations
* **Disk usage** — Similar to hoisted (uses symlinks, not file copies)
* **Memory usage** — Higher during install due to complex peer resolution

## Migration guide

### From npm/Yarn

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
# Remove existing node_modules and lockfiles
rm -rf node_modules package-lock.json yarn.lock

# Install with isolated linker
bun install --linker isolated
```

### From pnpm

Isolated installs are conceptually similar to pnpm, so migration is direct:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
# Remove pnpm files
rm -rf node_modules pnpm-lock.yaml

# Install with Bun's isolated linker
bun install --linker isolated
```

The main difference is that Bun keeps its store inside the project's `node_modules/.bun/` by default, while pnpm uses a global store with symlinks. With [`install.globalStore`](#global-virtual-store) enabled, Bun uses a global store as well.

## When to use isolated installs

**Use isolated installs when:**

* Working in monorepos with multiple packages
* Strict dependency management is required
* Preventing phantom dependencies is important
* 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 aren't well supported
* You prefer the traditional npm behavior

## Related documentation

* [Package manager > Workspaces](/docs/pm/workspaces) — Monorepo workspace management
* [Package manager > Lockfile](/docs/pm/lockfile) — Understanding Bun's lockfile format
* [CLI > install](/docs/pm/cli/install) — Complete `bun install` command reference


# Lifecycle scripts
Source: https://bun.com/docs/pm/lifecycle

How Bun handles package lifecycle scripts securely

Packages on `npm` can define *lifecycle scripts* in their `package.json`. These are some of the most common, among [many others](https://docs.npmjs.com/cli/v10/using-npm/scripts):

* `preinstall`: Runs before the package is installed
* `postinstall`: Runs after the package is installed
* `preuninstall`: Runs before the package is uninstalled
* `prepublishOnly`: Runs before the package is published

These scripts are arbitrary shell commands that the package manager is expected to run at the appropriate time. Because running arbitrary code is a security risk, Bun does not execute arbitrary lifecycle scripts by default, unlike other `npm` clients.

***

## `postinstall`

The `postinstall` script is particularly important. It's widely used to build or install platform-specific binaries for packages that are implemented as [native Node.js add-ons](https://nodejs.org/api/addons.html). For example, `node-sass` uses `postinstall` to build a native binary for Sass.

```json package.json icon="file-json" theme={"theme":{"light":"github-light","dark":"dracula"}}
{
  "name": "my-app",
  "version": "1.0.0",
  "dependencies": {
    "node-sass": "^6.0.1"
  }
}
```

***

## `trustedDependencies`

Bun is "default-secure": it only runs lifecycle scripts for packages on an allow list. To allow lifecycle scripts for a particular package, add its name to the `trustedDependencies` array in your `package.json`.

```json package.json icon="file-json" theme={"theme":{"light":"github-light","dark":"dracula"}}
{
  "name": "my-app",
  "version": "1.0.0",
  "trustedDependencies": ["node-sass"] // [!code ++]
}
```

After adding the package to `trustedDependencies`, install or re-install it. Bun reads the field and runs its lifecycle scripts.

A curated list of popular npm packages with lifecycle scripts is allowed by default. See [the full list](https://github.com/oven-sh/bun/blob/main/src/install/default-trusted-dependencies.txt).

<Note>
  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.
</Note>

### Behavior of the `trustedDependencies` field

Defining `trustedDependencies` in `package.json` **replaces** the default list rather than extending it. Exactly one of three modes applies per project:

| `package.json`                        | Packages allowed to run lifecycle scripts                  |
| ------------------------------------- | ---------------------------------------------------------- |
| `trustedDependencies` omitted         | The packages in Bun's built-in list (npm sources only).    |
| `trustedDependencies: ["pkg-a", ...]` | **Only** the listed packages. The default list is ignored. |
| `trustedDependencies: []`             | **No** packages, including none from the default list.     |

Set `trustedDependencies: []` when you want to opt out of the default allow list entirely without passing `--ignore-scripts` on every install. If you define `trustedDependencies` with an explicit list, include any packages from the [default list](https://github.com/oven-sh/bun/blob/main/src/install/default-trusted-dependencies.txt) whose lifecycle scripts you still need (for example, `sharp` or `esbuild`). Bun no longer trusts those packages implicitly.

***

## `--ignore-scripts`

To disable lifecycle scripts for all packages, use the `--ignore-scripts` flag.

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun install --ignore-scripts
```

To make this the default for a project, set [`install.ignoreScripts`](/docs/runtime/bunfig#install-ignorescripts) in `bunfig.toml`:

```toml bunfig.toml icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[install]
ignoreScripts = true
```

Or in `.npmrc`:

```ini .npmrc icon="npm" theme={"theme":{"light":"github-light","dark":"dracula"}}
ignore-scripts=true
```


# Lockfile
Source: https://bun.com/docs/pm/lockfile

Bun's lockfile format and configuration

`bun install` creates a lockfile called `bun.lock`.

#### Should it be committed to git?

Yes

#### Generate a lockfile without installing?

To generate a lockfile without installing to `node_modules`, use the `--lockfile-only` flag. Bun always saves the lockfile to disk, even if it is already up to date with your project's `package.json`(s). The exception is when `--frozen-lockfile` (or `--production`) is set.

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun install --lockfile-only
```

`bun add`, `bun remove`, and `bun update` also accept `--lockfile-only`.

<Note>
  `--lockfile-only` still populates the global install cache with registry metadata and git/tarball dependencies.
</Note>

#### Can I opt out?

To install without creating a lockfile:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun install --no-save
```

To write a Yarn lockfile *in addition* to `bun.lock`:

<CodeGroup>
  ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
  bun install --yarn
  ```

  ```toml bunfig.toml icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
  [install.lockfile]
  # whether to save a non-Bun lockfile alongside bun.lock
  # only "yarn" is supported
  print = "yarn"
  ```
</CodeGroup>

#### Text-based lockfile

Bun v1.2 changed the default lockfile format to the text-based `bun.lock`. To migrate an existing binary `bun.lockb`, run `bun install --save-text-lockfile --frozen-lockfile --lockfile-only` and delete `bun.lockb`.

For more on the format, see [the blog post](https://bun.com/blog/bun-lock-text-lockfile).

#### Automatic lockfile migration

When you run `bun install` in a project without a `bun.lock`, Bun automatically migrates existing lockfiles:

* `yarn.lock` (v1)
* `package-lock.json` (npm, `lockfileVersion` 2, 3 or 4)
* `pnpm-lock.yaml` (pnpm)

Bun does not migrate a `package-lock.json` from npm 6 or older (`lockfileVersion` 1); it prints a warning and resolves from `package.json` instead.

Bun preserves the original lockfile. You can remove it manually after verification.


# .npmrc support
Source: https://bun.com/docs/pm/npmrc



Bun loads configuration options from [`.npmrc`](https://docs.npmjs.com/cli/v10/configuring-npm/npmrc) files, so you can reuse your existing registry and scope configuration.

Configuration is loaded in this order, with later sources overriding earlier ones:

1. `~/.npmrc` (or `$XDG_CONFIG_HOME/.npmrc`)
2. `./.npmrc`
3. `bunfig.toml` (global, then project)
4. `BUN_CONFIG_REGISTRY` / `NPM_CONFIG_REGISTRY` and `BUN_CONFIG_TOKEN` / `NPM_CONFIG_TOKEN` environment variables
5. Command-line flags such as `--registry`

Bun matches credentials in `.npmrc` (`//<registry>/:_authToken`, etc.) to registries by host and path, even if you set the registry URL itself in `bunfig.toml`.

Values may reference environment variables. Bun replaces `${NAME}` with the variable's value, or leaves it as-is if the variable is unset. `${NAME?}` becomes an empty string if unset.

<Note>
  We recommend migrating your `.npmrc` file to Bun's [`bunfig.toml`](/docs/runtime/bunfig) format, which supports more
  options, including Bun-specific ones.
</Note>

***

## Supported options

### Set the default registry

Bun resolves packages from the default registry, which is `npm`'s official registry (`https://registry.npmjs.org/`).

To change it, set the `registry` option in `.npmrc`:

```ini .npmrc icon="npm" theme={"theme":{"light":"github-light","dark":"dracula"}}
registry=http://localhost:4873/
```

The equivalent `bunfig.toml` option is [`install.registry`](/docs/runtime/bunfig#install-registry):

```toml bunfig.toml icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
install.registry = "http://localhost:4873/"
```

### Set the registry for a specific scope

`@<scope>:registry` sets the registry for a specific scope:

```ini .npmrc icon="npm" theme={"theme":{"light":"github-light","dark":"dracula"}}
@myorg:registry=http://localhost:4873/
```

The equivalent `bunfig.toml` option is to add a key in [`install.scopes`](/docs/runtime/bunfig#install-scopes):

```toml bunfig.toml icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[install.scopes]
myorg = "http://localhost:4873/"
```

### Configure options for a specific registry

`//<registry_url>/:<key>=<value>` sets options for a specific registry:

```ini .npmrc icon="npm" theme={"theme":{"light":"github-light","dark":"dracula"}}
# set an auth token for the registry
# ${...} is a placeholder for environment variables
//http://localhost:4873/:_authToken=${NPM_TOKEN}


# or you could set a username and password
# note that the password is base64 encoded
//http://localhost:4873/:username=myusername

//http://localhost:4873/:_password=${NPM_PASSWORD}

# or use _auth, which is your username and password
# combined into a single string, which is then base 64 encoded
//http://localhost:4873/:_auth=${NPM_AUTH}
```

Bun supports the following options:

* `_authToken`
* `username`
* `_password` (base64 encoded password)
* `_auth` (base64 encoded username:password, for example `btoa(username + ":" + password)`)
* `email`

The equivalent `bunfig.toml` option is to add a key in [`install.scopes`](/docs/runtime/bunfig#install-scopes):

```toml bunfig.toml icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[install.scopes]
# unlike _password in .npmrc, password is not base64 encoded; Bun encodes it for you
myorg = { url = "http://localhost:4873/", username = "myusername", password = "$NPM_PASSWORD" }
```

### `link-workspace-packages`: Control workspace package installation

Controls how Bun installs workspace packages when they are available locally:

```ini .npmrc icon="npm" theme={"theme":{"light":"github-light","dark":"dracula"}}
link-workspace-packages=true
```

The equivalent `bunfig.toml` option is [`install.linkWorkspacePackages`](/docs/runtime/bunfig#install-linkworkspacepackages):

```toml bunfig.toml icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[install]
linkWorkspacePackages = true
```

### `save-exact`: Save exact versions

Always saves exact versions without the `^` prefix:

```ini .npmrc icon="npm" theme={"theme":{"light":"github-light","dark":"dracula"}}
save-exact=true
```

The equivalent `bunfig.toml` option is [`install.exact`](/docs/runtime/bunfig#install-exact):

```toml bunfig.toml icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[install]
exact = true
```

### `ignore-scripts`: Skip lifecycle scripts

Prevents running lifecycle scripts during installation:

```ini .npmrc icon="npm" theme={"theme":{"light":"github-light","dark":"dracula"}}
ignore-scripts=true
```

This is equivalent to using the `--ignore-scripts` flag with `bun install`.

### `dry-run`: Preview changes without installing

Shows what Bun would install without installing anything:

```ini .npmrc icon="npm" theme={"theme":{"light":"github-light","dark":"dracula"}}
dry-run=true
```

The equivalent `bunfig.toml` option is [`install.dryRun`](/docs/runtime/bunfig#install-dryrun):

```toml bunfig.toml icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[install]
dryRun = true
```

### `cache`: Configure cache directory

Set the cache directory path, or disable caching:

```ini .npmrc icon="npm" theme={"theme":{"light":"github-light","dark":"dracula"}}
# set a custom cache directory
cache=/path/to/cache

# or disable caching
cache=false
```

The equivalent `bunfig.toml` option is [`install.cache`](/docs/runtime/bunfig#install-cache):

```toml bunfig.toml icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[install.cache]
# set a custom cache directory
dir = "/path/to/cache"

# or disable caching
disable = true
```

### `ca` and `cafile`: Configure CA certificates

Configure custom CA certificates for registry connections:

```ini .npmrc icon="npm" theme={"theme":{"light":"github-light","dark":"dracula"}}
# single CA certificate
ca="-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----"

# multiple CA certificates
ca[]="-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----"
ca[]="-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----"

# or specify a path to a CA file
cafile=/path/to/ca-bundle.crt
```

### `omit` and `include`: Control dependency types

Control which dependency types Bun installs:

```ini .npmrc icon="npm" theme={"theme":{"light":"github-light","dark":"dracula"}}
# omit dev dependencies
omit=dev

# omit multiple types
omit[]=dev
omit[]=optional

# include specific types (overrides omit)
include=dev
```

Valid values: `dev`, `peer`, `optional`

### `install-strategy` and `node-linker`: Installation strategy

Control how Bun lays out packages in `node_modules`. For compatibility with other package managers, Bun accepts both npm's `install-strategy` and pnpm/yarn's `node-linker`. See [isolated installs](/docs/pm/isolated-installs) for how the hoisted and isolated layouts differ.

**npm's `install-strategy`:**

```ini .npmrc icon="npm" theme={"theme":{"light":"github-light","dark":"dracula"}}
# flat node_modules structure (default)
install-strategy=hoisted

# symlinked structure
install-strategy=linked
```

**pnpm/yarn's `node-linker`:**

`node-linker` controls the installation mode. Bun accepts values from both pnpm and yarn:

| Value          | Description                                      | Accepted by |
| -------------- | ------------------------------------------------ | ----------- |
| `isolated`     | Symlinked structure with isolated dependencies   | pnpm        |
| `hoisted`      | Flat node\_modules structure                     | pnpm        |
| `pnpm`         | Symlinked structure (same as `isolated`)         | yarn        |
| `node-modules` | Flat node\_modules structure (same as `hoisted`) | yarn        |

```ini .npmrc icon="npm" theme={"theme":{"light":"github-light","dark":"dracula"}}
# symlinked/isolated mode
node-linker=isolated
node-linker=pnpm

# flat/hoisted mode
node-linker=hoisted
node-linker=node-modules
```

### `public-hoist-pattern` and `hoist-pattern`: Control hoisting

Control which packages Bun hoists to the root `node_modules`:

```ini .npmrc icon="npm" theme={"theme":{"light":"github-light","dark":"dracula"}}
# packages matching this pattern will be hoisted to the root
public-hoist-pattern=*eslint*

# multiple patterns
public-hoist-pattern[]=*eslint*
public-hoist-pattern[]=*prettier*

# control general hoisting behavior
hoist-pattern=*

# isolated linker only: disable the node_modules/.bun/node_modules fallback,
# so store packages only resolve declared deps (plus the root node_modules).
# Hoisted installs ignore this setting.
hoist=false
```


# Overrides and resolutions
Source: https://bun.com/docs/pm/overrides

Control metadependency versions with npm overrides and Yarn resolutions

Bun supports npm's `"overrides"` and Yarn's `"resolutions"` in `package.json`. Both specify a version range for *metadependencies*, the dependencies of your dependencies.

```json package.json icon="file-json" theme={"theme":{"light":"github-light","dark":"dracula"}}
{
  "name": "my-app",
  "dependencies": {
    "foo": "^2.0.0"
  },
  "overrides": { // [!code ++]
    "bar": "~4.4.0" // [!code ++]
  } // [!code ++]
}
```

By default, Bun installs the latest version of all dependencies and metadependencies, according to the ranges specified in each package's `package.json`. Say your project has one dependency, `foo`, which in turn depends on `bar`. That makes `bar` a *metadependency* of your project.

```json package.json icon="file-json" theme={"theme":{"light":"github-light","dark":"dracula"}}
{
  "name": "my-app",
  "dependencies": {
    "foo": "^2.0.0"
  }
}
```

When you run `bun install`, Bun installs the latest version of each package.

```txt tree layout of node_modules icon="list-tree" theme={"theme":{"light":"github-light","dark":"dracula"}}
node_modules
├── foo@2.3.4
└── bar@4.5.6
```

If a security vulnerability is introduced in `bar@4.5.6`, you may want to pin `bar` to an older version that doesn't have it. That's what `"overrides"` and `"resolutions"` are for.

***

## `"overrides"`

Add `bar` to the `"overrides"` field in `package.json`. Bun defers to the specified version range when determining which version of `bar` to install, whether it's a dependency or a metadependency.

```json package.json icon="file-json" theme={"theme":{"light":"github-light","dark":"dracula"}}
{
  "name": "my-app",
  "dependencies": {
    "foo": "^2.0.0"
  },
  "overrides": { // [!code ++]
    "bar": "~4.4.0" // [!code ++]
  } // [!code ++]
}
```

Bun only reads overrides from the root `package.json`, not from workspace packages. Overrides apply to `peerDependencies` as well.

## `"resolutions"`

`"resolutions"` is Yarn's alternative to `"overrides"`, with similar syntax. Bun supports it to help projects migrate from Yarn.

```json package.json icon="file-json" theme={"theme":{"light":"github-light","dark":"dracula"}}
{
  "name": "my-app",
  "dependencies": {
    "foo": "^2.0.0"
  },
  "resolutions": { // [!code ++]
    "bar": "~4.4.0" // [!code ++]
  } // [!code ++]
}
```

## Values

A value can be any dependency specifier, not only a version range. `npm:` swaps a package for a fork, and `catalog:` (or `catalog:<name>`) keeps the overridden version in sync with a [workspace catalog](/docs/pm/catalogs):

```json package.json icon="file-json" theme={"theme":{"light":"github-light","dark":"dracula"}}
{
  "name": "my-app",
  "overrides": {
    "quux": "npm:@myorg/quux@^1.0.0",
    "foo": "catalog:"
  }
}
```

A value of `"$name"` reuses the range you declared for `name` in your own dependencies, so `"bar": "$foo"` pins `bar` to whatever range you declared for `foo`.

## Nested overrides

You can scope a rule to one parent package, so it only applies to that package's direct dependency. Bun accepts the npm object form, the pnpm `>` form, and a parent with a version range:

```json package.json icon="file-json" theme={"theme":{"light":"github-light","dark":"dracula"}}
{
  "name": "my-app",
  "overrides": {
    "micromatch": {
      ".": "^4.0.5",
      "picomatch": "^2.3.2"
    },
    "micromatch>picomatch": "^2.3.2",
    "micromatch@^4>picomatch": "^2.3.2"
  }
}
```

`"."` inside an object overrides `micromatch` itself, like a top-level `"micromatch"` rule.

`"resolutions"` accepts Yarn's path form. Bun accepts `**` for compatibility, but either way the rule only affects the parent's direct dependency:

```json package.json icon="file-json" theme={"theme":{"light":"github-light","dark":"dracula"}}
{
  "name": "my-app",
  "resolutions": {
    "micromatch/picomatch": "^2.3.2",
    "**/micromatch/**/picomatch": "^2.3.2"
  }
}
```

When several rules match, the most specific wins: parent-with-version > parent > top-level.

## Version-scoped overrides

The key can carry a version selector so the rule only applies to dependents whose declared range overlaps it. This is the form `pnpm audit --fix` writes:

```json package.json icon="file-json" theme={"theme":{"light":"github-light","dark":"dracula"}}
{
  "name": "my-app",
  "overrides": {
    "semver@<7.5.2": "7.5.2",
    "webpack>terser@4": "4.8.1",
    "terser@4": {
      ".": "4.8.1"
    }
  }
}
```

Bun compares the selector with the range each dependent *declares*, not the resolved version. `"semver@<7.5.2"` applies to a dependent that declares `^7.3.0` (which could still pick `7.3.x`) but not to one that declares `^7.5.2`. Dependents using a dist-tag, `catalog:`, `workspace:`, git, or URL specifier never match a selector.

## Limitations

* Bun supports only one parent level. It ignores `a>b>c`, `a/b/c`, and deeper object nesting with a warning.
* Bun does not support pnpm's `"pkg@"` (empty selector) and `"-"` (remove dependency) forms, and skips them with a warning.
* Bun writes a lockfile containing nested or version-scoped rules as `lockfileVersion` 3, which older versions of Bun cannot read.


# Scopes and registries
Source: https://bun.com/docs/pm/scopes-registries

Configure private registries and scoped packages

The default registry is `registry.npmjs.org`. To change it globally, set it in `bunfig.toml`:

```toml bunfig.toml icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[install]
# set default registry as a string
registry = "https://registry.npmjs.org"
# set a token
registry = { url = "https://registry.npmjs.org", token = "123456" }
# set a username/password
registry = "https://username:password@registry.npmjs.org"
```

To configure a private registry scoped to a particular organization:

```toml bunfig.toml icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[install.scopes]
# registry as string
"@myorg1" = "https://username:password@registry.myorg.com/"

# registry with username/password
# you can reference environment variables
"@myorg2" = { username = "myusername", password = "$NPM_PASS", url = "https://registry.myorg.com/" }

# registry with token
"@myorg3" = { token = "$npm_token", url = "https://registry.myorg.com/" }
```

### `.npmrc`

Bun also reads [`.npmrc`](/docs/pm/npmrc) files.


# Security Scanner API
Source: https://bun.com/docs/pm/security-scanner-api



Bun's package manager can scan packages before installing them, to catch supply chain attacks and known vulnerabilities.

***

## Quick Start

Configure a security scanner in your `bunfig.toml`:

```toml bunfig.toml icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[install.security]
scanner = "@oven/bun-security-scanner" # example name, replace with your scanner's package
```

With a scanner configured, Bun:

* Scans all packages before installation
* Displays security warnings and advisories
* Cancels installation if the scanner finds fatal advisories

***

## How It Works

Security scanners analyze packages during `bun install`, `bun add`, and other package operations. They can detect:

* Known security vulnerabilities (CVEs)
* Malicious packages
* License compliance issues

### Security Levels

Scanners report issues at two severity levels:

* **`fatal`** - Installation stops immediately, exits with non-zero code
* **`warn`** - In interactive terminals, prompts to continue; in CI, exits immediately

***

## Using Pre-built Scanners

Security companies publish Bun security scanners as npm packages.

### Installing a Scanner

Install a security scanner from npm:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun add -d @oven/bun-security-scanner
```

<Note>
  `@oven/bun-security-scanner` is an example package name, not a real package. Replace it with the scanner you want to
  use, and consult that scanner's documentation for the exact package name and installation instructions. You install
  most scanners with `bun add`.
</Note>

### Configuring the Scanner

After installation, configure it in your `bunfig.toml`:

```toml bunfig.toml icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[install.security]
scanner = "@oven/bun-security-scanner" # example name, replace with your scanner's package
```

### Enterprise Configuration

Some enterprise scanners take authentication and other configuration from environment variables:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
# This might go in ~/.bashrc, for example
export SECURITY_API_KEY="your-api-key"

# The scanner will now use these credentials automatically
bun install
```

Consult your scanner's documentation for which environment variables to set and any other required configuration.

### Authoring your own scanner

For a complete example with tests and CI setup, see the official template:
[github.com/oven-sh/security-scanner-template](https://github.com/oven-sh/security-scanner-template)

## Related

* [Configuration (bunfig.toml)](/docs/runtime/bunfig#install-security-scanner)
* [Package Manager](/docs/pm/cli/install)
* [Security Scanner Template](https://github.com/oven-sh/security-scanner-template)


# Workspaces
Source: https://bun.com/docs/pm/workspaces

Develop complex monorepos with multiple independent packages

Bun supports [`workspaces`](https://docs.npmjs.com/cli/v9/using-npm/workspaces?v=true#description) in `package.json`. With workspaces, you develop several independent packages in a single repository, a *monorepo*.

A monorepo commonly has this structure:

```txt File Tree icon="folder-tree" theme={"theme":{"light":"github-light","dark":"dracula"}}
<root>
├── README.md
├── bun.lock
├── package.json
├── tsconfig.json
└── packages
    ├── pkg-a
    │   ├── index.ts
    │   ├── package.json
    │   └── tsconfig.json
    ├── pkg-b
    │   ├── index.ts
    │   ├── package.json
    │   └── tsconfig.json
    └── pkg-c
        ├── index.ts
        ├── package.json
        └── tsconfig.json
```

The `"workspaces"` key in the root `package.json` lists the subdirectories to treat as workspaces. By convention, they live in a directory called `packages`.

```json package.json icon="file-json" theme={"theme":{"light":"github-light","dark":"dracula"}}
{
  "name": "my-project",
  "version": "1.0.0",
  "workspaces": ["packages/*"],
  "devDependencies": {
    "example-package-in-monorepo": "workspace:*"
  }
}
```

<Note>
  **Glob support** — Bun supports full glob syntax in `"workspaces"`, including negative patterns such as
  `!**/excluded/**`. See [supported glob patterns](/docs/runtime/glob#supported-glob-patterns).
</Note>

```json package.json icon="file-json" theme={"theme":{"light":"github-light","dark":"dracula"}}
{
  "name": "my-project",
  "version": "1.0.0",
  "workspaces": ["packages/**", "!packages/**/test/**", "!packages/**/template/**"]
}
```

Each workspace has its own `package.json`. To reference another package in the monorepo, use a semver range or the workspace protocol (for example `workspace:*`) as the version in your `package.json`.

```json packages/pkg-a/package.json icon="file-json" theme={"theme":{"light":"github-light","dark":"dracula"}}
{
  "name": "pkg-a",
  "version": "1.0.0",
  "dependencies": {
    "pkg-b": "workspace:*"
  }
}
```

`bun install` installs dependencies for all workspaces in the monorepo, de-duplicating packages if possible. To install dependencies for specific workspaces only, use the `--filter` flag.

```bash theme={"theme":{"light":"github-light","dark":"dracula"}}
# Install dependencies for all workspaces starting with `pkg-` except for `pkg-c`
bun install --filter "pkg-*" --filter "!pkg-c"

# Paths can also be used. This is equivalent to the command above.
bun install --filter "./packages/pkg-*" --filter "!pkg-c" # or --filter "!./packages/pkg-c"
```

When publishing, Bun replaces `workspace:` versions with the package's `package.json` version:

```
"workspace:*" -> "1.0.1"
"workspace:^" -> "^1.0.1"
"workspace:~" -> "~1.0.1"
```

A specific version takes precedence over the package's `package.json` version:

```
"workspace:1.0.2" -> "1.0.2" // Even if current version is 1.0.1
```

Workspaces have a few major benefits.

* **Split code into logical parts.** If one package relies on another, add it as a dependency in `package.json`. If package `b` depends on `a`, `bun install` installs your local `packages/a` directory into `node_modules` instead of downloading it from the npm registry.
* **Bun can de-duplicate dependencies.** If `a` and `b` share a common dependency, Bun *hoists* it to the root `node_modules` directory. This saves disk space and minimizes the "dependency hell" of multiple versions of a package installed at once.
* **Run scripts in multiple packages.** Use the [`--filter` flag](/docs/pm/filter) to run `package.json` scripts in several packages at once, or `--workspaces` to run scripts across all workspaces.

## Share versions with Catalogs

When many packages need the same dependency versions, define those versions once
in a catalog in the root `package.json` and reference them from your workspaces
with the `catalog:` protocol. Updating the catalog updates every package that
references it. See [Catalogs](/docs/pm/catalogs).

<Note>
  ⚡️ **Speed** — Installs are fast, even for big monorepos. Bun installs the [Remix](https://github.com/remix-run/remix) monorepo in about `500ms` on Linux.

  * 28x faster than `npm install`
  * 12x faster than `yarn install` (v1)
  * 8x faster than `pnpm install`

  <Image />
</Note>


# Benchmarking
Source: https://bun.com/docs/project/benchmarking

How to benchmark Bun

Bun is designed for speed. We profile and benchmark hot paths extensively. The source code for all of Bun's public benchmarks is in the [`/bench`](https://github.com/oven-sh/bun/tree/main/bench) directory of the Bun repo.

## Measuring time

To measure time precisely, Bun offers two runtime APIs:

1. The Web-standard [`performance.now()`](https://developer.mozilla.org/en-US/docs/Web/API/Performance/now) function
2. `Bun.nanoseconds()`, which is like `performance.now()` except it returns the time since the application started in nanoseconds. Use `performance.timeOrigin` to convert this to a Unix timestamp.

## Benchmarking tools

* For microbenchmarks, we recommend [`mitata`](https://github.com/evanwashere/mitata).
* For load testing, you *must use* an HTTP benchmarking tool that is at least as fast as `Bun.serve()`, or your results will be skewed. Some popular Node.js-based benchmarking tools like [`autocannon`](https://github.com/mcollina/autocannon) are not fast enough. We recommend one of the following:
  * [`bombardier`](https://github.com/codesenberg/bombardier)
  * [`oha`](https://github.com/hatoo/oha)
  * [`http_load_test`](https://github.com/uNetworking/uSockets/blob/master/examples/http_load_test.c)
* For benchmarking scripts or CLI commands, we recommend [`hyperfine`](https://github.com/sharkdp/hyperfine).

## Measuring memory usage

Bun has two heaps: one for the JavaScript runtime, and one for everything else.

### JavaScript heap stats

The `bun:jsc` module exposes a few functions for measuring memory usage:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
import { heapStats } from "bun:jsc";
console.log(heapStats());
```

<Accordion title="View example statistics">
  ```ts expandable icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
  {
    heapSize: 1657575,
    heapCapacity: 2872775,
    extraMemorySize: 598199,
    objectCount: 13790,
    protectedObjectCount: 62,
    globalObjectCount: 1,
    protectedGlobalObjectCount: 1,
    // A count of every object type in the heap
    objectTypeCounts: {
      CallbackObject: 25,
      FunctionExecutable: 2078,
      AsyncGeneratorFunction: 2,
      'RegExp String Iterator': 1,
      FunctionCodeBlock: 188,
      ModuleProgramExecutable: 13,
      String: 1,
      UnlinkedModuleProgramCodeBlock: 13,
      JSON: 1,
      AsyncGenerator: 1,
      Symbol: 1,
      GetterSetter: 68,
      ImportMeta: 10,
      DOMAttributeGetterSetter: 1,
      UnlinkedFunctionCodeBlock: 174,
      RegExp: 52,
      ModuleLoader: 1,
      Intl: 1,
      WeakMap: 4,
      Generator: 2,
      PropertyTable: 95,
      'Array Iterator': 1,
      JSLexicalEnvironment: 75,
      UnlinkedFunctionExecutable: 2067,
      WeakSet: 1,
      console: 1,
      Map: 23,
      SparseArrayValueMap: 14,
      StructureChain: 19,
      Set: 18,
      'String Iterator': 1,
      FunctionRareData: 3,
      JSGlobalLexicalEnvironment: 1,
      Object: 481,
      BigInt: 2,
      StructureRareData: 55,
      Array: 179,
      AbortController: 2,
      ModuleNamespaceObject: 11,
      ShadowRealm: 1,
      'Immutable Butterfly': 103,
      Primordials: 1,
      'Set Iterator': 1,
      JSGlobalProxy: 1,
      AsyncFromSyncIterator: 1,
      ModuleRecord: 13,
      FinalizationRegistry: 1,
      AsyncIterator: 1,
      InternalPromise: 22,
      Iterator: 1,
      CustomGetterSetter: 65,
      Promise: 19,
      WeakRef: 1,
      InternalPromisePrototype: 1,
      Function: 2381,
      AsyncFunction: 2,
      GlobalObject: 1,
      ArrayBuffer: 2,
      Boolean: 1,
      Math: 1,
      CallbackConstructor: 1,
      Error: 2,
      JSModuleEnvironment: 13,
      WebAssembly: 1,
      HashMapBucket: 300,
      Callee: 3,
      symbol: 37,
      string: 2484,
      Performance: 1,
      ModuleProgramCodeBlock: 12,
      JSSourceCode: 13,
      JSPropertyNameEnumerator: 3,
      NativeExecutable: 290,
      Number: 1,
      Structure: 1550,
      SymbolTable: 108,
      GeneratorFunction: 2,
      'Map Iterator': 1
    },
    protectedObjectTypeCounts: {
      CallbackConstructor: 1,
      BigInt: 1,
      RegExp: 2,
      GlobalObject: 1,
      UnlinkedModuleProgramCodeBlock: 13,
      HashMapBucket: 2,
      Structure: 41,
      JSPropertyNameEnumerator: 1
    }
  }
  ```
</Accordion>

JavaScript is a garbage-collected language, not reference counted. It's normal and correct for objects to not be freed immediately in all cases, though it's not normal for objects to never be freed.

To force garbage collection to run manually:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
Bun.gc(true); // synchronous
Bun.gc(false); // asynchronous
```

Heap snapshots show which objects are not being freed. Use `Bun.generateHeapSnapshot()` to take a heap snapshot, then view it with Safari or WebKit GTK developer tools. To generate a heap snapshot:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
import { generateHeapSnapshot } from "bun";

const snapshot = generateHeapSnapshot();
await Bun.write("heap.json", JSON.stringify(snapshot, null, 2));
```

To view the snapshot, open the `heap.json` file in Safari's Developer Tools (or WebKit GTK):

1. Open the Developer Tools
2. Click "Timeline"
3. Click "JavaScript Allocations" in the menu on the left. It might not be visible until you click the pencil icon to show all the timelines
4. Click "Import" and select your heap snapshot JSON

<Frame>
  <img alt="Importing a heap snapshot" />
</Frame>

Once imported, you should see something like this:

<Frame>
  <img alt="Viewing heap snapshot in Safari" />
</Frame>

> The [web debugger](/docs/runtime/debugger#inspect) timeline also tracks the memory usage of the running debug session.

### Native heap stats

Bun uses mimalloc for the other heap. To print a summary of non-JavaScript memory usage, call `Bun.unsafe.mimallocDump()`.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
Bun.unsafe.mimallocDump();
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
heap stats:    peak      total      freed    current       unit      count
  reserved:   64.0 MiB   64.0 MiB      0       64.0 MiB                        not all freed!
 committed:   64.0 MiB   64.0 MiB      0       64.0 MiB                        not all freed!
     reset:      0          0          0          0                            ok
   touched:  128.5 KiB  128.5 KiB    5.4 MiB   -5.3 MiB                        ok
  segments:      1          1          0          1                            not all freed!
-abandoned:      0          0          0          0                            ok
   -cached:      0          0          0          0                            ok
     pages:      0          0         53        -53                            ok
-abandoned:      0          0          0          0                            ok
 -extended:      0
 -noretire:      0
     mmaps:      0
   commits:      0
   threads:      0          0          0          0                            ok
  searches:     0.0 avg
numa nodes:       1
   elapsed:       0.068 s
   process: user: 0.061 s, system: 0.014 s, faults: 0, rss: 57.4 MiB, commit: 64.0 MiB
```

## CPU profiling

Profile JavaScript execution to identify performance bottlenecks with the `--cpu-prof` flag.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun --cpu-prof script.js
```

`--cpu-prof` writes a `.cpuprofile` file you can open in Chrome DevTools (Performance tab → Load profile) or VS Code's CPU profiler.

### Markdown output

Use `--cpu-prof-md` to generate a markdown CPU profile, which is grep-friendly and designed for LLM analysis:

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun --cpu-prof-md script.js
```

Combine `--cpu-prof` and `--cpu-prof-md` to generate both formats at once:

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun --cpu-prof --cpu-prof-md script.js
```

You can also pass the flag through the `BUN_OPTIONS` environment variable:

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
BUN_OPTIONS="--cpu-prof-md" bun script.js
```

### Options

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun --cpu-prof --cpu-prof-name my-profile.cpuprofile script.js
bun --cpu-prof --cpu-prof-dir ./profiles script.js
```

| Flag                         | Description                                                 |
| ---------------------------- | ----------------------------------------------------------- |
| `--cpu-prof`                 | Generate a `.cpuprofile` JSON file (Chrome DevTools format) |
| `--cpu-prof-md`              | Generate a markdown CPU profile (grep/LLM-friendly)         |
| `--cpu-prof-name <filename>` | Set output filename                                         |
| `--cpu-prof-dir <dir>`       | Set output directory                                        |

## Heap profiling

Write a heap profile on exit to analyze memory usage and find memory leaks.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun --heap-prof script.js
```

`--heap-prof` writes a full V8-format heap snapshot on exit, using Node.js's
diagnostic filename format
(`Heap.<yyyymmdd>.<hhmmss>.<pid>.<tid>.<seq>.heapprofile`). The extension
follows Node's `--heap-prof` filename contract. The content is the same as
`v8.writeHeapSnapshot()` /
`Bun.generateHeapSnapshot("v8")`. Load it in Chrome DevTools via
Memory tab → Load. Pick "All Files", or rename the file to `.heapsnapshot`.

### Markdown output

Use `--heap-prof-md` to generate a markdown heap profile for CLI analysis:

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun --heap-prof-md script.js
```

<Note>If you specify both `--heap-prof` and `--heap-prof-md`, Bun uses the markdown format.</Note>

### Options

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun --heap-prof --heap-prof-name my-profile.heapprofile script.js
bun --heap-prof --heap-prof-dir ./profiles script.js
```

| Flag                           | Description                                                                                                                                |
| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------ |
| `--heap-prof`                  | Write a `.heapprofile` file on exit                                                                                                        |
| `--heap-prof-md`               | Generate a markdown heap profile on exit                                                                                                   |
| `--heap-prof-name <filename>`  | Set output filename                                                                                                                        |
| `--heap-prof-dir <dir>`        | Set output directory                                                                                                                       |
| `--heap-prof-interval <bytes>` | Accepted for Node.js compatibility (the snapshot is taken once at exit; JavaScriptCore has no allocation sampling to apply an interval to) |


# Bindgen
Source: https://bun.com/docs/project/bindgen

Bindgen for Bun

<Note>This document is for maintainers and contributors to Bun, and describes internal implementation details.</Note>

The bindings generator scans for `*.bind.ts` files to find function and class
definitions, and generates glue code to interop between JavaScript and native
code.

There are other code generators and systems that achieve similar purposes;
the following will all eventually be phased out in favor of this one:

* "Classes generator", converting `*.classes.ts` for custom classes.
* "JS2Native", allowing ad-hoc calls from `src/js` to native code.

## Creating JS Functions in Rust

Given a file implementing a function, such as `add`:

```rust src/jsc/bindgen_test.rs theme={"theme":{"light":"github-light","dark":"dracula"}}
use crate::{JSGlobalObject, JsResult};
use crate::r#gen::bindgen_test as generated;

pub fn add(global: &JSGlobalObject, a: i32, b: i32) -> JsResult<i32> {
    match a.checked_add(b) {
        Some(v) => Ok(v),
        None => {
            // Binding functions can propagate out-of-memory and JS exceptions
            // directly; other failures (like this integer overflow) must be
            // converted into a thrown error. Remember to be descriptive.
            Err(global.throw(format_args!("Integer overflow while adding")))
        }
    }
}
```

Then describe the API schema using a `.bind.ts` file. The binding file goes
next to the Rust file.

```ts src/jsc/bindgen_test.bind.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { fn, t } from "bindgen";

export const add = fn({
  args: {
    global: t.globalObject,
    a: t.i32,
    b: t.i32.default(-1),
  },
  ret: t.i32,
});
```

This function declaration is equivalent to:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
/**
 * Throws if zero arguments are provided.
 * Wraps out of range numbers using modulo.
 */
declare function add(a: number, b: number = -1): number;
```

The code generator emits a C++ thunk that validates and coerces the JS
arguments, then calls the Rust implementation. On the Rust side bindgen emits
nothing: the matching module is hand-written in
`src/jsc/bindings/GeneratedBindings.rs` and is reachable as
`crate::r#gen::<basename>` (for `bindgen_test.bind.ts`, that's
`crate::r#gen::bindgen_test`). To construct a `JSFunction` wrapping the
native implementation, use `generated::create_add_callback(global)`:

```rust theme={"theme":{"light":"github-light","dark":"dracula"}}
use crate::r#gen::bindgen_test as generated;

let js_fn: JSValue = generated::create_add_callback(global);
```

In JS files in `src/js/`, `$bindgenFn("bindgen_test.bind.ts", "add")` returns
a handle to the implementation.

Exported bindgen functions are snake\_cased on the Rust side
(`requiredAndOptionalArg` → `required_and_optional_arg`). The hand-written
callback constructor follows the same convention
(`create_required_and_optional_arg_callback`).

## Strings

To receive a string, use [`t.DOMString`](https://webidl.spec.whatwg.org/#idl-DOMString), [`t.ByteString`](https://webidl.spec.whatwg.org/#idl-ByteString), or [`t.USVString`](https://webidl.spec.whatwg.org/#idl-USVString). These map directly to their WebIDL counterparts and have slightly different conversion logic. Bindgen passes `bun_core::String` to native code in all cases.

When in doubt, use DOMString.

`t.UTF8String` works in place of `t.DOMString`, but eagerly converts to UTF-8.
The native callback receives a `&[u8]` slice (WTF-8 data) that is
freed after the function returns.

TLDRs from the WebIDL spec:

* ByteString can only contain valid latin1 characters. It is not safe to assume `bun_core::String` is already in 8-bit format, but it is extremely likely.
* USVString does not contain invalid surrogate pairs, so its text can be represented correctly in UTF-8.
* DOMString is the loosest but also the most recommended strategy.

## Function Variants

The `variants` key declares multiple variants (also known as overloads) of a function.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
import { fn, t } from "bindgen";

export const action = fn({
  variants: [
    {
      args: {
        a: t.i32,
      },
      ret: t.i32,
    },
    {
      args: {
        a: t.DOMString,
      },
      ret: t.DOMString,
    },
  ],
});
```

Each variant gets a numbered Rust function:

```rust theme={"theme":{"light":"github-light","dark":"dracula"}}
pub fn action1(a: i32) -> i32 {
    a
}

pub fn action2(a: bun_core::String) -> bun_core::String {
    a
}
```

## `t.dictionary`

A `dictionary` describes a JavaScript object, typically a function input. For function outputs, prefer a class type so you can add methods and support destructuring.

## Enumerations

`t.stringEnum` creates a [WebIDL enumeration](https://webidl.spec.whatwg.org/#idl-enums) and generates a new enum type for it.

An example of `stringEnum` from `fmt_jsc.bind.ts` / `bun:internal-for-testing`:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
export const Formatter = t.stringEnum("highlight-javascript", "highlight-javascript-redacted", "escape-powershell");

export const fmtString = fn({
  args: {
    global: t.globalObject,
    code: t.UTF8String,
    formatter: Formatter,
  },
  ret: t.DOMString,
});
```

On the Rust side, the enum is mirrored as a `#[repr(u8)]` enum. Bindgen
**sorts `t.stringEnum` values alphabetically** before emitting the C++
`enum class`, so discriminants must match the generated header's order, not
the `.bind.ts` declaration order:

```rust theme={"theme":{"light":"github-light","dark":"dracula"}}
#[repr(u8)]
#[derive(Copy, Clone, Eq, PartialEq)]
pub enum Formatter {
    EscapePowershell = 0,
    HighlightJavascript = 1,
    HighlightJavascriptRedacted = 2,
}

pub fn fmt_string(
    global: &JSGlobalObject,
    code: &[u8],
    formatter_id: Formatter,
) -> JsResult<bun_core::String> {
    // ...
}
```

WebIDL strongly encourages kebab-case for enumeration values, to be consistent with existing Web APIs.

## `t.oneOf`

A `oneOf` is a union of two or more types. It is represented as a Rust
`enum` with one variant per member type.

## Attributes

You can chain attributes onto `t.*` types. On all types:

* `.required`, in dictionary parameters only
* `.optional`, in function arguments only
* `.default(T)`

When a value is `.optional`, it is lowered to a Rust `Option<T>`:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
export const requiredAndOptionalArg = fn({
  args: {
    a: t.boolean,
    b: t.usize.optional,
    c: t.i32.enforceRange(0, 100).default(42),
    d: t.u8.optional,
  },
  ret: t.i32,
});
```

```rust theme={"theme":{"light":"github-light","dark":"dracula"}}
pub fn required_and_optional_arg(a: bool, b: Option<usize>, c: i32, d: Option<u8>) -> i32 {
    // ...
}
```

Depending on the type, more attributes are available. See the type definitions
in auto-complete for details. You can apply only one of these three attributes,
and you must apply it last.

### Integer Attributes

Integer types take `clamp` or `enforceRange` to customize overflow behavior:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
import { fn, t } from "bindgen";

export const add = fn({
  args: {
    global: t.globalObject,
    // enforce in i32 range
    a: t.i32.enforceRange(),
    // clamp to u16 range
    b: t.u16,
    // enforce in arbitrary range, with a default if not provided
    c: t.i32.enforceRange(0, 1000).default(5),
    // clamp to arbitrary range, or None
    d: t.u16.clamp(0, 10).optional,
  },
  ret: t.i32,
});
```

Node.js validator functions such as `validateInteger` and `validateNumber`
are also available. Use these when implementing Node.js APIs so the error
messages match Node exactly.

Unlike `enforceRange`, which is taken from WebIDL, the `validate*` functions
are much stricter about the input they accept. For example, Node's numerical
validator checks `typeof value === 'number'`, while WebIDL uses `ToNumber` for
lossy conversion.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
import { fn, t } from "bindgen";

export const add = fn({
  args: {
    global: t.globalObject,
    // throw if not given a number
    a: t.f64.validateNumber(),
    // valid in i32 range
    b: t.i32.validateInt32(),
    // f64 within safe integer range
    c: t.f64.validateInteger(),
    // f64 in given range
    d: t.f64.validateNumber(-10000, 10000),
  },
  ret: t.i32,
});
```

## Callbacks

TODO

## Classes

TODO


# Building Windows
Source: https://bun.com/docs/project/building-windows

Building Bun on Windows

Use [PowerShell 7 (`pwsh.exe`)](https://learn.microsoft.com/en-us/powershell/scripting/install/installing-powershell-on-windows?view=powershell-7.4) instead of the default `powershell.exe`. If you run into problems, ask in the [#contributing channel on our Discord](http://bun.com/discord).

## Prerequisites

### Enable Scripts

By default, running unverified scripts is blocked.

```ps1 theme={"theme":{"light":"github-light","dark":"dracula"}}
Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy Unrestricted
```

### System Dependencies

Bun v1.1 or later. The build uses Bun to run its own code generators.

```ps1 theme={"theme":{"light":"github-light","dark":"dracula"}}
irm bun.sh/install.ps1 | iex
```

[Visual Studio](https://visualstudio.microsoft.com) with the "Desktop Development with C++" workload. While installing, also install Git if Git for Windows is not already installed.

Install Visual Studio with the graphical wizard or through WinGet:

```ps1 theme={"theme":{"light":"github-light","dark":"dracula"}}
winget install "Visual Studio Community 2022" --override "--add Microsoft.VisualStudio.Workload.NativeDesktop Microsoft.VisualStudio.Component.Git " -s msstore
```

After Visual Studio, you need the following:

* LLVM 21.1.8
* Go
* Rust (via rustup)
* NASM
* Perl
* Ruby
* Node.js

<Note>rustup installs the Rust nightly toolchain pinned in `rust-toolchain.toml` on the first build.</Note>

Use [Scoop](https://scoop.sh) to install these remaining tools.

```ps1 Scoop (x64) theme={"theme":{"light":"github-light","dark":"dracula"}}
irm https://get.scoop.sh | iex
scoop install nodejs-lts go rustup nasm ruby perl ccache
# scoop seems to be buggy if you install llvm and the rest at the same time
scoop install llvm@21.1.8
```

For Windows ARM64, download LLVM 21.1.8 directly from GitHub releases (first version with ARM64 Windows builds):

```ps1 ARM64 theme={"theme":{"light":"github-light","dark":"dracula"}}
# Download and install LLVM for ARM64
Invoke-WebRequest -Uri "https://github.com/llvm/llvm-project/releases/download/llvmorg-21.1.8/LLVM-21.1.8-woa64.exe" -OutFile "$env:TEMP\LLVM-21.1.8-woa64.exe"
Start-Process -FilePath "$env:TEMP\LLVM-21.1.8-woa64.exe" -ArgumentList "/S" -Wait
```

<Note>
  Do not install these with WinGet or another package manager: you will likely get Strawberry Perl instead of a more
  minimal installation of Perl. Strawberry Perl adds many other utilities to `$Env:PATH` that conflict with MSVC and
  break the build.
</Note>

To build WebKit locally (optional, x64 only), install these packages:

```ps1 Scoop theme={"theme":{"light":"github-light","dark":"dracula"}}
scoop install make cygwin python
```

<Note>ARM64 builds do not need Cygwin because WebKit is provided as a pre-built binary.</Note>

From here on out, **use a PowerShell terminal with `.\scripts\vs-shell.ps1` sourced**. Load the script by running it:

```ps1 theme={"theme":{"light":"github-light","dark":"dracula"}}
.\scripts\vs-shell.ps1
```

To verify, check for an MSVC-only command such as `mt.exe`:

```ps1 theme={"theme":{"light":"github-light","dark":"dracula"}}
Get-Command mt
```

<Note>
  Avoid installing `ninja` / `cmake` into your global path: you may end up building Bun without `.\scripts\vs-shell.ps1`
  sourced.
</Note>

## Building

```ps1 theme={"theme":{"light":"github-light","dark":"dracula"}}
bun run build

# after the initial `bun run build` you can use the following to build
ninja -Cbuild/debug
```

A successful build writes `bun-debug.exe` to the `build/debug` folder.

```ps1 theme={"theme":{"light":"github-light","dark":"dracula"}}
.\build\debug\bun-debug.exe --revision
```

Add this folder to `$Env:PATH`: open the Start menu, type "Path", and use the environment variables menu to add `C:\.....\bun\build\debug` to the user environment variable `PATH`. Then restart your editor (if it still does not pick up the change, log out and log back in).

## Extra paths

* The build extracts WebKit to `$Env:BUN_INSTALL\build-cache\webkit-<version>-debug` (`webkit-<version>-arm64-debug` on ARM64); `BUN_INSTALL` defaults to `~\.bun`

## Tests

Run the test suite with `bun-debug test <path>` or with the wrapper script `bun run test <path>`. The `bun run test` command runs every test file in a separate instance of `bun-debug.exe`, so a crash in the test runner does not stop the entire suite.

```ps1 theme={"theme":{"light":"github-light","dark":"dracula"}}
# Run the entire test suite with reporter
# the package.json script "test" uses "build/debug/bun-debug.exe" by default
bun run test

# Run an individual test file:
bun-debug test node\fs
bun-debug test "C:\bun\test\js\bun\resolve\import-meta.test.js"
```

## Troubleshooting

### .rc file fails to build

`llvm-rc.exe` is odd; don't use it. Use `rc.exe` instead: make sure you are in a Visual Studio dev terminal, and check `rc /?` to confirm it is `Microsoft Resource Compiler`.

### failed to write output 'bun-debug.exe': permission denied

You cannot overwrite `bun-debug.exe` while it is open. You likely have a running instance, maybe in the VS Code debugger.

## Cross-compiling from Linux

You can also build Windows binaries (both x64 and arm64) on a Linux host. The build uses the host LLVM's `clang-cl`, `lld-link`, `llvm-lib` and `llvm-rc` (part of every LLVM distribution). For headers and import libraries, the build also uses an "xwin splat" of the MSVC CRT/STL and Windows SDK.

### Prerequisites

1. The same LLVM version a native build uses (see `scripts/bootstrap.sh` `llvm_version_exact`), installed so that `clang-cl`, `lld-link`, `llvm-lib` and `llvm-rc` are available. On Debian/Ubuntu, `apt.llvm.org` packages provide all of them.
2. `nasm` (only needed for Windows x64; BoringSSL's x64 assembly is NASM syntax).
3. Rust std for the Windows targets (`rust-toolchain.toml` lists them; `rustup target add x86_64-pc-windows-msvc aarch64-pc-windows-msvc` if missing).
4. A Windows sysroot: an [xwin](https://github.com/Jake-Shadle/xwin) splat of the MSVC CRT, Windows SDK, and ATL laid out like a Visual Studio install. Downloading these components means accepting Microsoft's license terms for them.

```bash theme={"theme":{"light":"github-light","dark":"dracula"}}
cargo install xwin # or download a release binary
xwin --accept-license --arch x86_64,aarch64 --sdk-version 10.0.26100 --crt-version 14.44.17.14 --include-atl splat \
  --use-winsysroot-style --preserve-ms-arch-notation --include-debug-libs \
  --output /opt/winsysroot
# clang-cl/lld-link look up SDK paths as "Include"/"Lib"; the splat writes
# them lowercase, so alias both spellings (needs the same privileges as the
# splat — configure creates these itself when the directory is writable).
ln -s include "/opt/winsysroot/Windows Kits/10/Include"
ln -s lib "/opt/winsysroot/Windows Kits/10/Lib"
```

The build looks for the sysroot at `/opt/winsysroot` (or `/opt/xwin`) automatically. If the sysroot is elsewhere, set `WINDOWS_SYSROOT=<path>` or pass `--winsysroot=<path>`. A user-writable path also lets configure manage the aliases for you. Configure validates the splat at the start of every cross build. CI agents bake the same splat into their images (`.buildkite/Dockerfile`, `scripts/bootstrap.sh`); when an agent doesn't have one, the build fetches it into its cache dir at configure time.

### Building

```bash theme={"theme":{"light":"github-light","dark":"dracula"}}
# Debug builds
bun run build --profile=windows-x64
bun run build --profile=windows-arm64

# Release builds
bun run build --profile=windows-x64-release
bun run build --profile=windows-arm64-release
```

Output lands in `build/debug-windows-x64/bun-debug.exe`, `build/release-windows-aarch64/bun-profile.exe` + `bun.exe`, and so on. Equivalent raw flags: `bun run build --os=windows --arch=aarch64`.

The build does not run cross-compiled executables on the host (it skips the `--revision` smoke test), so test them on a Windows machine or under Wine.

### LTO

x64 release cross builds support ThinLTO with cross-language (Rust↔C++) LTO. It's opt-in:

```bash theme={"theme":{"light":"github-light","dark":"dracula"}}
bun run build --profile=windows-x64-release --lto=on
```

`--lto=on` does the following:

* compiles Bun's C/C++ with `-flto=thin`
* makes rustc emit LLVM bitcode (`-Clinker-plugin-lto`)
* pulls the `bun-webkit-windows-amd64-lto` ThinLTO prebuilt
* links everything with rustc's bundled `lld-link` (its LLVM is new enough to read both compilers' bitcode)

There is no LTO for arm64, because there is no `-lto` WebKit prebuilt: LLVM's CodeView emitter can't handle ARM64 NEON tuple registers during LTO codegen. There is also no LTO for `--baseline`.


# Contributing
Source: https://bun.com/docs/project/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](/docs/project/building-windows).

## Using Nix (Alternative)

The repository includes a Nix flake as an alternative to installing dependencies manually:

```bash theme={"theme":{"light":"github-light","dark":"dracula"}}
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:

<CodeGroup>
  ```bash macOS (Homebrew) theme={"theme":{"light":"github-light","dark":"dracula"}}
  brew install automake ccache cmake coreutils gnu-sed go icu4c libiconv libtool ninja pkg-config rustup-init ruby
  ```

  ```bash Ubuntu/Debian theme={"theme":{"light":"github-light","dark":"dracula"}}
  sudo apt install curl wget lsb-release software-properties-common cmake git golang libtool ninja-build pkg-config ruby-full xz-utils
  ```

  ```bash Arch theme={"theme":{"light":"github-light","dark":"dracula"}}
  sudo pacman -S base-devel cmake git go libiconv libtool make ninja pkg-config python rustup sed unzip ruby
  ```

  ```bash Fedora theme={"theme":{"light":"github-light","dark":"dracula"}}
  sudo dnf install clang21 llvm21 lld21 cmake git golang libtool ninja-build pkg-config ruby libatomic-static libstdc++-static sed unzip which libicu-devel 'perl(Math::BigInt)'
  ```

  ```bash openSUSE Tumbleweed theme={"theme":{"light":"github-light","dark":"dracula"}}
  sudo zypper install go cmake ninja automake git icu rustup
  ```
</CodeGroup>

Bun is written in Rust and requires a specific nightly toolchain (pinned in `rust-toolchain.toml`). Install Rust with [rustup](https://rustup.rs) rather than your distro's `rust`/`cargo` packages — the build scripts use rustup to automatically install and update the pinned nightly:

```bash theme={"theme":{"light":"github-light","dark":"dracula"}}
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.

<CodeGroup>
  ```bash Native theme={"theme":{"light":"github-light","dark":"dracula"}}
  curl -fsSL https://bun.com/install | bash
  ```

  ```bash npm theme={"theme":{"light":"github-light","dark":"dracula"}}
  npm install -g bun
  ```

  ```bash Homebrew theme={"theme":{"light":"github-light","dark":"dracula"}}
  brew tap oven-sh/bun
  brew install bun
  ```
</CodeGroup>

### Optional: Install `ccache`

`ccache` caches compilation artifacts, which speeds up rebuilds:

```bash theme={"theme":{"light":"github-light","dark":"dracula"}}
# 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:

<CodeGroup>
  ```bash macOS (Homebrew) theme={"theme":{"light":"github-light","dark":"dracula"}}
  brew install llvm@21
  ```

  ```bash Ubuntu/Debian theme={"theme":{"light":"github-light","dark":"dracula"}}
  # 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 all
  ```

  ```bash Arch theme={"theme":{"light":"github-light","dark":"dracula"}}
  sudo pacman -S llvm clang lld
  ```

  ```bash Fedora theme={"theme":{"light":"github-light","dark":"dracula"}}
  sudo dnf install llvm clang lld-devel
  ```

  ```bash openSUSE Tumbleweed theme={"theme":{"light":"github-light","dark":"dracula"}}
  sudo zypper install clang21 lld21 llvm21
  ```
</CodeGroup>

If none of these work, install it [manually](https://github.com/llvm/llvm-project/releases/tag/llvmorg-21.1.8).

Make sure Clang/LLVM 21 is in your path:

```bash theme={"theme":{"light":"github-light","dark":"dracula"}}
which clang-21
```

If not, add it manually:

<CodeGroup>
  ```bash macOS (Homebrew) theme={"theme":{"light":"github-light","dark":"dracula"}}
  # 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"
  ```

  ```bash Arch theme={"theme":{"light":"github-light","dark":"dracula"}}
  # use fish_add_path if you're using fish
  export PATH="$PATH:/usr/lib/llvm21/bin"
  ```
</CodeGroup>

<Warning>
  ⚠️ On Ubuntu \<= 20.04, you may need to install the C++ standard library separately. See the [troubleshooting section](#span-file-not-found-on-ubuntu).
</Warning>

## Building Bun

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

```bash theme={"theme":{"light":"github-light","dark":"dracula"}}
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:

```bash theme={"theme":{"light":"github-light","dark":"dracula"}}
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:

```sh theme={"theme":{"light":"github-light","dark":"dracula"}}
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.

```sh theme={"theme":{"light":"github-light","dark":"dracula"}}
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:

```bash theme={"theme":{"light":"github-light","dark":"dracula"}}
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:

```sh theme={"theme":{"light":"github-light","dark":"dracula"}}
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:

```sh theme={"theme":{"light":"github-light","dark":"dracula"}}
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](https://github.com/buildkite/cli) (`brew install buildkite/buildkite/bk`) and set `BUILDKITE_API_TOKEN` to a read-scoped [API token](https://buildkite.com/user/api-access-tokens). The repo includes a `.bk.yaml` so `bk` commands default to the `bun` pipeline.

```sh theme={"theme":{"light":"github-light","dark":"dracula"}}
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](https://en.wikipedia.org/wiki/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:

```bash theme={"theme":{"light":"github-light","dark":"dracula"}}
bun run build:asan
```

CI runs the test suite with at least one target built with AddressSanitizer.

## Building WebKit locally + Debug mode of JSC

WebKit is not cloned by default (to save time and disk space). To clone and build WebKit locally, run:

```bash theme={"theme":{"light":"github-light","dark":"dracula"}}
# Clone WebKit into ./vendor/WebKit
git clone https://github.com/oven-sh/WebKit vendor/WebKit

# Check out the version pinned in WEBKIT_VERSION in scripts/build/deps/webkit.ts
# (a commit sha or an autobuild-* release tag; this handles both)
bun sync-webkit-source

# Build bun with the local JSC build — this automatically configures and builds JSC
bun 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](https://github.com/oven-sh/WebKit), 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

<Warning>
  ⚠️ These instructions are specific to Ubuntu. The same issues are unlikely on other Linux distributions.
</Warning>

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:

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
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:

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
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:

```bash theme={"theme":{"light":"github-light","dark":"dracula"}}
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:

```bash theme={"theme":{"light":"github-light","dark":"dracula"}}
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:

```bash theme={"theme":{"light":"github-light","dark":"dracula"}}
brew install pkg-config
```

### macOS `library not found for -lSystem`

If you see this error when compiling, run:

```bash theme={"theme":{"light":"github-light","dark":"dracula"}}
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:

```bash theme={"theme":{"light":"github-light","dark":"dracula"}}
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](https://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](https://nextjs.org/docs/community/contribution-guide#voice):

* 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`](/docs/pm/cli/install)", rather than "see here".
* Run every code example before you publish it, and check option names and defaults against the implementation on `main`.


# License
Source: https://bun.com/docs/project/license

License for Bun

Bun itself is MIT-licensed.

## JavaScriptCore

Bun statically links JavaScriptCore (and WebKit), which is LGPL-2 licensed. WebCore files from WebKit are also licensed under LGPL-2. Per LGPL-2:

> (1) If you statically link against an LGPL'd library, you must also provide your application in an object (not necessarily source) format, so that a user has the opportunity to modify the library and relink the application.

Bun's patched version of WebKit lives at [https://github.com/oven-sh/webkit](https://github.com/oven-sh/webkit). To relink Bun with changes:

* `git clone https://github.com/oven-sh/WebKit vendor/WebKit`
* `bun sync-webkit-source` (checks out the version pinned in `WEBKIT_VERSION` in `scripts/build/deps/webkit.ts`)
* `bun run build:local`

`bun run build:local` compiles JavaScriptCore, compiles Bun's `.cpp` bindings for JavaScriptCore (the object files that use JavaScriptCore), and outputs a new `bun` binary with your changes.

## Linked libraries

Bun statically links these libraries:

| Library                                                                                                                                          | License                                                                                      |
| ------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------- |
| [`boringssl`](https://boringssl.googlesource.com/boringssl/)                                                                                     | [several licenses](https://boringssl.googlesource.com/boringssl/+/refs/heads/master/LICENSE) |
| [`brotli`](https://github.com/google/brotli)                                                                                                     | MIT                                                                                          |
| [`libarchive`](https://github.com/libarchive/libarchive)                                                                                         | [several licenses](https://github.com/libarchive/libarchive/blob/master/COPYING)             |
| [`lol-html`](https://github.com/cloudflare/lol-html/tree/master/c-api)                                                                           | BSD 3-Clause                                                                                 |
| [`ls-hpack`](https://github.com/litespeedtech/ls-hpack)                                                                                          | MIT                                                                                          |
| [`ls-qpack`](https://github.com/litespeedtech/ls-qpack)                                                                                          | MIT                                                                                          |
| [`lsquic`](https://github.com/litespeedtech/lsquic)                                                                                              | MIT (portions derived from Chromium proto-quic, BSD 3-Clause)                                |
| [`mimalloc`](https://github.com/microsoft/mimalloc)                                                                                              | MIT                                                                                          |
| [`picohttp`](https://github.com/h2o/picohttpparser)                                                                                              | dual-licensed under the Perl License or the MIT License                                      |
| [`zstd`](https://github.com/facebook/zstd)                                                                                                       | dual-licensed under the BSD License or GPLv2 license                                         |
| [`simdutf`](https://github.com/simdutf/simdutf)                                                                                                  | Apache 2.0                                                                                   |
| [`tinycc`](https://github.com/tinycc/tinycc)                                                                                                     | LGPL v2.1                                                                                    |
| [`uSockets`](https://github.com/uNetworking/uSockets)                                                                                            | Apache 2.0                                                                                   |
| [`zlib-ng`](https://github.com/zlib-ng/zlib-ng)                                                                                                  | zlib                                                                                         |
| [`c-ares`](https://github.com/c-ares/c-ares)                                                                                                     | MIT licensed                                                                                 |
| [`libicu`](https://github.com/unicode-org/icu) 78                                                                                                | [ICU license](https://github.com/unicode-org/icu/blob/main/icu4c/LICENSE)                    |
| [`libbase64`](https://github.com/aklomp/base64/blob/master/LICENSE)                                                                              | BSD 2-Clause                                                                                 |
| [`libuv`](https://github.com/libuv/libuv) (on Windows)                                                                                           | MIT                                                                                          |
| [`libdeflate`](https://github.com/ebiggers/libdeflate)                                                                                           | MIT                                                                                          |
| [`libjpeg-turbo`](https://github.com/libjpeg-turbo/libjpeg-turbo)                                                                                | BSD 3-Clause / IJG / zlib                                                                    |
| [`libspng`](https://github.com/randy408/libspng)                                                                                                 | BSD 2-Clause                                                                                 |
| [`libwebp`](https://github.com/webmproject/libwebp)                                                                                              | BSD 3-Clause                                                                                 |
| [`highway`](https://github.com/google/highway)                                                                                                   | Apache 2.0                                                                                   |
| [`HdrHistogram_c`](https://github.com/HdrHistogram/HdrHistogram_c)                                                                               | dual-licensed under CC0 1.0 or the BSD 2-Clause License                                      |
| [`sqlite`](https://sqlite.org) (on Linux and Windows)                                                                                            | [public domain](https://sqlite.org/copyright.html)                                           |
| A fork of [`uWebsockets`](https://github.com/jarred-sumner/uwebsockets)                                                                          | Apache 2.0 licensed                                                                          |
| Parts of [Tigerbeetle's IO code](https://github.com/tigerbeetle/tigerbeetle/blob/532c8b70b9142c17e07737ab6d3da68d7500cbca/src/io/windows.zig#L1) | Apache 2.0 licensed                                                                          |

## Polyfills

For compatibility, Bun embeds the following packages into its binary and injects them if imported.

| Package                                                                  | License |
| ------------------------------------------------------------------------ | ------- |
| [`acorn`](https://github.com/acornjs/acorn)                              | MIT     |
| [`acorn-walk`](https://github.com/acornjs/acorn)                         | MIT     |
| [`assert`](https://npmjs.com/package/assert)                             | MIT     |
| [`browserify-zlib`](https://npmjs.com/package/browserify-zlib)           | MIT     |
| [`buffer`](https://npmjs.com/package/buffer)                             | MIT     |
| [`constants-browserify`](https://npmjs.com/package/constants-browserify) | MIT     |
| [`crypto-browserify`](https://npmjs.com/package/crypto-browserify)       | MIT     |
| [`domain-browser`](https://npmjs.com/package/domain-browser)             | MIT     |
| [`events`](https://npmjs.com/package/events)                             | MIT     |
| [`https-browserify`](https://npmjs.com/package/https-browserify)         | MIT     |
| [`os-browserify`](https://npmjs.com/package/os-browserify)               | MIT     |
| [`path-browserify`](https://npmjs.com/package/path-browserify)           | MIT     |
| [`process`](https://npmjs.com/package/process)                           | MIT     |
| [`punycode`](https://npmjs.com/package/punycode)                         | MIT     |
| [`querystring-es3`](https://npmjs.com/package/querystring-es3)           | MIT     |
| [`stream-browserify`](https://npmjs.com/package/stream-browserify)       | MIT     |
| [`stream-http`](https://npmjs.com/package/stream-http)                   | MIT     |
| [`string_decoder`](https://npmjs.com/package/string_decoder)             | MIT     |
| [`timers-browserify`](https://npmjs.com/package/timers-browserify)       | MIT     |
| [`tty-browserify`](https://npmjs.com/package/tty-browserify)             | MIT     |
| [`url`](https://npmjs.com/package/url)                                   | MIT     |
| [`util`](https://npmjs.com/package/util)                                 | MIT     |
| [`vm-browserify`](https://npmjs.com/package/vm-browserify)               | MIT     |

## Additional credits

* Bun's JS transpiler and Node.js module resolver source code is a port of [@evanw](https://github.com/evanw)’s [esbuild](https://github.com/evanw/esbuild) project.
* Bun's CSS parser source code is derived from the [Lightning CSS](https://github.com/parcel-bundler/lightningcss) and [Servo](https://github.com/servo/servo) projects.
* Credit to [@kipply](https://github.com/kipply) for the name "Bun".


# Roadmap
Source: https://bun.com/docs/project/roadmap

Bun's roadmap and long-term plans

Bun is a project with a large scope and is still in its early days. Long-term, Bun aims to provide an all-in-one toolkit to replace the complex, fragmented toolchains common today: Node.js, Jest, Webpack, esbuild, Babel, yarn, PostCSS, and others.

See [Bun's Roadmap](https://github.com/oven-sh/bun/issues/159) on GitHub for the project's long-term plans and priorities.


# Quickstart
Source: https://bun.com/docs/quickstart

Build your first app with Bun

## Overview

Build a minimal HTTP server with `Bun.serve`, run it locally, then evolve it by installing a package.

<Info>Prerequisites: Bun installed and available on your `PATH`. See [installation](/docs/installation) for setup.</Info>

***

<Steps>
  <Step title="Step 1">
    Initialize a new project with `bun init`.

    ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
    bun init my-app
    ```

    `bun init` prompts you to pick a template: `Blank`, `React`, or `Library`. For this guide, pick `Blank`.

    ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
    bun init my-app
    ```

    ```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
    ✓ Select a project template: Blank

    + .gitignore
    + CLAUDE.md
    + .cursor/rules/use-bun-instead-of-node-vite-npm-pnpm.mdc -> CLAUDE.md
    + index.ts
    + tsconfig.json (for editor autocomplete)
    + README.md
    ```

    The new `my-app` directory contains a basic Bun app.
  </Step>

  <Step title="Step 2">
    Run `index.ts` with `bun run`.

    ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
    cd my-app
    bun run index.ts
    ```

    ```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
    Hello via Bun!
    ```
  </Step>

  <Step title="Step 3">
    Replace the contents of `index.ts` with the following code:

    ```ts index.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
    const server = Bun.serve({
      port: 3000,
      routes: {
        "/": () => new Response('Bun!'),
      }
    });

    console.log(`Listening on ${server.url}`);
    ```

    Run `index.ts` again.

    ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
    bun run index.ts
    ```

    ```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
    Listening on http://localhost:3000/
    ```

    Visit [`http://localhost:3000`](http://localhost:3000) to test the server. You should see a page that says `"Bun!"`.

    <Accordion title="Seeing TypeScript errors on Bun?">
      `bun init` installs Bun's TypeScript declarations and configures your `tsconfig.json`. If you're trying out Bun in an existing project, you may see a type error on the `Bun` global.

      To fix this, first install `@types/bun` as a dev dependency.

      ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
      bun add -d @types/bun
      ```

      Then add the following to your `compilerOptions` in `tsconfig.json`:

      ```json tsconfig.json icon="file-json" theme={"theme":{"light":"github-light","dark":"dracula"}}
      {
        "compilerOptions": {
          "lib": ["ESNext"],
          "target": "ESNext",
          "module": "Preserve",
          "moduleDetection": "force",
          "moduleResolution": "bundler",
          "allowImportingTsExtensions": true,
          "verbatimModuleSyntax": true,
          "noEmit": true
        }
      }
      ```
    </Accordion>
  </Step>

  <Step title="Step 4">
    Install the `figlet` package and its type declarations. Figlet is a utility for converting strings into ASCII art.

    ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
    bun add figlet
    bun add -d @types/figlet # TypeScript users only
    ```

    Update `index.ts` to use `figlet` in `routes`.

    ```ts index.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
    import figlet from 'figlet'; // [!code ++]

    const server = Bun.serve({
      port: 3000,
      routes: {
        "/": () => new Response('Bun!'),
        "/figlet": () => { // [!code ++]
          const body = figlet.textSync('Bun!'); // [!code ++]
          return new Response(body); // [!code ++]
        } // [!code ++]
      }
    });

    console.log(`Listening on ${server.url}`);
    ```

    Run `index.ts` again.

    ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
    bun run index.ts
    ```

    ```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
    Listening on http://localhost:3000/
    ```

    Visit [`http://localhost:3000/figlet`](http://localhost:3000/figlet) to test the server. You should see a page that says `"Bun!"` in ASCII art.

    ```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
    ____              _
    | __ ) _   _ _ __ | |
    |  _ \| | | | '_ \| |
    | |_) | |_| | | | |_|
    |____/ \__,_|_| |_(_)
    ```
  </Step>

  <Step title="Step 5">
    Now add some HTML. Create a new file called `index.html`:

    ```html index.html icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Bun</title>
      </head>
      <body>
        <h1>Bun!</h1>
      </body>
    </html>
    ```

    Then, import this file in `index.ts` and serve it from the root `/` route.

    ```ts index.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
    import figlet from 'figlet';
    import index from './index.html'; // [!code ++]

    const server = Bun.serve({
      port: 3000,
      routes: {
        "/": index, // [!code ++]
        "/figlet": () => {
          const body = figlet.textSync('Bun!');
          return new Response(body);
        }
      }
    });

    console.log(`Listening on ${server.url}`);
    ```

    Run `index.ts` again.

    ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
    bun run index.ts
    ```

    ```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
    Listening on http://localhost:3000/
    ```

    Visit [`http://localhost:3000`](http://localhost:3000) to test the server. You should see the static HTML page.
  </Step>
</Steps>

You've built an HTTP server with Bun and installed a package.

***

## Run a script

Bun can also execute `"scripts"` from your `package.json`. Add the following script:

```json package.json icon="file-json" theme={"theme":{"light":"github-light","dark":"dracula"}}
{
  "name": "my-app",
  "module": "index.ts",
  "type": "module",
  "private": true,
  "scripts": { // [!code ++]
    "start": "bun run index.ts" // [!code ++]
  }, // [!code ++]
  "devDependencies": {
    "@types/bun": "latest"
  },
  "peerDependencies": {
    "typescript": "^6"
  }
}
```

Then run it with `bun run start`.

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun run start
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
Listening on http://localhost:3000/
```

<Note>⚡️ **Performance** — `bun run` is roughly 28x faster than `npm run` (6ms vs 170ms of overhead).</Note>


# Archive
Source: https://bun.com/docs/runtime/archive

Create and extract tar archives with Bun's fast native implementation

`Bun.Archive` is Bun's native API for tar archives. It creates archives from in-memory data, extracts archives to disk, and reads archive contents without extraction.

## Quickstart

**Create an archive from files:**

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const archive = new Bun.Archive({
  "hello.txt": "Hello, World!",
  "data.json": JSON.stringify({ foo: "bar" }),
  "nested/file.txt": "Nested content",
});

// Write to disk
await Bun.write("bundle.tar", archive);
```

**Extract an archive:**

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const tarball = await Bun.file("package.tar.gz").bytes();
const archive = new Bun.Archive(tarball);
const entryCount = await archive.extract("./output");
console.log(`Extracted ${entryCount} entries`);
```

**Read archive contents without extracting:**

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const tarball = await Bun.file("package.tar.gz").bytes();
const archive = new Bun.Archive(tarball);
const files = await archive.files();

for (const [path, file] of files) {
  console.log(`${path}: ${await file.text()}`);
}
```

## Creating Archives

Use `new Bun.Archive()` to create an archive from an object where keys are file paths and values are file contents. By default, archives are uncompressed:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
// Creates an uncompressed tar archive (default)
const archive = new Bun.Archive({
  "README.md": "# My Project",
  "src/index.ts": "console.log('Hello');",
  "package.json": JSON.stringify({ name: "my-project" }),
});
```

File contents can be:

* **Strings** - Text content
* **Blobs** - Binary data
* **ArrayBufferViews** (such as `Uint8Array`) - Raw bytes
* **ArrayBuffers** - Raw binary data

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const data = "binary data";
const arrayBuffer = new ArrayBuffer(8);

const archive = new Bun.Archive({
  "text.txt": "Plain text",
  "blob.bin": new Blob([data]),
  "bytes.bin": new Uint8Array([1, 2, 3, 4]),
  "buffer.bin": arrayBuffer,
});
```

### Writing Archives to Disk

Use `Bun.write()` to write an archive to disk:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
// Write uncompressed tar (default)
const archive = new Bun.Archive({
  "file1.txt": "content1",
  "file2.txt": "content2",
});
await Bun.write("output.tar", archive);

// Write gzipped tar
const compressed = new Bun.Archive({ "src/index.ts": "console.log('Hello');" }, { compress: "gzip" });
await Bun.write("output.tar.gz", compressed);
```

### Getting Archive Bytes

Get the archive data as bytes or a Blob:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const archive = new Bun.Archive({ "hello.txt": "Hello, World!" });

// As Uint8Array
const bytes = await archive.bytes();

// As Blob
const blob = await archive.blob();

// With gzip compression (set at construction)
const gzipped = new Bun.Archive({ "hello.txt": "Hello, World!" }, { compress: "gzip" });
const gzippedBytes = await gzipped.bytes();
const gzippedBlob = await gzipped.blob();
```

## Extracting Archives

### From Existing Archive Data

Create an archive from existing tar/tar.gz data:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
// From a file
const tarball = await Bun.file("package.tar.gz").bytes();
const archiveFromFile = new Bun.Archive(tarball);
```

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
// From a fetch response
const response = await fetch("https://example.com/archive.tar.gz");
const archiveFromFetch = new Bun.Archive(await response.blob());
```

### Extracting to Disk

Use `.extract()` to write all files to a directory:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const tarball = await Bun.file("package.tar.gz").bytes();
const archive = new Bun.Archive(tarball);
const count = await archive.extract("./extracted");
console.log(`Extracted ${count} entries`);
```

`extract()` creates the target directory if it doesn't exist and overwrites existing files. The returned count includes files, directories, and symlinks (on POSIX systems).

**Note**: On Windows, Bun always skips symbolic links during extraction, regardless of privilege level. On Linux and macOS, Bun extracts symlinks normally.

**Security note**: Bun.Archive validates paths during extraction. It rejects absolute paths (POSIX `/`, Windows drive letters like `C:\` or `C:/`, and UNC paths like `\\server\share`) and unsafe symlink targets. It normalizes away path traversal components (`..`) to prevent directory escape attacks: `dir/sub/../file` becomes `dir/file`.

### Filtering Extracted Files

Use glob patterns to extract only specific files. Bun matches patterns against archive entry paths normalized to use forward slashes (`/`). Positive patterns specify what to include, and negative patterns (prefixed with `!`) specify what to exclude. When you pass only negative patterns, Bun includes all entries that don't match them:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const tarball = await Bun.file("package.tar.gz").bytes();
const archive = new Bun.Archive(tarball);

// Extract only TypeScript files
const tsCount = await archive.extract("./extracted", { glob: "**/*.ts" });

// Extract files from multiple directories
const multiCount = await archive.extract("./extracted", {
  glob: ["src/**", "lib/**"],
});
```

When mixing positive and negative patterns, entries must match at least one positive pattern and no negative pattern:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
// Extract everything except node_modules
const distCount = await archive.extract("./extracted", {
  glob: ["**", "!node_modules/**"],
});

// Extract source files but exclude tests
const srcCount = await archive.extract("./extracted", {
  glob: ["src/**", "!**/*.test.ts", "!**/__tests__/**"],
});
```

## Reading Archive Contents

### Get All Files

Use `.files()` to get archive contents as a `Map` of `File` objects without extracting to disk. Unlike `extract()`, which processes all entry types, `files()` returns only regular files (no directories):

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const tarball = await Bun.file("package.tar.gz").bytes();
const archive = new Bun.Archive(tarball);
const files = await archive.files();

for (const [path, file] of files) {
  console.log(`${path}: ${file.size} bytes`);
  console.log(await file.text());
}
```

Each `File` object includes:

* `name` - The file path within the archive (always uses forward slashes `/` as separators)
* `size` - File size in bytes
* `lastModified` - Modification timestamp
* Standard `Blob` methods such as `text()`, `arrayBuffer()`, and `stream()`

**Note**: `files()` loads file contents into memory. For large archives, use `extract()` to write directly to disk instead.

### Error Handling

Archive operations can fail due to corrupted data, I/O errors, or invalid paths. Use try/catch to handle these cases:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
try {
  const tarball = await Bun.file("package.tar.gz").bytes();
  const archive = new Bun.Archive(tarball);
  const count = await archive.extract("./output");
  console.log(`Extracted ${count} entries`);
} catch (e: unknown) {
  if (e instanceof Error) {
    const error = e as Error & { code?: string };
    if (error.code === "EACCES") {
      console.error("Permission denied");
    } else if (error.code === "ENOSPC") {
      console.error("Disk full");
    } else {
      console.error("Archive error:", error.message);
    }
  } else {
    console.error("Archive error:", String(e));
  }
}
```

Common error scenarios:

* **Corrupted/truncated archives** - `new Bun.Archive()` loads the archive data; Bun may defer errors until read/extract operations
* **Permission denied** - `extract()` throws if the target directory is not writable
* **Disk full** - `extract()` throws if there's insufficient space
* **Invalid paths** - Operations throw for malformed file paths

For additional security with untrusted archives, you can enumerate and validate paths before extraction:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const archive = new Bun.Archive(untrustedData);
const files = await archive.files();

// Optional: Custom validation for additional checks
for (const [path] of files) {
  // Example: Reject hidden files
  if (path.startsWith(".") || path.includes("/.")) {
    throw new Error(`Hidden file rejected: ${path}`);
  }
  // Example: Whitelist specific directories
  if (!path.startsWith("src/") && !path.startsWith("lib/")) {
    throw new Error(`Unexpected path: ${path}`);
  }
}

// Extract to a controlled destination
await archive.extract("./safe-output");
```

When called with a glob pattern, `files()` returns an empty `Map` if no files match:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const matches = await archive.files("*.nonexistent");
if (matches.size === 0) {
  console.log("No matching files found");
}
```

### Filtering with Glob Patterns

Pass a glob pattern to filter which files are returned:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
// Get only TypeScript files
const tsFiles = await archive.files("**/*.ts");

// Get files in src directory
const srcFiles = await archive.files("src/*");

// Get all JSON files (recursive)
const jsonFiles = await archive.files("**/*.json");

// Get multiple file types with array of patterns
const codeFiles = await archive.files(["**/*.ts", "**/*.js"]);
```

Supported glob patterns (subset of [Bun.Glob](/docs/runtime/glob) syntax):

* `*` - Match any characters except `/`
* `**` - Match any characters including `/`
* `?` - Match single character
* `[abc]` - Match character set
* `{a,b}` - Match alternatives
* `!pattern` - Exclude files matching pattern (negation). When you pass only negative patterns, Bun includes all files not matching them.

See [Bun.Glob](/docs/runtime/glob) for the full glob syntax including escaping and advanced patterns.

## Compression

Bun.Archive creates uncompressed tar archives by default. Use `{ compress: "gzip" }` to enable gzip compression:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
// Default: uncompressed tar
const archive = new Bun.Archive({ "hello.txt": "Hello, World!" });

// Reading: automatically detects gzip
const gzippedTarball = await Bun.file("archive.tar.gz").bytes();
const readArchive = new Bun.Archive(gzippedTarball);

// Enable gzip compression
const compressed = new Bun.Archive({ "hello.txt": "Hello, World!" }, { compress: "gzip" });

// Gzip with custom level (1-12)
const maxCompression = new Bun.Archive({ "hello.txt": "Hello, World!" }, { compress: "gzip", level: 12 });
```

The `options` argument accepts:

* No options or `undefined` - Uncompressed tar (default)
* `{ compress: "gzip" }` - Enable gzip compression at level 6
* `{ compress: "gzip", level: number }` - Gzip with custom level 1-12 (1 = fastest, 12 = smallest)

## Examples

### Bundle Project Files

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
import { Glob } from "bun";

// Collect source files
const files: Record<string, string> = {};
const glob = new Glob("src/**/*.ts");

for await (const path of glob.scan(".")) {
  // Normalize path separators to forward slashes for cross-platform compatibility
  const archivePath = path.replaceAll("\\", "/");
  files[archivePath] = await Bun.file(path).text();
}

// Add package.json
files["package.json"] = await Bun.file("package.json").text();

// Create compressed archive and write to disk
const archive = new Bun.Archive(files, { compress: "gzip" });
await Bun.write("bundle.tar.gz", archive);
```

### Extract and Process npm Package

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const response = await fetch("https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz");
const archive = new Bun.Archive(await response.blob());

// Get package.json
const files = await archive.files("package/package.json");
const packageJson = files.get("package/package.json");

if (packageJson) {
  const pkg = JSON.parse(await packageJson.text());
  console.log(`Package: ${pkg.name}@${pkg.version}`);
}
```

### Create Archive from Directory

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
import { readdir } from "node:fs/promises";
import { join } from "node:path";

async function archiveDirectory(dir: string, compress = false): Promise<Bun.Archive> {
  const files: Record<string, Blob> = {};

  async function walk(currentDir: string, prefix: string = "") {
    const entries = await readdir(currentDir, { withFileTypes: true });

    for (const entry of entries) {
      const fullPath = join(currentDir, entry.name);
      const archivePath = prefix ? `${prefix}/${entry.name}` : entry.name;

      if (entry.isDirectory()) {
        await walk(fullPath, archivePath);
      } else {
        files[archivePath] = Bun.file(fullPath);
      }
    }
  }

  await walk(dir);
  return new Bun.Archive(files, compress ? { compress: "gzip" } : undefined);
}

const archive = await archiveDirectory("./my-project", true);
await Bun.write("my-project.tar.gz", archive);
```

## Reference

> **Note**: The following type signatures are simplified. See [`packages/bun-types/bun.d.ts`](https://github.com/oven-sh/bun/blob/main/packages/bun-types/bun.d.ts) for the full type definitions.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
type ArchiveInput =
  | Record<string, string | Blob | Bun.ArrayBufferView | ArrayBufferLike>
  | Blob
  | Bun.ArrayBufferView
  | ArrayBufferLike;

type ArchiveOptions = {
  /** Compression algorithm. Currently only "gzip" is supported. */
  compress?: "gzip";
  /** Compression level 1-12 (default 6 when gzip is enabled). */
  level?: number;
};

interface ArchiveExtractOptions {
  /** Glob pattern(s) to filter extraction. Supports negative patterns with "!" prefix. */
  glob?: string | readonly string[];
}

class Archive {
  /**
   * Create an Archive from input data
   * @param data - Files to archive (as object) or existing archive data (as bytes/blob)
   * @param options - Compression options. Uncompressed by default.
   *                  Pass { compress: "gzip" } to enable compression.
   */
  constructor(data: ArchiveInput, options?: ArchiveOptions);

  /**
   * Extract archive to a directory
   * @returns Number of entries extracted (files, directories, and symlinks)
   */
  extract(path: string, options?: ArchiveExtractOptions): Promise<number>;

  /**
   * Get archive as a Blob (uses compression setting from constructor)
   */
  blob(): Promise<Blob>;

  /**
   * Get archive as a Uint8Array (uses compression setting from constructor)
   */
  bytes(): Promise<Uint8Array<ArrayBuffer>>;

  /**
   * Get archive contents as File objects (regular files only, no directories)
   */
  files(glob?: string | readonly string[]): Promise<Map<string, File>>;
}
```


# Auto-install
Source: https://bun.com/docs/runtime/auto-install

Bun's automatic package installation feature for standalone script execution

If Bun finds no `node_modules` directory in the working directory or higher, it abandons Node.js-style module resolution in favor of the **Bun module resolution algorithm**.

Under Bun-style module resolution, Bun auto-installs every imported package on the fly into a [global module cache](/docs/pm/global-cache) during execution. [`bun install`](/docs/pm/cli/install) uses the same cache.

```ts index.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { foo } from "foo"; // install `latest` version

foo();
```

The first time you run this script, Bun auto-installs `"foo"` and caches it. Later runs use the cached version.

***

## Version resolution

Bun determines which version to install as follows:

1. Check for a `bun.lock` file in the project root. If it exists, use the version specified in the lockfile.
2. Otherwise, scan up the tree for a `package.json` that includes `"foo"` as a dependency. If found, use the specified semver version or version range.
3. Otherwise, use `latest`.

***

## Cache behavior

Once Bun determines a version or version range, it:

1. Checks the module cache for a compatible version. If one exists, uses it.
2. When resolving `latest`, checks if `package@latest` was downloaded and cached in the last *24 hours*. If so, uses it.
3. Otherwise, downloads and installs the appropriate version from the `npm` registry.

***

## Installation

Bun installs and caches packages into `<cache>/<pkg>@<version>`, so Bun can cache multiple versions of the same package at once. It also creates a symlink under `<cache>/<pkg>/<version>` to speed up looking up all cached versions of a package.

***

## Version specifiers

To bypass version resolution entirely, specify a version or version range directly in your import statement.

```ts index.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { z } from "zod@3.0.0"; // specific version
import { z } from "zod@next"; // npm tag
import { z } from "zod@^3.20.0"; // semver range
```

***

## Benefits

* **Space efficiency** — Each version of a dependency exists in only one place on disk. This saves space and time compared to redundant per-project installations.
* **Portability** — Your source file is *self-contained*, so sharing scripts and gists doesn't mean zipping up a directory of code and config files. With version specifiers in `import` statements, even a `package.json` isn't necessary.
* **Convenience** — You don't need to run `npm install` or `bun install` before running a file or script with `bun run`.
* **Backwards compatibility** — Because Bun still respects the versions specified in `package.json` if one exists, you can switch to Bun-style resolution with a single command: `rm -rf node_modules`.

***

## Limitations

* No Intellisense. TypeScript auto-completion in IDEs relies on type declaration files inside `node_modules`. We are investigating solutions to this.
* No [patch-package](https://github.com/ds300/patch-package) support

***

## FAQ

<AccordionGroup>
  <Accordion title="How is this different from what pnpm does?">
    With pnpm, you have to run `pnpm install`, which creates a `node_modules` folder of symlinks for the runtime to resolve. By contrast, Bun resolves dependencies on the fly when you run a file; there's no need to run any `install` command ahead of time. Bun also doesn't create a `node_modules` folder.
  </Accordion>

  <Accordion title="How is this different from what Yarn Plug'N'Play does?">
    With Yarn, you must run `yarn install` before you run a script. By contrast, Bun resolves dependencies on the fly when you run a file; there's no need to run any `install` command ahead of time.

    Yarn Plug'N'Play also uses zip files to store dependencies. This makes dependency loading [slower at runtime](https://twitter.com/jarredsumner/status/1458207919636287490), as random access reads on zip files tend to be slower than the equivalent disk lookup.
  </Accordion>

  <Accordion title="How is this different from what Deno does?">
    Deno requires an `npm:` specifier before each npm `import`, lacks support for import maps through `compilerOptions.paths` in `tsconfig.json`, and has incomplete support for `package.json` settings. Unlike Deno, Bun does not currently support URL imports.
  </Accordion>
</AccordionGroup>


# Binary Data
Source: https://bun.com/docs/runtime/binary-data

Working with binary data in JavaScript

Bun implements several data types and utilities for working with binary data in JavaScript, most of which are Web-standard. Bun-specific APIs are noted as such.

This cheat sheet doubles as a table of contents; click a class in the left column to jump to its section.

| Class                       | Description                                                                                                                                                                                    |
| --------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [`TypedArray`](#typedarray) | A family of classes that provide an `Array`-like interface for interacting with binary data. Includes `Uint8Array`, `Uint16Array`, `Int8Array`, and more.                                      |
| [`Buffer`](#buffer)         | A subclass of `Uint8Array` that implements a wide range of convenience methods. Unlike the others in this table, it's a Node.js API (which Bun implements). It isn't available in the browser. |
| [`DataView`](#dataview)     | A class that provides a `get/set` API for writing some number of bytes to an `ArrayBuffer` at a particular byte offset. Often used for reading or writing binary protocols.                    |
| [`Blob`](#blob)             | A readonly blob of binary data usually representing a file. Has a MIME `type`, a `size`, and methods for converting to `ArrayBuffer`, `ReadableStream`, and string.                            |
| [`File`](#file)             | A subclass of `Blob` that represents a file. Has a `name` and `lastModified` timestamp. Node.js v20 has experimental support.                                                                  |
| [`BunFile`](#bunfile)       | *Bun only*. A subclass of `Blob` that represents a lazily-loaded file on disk. Created with `Bun.file(path)`.                                                                                  |

***

## `ArrayBuffer` and views

JavaScript had no language-native way to store and manipulate binary data until ECMAScript 2015 (ES6) introduced a range of mechanisms for it. The most fundamental building block is `ArrayBuffer`, a data structure that represents a sequence of bytes in memory.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
// this buffer can store 8 bytes
const buf = new ArrayBuffer(8);
```

Despite the name, it isn't an array and supports none of the array methods and operators you might expect. You can't read or write values from an `ArrayBuffer` directly; all you can do is check its size and create "slices" from it.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const buf = new ArrayBuffer(8);
buf.byteLength; // => 8

const slice = buf.slice(0, 4); // returns new ArrayBuffer
slice.byteLength; // => 4
```

To read or write the data, you need a "view": a class that *wraps* an `ArrayBuffer` instance and lets you read and manipulate the underlying data. There are two types of views: *typed arrays* and `DataView`.

### `DataView`

The `DataView` class is a lower-level interface for reading and manipulating the data in an `ArrayBuffer`.

The following creates a `DataView` and sets the first byte to 3.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const buf = new ArrayBuffer(4);
// [0b00000000, 0b00000000, 0b00000000, 0b00000000]

const dv = new DataView(buf);
dv.setUint8(0, 3); // write value 3 at byte offset 0
dv.getUint8(0); // => 3
// [0b00000011, 0b00000000, 0b00000000, 0b00000000]
```

Next, write a `Uint16` at byte offset `1`. This requires two bytes. The value `513` is `2 * 256 + 1`; in bytes, that's `00000010 00000001`.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
dv.setUint16(1, 513);
// [0b00000011, 0b00000010, 0b00000001, 0b00000000]

console.log(dv.getUint16(1)); // => 513
```

The first three bytes of the underlying `ArrayBuffer` now have values. Even though you wrote the second and third bytes with `setUint16()`, you can still read each component byte with `getUint8()`.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
console.log(dv.getUint8(1)); // => 2
console.log(dv.getUint8(2)); // => 1
```

Writing a value that needs more space than the underlying `ArrayBuffer` has throws an error. The following writes a `Float64` (which requires 8 bytes) at byte offset `0`, but the buffer is only four bytes long.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
dv.setFloat64(0, 3.1415);
// ^ RangeError: Out of bounds access
```

The following methods are available on `DataView`:

| Getters                                                                                                                    | Setters                                                                                                                    |
| -------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
| [`getBigInt64()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/getBigInt64)   | [`setBigInt64()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/setBigInt64)   |
| [`getBigUint64()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/getBigUint64) | [`setBigUint64()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/setBigUint64) |
| [`getFloat32()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/getFloat32)     | [`setFloat32()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/setFloat32)     |
| [`getFloat64()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/getFloat64)     | [`setFloat64()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/setFloat64)     |
| [`getInt16()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/getInt16)         | [`setInt16()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/setInt16)         |
| [`getInt32()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/getInt32)         | [`setInt32()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/setInt32)         |
| [`getInt8()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/getInt8)           | [`setInt8()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/setInt8)           |
| [`getUint16()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/getUint16)       | [`setUint16()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/setUint16)       |
| [`getUint32()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/getUint32)       | [`setUint32()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/setUint32)       |
| [`getUint8()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/getUint8)         | [`setUint8()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/setUint8)         |

### `TypedArray`

Typed arrays are a family of classes that provide an `Array`-like interface for interacting with data in an `ArrayBuffer`. Whereas a `DataView` lets you write numbers of varying size at a particular offset, a `TypedArray` interprets the underlying bytes as an array of numbers, each of a fixed size.

<Note>
  It's common to refer to this family of classes collectively by their shared superclass `TypedArray`. This class is
  *internal* to JavaScript; you can't directly create instances of it, and `TypedArray` is not defined in the global
  scope. Think of it as an `interface` or an abstract class.
</Note>

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const buffer = new ArrayBuffer(3);
const arr = new Uint8Array(buffer);

// contents are initialized to zero
console.log(arr); // Uint8Array(3) [0, 0, 0]

// assign values like an array
arr[0] = 0;
arr[1] = 10;
arr[2] = 255;
arr[3] = 255; // no-op, out of bounds
```

The typed array classes, and how each interprets the bytes in an `ArrayBuffer`:

| Class                                                                                                                     | Description                                                                                                                                                                |
| ------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [`Uint8Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array)               | Every one (1) byte is interpreted as an unsigned 8-bit integer. Range 0 to 255.                                                                                            |
| [`Uint16Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint16Array)             | Every two (2) bytes are interpreted as an unsigned 16-bit integer. Range 0 to 65535.                                                                                       |
| [`Uint32Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint32Array)             | Every four (4) bytes are interpreted as an unsigned 32-bit integer. Range 0 to 4294967295.                                                                                 |
| [`Int8Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Int8Array)                 | Every one (1) byte is interpreted as a signed 8-bit integer. Range -128 to 127.                                                                                            |
| [`Int16Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Int16Array)               | Every two (2) bytes are interpreted as a signed 16-bit integer. Range -32768 to 32767.                                                                                     |
| [`Int32Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Int32Array)               | Every four (4) bytes are interpreted as a signed 32-bit integer. Range -2147483648 to 2147483647.                                                                          |
| [`Float16Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Float16Array)           | Every two (2) bytes are interpreted as a 16-bit floating point number. Range -6.55e4 to 6.55e4.                                                                            |
| [`Float32Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Float32Array)           | Every four (4) bytes are interpreted as a 32-bit floating point number. Range -3.4e38 to 3.4e38.                                                                           |
| [`Float64Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Float64Array)           | Every eight (8) bytes are interpreted as a 64-bit floating point number. Range -1.7e308 to 1.7e308.                                                                        |
| [`BigInt64Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt64Array)         | Every eight (8) bytes are interpreted as a signed `BigInt`. Range -9223372036854775808 to 9223372036854775807 (though `BigInt` is capable of representing larger numbers). |
| [`BigUint64Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigUint64Array)       | Every eight (8) bytes are interpreted as an unsigned `BigInt`. Range 0 to 18446744073709551615 (though `BigInt` is capable of representing larger numbers).                |
| [`Uint8ClampedArray`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8ClampedArray) | Same as `Uint8Array`, but automatically "clamps" to the range 0-255 when assigning a value to an element.                                                                  |

The following table shows how different typed array classes interpret the same bytes in an `ArrayBuffer`.

|                  | Byte 0              | Byte 1     | Byte 2              | Byte 3     | Byte 4               | Byte 5     | Byte 6               | Byte 7     |
| ---------------- | ------------------- | ---------- | ------------------- | ---------- | -------------------- | ---------- | -------------------- | ---------- |
| `ArrayBuffer`    | `00000000`          | `00000001` | `00000010`          | `00000011` | `00000100`           | `00000101` | `00000110`           | `00000111` |
| `Uint8Array`     | 0                   | 1          | 2                   | 3          | 4                    | 5          | 6                    | 7          |
| `Uint16Array`    | 256 (`1 * 256 + 0`) |            | 770 (`3 * 256 + 2`) |            | 1284 (`5 * 256 + 4`) |            | 1798 (`7 * 256 + 6`) |            |
| `Uint32Array`    | 50462976            |            |                     |            | 117835012            |            |                      |            |
| `BigUint64Array` | 506097522914230528n |            |                     |            |                      |            |                      |            |

To create a typed array from a pre-defined `ArrayBuffer`:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
// create typed array from ArrayBuffer
const buf = new ArrayBuffer(10);
const arr = new Uint8Array(buf);

arr[0] = 30;
arr[1] = 60;

// all elements are initialized to zero
console.log(arr); // => Uint8Array(10) [ 30, 60, 0, 0, 0, 0, 0, 0, 0, 0 ];
```

Instantiating a `Uint32Array` from this same `ArrayBuffer` throws an error.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const buf = new ArrayBuffer(10);
const arr = new Uint32Array(buf);
//          ^  RangeError: ArrayBuffer length minus the byteOffset
//             is not a multiple of the element size
```

A `Uint32` value requires four bytes (32 bits). Because the `ArrayBuffer` is 10 bytes long, there's no way to cleanly divide its contents into 4-byte chunks.

To fix this, create a typed array over a particular "slice" of the `ArrayBuffer`. The following `Uint32Array` only "views" the *first* 8 bytes of the underlying `ArrayBuffer`: a `byteOffset` of `0` and a `length` of `2`, the number of `Uint32` values the array holds.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
// create typed array from ArrayBuffer slice
const buf = new ArrayBuffer(10);
const arr = new Uint32Array(buf, 0, 2);

/*
  buf    _ _ _ _ _ _ _ _ _ _    10 bytes
  arr   [_______,_______]       2 4-byte elements
*/

arr.byteOffset; // 0
arr.length; // 2
```

You don't need to create an `ArrayBuffer` instance first; pass a length to the typed array constructor instead:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const arr2 = new Uint8Array(5);

// all elements are initialized to zero
// => Uint8Array(5) [0, 0, 0, 0, 0]
```

You can also instantiate typed arrays directly from an array of numbers, or another typed array:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
// from an array of numbers
const arr1 = new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7]);
arr1[0]; // => 0;
arr1[7]; // => 7;

// from another typed array
const arr2 = new Uint8Array(arr1);
```

Typed arrays provide the same methods as regular arrays, with a few exceptions. For example, `push` and `pop` are not available, because they would require resizing the underlying `ArrayBuffer`.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const arr = new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7]);

// supports common array methods
arr.filter(n => n > 5); // Uint8Array(2) [6, 7]
arr.map(n => n * 2); // Uint8Array(8) [0, 2, 4, 6, 8, 10, 12, 14]
arr.reduce((acc, n) => acc + n, 0); // 28
arr.forEach(n => console.log(n)); // 0 1 2 3 4 5 6 7
arr.every(n => n < 10); // true
arr.find(n => n > 5); // 6
arr.includes(5); // true
arr.indexOf(5); // 5
```

See the [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray) for more on typed array properties and methods.

### `Uint8Array`

`Uint8Array` is the most common typed array in JavaScript. It represents a classic "byte array": a sequence of 8-bit unsigned integers between 0 and 255.

In Bun, it has methods for converting between byte arrays and their base64 or hex string representations.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
new Uint8Array([1, 2, 3, 4, 5]).toBase64(); // "AQIDBAU="
Uint8Array.fromBase64("AQIDBAU="); // Uint8Array(5) [1, 2, 3, 4, 5]

new Uint8Array([255, 254, 253, 252, 251]).toHex(); // "fffefdfcfb"
Uint8Array.fromHex("fffefdfcfb"); // Uint8Array(5) [255, 254, 253, 252, 251]
```

It is the return value of [`TextEncoder#encode`](https://developer.mozilla.org/en-US/docs/Web/API/TextEncoder) and the input type of [`TextDecoder#decode`](https://developer.mozilla.org/en-US/docs/Web/API/TextDecoder). These two utility classes translate between strings and various binary encodings, most notably `"utf-8"`.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const encoder = new TextEncoder();
const bytes = encoder.encode("hello world");
// => Uint8Array(11) [ 104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100 ]

const decoder = new TextDecoder();
const text = decoder.decode(bytes);
// => hello world
```

### `Buffer`

Bun implements `Buffer`, a Node.js API for working with binary data that pre-dates the introduction of typed arrays in the JavaScript spec. It has since been re-implemented as a subclass of `Uint8Array`. It provides a wide range of methods, including several Array-like and `DataView`-like methods.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const buf = Buffer.from("hello world");
// => Buffer(11) [ 104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100 ]

buf.length; // => 11
buf[0]; // => 104, ascii for 'h'
buf.writeUInt8(72, 0); // => ascii for 'H'

console.log(buf.toString());
// => Hello world
```

See the [Node.js documentation](https://nodejs.org/api/buffer.html).

## `Blob`

`Blob` is a Web API commonly used for representing files. It originated in browsers (unlike `ArrayBuffer`, which is part of JavaScript itself), but Node.js and Bun support it too.

You rarely create `Blob` instances directly; they usually come from an external source (like an `<input type="file">` element in the browser) or a library. That said, you can create a `Blob` from one or more string or binary "blob parts".

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const blob = new Blob(["<html>Hello</html>"], {
  type: "text/html",
});

blob.type; // => text/html
blob.size; // => 18
```

These parts can be `string`, `ArrayBuffer`, `TypedArray`, `DataView`, or other `Blob` instances. The constructor concatenates the parts in the order you give them.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const blob = new Blob([
  "<html>",
  new Blob(["<body>"]),
  new Uint8Array([104, 101, 108, 108, 111]), // "hello" in binary
  "</body></html>",
]);
```

Read the contents of a `Blob` asynchronously in various formats.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
await blob.text(); // => <html><body>hello</body></html>
await blob.bytes(); // => Uint8Array (copies contents)
await blob.arrayBuffer(); // => ArrayBuffer (copies contents)
blob.stream(); // => ReadableStream
```

### `BunFile`

`BunFile` is a subclass of `Blob` that represents a lazily-loaded file on disk. Like `File`, it adds a `name` and `lastModified` property. Unlike `File`, it does not require the file to be loaded into memory.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const file = Bun.file("index.txt");
// => BunFile
```

### `File`

[`File`](https://developer.mozilla.org/en-US/docs/Web/API/File) is a subclass of `Blob` that adds a `name` and `lastModified` property. It's commonly used in the browser to represent files uploaded with an `<input type="file">` element. Node.js and Bun implement `File`.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
// on browser!
// <input type="file" id="file" />

const files = document.getElementById("file").files;
// => File[]
```

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const file = new File(["<html>Hello</html>"], "index.html", {
  type: "text/html",
});
```

See the [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/API/Blob).

***

## Streams

Streams let you work with binary data without loading it all into memory at once. They're commonly used for reading and writing files, sending and receiving network requests, and processing large amounts of data.

Bun implements the Web APIs [`ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream) and [`WritableStream`](https://developer.mozilla.org/en-US/docs/Web/API/WritableStream).

<Note>
  Bun also implements the `node:stream` module, including
  [`Readable`](https://nodejs.org/api/stream.html#stream_readable_streams),
  [`Writable`](https://nodejs.org/api/stream.html#stream_writable_streams), and
  [`Duplex`](https://nodejs.org/api/stream.html#stream_duplex_and_transform_streams). For complete documentation, refer
  to the Node.js docs.
</Note>

To create a readable stream:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const stream = new ReadableStream({
  start(controller) {
    controller.enqueue("hello");
    controller.enqueue("world");
    controller.close();
  },
});
```

Read the stream chunk-by-chunk with `for await`.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
for await (const chunk of stream) {
  console.log(chunk);
}

// => "hello"
// => "world"
```

For more on streams in Bun, see [Streams](/docs/runtime/streams).

***

## Conversion

Use this section as a reference for converting one binary format to another.

### From `ArrayBuffer`

Since `ArrayBuffer` stores the data that underlies other binary structures like `TypedArray`, the following snippets are not *converting* from `ArrayBuffer` to another format. Instead, they *create* a new instance using the underlying data.

#### To `TypedArray`

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
new Uint8Array(buf);
```

#### To `DataView`

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
new DataView(buf);
```

#### To `Buffer`

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
// create Buffer over entire ArrayBuffer
Buffer.from(buf);

// create Buffer over a slice of the ArrayBuffer
Buffer.from(buf, 0, 10);
```

#### To `string`

As UTF-8:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
new TextDecoder().decode(buf);
```

#### To `number[]`

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
Array.from(new Uint8Array(buf));
```

#### To `Blob`

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
new Blob([buf], { type: "text/plain" });
```

#### To `ReadableStream`

The following snippet creates a `ReadableStream` and enqueues the entire `ArrayBuffer` as a single chunk.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
new ReadableStream({
  start(controller) {
    controller.enqueue(buf);
    controller.close();
  },
});
```

<Accordion title="With chunking">
  To stream the `ArrayBuffer` in chunks, use a `Uint8Array` view and enqueue each chunk.

  ```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
  const view = new Uint8Array(buf);
  const chunkSize = 1024;

  new ReadableStream({
    start(controller) {
      for (let i = 0; i < view.length; i += chunkSize) {
        controller.enqueue(view.slice(i, i + chunkSize));
      }
      controller.close();
    },
  });
  ```
</Accordion>

### From `TypedArray`

#### To `ArrayBuffer`

The `buffer` property is the underlying `ArrayBuffer`. A `TypedArray` can be a view of a *slice* of that buffer, so the sizes may differ.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
arr.buffer;
```

#### To `DataView`

To create a `DataView` over the same byte range as the `TypedArray`:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
new DataView(arr.buffer, arr.byteOffset, arr.byteLength);
```

#### To `Buffer`

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
Buffer.from(arr);
```

#### To `string`

As UTF-8:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
new TextDecoder().decode(arr);
```

#### To `number[]`

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
Array.from(arr);
```

#### To `Blob`

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
// only if arr is a view of its entire backing ArrayBuffer
new Blob([arr.buffer], { type: "text/plain" });
```

#### To `ReadableStream`

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
new ReadableStream({
  start(controller) {
    controller.enqueue(arr);
    controller.close();
  },
});
```

<Accordion title="With chunking">
  To stream the `ArrayBuffer` in chunks, split the `TypedArray` into chunks and enqueue each one individually.

  ```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
  new ReadableStream({
    start(controller) {
      for (let i = 0; i < arr.length; i += chunkSize) {
        controller.enqueue(arr.slice(i, i + chunkSize));
      }
      controller.close();
    },
  });
  ```
</Accordion>

### From `DataView`

#### To `ArrayBuffer`

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
view.buffer;
```

#### To `TypedArray`

Only works if the `byteLength` of the `DataView` is a multiple of the `BYTES_PER_ELEMENT` of the `TypedArray` subclass.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
new Uint8Array(view.buffer, view.byteOffset, view.byteLength);
new Uint16Array(view.buffer, view.byteOffset, view.byteLength / 2);
new Uint32Array(view.buffer, view.byteOffset, view.byteLength / 4);
// etc...
```

#### To `Buffer`

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
Buffer.from(view.buffer, view.byteOffset, view.byteLength);
```

#### To `string`

As UTF-8:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
new TextDecoder().decode(view);
```

#### To `number[]`

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
Array.from(new Uint8Array(view.buffer, view.byteOffset, view.byteLength));
```

#### To `Blob`

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
new Blob([view], { type: "text/plain" });
```

#### To `ReadableStream`

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
new ReadableStream({
  start(controller) {
    controller.enqueue(new Uint8Array(view.buffer, view.byteOffset, view.byteLength));
    controller.close();
  },
});
```

<Accordion title="With chunking">
  To stream the `ArrayBuffer` in chunks, split the `DataView` into chunks and enqueue each one individually.

  ```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
  const bytes = new Uint8Array(view.buffer, view.byteOffset, view.byteLength);

  new ReadableStream({
    start(controller) {
      for (let i = 0; i < bytes.length; i += chunkSize) {
        controller.enqueue(bytes.slice(i, i + chunkSize));
      }
      controller.close();
    },
  });
  ```
</Accordion>

### From `Buffer`

#### To `ArrayBuffer`

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
buf.buffer;
```

#### To `TypedArray`

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
new Uint8Array(buf);
```

#### To `DataView`

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
new DataView(buf.buffer, buf.byteOffset, buf.byteLength);
```

#### To `string`

As UTF-8:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
buf.toString();
```

As base64:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
buf.toString("base64");
```

As hex:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
buf.toString("hex");
```

#### To `number[]`

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
Array.from(buf);
```

#### To `Blob`

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
new Blob([buf], { type: "text/plain" });
```

#### To `ReadableStream`

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
new ReadableStream({
  start(controller) {
    controller.enqueue(buf);
    controller.close();
  },
});
```

<Accordion title="With chunking">
  To stream the `ArrayBuffer` in chunks, split the `Buffer` into chunks and enqueue each one individually.

  ```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
  new ReadableStream({
    start(controller) {
      for (let i = 0; i < buf.length; i += chunkSize) {
        controller.enqueue(buf.slice(i, i + chunkSize));
      }
      controller.close();
    },
  });
  ```
</Accordion>

### From `Blob`

#### To `ArrayBuffer`

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
await blob.arrayBuffer();
```

#### To `TypedArray`

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
await blob.bytes();
```

#### To `DataView`

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
new DataView(await blob.arrayBuffer());
```

#### To `Buffer`

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
Buffer.from(await blob.arrayBuffer());
```

#### To `string`

As UTF-8:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
await blob.text();
```

#### To `number[]`

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
Array.from(await blob.bytes());
```

#### To `ReadableStream`

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
blob.stream();
```

### From `ReadableStream`

[`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response) is a common intermediate for converting a `ReadableStream` to other formats.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
stream; // ReadableStream

const buffer = new Response(stream).arrayBuffer();
```

But this approach is verbose and adds unnecessary overhead. Bun implements optimized convenience functions for converting a `ReadableStream` to various binary formats.

#### To `ArrayBuffer`

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
// with Response
new Response(stream).arrayBuffer();

// with Bun function
Bun.readableStreamToArrayBuffer(stream);
```

#### To `Uint8Array`

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
// with Response
new Response(stream).bytes();

// with Bun function
Bun.readableStreamToBytes(stream);
```

#### To `TypedArray`

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
// with Response
const buf = await new Response(stream).arrayBuffer();
new Int8Array(buf);

// with Bun function
new Int8Array(await Bun.readableStreamToArrayBuffer(stream));
```

#### To `DataView`

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
// with Response
const buf = await new Response(stream).arrayBuffer();
new DataView(buf);

// with Bun function
new DataView(await Bun.readableStreamToArrayBuffer(stream));
```

#### To `Buffer`

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
// with Response
const buf = await new Response(stream).arrayBuffer();
Buffer.from(buf);

// with Bun function
Buffer.from(await Bun.readableStreamToArrayBuffer(stream));
```

#### To `string`

As UTF-8:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
// with Response
await new Response(stream).text();

// with Bun function
await Bun.readableStreamToText(stream);
```

#### To `number[]`

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
// with Response
const arr = await new Response(stream).bytes();
Array.from(arr);

// with Bun function
Array.from(new Uint8Array(await Bun.readableStreamToArrayBuffer(stream)));
```

Bun provides a utility for resolving a `ReadableStream` to an array of its chunks. Each chunk may be a string, typed array, or `ArrayBuffer`.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
// with Bun function
Bun.readableStreamToArray(stream);
```

#### To `Blob`

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
new Response(stream).blob();
```

#### To `ReadableStream`

To split a `ReadableStream` into two streams that you can consume independently:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const [a, b] = stream.tee();
```


# Bun APIs
Source: https://bun.com/docs/runtime/bun-apis

Overview of Bun's native APIs available on the Bun global object and built-in modules

Bun implements a set of native APIs on the `Bun` global object and through several built-in modules. These APIs are heavily optimized and are the canonical "Bun-native" way to implement common functionality.

Bun strives to implement standard Web APIs wherever possible. Bun introduces new APIs primarily for server-side tasks where no standard exists, such as file I/O and starting an HTTP server. In these cases, Bun's approach still builds atop standard APIs like `Blob`, `URL`, and `Request`.

```ts server.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
Bun.serve({
  fetch(req: Request) {
    return new Response("Success!");
  },
});
```

Use the links in the table to jump to the associated documentation.

| Topic                            | APIs                                                                                                                                                                                                                                                                                                                   |
| -------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| HTTP Server                      | [`Bun.serve`](/docs/runtime/http/server)                                                                                                                                                                                                                                                                                    |
| Shell                            | [`$`](/docs/runtime/shell)                                                                                                                                                                                                                                                                                                  |
| Bundler                          | [`Bun.build`](/docs/bundler)                                                                                                                                                                                                                                                                                                |
| File I/O                         | [`Bun.file`](/docs/runtime/file-io#reading-files-bun-file), [`Bun.write`](/docs/runtime/file-io#writing-files-bun-write), `Bun.stdin`, `Bun.stdout`, `Bun.stderr`                                                                                                                                                                |
| Child Processes                  | [`Bun.spawn`](/docs/runtime/child-process#spawn-a-process-bun-spawn), [`Bun.spawnSync`](/docs/runtime/child-process#blocking-api-bun-spawnsync)                                                                                                                                                                                  |
| TCP Sockets                      | [`Bun.listen`](/docs/runtime/networking/tcp#start-a-server-bun-listen), [`Bun.connect`](/docs/runtime/networking/tcp#create-a-connection-bun-connect)                                                                                                                                                                            |
| UDP Sockets                      | [`Bun.udpSocket`](/docs/runtime/networking/udp)                                                                                                                                                                                                                                                                             |
| WebSockets                       | `new WebSocket()` (client), [`Bun.serve`](/docs/runtime/http/websockets) (server)                                                                                                                                                                                                                                           |
| Transpiler                       | [`Bun.Transpiler`](/docs/runtime/transpiler)                                                                                                                                                                                                                                                                                |
| Routing                          | [`Bun.FileSystemRouter`](/docs/runtime/file-system-router)                                                                                                                                                                                                                                                                  |
| Streaming HTML                   | [`HTMLRewriter`](/docs/runtime/html-rewriter)                                                                                                                                                                                                                                                                               |
| Headless Browser                 | [`Bun.WebView`](/docs/runtime/webview)                                                                                                                                                                                                                                                                                      |
| Hashing                          | [`Bun.password`](/docs/runtime/hashing#bun-password), [`Bun.hash`](/docs/runtime/hashing#bun-hash), [`Bun.CryptoHasher`](/docs/runtime/hashing#bun-cryptohasher), `Bun.sha`                                                                                                                                                           |
| CSRF Protection                  | [`Bun.CSRF.generate`](/docs/runtime/csrf), [`Bun.CSRF.verify`](/docs/runtime/csrf)                                                                                                                                                                                                                                               |
| SQLite                           | [`bun:sqlite`](/docs/runtime/sqlite)                                                                                                                                                                                                                                                                                        |
| SQL Client                       | [`Bun.SQL`](/docs/runtime/sql), `Bun.sql`                                                                                                                                                                                                                                                                                   |
| Redis (Valkey) Client            | [`Bun.RedisClient`](/docs/runtime/redis), `Bun.redis`                                                                                                                                                                                                                                                                       |
| FFI (Foreign Function Interface) | [`bun:ffi`](/docs/runtime/ffi)                                                                                                                                                                                                                                                                                              |
| DNS                              | [`Bun.dns.lookup`](/docs/runtime/networking/dns), `Bun.dns.prefetch`, `Bun.dns.getCacheStats`                                                                                                                                                                                                                               |
| Testing                          | [`bun:test`](/docs/test)                                                                                                                                                                                                                                                                                                    |
| Workers                          | [`new Worker()`](/docs/runtime/workers)                                                                                                                                                                                                                                                                                     |
| Module Loaders                   | [`Bun.plugin`](/docs/bundler/plugins)                                                                                                                                                                                                                                                                                       |
| Glob                             | [`Bun.Glob`](/docs/runtime/glob)                                                                                                                                                                                                                                                                                            |
| Cookies                          | [`Bun.Cookie`](/docs/runtime/cookies), [`Bun.CookieMap`](/docs/runtime/cookies)                                                                                                                                                                                                                                                  |
| Node-API                         | [`Node-API`](/docs/runtime/node-api)                                                                                                                                                                                                                                                                                        |
| `import.meta`                    | [`import.meta`](/docs/runtime/module-resolution#import-meta)                                                                                                                                                                                                                                                                |
| Utilities                        | [`Bun.version`](/docs/runtime/utils#bun-version), [`Bun.revision`](/docs/runtime/utils#bun-revision), [`Bun.env`](/docs/runtime/utils#bun-env), [`Bun.main`](/docs/runtime/utils#bun-main)                                                                                                                                                 |
| Sleep & Timing                   | [`Bun.sleep()`](/docs/runtime/utils#bun-sleep), [`Bun.sleepSync()`](/docs/runtime/utils#bun-sleepsync), [`Bun.nanoseconds()`](/docs/runtime/utils#bun-nanoseconds)                                                                                                                                                                    |
| Random & UUID                    | [`Bun.randomUUIDv7()`](/docs/runtime/utils#bun-randomuuidv7)                                                                                                                                                                                                                                                                |
| System & Environment             | [`Bun.which()`](/docs/runtime/utils#bun-which)                                                                                                                                                                                                                                                                              |
| Comparison & Inspection          | [`Bun.peek()`](/docs/runtime/utils#bun-peek), [`Bun.deepEquals()`](/docs/runtime/utils#bun-deepequals), `Bun.deepMatch`, [`Bun.inspect()`](/docs/runtime/utils#bun-inspect)                                                                                                                                                           |
| String & Text Processing         | [`Bun.escapeHTML()`](/docs/runtime/utils#bun-escapehtml), [`Bun.stringWidth()`](/docs/runtime/utils#bun-stringwidth), `Bun.indexOfLine`                                                                                                                                                                                          |
| URL & Path Utilities             | [`Bun.fileURLToPath()`](/docs/runtime/utils#bun-fileurltopath), [`Bun.pathToFileURL()`](/docs/runtime/utils#bun-pathtofileurl)                                                                                                                                                                                                   |
| Compression                      | [`Bun.gzipSync()`](/docs/runtime/utils#bun-gzipsync), [`Bun.gunzipSync()`](/docs/runtime/utils#bun-gunzipsync), [`Bun.deflateSync()`](/docs/runtime/utils#bun-deflatesync), [`Bun.inflateSync()`](/docs/runtime/utils#bun-inflatesync), `Bun.zstdCompressSync()`, `Bun.zstdDecompressSync()`, `Bun.zstdCompress()`, `Bun.zstdDecompress()` |
| Stream Processing                | [`Bun.readableStreamTo*()`](/docs/runtime/utils#bun-readablestreamto), `Bun.readableStreamToBytes()`, `Bun.readableStreamToBlob()`, `Bun.readableStreamToFormData()`, `Bun.readableStreamToJSON()`, `Bun.readableStreamToArray()`                                                                                           |
| Memory & Buffer Management       | `Bun.ArrayBufferSink`, `Bun.allocUnsafe`, `Bun.concatArrayBuffers`                                                                                                                                                                                                                                                     |
| Module Resolution                | [`Bun.resolveSync()`](/docs/runtime/utils#bun-resolvesync)                                                                                                                                                                                                                                                                  |
| Parsing & Formatting             | [`Bun.semver`](/docs/runtime/semver), [`Bun.TOML.parse`](/docs/runtime/toml), [`Bun.XML`](/docs/runtime/xml), [`Bun.markdown`](/docs/runtime/markdown), [`Bun.color`](/docs/runtime/color), [`Bun.Image`](/docs/runtime/image)                                                                                                                       |
| Low-level / Internals            | `Bun.mmap`, `Bun.gc`, `Bun.generateHeapSnapshot`, [`bun:jsc`](https://bun.com/reference/bun/jsc)                                                                                                                                                                                                                       |


# bunfig.toml
Source: https://bun.com/docs/runtime/bunfig

Configure Bun's behavior using its configuration file bunfig.toml

`bunfig.toml` is Bun's configuration file.

Bun relies on pre-existing configuration files like `package.json` and `tsconfig.json` where it can; `bunfig.toml` is only for Bun-specific settings. The file is optional, and Bun works without it.

## Global vs. local

Put `bunfig.toml` in your project root, alongside your `package.json`.

To configure Bun's package manager globally, you can also create a `.bunfig.toml` file at one of the following paths:

* `$HOME/.bunfig.toml`
* `$XDG_CONFIG_HOME/.bunfig.toml`

Only package manager commands (`bun install`, `bun add`, `bun remove`, `bun update`, `bun pm`, `bunx`, and so on) read the global file. If Bun finds both a global and a local `bunfig`, it shallow-merges them, with local overriding global. CLI flags override `bunfig` settings where applicable.

## Runtime

Top-level fields in `bunfig.toml` configure Bun's runtime behavior.

### `preload`

An array of scripts/plugins to execute before running a file or script.

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
# scripts to run before `bun run`-ing a file or script
# register plugins by adding them to this list
preload = ["./preload.ts"]
```

### `jsx`

Configure how Bun handles JSX. You can also set these fields in the `compilerOptions` of your `tsconfig.json`, but `bunfig.toml` supports them for non-TypeScript projects.

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
jsx = "react"
jsxFactory = "h"
jsxFragment = "Fragment"
jsxImportSource = "react"
```

Refer to the tsconfig docs for more information on these fields.

* [`jsx`](https://www.typescriptlang.org/tsconfig#jsx)
* [`jsxFactory`](https://www.typescriptlang.org/tsconfig#jsxFactory)
* [`jsxFragment`](https://www.typescriptlang.org/tsconfig#jsxFragment)
* [`jsxImportSource`](https://www.typescriptlang.org/tsconfig#jsxImportSource)

### `smol`

Enable `smol` mode. This reduces memory usage at the cost of performance.

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
# Reduce memory usage at the cost of performance
smol = true
```

### `logLevel`

Set the log level: `"debug"`, `"warn"`, or `"error"`.

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
logLevel = "debug" # "debug" | "warn" | "error"
```

### `define`

The `define` field replaces global identifiers with constant expressions wherever they appear. The expression should be a JSON string.

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[define]
# Replace any usage of "process.env.bagel" with the string `lox`.
# The values are parsed as JSON, except single-quoted strings are supported and `'undefined'` becomes `undefined` in JS.
# This will probably change in a future release to be just regular TOML instead. It is a holdover from the CLI argument parsing.
"process.env.bagel" = "'lox'"
```

### `loader`

Configure how Bun maps file extensions to [loaders](/docs/bundler/loaders). Use this to load file types Bun doesn't support natively.

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[loader]
# when a .bagel file is imported, treat it like a tsx file
".bagel" = "tsx"
```

Bun supports the following loaders:

* `jsx`
* `js`
* `ts`
* `tsx`
* `css`
* `file`
* `json`
* `toml`
* `wasm`
* `napi`
* `base64`
* `dataurl`
* `text`

### `telemetry`

The `telemetry` field enables or disables analytics. By default, telemetry is enabled. This setting is equivalent to the `DO_NOT_TRACK` environment variable.

We do not currently collect telemetry; this setting only controls anonymous crash reports. We plan to collect information like which Bun APIs are used most or how long `bun build` takes.

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
telemetry = false
```

### `env`

Configure automatic `.env` file loading. Bun loads `.env` files by default. To disable this behavior:

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
# Disable automatic .env file loading
env = false
```

You can also use object syntax with the `file` property:

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[env]
file = false
```

Use this in production or CI/CD pipelines where you want to rely solely on system environment variables.

Bun still loads files passed explicitly with `--env-file` even when default loading is disabled.

### `console`

Configure console output behavior.

#### `console.depth`

Set the default depth for `console.log()` object inspection. Default `2`.

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[console]
depth = 3
```

Higher values show more nested properties but may produce verbose output for complex objects. The `--console-depth` CLI flag overrides this setting.

## Serve

The `[serve]` section configures `Bun.serve` and `bun run` when serving HTTP.

### `serve.port`

The default port for `Bun.serve` to listen on. Default `3000`. You can also set it with the `BUN_PORT` or `PORT` environment variables, or the `--port` flag.

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[serve]
port = 3000
```

## Test runner

The `[test]` section of `bunfig.toml` configures the test runner.

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[test]
# configuration goes here
```

### `test.root`

The root directory to run tests from. Default `.`.

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[test]
root = "./__tests__"
```

### `test.preload`

Same as the top-level `preload` field, but only applies to `bun test`.

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[test]
preload = ["./setup.ts"]
```

### `test.pathIgnorePatterns`

Exclude files and directories from test discovery using glob patterns. The test runner prunes matched directories during scanning, so it never traverses their contents. Use this when your project contains submodules or vendored code with `*.test.ts` files that you don't want `bun test` to pick up.

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[test]
pathIgnorePatterns = ["vendor/**", "submodules/**", "fixtures/**"]
```

Equivalent CLI flag: `--path-ignore-patterns`. CLI flags override the `bunfig.toml` value entirely.

### `test.smol`

Same as the top-level `smol` field, but only applies to `bun test`.

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[test]
smol = true
```

### `test.coverage`

Enables coverage reporting. Default `false`. Use `--coverage` to override.

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[test]
coverage = false
```

### `test.coverageThreshold`

The coverage threshold. By default, no threshold is set. If your test suite does not meet it, `bun test` exits with a non-zero exit code.

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[test]

# to require 90% line-level and function-level coverage
coverageThreshold = 0.9
```

You can set separate thresholds for line, function, and statement coverage.

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[test]
coverageThreshold = { lines = 0.7, functions = 0.8, statements = 0.9 }
```

### `test.coverageSkipTestFiles`

Whether to skip test files when computing coverage statistics. Default `false`.

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[test]
coverageSkipTestFiles = false
```

### `test.coverageIgnoreSourcemaps`

Whether to report coverage against transpiled output instead of remapping line numbers through sourcemaps back to the original source. Default `false`. Primarily useful for debugging.

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[test]
coverageIgnoreSourcemaps = false
```

### `test.coveragePathIgnorePatterns`

Exclude files from coverage reports using glob patterns. Accepts a single pattern or an array of patterns.

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[test]
# Single pattern
coveragePathIgnorePatterns = "**/*.spec.ts"

# Multiple patterns
coveragePathIgnorePatterns = [
  "**/*.spec.ts",
  "**/*.test.ts",
  "src/utils/**",
  "*.config.js"
]
```

### `test.coverageReporter`

By default, the test runner prints coverage reports to the console. For persistent reports that CI and other tools can read, use `lcov`.

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[test]
coverageReporter  = ["text", "lcov"]  # default ["text"]
```

### `test.coverageDir`

Set the path where coverage reports are saved. This only applies to persistent reporters like `lcov`.

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[test]
coverageDir = "path/to/somewhere"  # default "coverage"
```

### `test.randomize`

Run tests in random order. Default `false`.

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[test]
randomize = true
```

Running tests in a different order each time helps catch bugs related to test interdependencies. When combined with `seed`, the random order becomes reproducible.

The `--randomize` CLI flag overrides this setting.

### `test.seed`

Set the random seed for test randomization. This option requires `randomize` to be `true`.

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[test]
randomize = true
seed = 2444615283
```

A seed makes the randomized test order reproducible across runs, which helps when debugging flaky tests.

The `--seed` CLI flag overrides this setting.

### `test.rerunEach`

Re-run each test file a specified number of times. Default `0` (run once).

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[test]
rerunEach = 3
```

Use this to catch flaky tests or non-deterministic behavior.

The `--rerun-each` CLI flag overrides this setting.

### `test.retry`

Default retry count for all tests. The test runner retries failed tests up to this many times. Per-test `{ retry: N }` overrides this value. Default `0` (no retries).

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[test]
retry = 3
```

The `--retry` CLI flag overrides this setting.

### `test.concurrentTestGlob`

Test files matching this glob pattern run all of their tests concurrently, as if you passed the `--concurrent` flag.

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[test]
concurrentTestGlob = "**/concurrent-*.test.ts"
```

Use this to migrate a test suite to concurrent execution gradually, or to run integration tests concurrently while keeping unit tests sequential.

The `--concurrent` CLI flag overrides this setting.

### `test.onlyFailures`

When enabled, the output shows only failed tests, which reduces noise in large test suites. Default `false`.

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[test]
onlyFailures = true
```

Equivalent to the `--only-failures` flag.

### `test.reporter`

Configure the test reporter settings.

#### `test.reporter.dots`

Enable the dots reporter, which prints one dot per test. Default `false`.

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[test.reporter]
dots = true
```

#### `test.reporter.junit`

Enable JUnit XML reporting and specify the output file path.

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[test.reporter]
junit = "test-results.xml"
```

CI systems and other tools can consume the report.

## Package manager

The `[install]` section configures the behavior of `bun install`.

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[install]
# configuration here
```

### `install.optional`

Whether to install optional dependencies. Default `true`.

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[install]
optional = true
```

### `install.dev`

Whether to install development dependencies. Default `true`.

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[install]
dev = true
```

### `install.peer`

Whether to install peer dependencies. Default `true`.

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[install]
peer = true
```

### `install.production`

Whether `bun install` runs in "production mode". Default `false`.

In production mode, Bun does not install `"devDependencies"` and freezes the lockfile (same as [`install.frozenLockfile`](#install-frozenlockfile)). Use the `--production` CLI flag to enable it for a single install. Since production mode freezes the lockfile, `bun add`, `bun remove`, and `bun update` fail while it is set.

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[install]
production = false
```

### `install.exact`

Whether to set an exact version in `package.json`. Default `false`.

By default Bun uses caret ranges. If the `latest` version of a package is `2.4.1`, Bun writes `^2.4.1` to your `package.json`. That range accepts any version from `2.4.1` up to (but not including) `3.0.0`.

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[install]
exact = false
```

### `install.ignoreScripts`

Whether to skip lifecycle scripts during install. Default `false`. Equivalent to the `--ignore-scripts` flag.

When `true`, Bun does not run any `preinstall` / `install` / `postinstall` / `prepare` scripts, both for your project and for packages in `trustedDependencies`. See [Lifecycle scripts](/docs/pm/lifecycle).

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[install]
ignoreScripts = false
```

### `install.concurrentScripts`

The maximum number of concurrent lifecycle scripts to run at once. Defaults to two times the number of CPU cores. Equivalent to the `--concurrent-scripts` flag.

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[install]
concurrentScripts = 5
```

### `install.saveTextLockfile`

If `false`, `bun install` generates a binary `bun.lockb` instead of a text-based `bun.lock` file when no lockfile is present.

Default `true` (since Bun v1.2).

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[install]
saveTextLockfile = false
```

### `install.auto`

Configure Bun's [auto-install](/docs/runtime/auto-install) behavior. Default `"auto"` — when no `node_modules` folder is found, Bun installs dependencies on the fly during execution.

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[install]
auto = "auto"
```

Valid values are:

| Value        | Description                                                                                                                         |
| ------------ | ----------------------------------------------------------------------------------------------------------------------------------- |
| `"auto"`     | Resolve modules from local `node_modules` if it exists. Otherwise, auto-install dependencies on the fly.                            |
| `"force"`    | Always auto-install dependencies, even if `node_modules` exists.                                                                    |
| `"disable"`  | Never auto-install dependencies.                                                                                                    |
| `"fallback"` | Check local `node_modules` first, then auto-install any packages that aren't found. You can enable this from the CLI with `bun -i`. |

### `install.prefer`

Configure how Bun resolves package versions against the npm registry when running scripts. Default `"online"`.

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[install]
prefer = "online"
```

Valid values are:

| Value       | Description                                                                                        |
| ----------- | -------------------------------------------------------------------------------------------------- |
| `"online"`  | Default. Check the registry for stale packages as needed.                                          |
| `"offline"` | Skip staleness checks and resolve packages from the local cache. Equivalent to `--prefer-offline`. |
| `"latest"`  | Always check npm for the latest matching versions. Equivalent to `--prefer-latest`.                |

### `install.frozenLockfile`

When `true`, `bun install` does not update `bun.lock`. Default `false`. If `package.json` and the existing `bun.lock` disagree, the install errors.

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[install]
frozenLockfile = false
```

### `install.dryRun`

Whether `bun install` resolves dependencies without installing them. Default `false`. When `true`, it's equivalent to passing `--dry-run` to all `bun install` commands.

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[install]
dryRun = false
```

### `install.globalDir`

The directory where Bun puts globally installed packages.

Environment variable: `BUN_INSTALL_GLOBAL_DIR`

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[install]
# where `bun install --global` installs packages
globalDir = "~/.bun/install/global"
```

### `install.globalBinDir`

The directory where Bun links the binaries of globally installed packages.

Environment variable: `BUN_INSTALL_BIN`

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[install]
# where globally-installed package bins are linked
globalBinDir = "~/.bun/bin"
```

### `install.registry`

The default registry is `https://registry.npmjs.org/`. To change it:

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[install]
# set default registry as a string
registry = "https://registry.npmjs.org"
# set a token
registry = { url = "https://registry.npmjs.org", token = "123456" }
# set a username/password
registry = "https://username:password@registry.npmjs.org"
```

### `install.linkWorkspacePackages`

Whether to link workspace packages from the monorepo root to their respective `node_modules` directories. Default `true`.

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[install]
linkWorkspacePackages = true
```

### `install.scopes`

To configure a registry for a particular scope (for example, `@myorg/<package>`), use `install.scopes`. You can reference environment variables with `$variable` notation.

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[install.scopes]
# registry as string
myorg = "https://username:password@registry.myorg.com/"

# registry with username/password
# you can reference environment variables
myorg = { username = "myusername", password = "$npm_password", url = "https://registry.myorg.com/" }

# registry with token
myorg = { token = "$npm_token", url = "https://registry.myorg.com/" }
```

### `install.ca` and `install.cafile`

To configure a CA certificate, set `install.ca` to the certificate string or `install.cafile` to the path of a certificate file.

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[install]
# The CA certificate as a string
ca = "-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----"

# A path to a CA certificate file. The file can contain multiple certificates.
cafile = "path/to/cafile"
```

### `install.cache`

To configure the cache behavior:

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[install.cache]

# the directory to use for the cache
dir = "~/.bun/install/cache"

# when true, don't load from the global cache.
# Bun may still write to node_modules/.cache
disable = false

# when true, always resolve the latest versions from the registry
disableManifest = false
```

### `install.lockfile`

Whether to generate a lockfile on `bun install`. Default `true`.

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[install.lockfile]
save = true
```

Whether to generate a non-Bun lockfile alongside `bun.lock`. (Bun always creates a `bun.lock`.) `"yarn"` is the only supported value.

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[install.lockfile]
print = "yarn"
```

### `install.linker`

Configure the linker strategy: how `bun install` lays out dependencies in `node_modules`. Defaults to `"isolated"` for new workspaces, `"hoisted"` for new single-package projects and existing projects (made pre-v1.3.2).

See [Isolated installs](/docs/pm/isolated-installs).

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[install]
linker = "hoisted"
```

Valid values are:

| Value        | Description                                             |
| ------------ | ------------------------------------------------------- |
| `"hoisted"`  | Link dependencies in a shared `node_modules` directory. |
| `"isolated"` | Link dependencies inside each package installation.     |

### `install.globalStore`

When using the `"isolated"` linker, share package installations across projects in a global virtual store at `<cache>/links/`. Bun links `node_modules/.bun/<pkg>@<ver>` into the store instead of materializing each package into the project. Makes warm installs after `rm -rf node_modules` an order of magnitude faster. Default `false`. You can also set it with the `BUN_INSTALL_GLOBAL_STORE` environment variable.

See [Global virtual store](/docs/pm/global-store).

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[install]
globalStore = true
```

### `install.publicHoistPattern`

When using the `"isolated"` linker, Bun hoists packages matching these glob patterns to the root `node_modules` directory so any package in the project can resolve them. Default `[]`. Similar to pnpm's `public-hoist-pattern`.

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[install]
publicHoistPattern = ["*eslint*", "*prettier*"]
```

### `install.hoistPattern`

When using the `"isolated"` linker, Bun hoists packages matching these glob patterns to a fallback directory inside the virtual store (`node_modules/.bun/node_modules`) so other packages in the virtual store can resolve them. By default Bun hoists every package there, equivalent to `["*"]`. Similar to pnpm's `hoist-pattern`.

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[install]
hoistPattern = ["*"]
```

### `install.hoist`

When using the `"isolated"` linker, Bun creates `node_modules/.bun/node_modules`. This fallback directory contains a symlink to every installed package, or only to the packages matching `install.hoistPattern` when one is set. The directory sits on the upward resolution path of every package in the store, so a package can still resolve dependencies it never declared ("phantom dependencies"). Set `hoist = false` to skip creating this directory entirely. An undeclared import from a store package then fails unless the imported package is linked at the project root `node_modules` (a direct dependency, a `publicHoistPattern` match, or a workspace package). The root `node_modules` stays on the resolution path because `.bun` lives inside it. The project's own `node_modules` symlinks are unchanged. Default `true`. Equivalent to pnpm's `hoist` setting, including this root-`node_modules` caveat; takes precedence over `install.hoistPattern`.

This setting only applies to the `"isolated"` linker. Hoisted installs are unaffected: there the flat `node_modules` tree is the layout itself, not something this switch controls.

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[install]
hoist = false
```

### `install.logLevel`

Set the log level for `bun install`. This can be one of `"debug"`, `"warn"`, or `"error"`.

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[install]
logLevel = "warn"
```

### `install.security.scanner`

Configure a security scanner to scan packages for vulnerabilities before installation.

First, install a security scanner from npm:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun add -d @oven/bun-security-scanner
```

<Note>
  `@oven/bun-security-scanner` is an example package name, not a real package. Replace it with the scanner you want to
  use, and consult that scanner's documentation for the exact package name and installation instructions. Most scanners
  are installed with `bun add`.
</Note>

Then configure it in your `bunfig.toml`:

```toml bunfig.toml icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[install.security]
scanner = "@oven/bun-security-scanner" # example name, replace with your scanner's package
```

When a security scanner is configured:

* Auto-install is automatically disabled for security
* Packages are scanned before installation
* Installation is cancelled if fatal issues are found
* Security warnings are displayed during installation

Learn more about [using and writing security scanners](/docs/pm/security-scanner-api).

### `install.minimumReleaseAge`

Configure a minimum age (in seconds) for npm package versions. During installation, Bun filters out package versions published more recently than this threshold. Default `null` (disabled).

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[install]
# Only install package versions published at least 3 days ago
minimumReleaseAge = 259200
```

See [Minimum release age](/docs/pm/cli/install#minimum-release-age).

### `install.minimumReleaseAgeExcludes`

An array of package names that are exempt from the `minimumReleaseAge` check. Default `[]`.

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[install]
minimumReleaseAge = 259200
# These packages will bypass the 3-day minimum age requirement
minimumReleaseAgeExcludes = ["@types/bun", "typescript"]
```

## `bun run`

The `[run]` section configures the `bun run` command. These settings also apply to the `bun` command when running a file, script, or executable.

For `bun run`, Bun only loads the local project's `bunfig.toml` automatically (it doesn't check for a global `.bunfig.toml`).

### `run.shell` - use the system shell or Bun's shell

The shell used to run `package.json` scripts with `bun run` or `bun`. Defaults to `"bun"` on Windows and `"system"` on other platforms.

To always use the system shell instead of Bun's shell (the default everywhere except Windows):

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[run]
# default outside of Windows
shell = "system"
```

To always use Bun's shell instead of the system shell:

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[run]
# default on Windows
shell = "bun"
```

### `run.bun` - auto alias `node` to `bun`

When `true`, Bun prepends `$PATH` with a `node` symlink that points to the `bun` binary for all scripts or executables invoked by `bun run` or `bun`.

A script that runs `node` runs `bun` instead, with no changes to the script. This works recursively, so a script that runs another script that runs `node` also runs `bun`. The alias also applies to shebangs that point to `node`.

By default, this is enabled if `node` is not already in your `$PATH`.

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[run]
# equivalent to `bun --bun` for all `bun run` commands
bun = true
```

You can test this by running:

```sh theme={"theme":{"light":"github-light","dark":"dracula"}}
bun --bun which node # /path/to/bun
bun which node # /path/to/node
```

This option is equivalent to prefixing all `bun run` commands with `--bun`:

```sh theme={"theme":{"light":"github-light","dark":"dracula"}}
bun --bun run dev
bun --bun dev
bun run --bun dev
```

Set to `false` to disable the `node` symlink.

### `run.silent` - suppress reporting the command being run

When `true`, `bun run` and `bun` don't print the command being run.

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[run]
silent = true
```

Without this option, Bun prints the command being run to the console:

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun run dev
echo "Running \"dev\"..."
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
Running "dev"...
```

With this option, Bun does not print the command being run:

```sh theme={"theme":{"light":"github-light","dark":"dracula"}}
bun run dev
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
Running "dev"...
```

This is equivalent to passing `--silent` to all `bun run` commands:

```sh theme={"theme":{"light":"github-light","dark":"dracula"}}
bun --silent run dev
bun --silent dev
bun run --silent dev
```

### `run.elide-lines` - truncate filtered output

The number of lines of script output shown per script when using `--filter`. Default `10`. Set to `0` to show all lines. Equivalent to the `--elide-lines` flag.

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[run]
elide-lines = 10
```

### `run.noOrphans` - don't leave orphan processes behind

When `true`, Bun watches the process that spawned it and exits as soon as that parent goes away, even if the parent was force-killed and never got a chance to forward a signal. On its own exit, Bun also terminates every descendant process so nothing it spawned outlives it. Useful when Bun is launched by a supervisor (Electron, a CI runner, a thin shim) that may be force-killed.

On Linux, Bun uses `prctl(PR_SET_PDEATHSIG)` and a `/proc` descendant walk. On macOS, it uses `EVFILT_PROC`/`NOTE_EXIT` on the event loop's kqueue and a libproc descendant walk. On Windows, it uses a thread-pool wait on the parent's process handle and a kill-on-close Job Object.

Equivalent to the `--no-orphans` CLI flag or the `BUN_FEATURE_FLAG_NO_ORPHANS=1` environment variable.

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[run]
noOrphans = true
```


# C Compiler
Source: https://bun.com/docs/runtime/c-compiler

Compile and run C from JavaScript with low overhead

`bun:ffi` has experimental support for compiling and running C from JavaScript with low overhead.

***

## Usage (cc in `bun:ffi`)

See the [introduction blog post](https://bun.com/blog/compile-and-run-c-in-js) for background.

JavaScript:

```ts hello.ts icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { cc } from "bun:ffi";
import source from "./hello.c" with { type: "file" };

const {
  symbols: { hello },
} = cc({
  source,
  symbols: {
    hello: {
      args: [],
      returns: "int",
    },
  },
});

console.log("What is the answer to the universe?", hello());
```

C source:

```c hello.c theme={"theme":{"light":"github-light","dark":"dracula"}}
int hello() {
  return 42;
}
```

Running `hello.ts` prints:

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun hello.ts
What is the answer to the universe? 42
```

`cc` uses [TinyCC](https://bellard.org/tcc/) to compile the C code, then links it with the JavaScript runtime, converting types in-place.

### Primitive types

`cc` supports the same `FFIType` values as [`dlopen`](/docs/runtime/ffi).

| `FFIType`   | C Type         | Aliases                     |
| ----------- | -------------- | --------------------------- |
| cstring     | `char*`        |                             |
| function    | `(void*)(*)()` | `fn`, `callback`            |
| ptr         | `void*`        | `pointer`, `void*`, `char*` |
| i8          | `int8_t`       | `int8_t`                    |
| i16         | `int16_t`      | `int16_t`                   |
| i32         | `int32_t`      | `int32_t`, `int`            |
| i64         | `int64_t`      | `int64_t`                   |
| i64\_fast   | `int64_t`      |                             |
| u8          | `uint8_t`      | `uint8_t`                   |
| u16         | `uint16_t`     | `uint16_t`                  |
| u32         | `uint32_t`     | `uint32_t`                  |
| u64         | `uint64_t`     | `uint64_t`, `usize`         |
| u64\_fast   | `uint64_t`     |                             |
| f32         | `float`        | `float`                     |
| f64         | `double`       | `double`                    |
| bool        | `bool`         |                             |
| char        | `char`         |                             |
| napi\_env   | `napi_env`     |                             |
| napi\_value | `napi_value`   |                             |

### Strings, objects, and non-primitive types

For strings, objects, and other non-primitive types that don't map 1:1 to C types, `cc` supports N-API.

Use `napi_value` to pass or receive JavaScript values from a C function without any type conversions.

You can also pass a `napi_env` to receive the N-API environment used to call the JavaScript function.

#### Returning a C string to JavaScript

For example, to return a string from C to JavaScript:

```ts hello.ts theme={"theme":{"light":"github-light","dark":"dracula"}}
import { cc } from "bun:ffi";
import source from "./hello.c" with { type: "file" };

const {
  symbols: { hello },
} = cc({
  source,
  symbols: {
    hello: {
      args: ["napi_env"],
      returns: "napi_value",
    },
  },
});

const result = hello();
```

And in C:

```c hello.c theme={"theme":{"light":"github-light","dark":"dracula"}}
#include <node/node_api.h>

napi_value hello(napi_env env) {
  napi_value result;
  napi_create_string_utf8(env, "Hello, Napi!", NAPI_AUTO_LENGTH, &result);
  return result;
}
```

The same approach returns other types like objects and arrays:

```c hello.c theme={"theme":{"light":"github-light","dark":"dracula"}}
#include <node/node_api.h>

napi_value hello(napi_env env) {
  napi_value result;
  napi_create_object(env, &result);
  return result;
}
```

### `cc` Reference

#### `library: string[]`

Use the `library` array to specify the libraries to link with the C code.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
type Library = string[];

cc({
  source: "hello.c",
  library: ["sqlite3"],
});
```

#### `symbols`

Use the `symbols` object to specify the functions and variables to expose to JavaScript.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
type Symbols = {
  [key: string]: {
    args: FFIType[];
    returns: FFIType;
  };
};
```

#### `source`

`source` is the path to the C code to compile and link with the JavaScript runtime.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
type Source = string | URL | BunFile;

cc({
  source: "hello.c",
  symbols: {
    hello: {
      args: [],
      returns: "int",
    },
  },
});
```

#### `flags: string | string[]`

`flags` is an optional string or array of strings passed to the TinyCC compiler.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
type Flags = string | string[];
```

These are flags like `-I` for include directories and `-D` for preprocessor definitions.

#### `define: Record<string, string>`

`define` is an optional object of preprocessor definitions passed to the TinyCC compiler.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
type Defines = Record<string, string>;

cc({
  source: "hello.c",
  define: {
    NDEBUG: "1",
  },
});
```


# Spawn
Source: https://bun.com/docs/runtime/child-process

Spawn child processes with `Bun.spawn` or `Bun.spawnSync`

## Spawn a process (`Bun.spawn()`)

Provide a command as an array of strings. The result of `Bun.spawn()` is a `Bun.Subprocess` object.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const proc = Bun.spawn(["bun", "--version"]);
console.log(await proc.exited); // 0
```

The second argument to `Bun.spawn` is a parameters object that configures the subprocess.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const proc = Bun.spawn(["bun", "--version"], {
  cwd: "./path/to/subdir", // specify a working directory
  env: { ...process.env, FOO: "bar" }, // specify environment variables
  onExit(proc, exitCode, signalCode, error) {
    // exit handler
  },
});

proc.pid; // process ID of subprocess
```

## Input stream

By default, the input stream of the subprocess is undefined; configure it with the `stdin` parameter.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const proc = Bun.spawn(["cat"], {
  stdin: await fetch("https://raw.githubusercontent.com/oven-sh/bun/main/examples/hashing.js"),
});

const text = await proc.stdout.text();
console.log(text); // "const input = "hello world".repeat(400); ..."
```

| Value                    | Description                                      |
| ------------------------ | ------------------------------------------------ |
| `null`                   | **Default.** Provide no input to the subprocess  |
| `"pipe"`                 | Return a `FileSink` for fast incremental writing |
| `"inherit"`              | Inherit the `stdin` of the parent process        |
| `Bun.file()`             | Read from the specified file                     |
| `TypedArray \| DataView` | Use a binary buffer as input                     |
| `Response`               | Use the response `body` as input                 |
| `Request`                | Use the request `body` as input                  |
| `ReadableStream`         | Use a readable stream as input                   |
| `Blob`                   | Use a blob as input                              |
| `number`                 | Read from the file with a given file descriptor  |

With `"pipe"`, the parent process can incrementally write to the subprocess's input stream.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const proc = Bun.spawn(["cat"], {
  stdin: "pipe", // return a FileSink for writing
});

// enqueue string data
proc.stdin.write("hello");

// enqueue binary data
const enc = new TextEncoder();
proc.stdin.write(enc.encode(" world!"));

// send buffered data
proc.stdin.flush();

// close the input stream
proc.stdin.end();
```

Passing a `ReadableStream` to `stdin` pipes its data directly to the subprocess's input:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const stream = new ReadableStream({
  start(controller) {
    controller.enqueue("Hello from ");
    controller.enqueue("ReadableStream!");
    controller.close();
  },
});

const proc = Bun.spawn(["cat"], {
  stdin: stream,
  stdout: "pipe",
});

const output = await proc.stdout.text();
console.log(output); // "Hello from ReadableStream!"
```

## Output streams

Read the subprocess's output from the `stdout` and `stderr` properties. By default `stdout` is an instance of `ReadableStream`; `stderr` is inherited from the parent process, so `proc.stderr` is `undefined` unless you pass `stderr: "pipe"`.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const proc = Bun.spawn(["bun", "--version"]);
const text = await proc.stdout.text();
console.log(text); // => "1.3.3\n"
```

Configure the output stream by passing one of the following values to `stdout/stderr`:

| Value        | Description                                                                                         |
| ------------ | --------------------------------------------------------------------------------------------------- |
| `"pipe"`     | **Default for `stdout`.** Pipe the output to a `ReadableStream` on the returned `Subprocess` object |
| `"inherit"`  | **Default for `stderr`.** Inherit from the parent process                                           |
| `"ignore"`   | Discard the output                                                                                  |
| `Bun.file()` | Write to the specified file                                                                         |
| `number`     | Write to the file with the given file descriptor                                                    |

## Exit handling

Use the `onExit` callback to listen for the process exiting or being killed.

```ts index.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
const proc = Bun.spawn(["bun", "--version"], {
  onExit(proc, exitCode, signalCode, error) {
    // exit handler
  },
});
```

The `exited` property is a `Promise` that resolves when the process exits.

```ts index.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
const proc = Bun.spawn(["bun", "--version"]);

await proc.exited; // resolves when process exit
proc.killed; // boolean — was the process killed?
proc.exitCode; // null | number
proc.signalCode; // null | "SIGABRT" | "SIGALRM" | ...
```

To kill a process:

```ts index.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
const proc = Bun.spawn(["bun", "--version"]);
proc.kill();
proc.killed; // true

proc.kill(15); // specify a signal code
proc.kill("SIGTERM"); // specify a signal name
```

The parent `bun` process does not terminate until all child processes have exited. Use `proc.unref()` to detach the child process from the parent.

```ts index.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
const proc = Bun.spawn(["bun", "--version"]);
proc.unref();
```

## Resource usage

After the process exits, `resourceUsage()` reports its resource usage:

```ts index.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
const proc = Bun.spawn(["bun", "--version"]);
await proc.exited;

const usage = proc.resourceUsage();
console.log(`Max memory used: ${usage.maxRSS} bytes`);
console.log(`CPU time (user): ${usage.cpuTime.user} µs`);
console.log(`CPU time (system): ${usage.cpuTime.system} µs`);
```

## Resource limits with cgroups (Linux)

On Linux, pass `cgroup` to start the subprocess inside a [control group](https://docs.kernel.org/admin-guide/cgroup-v2.html). The child joins the cgroup before it begins executing, so limits configured on the cgroup (memory, pids, CPU) apply from the first instruction. They also apply to every process the child spawns in turn. When a memory limit is exceeded the kernel OOM-kills a process *inside* the cgroup instead of reclaiming memory from the parent.

A cgroup is a directory under `/sys/fs/cgroup`; create and configure it with ordinary file operations, then pass its path (or an open directory file descriptor):

```ts index.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { mkdirSync, writeFileSync } from "node:fs";

const dir = "/sys/fs/cgroup/build-jobs";
mkdirSync(dir, { recursive: true });
writeFileSync(`${dir}/memory.max`, String(2 * 1024 ** 3)); // cgroup v1: memory.limit_in_bytes

const proc = Bun.spawn({
  cmd: ["make", "-j8"],
  cgroup: dir,
});
```

You can pass the same directory to any number of spawns; the limit applies to their combined usage. Bun supports both cgroup v1 and v2 hierarchies. Creating cgroups typically requires root or a delegated subtree. Bun ignores the option on other platforms; on Linux, the spawn fails if the child cannot join the cgroup.

## Using AbortSignal

You can abort a subprocess using an `AbortSignal`:

```ts index.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
const controller = new AbortController();
const { signal } = controller;

const proc = Bun.spawn({
  cmd: ["sleep", "100"],
  signal,
});

// Later, to abort the process:
controller.abort();
```

## Using timeout and killSignal

Set `timeout` to terminate a subprocess after a duration in milliseconds:

```ts index.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
// Kill the process after 5 seconds
const proc = Bun.spawn({
  cmd: ["sleep", "10"],
  timeout: 5000, // 5 seconds in milliseconds
});

await proc.exited; // Will resolve after 5 seconds
```

By default, Bun kills timed-out processes with `SIGTERM`. Specify a different signal with the `killSignal` option:

```ts index.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
// Kill the process with SIGKILL after 5 seconds
const proc = Bun.spawn({
  cmd: ["sleep", "10"],
  timeout: 5000,
  killSignal: "SIGKILL", // Can be string name or signal number
});
```

The `killSignal` option also controls which signal Bun sends when an AbortSignal is aborted.

## Using maxBuffer

For `Bun.spawnSync`, `maxBuffer` limits how many bytes of output the process can emit before Bun kills it:

```ts index.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
// Kill 'yes' after it emits over 100 bytes of output
const result = Bun.spawnSync({
  cmd: ["yes"], // or ["bun", "exec", "yes"] on Windows
  maxBuffer: 100,
});
// process exits
```

Bun stops reading as soon as the limit is passed. The returned output can
therefore exceed `maxBuffer` only by the single read that passed it, never by
whatever the process manages to write before the kill lands. This matches
Node.js.

## Inter-process communication (IPC)

Bun supports a direct inter-process communication channel between two `bun` processes. To receive messages from a spawned Bun subprocess, specify an `ipc` handler.

```ts parent.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
const child = Bun.spawn(["bun", "child.ts"], {
  ipc(message) {
    /**
     * The message received from the sub process
     **/
  },
});
```

The parent process sends messages to the subprocess with the `.send()` method on the returned `Subprocess` instance. The `ipc` handler also receives the sending subprocess as its second argument.

```ts parent.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
const childProc = Bun.spawn(["bun", "child.ts"], {
  ipc(message, childProc) {
    /**
     * The message received from the sub process
     **/
    childProc.send("Respond to child");
  },
});

childProc.send("I am your father"); // The parent can send messages to the child as well
```

The child process sends messages to its parent with `process.send()` and receives them with `process.on("message")`. Node.js uses the same API for `child_process.fork()`.

```ts child.ts theme={"theme":{"light":"github-light","dark":"dracula"}}
process.send("Hello from child as string");
process.send({ message: "Hello from child as object" });

process.on("message", message => {
  // print message from parent
  console.log(message);
});
```

```ts child.ts theme={"theme":{"light":"github-light","dark":"dracula"}}
// send a string
process.send("Hello from child as string");

// send an object
process.send({ message: "Hello from child as object" });
```

The `serialization` option controls the underlying communication format between the two processes:

* `advanced`: (default) Bun serializes messages using the JSC `serialize` API, which supports cloning [everything `structuredClone` supports](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm). This does not support transferring ownership of objects.
* `json`: Bun serializes messages using `JSON.stringify` and `JSON.parse`, which does not support as many object types as `advanced` does.

To disconnect the IPC channel from the parent process, call:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
childProc.disconnect();
```

### IPC between Bun & Node.js

To use IPC between a `bun` process and a Node.js process, set `serialization: "json"` in `Bun.spawn`. This is because Node.js and Bun use different JavaScript engines with different object serialization formats.

```js bun-node-ipc.js icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
if (typeof Bun !== "undefined") {
  const prefix = `[bun ${process.versions.bun} 🐇]`;
  const node = Bun.spawn({
    cmd: ["node", __filename],
    ipc({ message }) {
      console.log(message);
      node.send({ message: `${prefix} 👋 hey node` });
      node.kill();
    },
    stdio: ["inherit", "inherit", "inherit"],
    serialization: "json",
  });

  node.send({ message: `${prefix} 👋 hey node` });
} else {
  const prefix = `[node ${process.version}]`;
  process.on("message", ({ message }) => {
    console.log(message);
    process.send({ message: `${prefix} 👋 hey bun` });
  });
}
```

***

## Terminal (PTY) support

For interactive terminal applications, use the `terminal` option to spawn a subprocess with a pseudo-terminal (PTY) attached. The subprocess sees a real terminal, which enables colored output, cursor movement, and interactive prompts.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const proc = Bun.spawn(["bash"], {
  terminal: {
    cols: 80,
    rows: 24,
    data(terminal, data) {
      // Called when data is received from the terminal
      process.stdout.write(data);
    },
  },
});

// Write to the terminal
proc.terminal.write("echo hello\n");

// Wait for the process to exit
await proc.exited;

// Close the terminal
proc.terminal.close();
```

When you pass the `terminal` option:

* The subprocess sees `process.stdout.isTTY` as `true`
* `stdin`, `stdout`, and `stderr` are all connected to the terminal
* `proc.stdin`, `proc.stdout`, and `proc.stderr` return `null` — use the terminal instead
* Access the terminal via `proc.terminal`

### Terminal options

| Option  | Description                                                                                                                                                        | Default            |
| ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------ |
| `cols`  | Number of columns                                                                                                                                                  | `80`               |
| `rows`  | Number of rows                                                                                                                                                     | `24`               |
| `name`  | Terminal type for PTY configuration (set the `TERM` env var separately with the `env` option)                                                                      | `"xterm-256color"` |
| `data`  | Callback when data is received `(terminal, data) => void`                                                                                                          | —                  |
| `exit`  | Callback when PTY stream closes (EOF or error). `exitCode` is PTY lifecycle status (0=EOF, 1=error), not subprocess exit code. Use `proc.exited` for process exit. | —                  |
| `drain` | Callback when ready for more data `(terminal) => void`                                                                                                             | —                  |

### Terminal methods

The `Terminal` object returned by `proc.terminal` has the following methods:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
// Write data to the terminal
proc.terminal.write("echo hello\n");

// Resize the terminal
proc.terminal.resize(120, 40);

// Set raw mode (disable line buffering and echo)
proc.terminal.setRawMode(true);

// Keep event loop alive while terminal is open
proc.terminal.ref();
proc.terminal.unref();

// Close the terminal
proc.terminal.close();
```

### Reusable Terminal

To run multiple commands in sequence through the same terminal session, create a terminal independently and reuse it across subprocesses:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
await using terminal = new Bun.Terminal({
  cols: 80,
  rows: 24,
  data(term, data) {
    process.stdout.write(data);
  },
});

// Spawn first process
const proc1 = Bun.spawn(["echo", "first"], { terminal });
await proc1.exited;

// Reuse terminal for another process
const proc2 = Bun.spawn(["echo", "second"], { terminal });
await proc2.exited;

// Terminal is closed automatically by `await using`
```

When passing an existing `Terminal` object:

* You can reuse the terminal across multiple spawns
* You control when to close the terminal
* The `exit` callback fires when you call `terminal.close()`, not when each subprocess exits
* Use `proc.exited` to detect individual subprocess exits

### Platform differences

`Bun.Terminal` uses `openpty()` on Linux and macOS, and ConPTY (`CreatePseudoConsole`) on Windows. The core behavior is the same on every platform: the child sees a TTY, `write()` reaches the child's stdin, child output reaches the `data` callback, and `resize()` updates the child's view. A few details differ:

* **No termios on Windows.** `inputFlags`, `outputFlags`, `localFlags`, and `controlFlags` always read as `0` and setting them is a no-op. `setRawMode()` records the flag but has no effect on the child; the child controls its own console mode.
* **No echo without a child process on Windows.** On POSIX, the kernel line discipline echoes `write()` input back to the `data` callback even with no process attached. ConPTY has no line discipline; it buffers input for the next reader. If you need echo, spawn a process that echoes.
* **ConPTY re-encodes output.** ConPTY renders the child's output to a virtual screen and emits whatever VT sequences describe the result. The `data` callback therefore receives semantically equivalent, but not byte-identical, escape sequences. ConPTY preserves colors and text; it may reorder or coalesce cursor-positioning and reset sequences. ConPTY also emits a short VT init sequence (`\x1b[?9001h\x1b[?1004h…`) before any child output.
* **Input `\r` is not translated to `\n` on Windows.** POSIX `ICRNL` maps carriage return to newline on input; ConPTY passes `\r` through unchanged.
* **`process.on('SIGWINCH')` in the child does not fire under ConPTY** unless the child is reading stdin in raw mode. `process.stdout.columns`/`rows` do update after `resize()`. The missing signal is a libuv limitation that affects any libuv-based child (Node.js included).
* On Windows before 11 24H2 (build 26100), `terminal.close()` may not terminate a still-running child promptly. The delay comes from [`ClosePseudoConsole`](https://learn.microsoft.com/en-us/windows/console/closepseudoconsole), which blocks on those versions until conhost has flushed its output through the pipe. Kill the attached process first if you need to tear down with a running child.

***

## Blocking API (`Bun.spawnSync()`)

`Bun.spawnSync` is the blocking equivalent of `Bun.spawn`. It supports the same inputs and parameters and returns a `SyncSubprocess` object, which differs from `Subprocess` in a few ways.

1. The returned object contains a `success` property that indicates whether the process exited with a zero exit code.
2. The `stdout` and `stderr` properties are instances of `Buffer` instead of `ReadableStream`.
3. There is no `stdin` property. Use `Bun.spawn` to incrementally write to the subprocess's input stream.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const proc = Bun.spawnSync(["echo", "hello"]);

console.log(proc.stdout.toString());
// => "hello\n"
```

As a rule of thumb, the asynchronous `Bun.spawn` API is better for HTTP servers and apps, and `Bun.spawnSync` is better for building command-line tools.

***

## Benchmarks

<Note>
  ⚡️ `Bun.spawn` and `Bun.spawnSync` use [`posix_spawn(3)`](https://man7.org/linux/man-pages/man3/posix_spawn.3.html).
</Note>

Bun's `spawnSync` spawns processes 60% faster than the Node.js `child_process` module.

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun spawn.mjs
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
cpu: Apple M1 Max
runtime: bun 1.x (arm64-darwin)

benchmark              time (avg)             (min … max)       p75       p99      p995
--------------------------------------------------------- -----------------------------
spawnSync echo hi  888.14 µs/iter    (821.83 µs … 1.2 ms) 905.92 µs      1 ms   1.03 ms
```

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
node spawn.node.mjs
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
cpu: Apple M1 Max
runtime: node v18.9.1 (arm64-darwin)

benchmark              time (avg)             (min … max)       p75       p99      p995
--------------------------------------------------------- -----------------------------
spawnSync echo hi    1.47 ms/iter     (1.14 ms … 2.64 ms)   1.57 ms   2.37 ms   2.52 ms
```

***

## Reference

The following is a reference of the Spawn API and types. The real types have complex generics to strongly type the `Subprocess` streams with the options passed to `Bun.spawn` and `Bun.spawnSync`. For full details, see [bun.d.ts](https://github.com/oven-sh/bun/blob/main/packages/bun-types/bun.d.ts).

```ts See Typescript Definitions expandable theme={"theme":{"light":"github-light","dark":"dracula"}}
interface Bun {
  spawn(command: string[], options?: SpawnOptions.OptionsObject): Subprocess;
  spawnSync(command: string[], options?: SpawnOptions.OptionsObject): SyncSubprocess;

  spawn(options: { cmd: string[] } & SpawnOptions.OptionsObject): Subprocess;
  spawnSync(options: { cmd: string[] } & SpawnOptions.OptionsObject): SyncSubprocess;
}

namespace SpawnOptions {
  interface OptionsObject {
    cwd?: string;
    env?: Record<string, string | undefined>;
    stdio?: [Writable, Readable, Readable];
    stdin?: Writable;
    stdout?: Readable;
    stderr?: Readable;
    onExit?(
      subprocess: Subprocess,
      exitCode: number | null,
      signalCode: number | null,
      error?: ErrorLike,
    ): void | Promise<void>;
    ipc?(message: any, subprocess: Subprocess): void;
    serialization?: "json" | "advanced";
    windowsHide?: boolean;
    windowsVerbatimArguments?: boolean;
    argv0?: string;
    signal?: AbortSignal;
    timeout?: number;
    killSignal?: string | number;
    maxBuffer?: number;
    cgroup?: string | number; // Linux only; cgroup directory path or open directory fd
    terminal?: TerminalOptions | Terminal; // PTY (POSIX) / ConPTY (Windows) support
  }

  type Readable =
    | "pipe"
    | "inherit"
    | "ignore"
    | null // equivalent to "ignore"
    | undefined // to use default
    | BunFile
    | ArrayBufferView
    | number;

  type Writable =
    | "pipe"
    | "inherit"
    | "ignore"
    | null // equivalent to "ignore"
    | undefined // to use default
    | BunFile
    | ArrayBufferView
    | number
    | ReadableStream
    | Blob
    | Response
    | Request;
}

interface Subprocess extends AsyncDisposable {
  readonly stdin: FileSink | number | undefined | null;
  readonly stdout: ReadableStream<Uint8Array<ArrayBuffer>> | number | undefined | null;
  readonly stderr: ReadableStream<Uint8Array<ArrayBuffer>> | number | undefined | null;
  readonly readable: ReadableStream<Uint8Array<ArrayBuffer>> | number | undefined | null;
  readonly terminal: Terminal | undefined;
  readonly pid: number;
  readonly exited: Promise<number>;
  readonly exitCode: number | null;
  readonly signalCode: NodeJS.Signals | null;
  readonly killed: boolean;

  kill(exitCode?: number | NodeJS.Signals): void;
  ref(): void;
  unref(): void;

  send(message: any): void;
  disconnect(): void;
  resourceUsage(): ResourceUsage | undefined;
}

interface SyncSubprocess {
  stdout: Buffer | undefined;
  stderr: Buffer | undefined;
  exitCode: number;
  success: boolean;
  resourceUsage: ResourceUsage;
  signalCode?: string;
  exitedDueToTimeout?: true;
  pid: number;
}

interface TerminalOptions {
  cols?: number;
  rows?: number;
  name?: string;
  data?: (terminal: Terminal, data: Uint8Array<ArrayBuffer>) => void;
  /** Called when PTY stream closes (EOF or error). exitCode is PTY lifecycle status (0=EOF, 1=error), not subprocess exit code. */
  exit?: (terminal: Terminal, exitCode: number, signal: string | null) => void;
  drain?: (terminal: Terminal) => void;
}

interface Terminal extends AsyncDisposable {
  readonly closed: boolean;
  inputFlags: number; // termios c_iflag
  outputFlags: number; // termios c_oflag
  localFlags: number; // termios c_lflag
  controlFlags: number; // termios c_cflag
  write(data: string | BufferSource): number;
  resize(cols: number, rows: number): void;
  setRawMode(enabled: boolean): void;
  ref(): void;
  unref(): void;
  close(): void;
}

interface ResourceUsage {
  contextSwitches: {
    voluntary: number;
    involuntary: number;
  };

  cpuTime: {
    user: number;
    system: number;
    total: number;
  };
  maxRSS: number;

  messages: {
    sent: number;
    received: number;
  };
  ops: {
    in: number;
    out: number;
  };
  shmSize: number;
  signalCount: number;
  swapCount: number;
}

type Signal =
  | "SIGABRT"
  | "SIGALRM"
  | "SIGBUS"
  | "SIGCHLD"
  | "SIGCONT"
  | "SIGFPE"
  | "SIGHUP"
  | "SIGILL"
  | "SIGINT"
  | "SIGIO"
  | "SIGIOT"
  | "SIGKILL"
  | "SIGPIPE"
  | "SIGPOLL"
  | "SIGPROF"
  | "SIGPWR"
  | "SIGQUIT"
  | "SIGSEGV"
  | "SIGSTKFLT"
  | "SIGSTOP"
  | "SIGSYS"
  | "SIGTERM"
  | "SIGTRAP"
  | "SIGTSTP"
  | "SIGTTIN"
  | "SIGTTOU"
  | "SIGUNUSED"
  | "SIGURG"
  | "SIGUSR1"
  | "SIGUSR2"
  | "SIGVTALRM"
  | "SIGWINCH"
  | "SIGXCPU"
  | "SIGXFSZ"
  | "SIGBREAK"
  | "SIGLOST"
  | "SIGINFO";
```


# Color
Source: https://bun.com/docs/runtime/color

Format colors as CSS, ANSI, numbers, hex strings, and more

`Bun.color(input, outputFormat?)` uses Bun's CSS parser to parse, normalize, and convert colors from user input to any of these output formats:

| Format       | Example                          |
| ------------ | -------------------------------- |
| `"css"`      | `"red"`                          |
| `"ansi"`     | `"\x1b[38;2;255;0;0m"`           |
| `"ansi-16"`  | `"\x1b[91m"`                     |
| `"ansi-256"` | `"\x1b[38;5;196m"`               |
| `"ansi-16m"` | `"\x1b[38;2;255;0;0m"`           |
| `"number"`   | `0x1a2b3c`                       |
| `"rgb"`      | `"rgb(255, 99, 71)"`             |
| `"rgba"`     | `"rgba(255, 99, 71, 0.5)"`       |
| `"hsl"`      | `"hsl(120, 50%, 50%)"`           |
| `"hex"`      | `"#1a2b3c"`                      |
| `"HEX"`      | `"#1A2B3C"`                      |
| `"{rgb}"`    | `{ r: 255, g: 99, b: 71 }`       |
| `"{rgba}"`   | `{ r: 255, g: 99, b: 71, a: 1 }` |
| `"[rgb]"`    | `[ 255, 99, 71 ]`                |
| `"[rgba]"`   | `[ 255, 99, 71, 255]`            |

Use it to:

* Validate and normalize colors to persist in a database (`number` is the most database-friendly)
* Convert colors to different formats
* Color terminal output beyond the basic 16 colors (use `ansi` to auto-detect terminal color support, or `ansi-16`, `ansi-256`, or `ansi-16m` to target a specific color depth)
* Format colors for use in CSS injected into HTML
* Get the `r`, `g`, `b`, and `a` color components as JavaScript objects or numbers from a CSS color string

It's a built-in alternative to the npm packages [`color`](https://github.com/Qix-/color) and [`tinycolor2`](https://github.com/bgrins/TinyColor), with full support for parsing CSS color strings and zero dependencies.

### Flexible input

`Bun.color` accepts any of the following:

* Standard CSS color names like `"red"`
* Numbers like `0xff0000`
* Hex strings like `"#f00"`
* RGB strings like `"rgb(255, 0, 0)"`
* RGBA strings like `"rgba(255, 0, 0, 1)"`
* HSL strings like `"hsl(0, 100%, 50%)"`
* HSLA strings like `"hsla(0, 100%, 50%, 1)"`
* RGB objects like `{ r: 255, g: 0, b: 0 }`
* RGBA objects like `{ r: 255, g: 0, b: 0, a: 1 }`
* RGB arrays like `[255, 0, 0]`
* RGBA arrays like `[255, 0, 0, 255]`
* LAB strings like `"lab(50% 50 50)"`
* ... anything else that CSS can parse as a single color value

### Format colors as CSS

The `"css"` format outputs valid CSS for use in stylesheets, inline styles, CSS variables, or CSS-in-JS. It returns the most compact string representation of the color.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
Bun.color("red", "css"); // "red"
Bun.color(0xff0000, "css"); // "red"
Bun.color("#f00", "css"); // "red"
Bun.color("#ff0000", "css"); // "red"
Bun.color("rgb(255, 0, 0)", "css"); // "red"
Bun.color("rgba(255, 0, 0, 1)", "css"); // "red"
Bun.color("hsl(0, 100%, 50%)", "css"); // "red"
Bun.color("hsla(0, 100%, 50%, 1)", "css"); // "red"
Bun.color({ r: 255, g: 0, b: 0 }, "css"); // "red"
Bun.color({ r: 255, g: 0, b: 0, a: 1 }, "css"); // "red"
Bun.color([255, 0, 0], "css"); // "red"
Bun.color([255, 0, 0, 255], "css"); // "red"
```

If the input is unknown or fails to parse, `Bun.color` returns `null`.

### Format colors as ANSI (for terminals)

The `"ansi"` format outputs ANSI escape codes that color text in terminals.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
Bun.color("red", "ansi"); // "\u001b[38;2;255;0;0m"
Bun.color(0xff0000, "ansi"); // "\u001b[38;2;255;0;0m"
Bun.color("#f00", "ansi"); // "\u001b[38;2;255;0;0m"
Bun.color("#ff0000", "ansi"); // "\u001b[38;2;255;0;0m"
Bun.color("rgb(255, 0, 0)", "ansi"); // "\u001b[38;2;255;0;0m"
Bun.color("rgba(255, 0, 0, 1)", "ansi"); // "\u001b[38;2;255;0;0m"
Bun.color("hsl(0, 100%, 50%)", "ansi"); // "\u001b[38;2;255;0;0m"
Bun.color("hsla(0, 100%, 50%, 1)", "ansi"); // "\u001b[38;2;255;0;0m"
Bun.color({ r: 255, g: 0, b: 0 }, "ansi"); // "\u001b[38;2;255;0;0m"
Bun.color({ r: 255, g: 0, b: 0, a: 1 }, "ansi"); // "\u001b[38;2;255;0;0m"
Bun.color([255, 0, 0], "ansi"); // "\u001b[38;2;255;0;0m"
Bun.color([255, 0, 0, 255], "ansi"); // "\u001b[38;2;255;0;0m"
```

The `"ansi"` format detects the color depth of stdout from environment variables and picks `"ansi-16m"`, `"ansi-256"`, or `"ansi-16"` accordingly. If stdout doesn't support any form of ANSI color, it returns an empty string. As with the rest of Bun's color API, if the input is unknown or fails to parse, it returns `null`.

#### 24-bit ANSI colors (`ansi-16m`)

The `"ansi-16m"` format outputs 24-bit ANSI colors, which can display 16 million colors but require a modern terminal that supports them.

Bun converts the input color to RGBA, then outputs that as an ANSI color.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
Bun.color("red", "ansi-16m"); // "\x1b[38;2;255;0;0m"
Bun.color(0xff0000, "ansi-16m"); // "\x1b[38;2;255;0;0m"
Bun.color("#f00", "ansi-16m"); // "\x1b[38;2;255;0;0m"
Bun.color("#ff0000", "ansi-16m"); // "\x1b[38;2;255;0;0m"
```

#### 256 ANSI colors (`ansi-256`)

The `"ansi-256"` format approximates the input color to the nearest of the 256 ANSI colors supported by some terminals.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
Bun.color("red", "ansi-256"); // "\u001b[38;5;196m"
Bun.color(0xff0000, "ansi-256"); // "\u001b[38;5;196m"
Bun.color("#f00", "ansi-256"); // "\u001b[38;5;196m"
Bun.color("#ff0000", "ansi-256"); // "\u001b[38;5;196m"
```

To convert from RGBA to one of the 256 ANSI colors, we ported the algorithm that [`tmux` uses](https://github.com/tmux/tmux/blob/dae2868d1227b95fd076fb4a5efa6256c7245943/colour.c#L44-L55).

#### 16 ANSI colors (`ansi-16`)

The `"ansi-16"` format approximates the input color to the nearest of the 16 ANSI colors supported by most terminals.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
Bun.color("red", "ansi-16"); // "\u001b[91m"
Bun.color(0xff0000, "ansi-16"); // "\u001b[91m"
Bun.color("#f00", "ansi-16"); // "\u001b[91m"
Bun.color("#ff0000", "ansi-16"); // "\u001b[91m"
```

Bun converts the input to a 24-bit RGB color space, then to `ansi-256`, then to the nearest of the 16 ANSI colors.

### Format colors as numbers

The `"number"` format outputs the color as a 24-bit number, a compact representation for databases and configuration.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
Bun.color("red", "number"); // 16711680
Bun.color(0xff0000, "number"); // 16711680
Bun.color({ r: 255, g: 0, b: 0 }, "number"); // 16711680
Bun.color([255, 0, 0], "number"); // 16711680
Bun.color("rgb(255, 0, 0)", "number"); // 16711680
Bun.color("rgba(255, 0, 0, 1)", "number"); // 16711680
Bun.color("hsl(0, 100%, 50%)", "number"); // 16711680
Bun.color("hsla(0, 100%, 50%, 1)", "number"); // 16711680
```

### Get the red, green, blue, and alpha channels

The `"{rgba}"`, `"{rgb}"`, `"[rgba]"`, and `"[rgb]"` formats return the red, green, blue, and alpha channels as objects or arrays.

#### `{rgba}` object

The `"{rgba}"` format outputs an object with the red, green, blue, and alpha channels.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
type RGBAObject = {
  // 0 - 255
  r: number;
  // 0 - 255
  g: number;
  // 0 - 255
  b: number;
  // 0 - 1
  a: number;
};
```

Example:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
Bun.color("hsl(0, 0%, 50%)", "{rgba}"); // { r: 128, g: 128, b: 128, a: 1 }
Bun.color("red", "{rgba}"); // { r: 255, g: 0, b: 0, a: 1 }
Bun.color(0xff0000, "{rgba}"); // { r: 255, g: 0, b: 0, a: 1 }
Bun.color({ r: 255, g: 0, b: 0 }, "{rgba}"); // { r: 255, g: 0, b: 0, a: 1 }
Bun.color([255, 0, 0], "{rgba}"); // { r: 255, g: 0, b: 0, a: 1 }
```

As in CSS, the `a` channel is a decimal number between `0` and `1`.

The `"{rgb}"` format is similar, but it doesn't include the alpha channel.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
Bun.color("hsl(0, 0%, 50%)", "{rgb}"); // { r: 128, g: 128, b: 128 }
Bun.color("red", "{rgb}"); // { r: 255, g: 0, b: 0 }
Bun.color(0xff0000, "{rgb}"); // { r: 255, g: 0, b: 0 }
Bun.color({ r: 255, g: 0, b: 0 }, "{rgb}"); // { r: 255, g: 0, b: 0 }
Bun.color([255, 0, 0], "{rgb}"); // { r: 255, g: 0, b: 0 }
```

#### `[rgba]` array

The `"[rgba]"` format outputs an array with the red, green, blue, and alpha channels.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
// All values are 0 - 255
type RGBAArray = [number, number, number, number];
```

Example:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
Bun.color("hsl(0, 0%, 50%)", "[rgba]"); // [128, 128, 128, 255]
Bun.color("red", "[rgba]"); // [255, 0, 0, 255]
Bun.color(0xff0000, "[rgba]"); // [255, 0, 0, 255]
Bun.color({ r: 255, g: 0, b: 0 }, "[rgba]"); // [255, 0, 0, 255]
Bun.color([255, 0, 0], "[rgba]"); // [255, 0, 0, 255]
```

Unlike the `"{rgba}"` format, the alpha channel is an integer between `0` and `255`. This is useful for typed arrays where each channel must be the same underlying type.

The `"[rgb]"` format is similar, but it doesn't include the alpha channel.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
Bun.color("hsl(0, 0%, 50%)", "[rgb]"); // [128, 128, 128]
Bun.color("red", "[rgb]"); // [255, 0, 0]
Bun.color(0xff0000, "[rgb]"); // [255, 0, 0]
Bun.color({ r: 255, g: 0, b: 0 }, "[rgb]"); // [255, 0, 0]
Bun.color([255, 0, 0], "[rgb]"); // [255, 0, 0]
```

### Format colors as hex strings

The `"hex"` format outputs a lowercase hex string.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
Bun.color("hsl(0, 0%, 50%)", "hex"); // "#808080"
Bun.color("red", "hex"); // "#ff0000"
Bun.color(0xff0000, "hex"); // "#ff0000"
Bun.color({ r: 255, g: 0, b: 0 }, "hex"); // "#ff0000"
Bun.color([255, 0, 0], "hex"); // "#ff0000"
```

The `"HEX"` format is the same, but uppercase.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
Bun.color("hsl(0, 0%, 50%)", "HEX"); // "#808080"
Bun.color("red", "HEX"); // "#FF0000"
Bun.color(0xff0000, "HEX"); // "#FF0000"
Bun.color({ r: 255, g: 0, b: 0 }, "HEX"); // "#FF0000"
Bun.color([255, 0, 0], "HEX"); // "#FF0000"
```

### Bundle-time client-side color formatting

Like many of Bun's APIs, you can invoke `Bun.color` at bundle time with a [macro](/docs/bundler/macros) for use in client-side JavaScript builds:

```ts client-side.ts theme={"theme":{"light":"github-light","dark":"dracula"}}
import { color } from "bun" with { type: "macro" };

console.log(color("#f00", "css"));
```

Then, build the client-side code:

```sh theme={"theme":{"light":"github-light","dark":"dracula"}}
bun build ./client-side.ts
```

`bun build` prints the following to stdout:

```js theme={"theme":{"light":"github-light","dark":"dracula"}}
// client-side.ts
console.log("red");
```


# Console
Source: https://bun.com/docs/runtime/console

The console object in Bun

<Note>
  Bun provides a browser- and Node.js-compatible [console](https://developer.mozilla.org/en-US/docs/Web/API/console)
  global. This page only documents Bun-native APIs.
</Note>

***

## Object inspection depth

You can configure how deeply `console.log()` prints nested objects:

* **CLI flag**: Use `--console-depth <number>` to set the depth for a single run
* **Configuration**: Set `console.depth` in your `bunfig.toml` to persist it across runs
* **Default**: Bun inspects objects to a depth of `2` levels

```js theme={"theme":{"light":"github-light","dark":"dracula"}}
const nested = { a: { b: { c: { d: "deep" } } } };
console.log(nested);
// Default (depth 2): { a: { b: { c: [Object ...] } } }
// With depth 4: { a: { b: { c: { d: 'deep' } } } }
```

The CLI flag takes precedence over the configuration file setting.

***

## Reading from stdin

In Bun, the `console` object is also an `AsyncIterable` that reads `process.stdin` line by line.

```ts adder.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
for await (const line of console) {
  console.log(line);
}
```

Use this for interactive programs, like the following addition calculator.

```ts adder.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
console.log(`Let's add some numbers!`);
console.write(`Count: 0\n> `);

let count = 0;
for await (const line of console) {
  count += Number(line);
  console.write(`Count: ${count}\n> `);
}
```

To run the file:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun adder.ts
Let's add some numbers!
Count: 0
> 5
Count: 5
> 5
Count: 10
> 5
Count: 15
```


# Cookies
Source: https://bun.com/docs/runtime/cookies

Use Bun's native APIs for working with HTTP cookies

Bun provides two native APIs for working with HTTP cookies: `Bun.Cookie` and `Bun.CookieMap`. They parse, generate, and manipulate cookies in HTTP requests and responses.

## CookieMap class

`Bun.CookieMap` is a Map-like collection of cookies. It implements `Iterable`, so it works with `for...of` loops and the other iteration methods.

```ts title="cookies.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
// Empty cookie map
const cookies = new Bun.CookieMap();

// From a cookie string
const cookies1 = new Bun.CookieMap("name=value; foo=bar");

// From an object
const cookies2 = new Bun.CookieMap({
  session: "abc123",
  theme: "dark",
});

// From an array of name/value pairs
const cookies3 = new Bun.CookieMap([
  ["session", "abc123"],
  ["theme", "dark"],
]);
```

### In HTTP servers

In Bun's HTTP server, the `cookies` property on the request object (in `routes`) is an instance of `CookieMap`:

```ts title="server.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
const server = Bun.serve({
  routes: {
    "/": req => {
      // Access request cookies
      const cookies = req.cookies;

      // Get a specific cookie
      const sessionCookie = cookies.get("session");
      if (sessionCookie != null) {
        console.log(sessionCookie);
      }

      // Check if a cookie exists
      if (cookies.has("theme")) {
        // ...
      }

      // Set a cookie, it will be automatically applied to the response
      cookies.set("visited", "true");

      return new Response("Hello");
    },
  },
});

console.log("Server listening at: " + server.url);
```

### Methods

#### `get(name: string): string | null`

Retrieves a cookie by name. Returns `null` if the cookie doesn't exist.

```ts title="get-cookie.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
// Get by name
const cookie = cookies.get("session");

if (cookie != null) {
  console.log(cookie);
}
```

#### `has(name: string): boolean`

Checks if a cookie with the given name exists.

```ts title="has-cookie.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
// Check if cookie exists
if (cookies.has("session")) {
  // Cookie exists
}
```

#### `set(name: string, value: string, options?: CookieInit): void`

#### `set(options: CookieInit): void`

#### `set(cookie: Cookie): void`

Adds or updates a cookie in the map. Cookies default to `{ path: "/", sameSite: "lax" }`.

```ts title="set-cookie.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
// Set by name and value
cookies.set("session", "abc123");

// Set using options object
cookies.set({
  name: "theme",
  value: "dark",
  maxAge: 3600,
  secure: true,
});

// Set using Cookie instance
const cookie = new Bun.Cookie("visited", "true");
cookies.set(cookie);
```

#### `delete(name: string): void`

#### `delete(options: CookieStoreDeleteOptions): void`

#### `delete(name: string, options: Omit<CookieStoreDeleteOptions, "name">): void`

Removes a cookie from the map. When applied to a Response, the deletion adds a cookie with an empty string value and an expiry date in the past. The browser only deletes the cookie if the domain and path match the ones it was created with.

```ts title="delete-cookie.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
// Delete by name using default domain and path.
cookies.delete("session");

// Delete with domain/path options.
cookies.delete({
  name: "session",
  domain: "example.com",
  path: "/admin",
});
```

#### `toJSON(): Record<string, string>`

Converts the cookie map to a serializable format.

```ts title="cookie-to-json.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
const json = cookies.toJSON();
```

#### `toSetCookieHeaders(): string[]`

Returns an array of values for Set-Cookie headers that apply all cookie changes.

Use this with HTTP servers other than `Bun.serve()`. In `Bun.serve()`, you don't need to call it: Bun automatically applies any changes you make to the `req.cookies` map to the response headers.

```js title="node-server.js" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { createServer } from "node:http";
import { CookieMap } from "bun";

const server = createServer((req, res) => {
  const cookieHeader = req.headers.cookie || "";
  const cookies = new CookieMap(cookieHeader);

  cookies.set("view-count", Number(cookies.get("view-count") || "0") + 1);
  cookies.delete("session");

  res.writeHead(200, {
    "Content-Type": "text/plain",
    "Set-Cookie": cookies.toSetCookieHeaders(),
  });
  res.end(`Found ${cookies.size} cookies`);
});

server.listen(3000, () => {
  console.log("Server running at http://localhost:3000/");
});
```

### Iteration

`CookieMap` provides several methods for iteration:

```ts title="iterate-cookies.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
// Iterate over [name, cookie] entries
for (const [name, value] of cookies) {
  console.log(`${name}: ${value}`);
}

// Using entries()
for (const [name, value] of cookies.entries()) {
  console.log(`${name}: ${value}`);
}

// Using keys()
for (const name of cookies.keys()) {
  console.log(name);
}

// Using values()
for (const value of cookies.values()) {
  console.log(value);
}

// Using forEach
cookies.forEach((value, name) => {
  console.log(`${name}: ${value}`);
});
```

### Properties

#### `size: number`

Returns the number of cookies in the map.

```ts title="cookie-size.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
console.log(cookies.size); // Number of cookies
```

## Cookie class

`Bun.Cookie` represents an HTTP cookie with its name, value, and attributes.

```ts title="cookie-class.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { Cookie } from "bun";

// Create a basic cookie
const cookie = new Bun.Cookie("name", "value");

// Create a cookie with options
const secureSessionCookie = new Bun.Cookie("session", "abc123", {
  domain: "example.com",
  path: "/admin",
  expires: new Date(Date.now() + 86400000), // 1 day
  httpOnly: true,
  secure: true,
  sameSite: "strict",
});

// Parse from a cookie string
const parsedCookie = new Bun.Cookie("name=value; Path=/; HttpOnly");

// Create from an options object
const objCookie = new Bun.Cookie({
  name: "theme",
  value: "dark",
  maxAge: 3600,
  secure: true,
});
```

### Constructors

```ts title="constructors.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
// Basic constructor with name/value
new Bun.Cookie(name: string, value: string);

// Constructor with name, value, and options
new Bun.Cookie(name: string, value: string, options: CookieInit);

// Constructor from cookie string
new Bun.Cookie(cookieString: string);

// Constructor from cookie object
new Bun.Cookie(options: CookieInit);
```

### Properties

```ts title="cookie-properties.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
cookie.name; // string - Cookie name
cookie.value; // string - Cookie value
cookie.domain; // string | null - Domain scope (null if not specified)
cookie.path; // string - URL path scope (defaults to "/")
cookie.expires; // Date | undefined - Expiration date
cookie.secure; // boolean - Require HTTPS
cookie.sameSite; // "strict" | "lax" | "none" - SameSite setting
cookie.partitioned; // boolean - Whether the cookie is partitioned (CHIPS)
cookie.maxAge; // number | undefined - Max age in seconds
cookie.httpOnly; // boolean - Accessible only via HTTP (not JavaScript)
```

### Methods

#### `isExpired(): boolean`

Checks if the cookie has expired. When both `maxAge` and `expires` are set, `maxAge` takes precedence, as required by [RFC 6265](https://datatracker.ietf.org/doc/html/rfc6265#section-5.3).

```ts title="is-expired.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
// Expired cookie (Date in the past)
const expiredCookie = new Bun.Cookie("name", "value", {
  expires: new Date(Date.now() - 1000),
});
console.log(expiredCookie.isExpired()); // true

// Valid cookie (Using maxAge instead of expires)
const validCookie = new Bun.Cookie("name", "value", {
  maxAge: 3600, // 1 hour in seconds
});
console.log(validCookie.isExpired()); // false

// A non-positive maxAge expires the cookie immediately
const deletedCookie = new Bun.Cookie("name", "value", { maxAge: 0 });
console.log(deletedCookie.isExpired()); // true

// Session cookie (no expiration)
const sessionCookie = new Bun.Cookie("name", "value");
console.log(sessionCookie.isExpired()); // false
```

#### `serialize(): string`

#### `toString(): string`

Returns a string representation of the cookie suitable for a `Set-Cookie` header.

```ts title="serialize-cookie.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
const cookie = new Bun.Cookie("session", "abc123", {
  domain: "example.com",
  path: "/admin",
  expires: new Date(Date.now() + 86400000),
  secure: true,
  httpOnly: true,
  sameSite: "strict",
});

console.log(cookie.serialize());
// => "session=abc123; Domain=example.com; Path=/admin; Expires=Wed, 19 Mar 2025 15:03:26 GMT; Secure; HttpOnly; SameSite=Strict"
console.log(cookie.toString());
// => "session=abc123; Domain=example.com; Path=/admin; Expires=Wed, 19 Mar 2025 15:03:26 GMT; Secure; HttpOnly; SameSite=Strict"
```

#### `toJSON(): CookieInit`

Converts the cookie to a plain object suitable for JSON serialization.

```ts title="cookie-json.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
const cookie = new Bun.Cookie("session", "abc123", {
  secure: true,
  httpOnly: true,
});

const json = cookie.toJSON();
// => {
//   name: "session",
//   value: "abc123",
//   path: "/",
//   secure: true,
//   httpOnly: true,
//   sameSite: "lax",
//   partitioned: false
// }

// Works with JSON.stringify
const jsonString = JSON.stringify(cookie);
```

### Static methods

#### `Cookie.parse(cookieString: string): Cookie`

Parses a cookie string into a `Cookie` instance.

```ts title="parse-cookie.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
const cookie = Bun.Cookie.parse("name=value; Path=/; Secure; SameSite=Lax");

console.log(cookie.name); // "name"
console.log(cookie.value); // "value"
console.log(cookie.path); // "/"
console.log(cookie.secure); // true
console.log(cookie.sameSite); // "lax"
```

#### `Cookie.from(name: string, value: string, options?: CookieInit): Cookie`

Factory method to create a cookie.

```ts title="cookie-from.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
const cookie = Bun.Cookie.from("session", "abc123", {
  httpOnly: true,
  secure: true,
  maxAge: 3600,
});
```

## Types

```ts title="types.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
interface CookieInit {
  name?: string;
  value?: string;
  domain?: string;
  /** Defaults to '/'. To allow the browser to set the path, use an empty string. */
  path?: string;
  expires?: number | Date | string;
  secure?: boolean;
  /** Defaults to `lax`. */
  sameSite?: CookieSameSite;
  httpOnly?: boolean;
  partitioned?: boolean;
  maxAge?: number;
}

interface CookieStoreDeleteOptions {
  name: string;
  domain?: string | null;
  path?: string;
}

interface CookieStoreGetOptions {
  name?: string;
  url?: string;
}

type CookieSameSite = "strict" | "lax" | "none";

class Cookie {
  constructor(name: string, value: string, options?: CookieInit);
  constructor(cookieString: string);
  constructor(cookieObject?: CookieInit);

  readonly name: string;
  value: string;
  domain: string | null;
  path: string;
  expires?: Date;
  secure: boolean;
  sameSite: CookieSameSite;
  partitioned: boolean;
  maxAge?: number;
  httpOnly: boolean;

  isExpired(): boolean;

  serialize(): string;
  toString(): string;
  toJSON(): CookieInit;

  static parse(cookieString: string): Cookie;
  static from(name: string, value: string, options?: CookieInit): Cookie;
}

class CookieMap implements Iterable<[string, string]> {
  constructor(init?: string[][] | Record<string, string> | string);

  get(name: string): string | null;

  toSetCookieHeaders(): string[];

  has(name: string): boolean;
  set(name: string, value: string, options?: CookieInit): void;
  set(options: CookieInit): void;
  delete(name: string): void;
  delete(options: CookieStoreDeleteOptions): void;
  delete(name: string, options: Omit<CookieStoreDeleteOptions, "name">): void;
  toJSON(): Record<string, string>;

  readonly size: number;

  entries(): IterableIterator<[string, string]>;
  keys(): IterableIterator<string>;
  values(): IterableIterator<string>;
  forEach(callback: (value: string, key: string, map: CookieMap) => void): void;
  [Symbol.iterator](): IterableIterator<[string, string]>;
}
```


# Cron
Source: https://bun.com/docs/runtime/cron

Schedule and parse cron jobs with Bun

Bun has built-in support for cron — parse expressions, run a callback on a schedule inside your process, or register OS-level jobs that survive restarts.

## Quickstart

**Run a callback on a schedule in the current process:**

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
Bun.cron("0 * * * *", async () => {
  await cleanupTempFiles();
});
```

**Parse a cron expression to find the next matching time:**

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
// Next weekday at 9:30 AM local time
const next = Bun.cron.parse("30 9 * * MON-FRI");
```

**Register an OS-level cron job that runs a script on a schedule:**

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
await Bun.cron("./worker.ts", "30 2 * * MON", "weekly-report");
```

***

## `Bun.cron.parse()`

Parse a cron expression and return the next matching `Date` in the system's local time zone.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const next = Bun.cron.parse("*/15 * * * *");
console.log(next); // => next quarter-hour boundary
```

### Parameters

| Parameter      | Type              | Description                                                                    |
| -------------- | ----------------- | ------------------------------------------------------------------------------ |
| `expression`   | `string`          | A 5-field cron expression or predefined nickname                               |
| `relativeDate` | `Date \| number`  | Starting point for the search (defaults to `Date.now()`)                       |
| `options`      | `{ tz?: string }` | IANA time-zone name to interpret the schedule in (defaults to the system zone) |

### Returns

`Date | null` — the next matching time, or `null` if no match exists within 8 years (for example, February 30th).

### Chaining calls

Call `parse()` repeatedly to get a sequence of upcoming times:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
let cursor: Date | number = Date.now();
for (let i = 0; i < 3; i++) {
  cursor = Bun.cron.parse("0 * * * *", cursor)!;
  console.log(cursor.toLocaleString()); // next three top-of-hour boundaries
}
```

***

## Cron expression syntax

Standard 5-field format: `minute hour day-of-month month day-of-week`

| Field        | Values                  | Special characters |
| ------------ | ----------------------- | ------------------ |
| Minute       | `0`–`59`                | `*` `,` `-` `/`    |
| Hour         | `0`–`23`                | `*` `,` `-` `/`    |
| Day of month | `1`–`31`                | `*` `,` `-` `/`    |
| Month        | `1`–`12` or `JAN`–`DEC` | `*` `,` `-` `/`    |
| Day of week  | `0`–`7` or `SUN`–`SAT`  | `*` `,` `-` `/`    |

### Special characters

| Character | Description | Example                               |
| --------- | ----------- | ------------------------------------- |
| `*`       | All values  | `* * * * *` — every minute            |
| `,`       | List        | `1,15 * * * *` — minute 1 and 15      |
| `-`       | Range       | `9-17 * * * *` — minutes 9 through 17 |
| `/`       | Step        | `*/15 * * * *` — every 15 minutes     |

### Named values

Month and weekday fields accept case-insensitive names:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
// 3-letter abbreviations
Bun.cron.parse("0 9 * * MON-FRI"); // weekdays
Bun.cron.parse("0 0 1 JAN,JUN *"); // January and June

// Full names
Bun.cron.parse("0 9 * * Monday-Friday");
Bun.cron.parse("0 0 1 January *");
```

Both `0` and `7` mean Sunday in the weekday field.

### Predefined nicknames

| Nickname                | Equivalent  | Description               |
| ----------------------- | ----------- | ------------------------- |
| `@yearly` / `@annually` | `0 0 1 1 *` | Once a year (January 1st) |
| `@monthly`              | `0 0 1 * *` | Once a month (1st day)    |
| `@weekly`               | `0 0 * * 0` | Once a week (Sunday)      |
| `@daily` / `@midnight`  | `0 0 * * *` | Once a day (midnight)     |
| `@hourly`               | `0 * * * *` | Once an hour              |

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const next = Bun.cron.parse("@daily");
console.log(next); // => next local midnight
```

### Time zone

Bun interprets schedules in the system's **local time zone**, the same way crontab, launchd, and Windows Task Scheduler read them. The OS-level form and the in-process callback form fire at the same wall-clock time.

To override, pass an IANA time-zone name as `{ tz }` to `Bun.cron.parse()` or the in-process `Bun.cron(schedule, handler, options)`:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
// 09:00 UTC, regardless of the server's TZ
Bun.cron.parse("0 9 * * *", Date.now(), { tz: "UTC" });

// Fire at 09:00 New York time
Bun.cron("0 9 * * *", handler, { tz: "America/New_York" });
```

DST transitions:

* **Spring-forward** — a schedule that lands in the missing hour fires that day, shifted forward by the gap (e.g. `30 2 * * *` runs at 3:30 on the spring-forward day). For multi-minute patterns inside the gap (`*/15 2 * * *`), only the first match fires.
* **Fall-back** — a fixed-time schedule in the duplicated hour (`30 1 * * *`) fires once, at the first occurrence. A schedule whose minute or hour field is `*` (`0 * * * *`, `* * * * *`) fires through **both** occurrences — once per real-time minute, matching crontab on Linux.

### Day-of-month and day-of-week interaction

When you specify **both** day-of-month and day-of-week (neither is `*`), the expression matches when **either** condition is true. This follows the [POSIX cron](https://pubs.opengroup.org/onlinepubs/9699919799/utilities/crontab.html) standard.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
// Fires on the 15th of every month OR every Friday
Bun.cron.parse("0 0 15 * FRI");
```

When you specify only one (the other is `*`), Bun uses only that field for matching.

***

## `Bun.cron(schedule, handler)` — in-process

Run a callback on a cron schedule inside the current process.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const job = Bun.cron("*/5 * * * *", async () => {
  await syncToDatabase();
});
```

In-process scheduling is the lightweight option for long-running servers and workers. It requires no system cron daemon, works the same on every platform, and shares state (database pools, caches, module-level variables) between invocations.

|                              | In-process                       | [OS-level](#bun-cron-path-schedule-title-os-level) |
| ---------------------------- | -------------------------------- | -------------------------------------------------- |
| Survives process exit/reboot | No                               | Yes                                                |
| Shared state between runs    | Yes                              | No (fresh process each time)                       |
| Platform requirements        | None                             | crontab / launchd / Task Scheduler                 |
| Windows expression limits    | None                             | [48-trigger cap](#trigger-limit)                   |
| Return type                  | [`CronJob`](#the-cronjob-handle) | `Promise<void>`                                    |

### Parameters

| Parameter  | Type                         | Description                                                                                                                                                                  |
| ---------- | ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `schedule` | `string`                     | A [cron expression](#cron-expression-syntax) or nickname like `"@hourly"`.                                                                                                   |
| `handler`  | `(this: CronJob) => unknown` | Called on each fire. May return a Promise; Bun schedules the next fire only once it settles. Inside a `function` callback, `this` is the `CronJob` (so `this.stop()` works). |
| `options`  | `{ tz?: string }`            | IANA time-zone name to interpret the schedule in (defaults to the system zone).                                                                                              |

Returns a [`CronJob`](#the-cronjob-handle) synchronously. Throws a `TypeError` if the expression is invalid, the time-zone name is unknown, or the expression has no future occurrences, like `"0 0 30 2 *"` (February 30th).

### No-overlap guarantee

Bun computes the next fire time only after the handler and any returned `Promise` settle. If your handler takes 90 seconds and the schedule is `* * * * *`, the second fire is the first minute boundary *after* the handler finishes, not 60 seconds after the first fire. Invocations never stack.

### Error handling

Errors match `setTimeout` semantics:

* A synchronous `throw` emits `process.on("uncaughtException")`.
* A rejected returned `Promise` emits `process.on("unhandledRejection")`.

Without a listener, the process exits with code `1`. With a listener, the job keeps running — it does not stop on the first failure.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
process.on("unhandledRejection", err => log.error("cron failed:", err));

Bun.cron("* * * * *", async () => {
  await mightThrow(); // logged and retried next minute
});
```

### `bun --hot`

Under `bun --hot`, Bun stops all in-process cron jobs immediately before the module graph re-evaluates. Every `Bun.cron()` call still in your source then re-registers. Editing the schedule, editing the handler, or deleting the line entirely all take effect on save without leaking timers.

### The `CronJob` handle

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
using job = Bun.cron("0 * * * *", () => {});

job.cron; // => "0 * * * *"
job.stop(); // cancel — the handler will not fire again
job.unref(); // allow the process to exit even while scheduled
job.ref(); // keep the process alive (default)
```

`CronJob` is [`Disposable`](https://github.com/tc39/proposal-explicit-resource-management) — `using job = Bun.cron(...)` auto-stops at scope exit. `stop()`, `ref()`, and `unref()` all return the job for chaining.

### Fake timers

In-process cron honors `jest.useFakeTimers()`. `setSystemTime()`, `advanceTimersByTime()`, and `runAllTimers()` control when it fires, so you can test scheduled callbacks without waiting on the real clock.

***

## `Bun.cron(path, schedule, title)` — OS-level

Register an OS-level cron job that runs a JavaScript/TypeScript module on a schedule.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
await Bun.cron("./worker.ts", "30 2 * * MON", "weekly-report");
```

### Parameters

| Parameter  | Type     | Description                                                |
| ---------- | -------- | ---------------------------------------------------------- |
| `path`     | `string` | Path to the script (resolved relative to caller)           |
| `schedule` | `string` | Cron expression or nickname                                |
| `title`    | `string` | Unique job identifier (alphanumeric, hyphens, underscores) |

Re-registering with the same `title` overwrites the existing job in-place. Bun replaces the old schedule instead of duplicating it.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
await Bun.cron("./worker.ts", "0 * * * *", "my-job"); // every hour
await Bun.cron("./worker.ts", "*/15 * * * *", "my-job"); // replaces: every 15 min
```

### The `scheduled()` handler

The registered script must export a default object with a `scheduled()` method, following the [Cloudflare Workers Cron Triggers API](https://developers.cloudflare.com/workers/runtime-apis/handlers/scheduled/):

```ts worker.ts theme={"theme":{"light":"github-light","dark":"dracula"}}
export default {
  scheduled(controller: Bun.CronController) {
    console.log(controller.cron); // "30 2 * * 1"
    console.log(controller.type); // "scheduled"
    console.log(controller.scheduledTime); // 1737340201847 (Date.now() at invocation)
  },
};
```

The handler can be `async`. Bun waits for the returned promise to settle before exiting.

***

## How it works per platform

### Linux

Bun uses [crontab](https://man7.org/linux/man-pages/man5/crontab.5.html) to register jobs. Bun stores each job as a line in your user's crontab with a `# bun-cron: <title>` marker comment above it.

The crontab entry looks like:

```
<schedule> '<bun-path>' run --cron-title=<title> --cron-period='<schedule>' '<script-path>'
```

When the cron daemon fires the job, Bun imports your module and calls the `scheduled()` handler.

**Viewing registered jobs:**

```sh theme={"theme":{"light":"github-light","dark":"dracula"}}
crontab -l
```

**Logs:** On Linux, cron output goes to the system log. Check with:

```sh theme={"theme":{"light":"github-light","dark":"dracula"}}
# systemd-based (Ubuntu, Fedora, Arch, etc.)
journalctl -u cron       # or crond on some distros
journalctl -u cron --since "1 hour ago"

# syslog-based (older systems)
grep CRON /var/log/syslog
```

To capture stdout/stderr to a file, redirect output in the crontab entry directly, or add logging inside your `scheduled()` handler.

**Manually uninstalling without code:**

```sh theme={"theme":{"light":"github-light","dark":"dracula"}}
# Edit your crontab and remove the "# bun-cron: <title>" comment
# and the command line below it
crontab -e

# Or remove ALL bun cron jobs at once by filtering them out:
crontab -l | grep -v "# bun-cron:" | grep -v "\-\-cron-title=" | crontab -
```

### macOS

Bun uses [launchd](https://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/BPSystemStartup/Chapters/CreatingLaunchdJobs.html) to register jobs. Bun installs each job as a plist file at:

```
~/Library/LaunchAgents/bun.cron.<title>.plist
```

The plist uses `StartCalendarInterval` to define the schedule. Complex patterns with ranges, lists, or steps are supported. Bun expands them into multiple `StartCalendarInterval` dicts as a Cartesian product.

**Viewing registered jobs:**

```sh theme={"theme":{"light":"github-light","dark":"dracula"}}
launchctl list | grep bun.cron
```

**Logs:** stdout and stderr are written to:

```
/tmp/bun.cron.<title>.stdout.log
/tmp/bun.cron.<title>.stderr.log
```

For example, a job titled `weekly-report`:

```sh theme={"theme":{"light":"github-light","dark":"dracula"}}
cat /tmp/bun.cron.weekly-report.stdout.log
tail -f /tmp/bun.cron.weekly-report.stderr.log
```

**Manually uninstalling without code:**

```sh theme={"theme":{"light":"github-light","dark":"dracula"}}
# Unload the job from launchd
launchctl bootout gui/$(id -u)/bun.cron.<title>

# Delete the plist file
rm ~/Library/LaunchAgents/bun.cron.<title>.plist

# Example for a job titled "weekly-report":
launchctl bootout gui/$(id -u)/bun.cron.weekly-report
rm ~/Library/LaunchAgents/bun.cron.weekly-report.plist
```

### Windows

Bun uses [Windows Task Scheduler](https://learn.microsoft.com/en-us/windows/win32/taskschd/task-scheduler-start-page) with XML-based task definitions. Bun registers each job as a scheduled task named `bun-cron-<title>` using [`CalendarTrigger`](https://learn.microsoft.com/en-us/windows/win32/taskschd/taskschedulerschema-calendartrigger-triggergroup-element) elements and [`Repetition`](https://learn.microsoft.com/en-us/windows/win32/taskschd/taskschedulerschema-repetition-triggerbasetype-element) patterns.

Most cron expressions are fully supported, including `@daily`, `@weekly`, `@monthly`, `@yearly`, ranges (`1-5`), lists (`1,15`), named days/months, and day-of-month patterns.

#### User context

Bun registers tasks with the [`S4U` (Service-for-User)](https://learn.microsoft.com/en-us/windows/win32/taskschd/taskschedulerschema-logontype-simpletype) logon type, which runs jobs as the registering user even when not logged in. Linux `crontab` behaves the same way. No password is stored.

TCP/IP networking (`fetch()`, HTTP, WebSocket, database connections) works normally. The only restriction is that S4U tasks cannot access [Windows-authenticated network resources](https://learn.microsoft.com/en-us/windows/win32/taskschd/security-contexts-for-running-tasks) (SMB file shares, mapped drives, Kerberos/NTLM services).

On some headless servers and CI environments, the current user's [Security Identifier (SID)](https://learn.microsoft.com/en-us/windows/security/identity-protection/access-control/security-identifiers) cannot be resolved, for example with service accounts created by [NSSM](https://nssm.cc/) or similar tools. In that case, `Bun.cron()` fails with an error explaining the issue. To work around this, either run Bun as a regular user account, or create the scheduled task manually with `schtasks /create /xml <file> /tn <name> /ru SYSTEM /f`.

#### Trigger limit

<Warning>
  Windows Task Scheduler enforces a limit of [48 triggers per
  task](https://learn.microsoft.com/en-us/windows/win32/taskschd/taskschedulerschema-triggers-tasktype-element) (the
  `CalendarTrigger` element has
  [`maxOccurs="48"`](https://learn.microsoft.com/en-us/windows/win32/taskschd/taskschedulerschema-calendartrigger-triggergroup-element)).
  Some cron expressions that work on Linux and macOS exceed this limit on Windows. When a pattern exceeds the limit,
  `Bun.cron()` rejects it with an error message.
</Warning>

**Expressions that work on all platforms:**

| Pattern                                    | Trigger strategy                                                                                                                                           | Count |
| ------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- |
| `*/5 * * * *`                              | Single trigger with [`Repetition`](https://learn.microsoft.com/en-us/windows/win32/taskschd/taskschedulerschema-repetition-triggerbasetype-element) (PT5M) | 1     |
| `*/15 * * * *`                             | Single trigger with Repetition (PT15M)                                                                                                                     | 1     |
| `0 9 * * MON-FRI`                          | One `CalendarTrigger` listing all five weekdays                                                                                                            | 1     |
| `0,30 9-17 * * *`                          | 2 minutes × 9 hours                                                                                                                                        | 18    |
| `@daily`, `@weekly`, `@monthly`, `@yearly` | Single trigger                                                                                                                                             | 1     |

**Expressions that fail on Windows** (but work on Linux and macOS):

| Pattern           | Why                                           | Trigger count |
| ----------------- | --------------------------------------------- | ------------- |
| `*/7 * * * *`     | 9 minute values × 24 hours                    | 216           |
| `*/8 * * * *`     | 8 minute values × 24 hours                    | 192           |
| `*/9 * * * *`     | 7 minute values × 24 hours                    | 168           |
| `*/11 * * * *`    | 6 minute values × 24 hours                    | 144           |
| `*/13 * * * *`    | 5 minute values × 24 hours                    | 120           |
| `*/15 * * 6 *`    | Month restriction prevents Repetition: 4 × 24 | 96            |
| `0,30 * 15 * FRI` | OR-split doubles triggers: 2 × 24 × 2         | 96            |

The key factor is whether the expression can use a [`Repetition`](https://learn.microsoft.com/en-us/windows/win32/taskschd/taskschedulerschema-repetition-triggerbasetype-element) interval (single trigger) or must expand to individual `CalendarTrigger` elements. Minute steps that **evenly divide 60** (`*/1`, `*/2`, `*/3`, `*/4`, `*/5`, `*/6`, `*/10`, `*/12`, `*/15`, `*/20`, `*/30`) use Repetition only when the other four fields are unrestricted (`*`). Steps that don't divide 60 (`*/7`, `*/8`, `*/9`, `*/11`, `*/13`, etc.) must be expanded, and with 24 hours active, the count quickly exceeds 48.

To work around it, simplify the expression or restrict the hour range:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
// ❌ Fails on Windows: */7 with all hours = 216 triggers
await Bun.cron("./job.ts", "*/7 * * * *", "my-job");

// ✅ Works: restrict to specific hours (9 values × 5 hours = 45 triggers)
await Bun.cron("./job.ts", "*/7 9-13 * * *", "my-job");

// ✅ Works: use a divisor of 60 instead (Repetition, 1 trigger)
await Bun.cron("./job.ts", "*/5 * * * *", "my-job");
```

#### Windows containers

<Warning>
  `Bun.cron()` is not supported in Windows Docker containers. The Task Scheduler service is not running in `servercore`
  or `nanoserver` images. Use an in-process scheduler for containerized workloads.
</Warning>

**Viewing registered jobs:**

```powershell theme={"theme":{"light":"github-light","dark":"dracula"}}
schtasks /query /tn "bun-cron-<title>"

# List all bun cron tasks
schtasks /query | findstr "bun-cron-"
```

**Manually uninstalling without code:**

```powershell theme={"theme":{"light":"github-light","dark":"dracula"}}
schtasks /delete /tn "bun-cron-<title>" /f

# Example:
schtasks /delete /tn "bun-cron-weekly-report" /f
```

Or open **Task Scheduler** (taskschd.msc), find the task named `bun-cron-<title>`, right-click, and delete it.

***

## `Bun.cron.remove()`

Remove a previously registered cron job by its title. Works on all platforms.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
await Bun.cron.remove("weekly-report");
```

This reverses what `Bun.cron()` did:

| Platform | What `remove()` does                                     |
| -------- | -------------------------------------------------------- |
| Linux    | Edits crontab to remove the entry and its marker comment |
| macOS    | Runs `launchctl bootout` and deletes the plist file      |
| Windows  | Runs `schtasks /delete` to remove the scheduled task     |

Removing a job that doesn't exist resolves without error.


# CSRF Protection
Source: https://bun.com/docs/runtime/csrf

Generate and verify CSRF tokens with Bun's built-in API

`Bun.CSRF` generates and verifies [CSRF (Cross-Site Request Forgery)](https://owasp.org/www-community/attacks/csrf) tokens. Bun signs tokens with HMAC. Each token includes an issue timestamp and an expiry duration.

```ts title="csrf.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
// Generate a token bound to the requester's session
const token = Bun.CSRF.generate("my-secret", { sessionId: "user-session-id" });

// Verify it
const isValid = Bun.CSRF.verify(token, { secret: "my-secret", sessionId: "user-session-id" });
console.log(isValid); // true
```

<Callout type="warning">
  Always pass a `sessionId` (the requester's session identifier or user ID) to both `generate()` and `verify()`. Without
  it, a token is only bound to the secret, so any token the server has ever issued validates for every user. An attacker
  can therefore obtain a token in their own session and replay it in a forged cross-site request from a victim's
  browser.
</Callout>

***

## `Bun.CSRF.generate()`

Generate a CSRF token. The token contains a cryptographic nonce, an issue timestamp, the `expiresIn` duration, and an HMAC signature, encoded as a string.

```ts title="generate.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
const token = Bun.CSRF.generate("my-secret-key");
```

**Parameters:**

* `secret` (string, optional) — The secret key used to sign the token. If not provided, Bun generates a random in-memory default secret (unique per thread).
* `options` (object, optional):

| Option      | Type     | Default       | Description                                                                                                                                                 |
| ----------- | -------- | ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `expiresIn` | `number` | `86400000`    | Milliseconds until the token expires. Defaults to 24 hours.                                                                                                 |
| `encoding`  | `string` | `"base64url"` | Token encoding format: `"base64"`, `"base64url"`, or `"hex"`.                                                                                               |
| `algorithm` | `string` | `"sha256"`    | HMAC algorithm: `"sha256"`, `"sha384"`, `"sha512"`, `"sha512-256"`, `"blake2b256"`, or `"blake2b512"`.                                                      |
| `sessionId` | `string` | (none)        | Binds the token to the requesting principal (session ID, user ID, or equivalent). The token only verifies when you pass the same `sessionId` to `verify()`. |

**Returns:** `string` — the encoded token.

```ts title="generate-options.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
// Token bound to the requester's session that expires in 1 hour, encoded as hex
const token = Bun.CSRF.generate("my-secret", {
  sessionId: "user-session-id",
  expiresIn: 60 * 60 * 1000,
  encoding: "hex",
});

// Using a different algorithm
const token2 = Bun.CSRF.generate("my-secret", {
  sessionId: "user-session-id",
  algorithm: "sha512",
});
```

***

## `Bun.CSRF.verify()`

Verify a CSRF token. Returns `true` if the token is valid and has not expired, `false` otherwise.

```ts title="verify.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
const isValid = Bun.CSRF.verify(token, { secret: "my-secret-key" });
```

**Parameters:**

* `token` (string, required) — The token to verify.
* `options` (object, optional):

| Option      | Type     | Default       | Description                                                                                                                                                                                                     |
| ----------- | -------- | ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `secret`    | `string` | (auto)        | The secret used to sign the token. If not provided, uses the same in-memory default as `generate()`.                                                                                                            |
| `maxAge`    | `number` | `86400000`    | Maximum token age in milliseconds, independent of the token's own `expiresIn`.                                                                                                                                  |
| `encoding`  | `string` | `"base64url"` | Must match the encoding used during `generate()`.                                                                                                                                                               |
| `algorithm` | `string` | `"sha256"`    | Must match the algorithm used during `generate()`.                                                                                                                                                              |
| `sessionId` | `string` | (none)        | Must match the `sessionId` used during `generate()`. A token bound to one principal fails verification for any other principal. A token generated without a `sessionId` fails verification when you supply one. |

**Returns:** `boolean`

```ts title="verify-options.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
// Verify a token bound to the requester's session
const isValid = Bun.CSRF.verify(token, {
  secret: "my-secret",
  sessionId: "user-session-id",
});

// Enforce a shorter max age than what the token was generated with
const isValid2 = Bun.CSRF.verify(token, {
  secret: "my-secret",
  sessionId: "user-session-id",
  maxAge: 60 * 1000, // reject tokens older than 1 minute
});
```

***

## Using with `Bun.serve()`

A typical pattern is to generate a token when rendering a form, embed it in a hidden field, and verify it when the form is submitted. Pass the requester's session identifier as `sessionId` to both calls so the token only works for the user it was issued to.

```ts title="server.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
const SECRET = process.env.CSRF_SECRET || "my-secret";

// Resolve the requester's session identifier from a session cookie. Returns
// null when the visitor has no session yet — never fall back to a shared
// placeholder, or every session-less visitor would share one token binding.
function getSessionId(req: Request): string | null {
  return req.headers.get("cookie")?.match(/(?:^|;\s*)session=([^;]+)/)?.[1] ?? null;
}

const server = Bun.serve({
  routes: {
    "/form": req => {
      // Create a per-visitor session before issuing the form so the token is
      // bound to this visitor and no one else.
      let sessionId = getSessionId(req);
      const headers = new Headers({ "Content-Type": "text/html" });
      if (!sessionId) {
        sessionId = crypto.randomUUID();
        headers.append("Set-Cookie", `session=${sessionId}; HttpOnly; SameSite=Lax; Path=/`);
      }

      const token = Bun.CSRF.generate(SECRET, { sessionId });

      return new Response(
        `<form method="POST" action="/submit">
          <input type="hidden" name="_csrf" value="${token}" />
          <input type="text" name="message" />
          <button type="submit">Send</button>
        </form>`,
        { headers },
      );
    },

    "/submit": {
      POST: async req => {
        const sessionId = getSessionId(req);
        const formData = await req.formData();
        const csrfToken = formData.get("_csrf");

        if (!sessionId || typeof csrfToken !== "string" || !Bun.CSRF.verify(csrfToken, { secret: SECRET, sessionId })) {
          return new Response("Invalid CSRF token", { status: 403 });
        }

        return new Response("OK");
      },
    },
  },
});

console.log(`Listening on ${server.url}`);
```

***

## Default secret

If you omit the `secret` parameter in both `generate()` and `verify()`, Bun uses a random secret generated once per thread. The default secret is convenient for single-threaded applications, but tokens don't verify across servers or workers, or after a restart.

```ts title="default-secret.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
// Both calls use the same per-thread default secret within this runtime context.
const token = Bun.CSRF.generate();
const isValid = Bun.CSRF.verify(token); // true
```

For production use, always provide an explicit secret shared across your infrastructure.

***

## TypeScript

```ts title="types.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
type CSRFAlgorithm = "blake2b256" | "blake2b512" | "sha256" | "sha384" | "sha512" | "sha512-256";

interface CSRFGenerateOptions {
  expiresIn?: number;
  encoding?: "base64" | "base64url" | "hex";
  algorithm?: CSRFAlgorithm;
  sessionId?: string;
}

interface CSRFVerifyOptions {
  secret?: string;
  encoding?: "base64" | "base64url" | "hex";
  algorithm?: CSRFAlgorithm;
  maxAge?: number;
  sessionId?: string;
}

namespace Bun.CSRF {
  function generate(secret?: string, options?: CSRFGenerateOptions): string;
  function verify(token: string, options?: CSRFVerifyOptions): boolean;
}
```


# Debugging
Source: https://bun.com/docs/runtime/debugger

Debug your Bun code with an interactive debugger using WebKit Inspector Protocol

Bun speaks the [WebKit Inspector Protocol](https://github.com/oven-sh/bun/blob/main/packages/bun-inspector-protocol/src/protocol/jsc/index.d.ts), so you can debug your code with an interactive debugger. For demonstration purposes, consider the following web server.

## Debugging JavaScript and TypeScript

```typescript icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" title="server.ts" theme={"theme":{"light":"github-light","dark":"dracula"}}
Bun.serve({
  fetch(req) {
    console.log(req.url);
    return new Response("Hello, world!");
  },
});
```

### `--inspect`

To enable debugging when running code with Bun, use the `--inspect` flag. This starts a WebSocket server on an available port for introspecting the running Bun process.

```sh icon="terminal" title="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun --inspect server.ts
```

```txt id="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
--------------------- Bun Inspector ---------------------
Listening:
  ws://localhost:6499/0tqxs9exrgrm
Inspect in browser:
  https://debug.bun.sh/#localhost:6499/0tqxs9exrgrm
--------------------- Bun Inspector ---------------------
```

### `--inspect-brk`

The `--inspect-brk` flag behaves identically to `--inspect`, except it injects a breakpoint at the first line of the executed script. Use it to debug scripts that run quickly and exit immediately.

### `--inspect-wait`

The `--inspect-wait` flag behaves identically to `--inspect`, except the code does not execute until a debugger attaches to the running process.

### Setting a port or URL for the debugger

Regardless of which flag you use, you can specify a port number, URL prefix, or both.

```sh icon="terminal" title="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun --inspect=4000 server.ts
bun --inspect=localhost:4000 server.ts
bun --inspect=localhost:4000/prefix server.ts
```

***

## Debuggers

Several debugging tools can connect to this server.

### `debug.bun.sh`

Bun hosts a web-based debugger at [debug.bun.sh](https://debug.bun.sh). It is a modified version of WebKit's [Web Inspector Interface](https://webkit.org/web-inspector/web-inspector-interface/), which looks familiar to Safari users.

Open the provided `debug.bun.sh` URL in your browser to start a debugging session. From this interface, you can view the source code of the running file, view and set breakpoints, and execute code with the built-in console.

<Frame>
  ![Screenshot of Bun debugger, Console
  tab](https://github.com/oven-sh/bun/assets/3084745/e6a976a8-80cc-4394-8925-539025cc025d)
</Frame>

Open the Sources tab; you should see the code from earlier. Click line number `3` to set a breakpoint on the `console.log(req.url)` statement.

<Frame>
  ![screenshot of Bun debugger](https://github.com/oven-sh/bun/assets/3084745/3b69c7e9-25ff-4f9d-acc4-caa736862935)
</Frame>

Then visit [`http://localhost:3000`](http://localhost:3000) in your web browser. The page never finishes loading because the program has paused at the breakpoint.

Note how the UI has changed.

<Frame>
  ![screenshot of Bun debugger](https://github.com/oven-sh/bun/assets/3084745/8b565e58-5445-4061-9bc4-f41090dfe769)
</Frame>

While paused, use the console at the bottom to run arbitrary code in the context of the program, with full access to the variables in scope at the breakpoint.

<Frame>
  ![Bun debugger console](https://github.com/oven-sh/bun/assets/3084745/f4312b76-48ba-4a7d-b3b6-6205968ac681)
</Frame>

The right side of the Sources pane lists all local variables currently in scope. Drill into one, like `req` here, to see its properties and methods.

<Frame>
  ![Bun debugger variables](https://github.com/oven-sh/bun/assets/3084745/63d7f843-5180-489c-aa94-87c486e68646)
</Frame>

The controls in the upper left of the Sources pane drive the program's execution.

<Frame>
  ![Bun debugger controls](https://github.com/oven-sh/bun/assets/3084745/41b76deb-7371-4461-9d5d-81b5a6d2f7a4)
</Frame>

What each button does:

* *Continue script execution* — run until the next breakpoint or exception.
* *Step over* — advance to the next line.
* *Step into* — if the current statement contains a function call, enter the called function.
* *Step out* — if the current statement is a function call, finish executing it, then return to the location where it was called.

<Frame>
  ![Bun debugger execution
  controls](https://github-production-user-asset-6210df.s3.amazonaws.com/3084745/261510346-6a94441c-75d3-413a-99a7-efa62365f83d.png)
</Frame>

### Visual Studio Code Debugger

Visual Studio Code support for debugging Bun scripts is experimental. To use it, install the [Bun VSCode extension](/docs/guides/runtime/vscode-debugger).

***

## Debugging Network Requests

Set the `BUN_CONFIG_VERBOSE_FETCH` environment variable to log network requests made with `fetch()` or `node:http`.

| Value   | Description                       |
| ------- | --------------------------------- |
| `curl`  | Print requests as `curl` commands |
| `true`  | Print request & response info     |
| `false` | Don't print anything (default)    |

### Print fetch & node:http requests as curl commands

Set `BUN_CONFIG_VERBOSE_FETCH` to `curl` to print each `fetch()` and `node:http` request as a single-line `curl` command. You can copy-paste the command into your terminal to replicate the request.

```ts index.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
process.env.BUN_CONFIG_VERBOSE_FETCH = "curl";

await fetch("https://example.com", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ foo: "bar" }),
});
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
[fetch] $ curl --http1.1 "https://example.com/" -X POST -H "content-type: application/json" -H "Connection: keep-alive" -H "User-Agent: Bun/1.3.3" -H "Accept: */*" -H "Host: example.com" -H "Accept-Encoding: gzip, deflate, br" --compressed -H "Content-Length: 13" --data-raw "{\"foo\":\"bar\"}"
[fetch] > HTTP/1.1 POST https://example.com/
[fetch] > content-type: application/json
[fetch] > Connection: keep-alive
[fetch] > User-Agent: Bun/1.3.3
[fetch] > Accept: */*
[fetch] > Host: example.com
[fetch] > Accept-Encoding: gzip, deflate, br
[fetch] > Content-Length: 13

[fetch] < 200 OK
[fetch] < Accept-Ranges: bytes
[fetch] < Cache-Control: max-age=604800
[fetch] < Content-Type: text/html; charset=UTF-8
[fetch] < Date: Tue, 18 Jun 2024 05:12:07 GMT
[fetch] < Etag: "3147526947"
[fetch] < Expires: Tue, 25 Jun 2024 05:12:07 GMT
[fetch] < Last-Modified: Thu, 17 Oct 2019 07:18:26 GMT
[fetch] < Server: EOS (vny/044F)
[fetch] < Content-Length: 1256
```

The lines with `[fetch] >` are the request from your local code, and the lines with `[fetch] <` are the response from the remote server.

To print without the `curl` command, set `BUN_CONFIG_VERBOSE_FETCH` to `true`.

```ts index.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
process.env.BUN_CONFIG_VERBOSE_FETCH = "true";

await fetch("https://example.com", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ foo: "bar" }),
});
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
[fetch] > HTTP/1.1 POST https://example.com/
[fetch] > content-type: application/json
[fetch] > Connection: keep-alive
[fetch] > User-Agent: Bun/1.3.3
[fetch] > Accept: */*
[fetch] > Host: example.com
[fetch] > Accept-Encoding: gzip, deflate, br
[fetch] > Content-Length: 13

[fetch] < 200 OK
[fetch] < Accept-Ranges: bytes
[fetch] < Cache-Control: max-age=604800
[fetch] < Content-Type: text/html; charset=UTF-8
[fetch] < Date: Tue, 18 Jun 2024 05:12:07 GMT
[fetch] < Etag: "3147526947"
[fetch] < Expires: Tue, 25 Jun 2024 05:12:07 GMT
[fetch] < Last-Modified: Thu, 17 Oct 2019 07:18:26 GMT
[fetch] < Server: EOS (vny/044F)
[fetch] < Content-Length: 1256
```

***

## Stacktraces & sourcemaps

Bun transpiles every file, which could leave stack traces pointing at the transpiled output. To avoid this, Bun generates and serves sourcemapped files for every file it transpiles. When you see a stack trace in the console, you can click on the file path and land in the original source code, even though it was written in TypeScript or JSX, or has some other transformation applied.

Bun loads sourcemaps both at runtime when it transpiles files on-demand, and when you use `bun build` to precompile files ahead of time.

### Syntax-highlighted source code preview

Bun prints a small source-code preview when an unhandled exception or rejection occurs. To produce the same output yourself, call `Bun.inspect(error)`:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
// Create an error
const err = new Error("Something went wrong");
console.log(Bun.inspect(err, { colors: true }));
```

The output is a syntax-highlighted preview of the source code where the error occurred, along with the error message and stack trace.

```ts icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
1 | // Create an error
2 | const err = new Error("Something went wrong");
                ^
error: Something went wrong
      at file.js:2:13
```

### V8 Stack Traces

Bun uses JavaScriptCore as its engine, but much of the Node.js ecosystem and npm expects V8. JavaScript engines differ in how they format `error.stack`. Because Bun aims to be a drop-in replacement for Node.js, it formats `error.stack` the same way V8 does. This matters most when you use libraries that expect V8 stack traces.

#### V8 Stack Trace API

Bun implements the [V8 Stack Trace API](https://v8.dev/docs/stack-trace-api), a set of functions for manipulating stack traces.

##### `Error.prepareStackTrace`

Define a global `Error.prepareStackTrace` function to customize the stack trace output. It receives the error object and an array of `CallSite` objects, and its return value becomes `error.stack`.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
Error.prepareStackTrace = (err, stack) => {
  return stack.map(callSite => {
    return callSite.getFileName();
  });
};

const err = new Error("Something went wrong");
console.log(err.stack);
// [ "error.js" ]
```

The `CallSite` object has the following methods:

| Method                     | Returns                                               |
| -------------------------- | ----------------------------------------------------- |
| `getThis`                  | `this` value of the function call                     |
| `getTypeName`              | typeof `this`                                         |
| `getFunction`              | function object                                       |
| `getFunctionName`          | function name as a string                             |
| `getMethodName`            | method name as a string                               |
| `getFileName`              | file name or URL                                      |
| `getLineNumber`            | line number                                           |
| `getColumnNumber`          | column number                                         |
| `getEvalOrigin`            | `undefined`                                           |
| `getScriptNameOrSourceURL` | source URL                                            |
| `isToplevel`               | returns `true` if the function is in the global scope |
| `isEval`                   | returns `true` if the function is an `eval` call      |
| `isNative`                 | returns `true` if the function is native              |
| `isConstructor`            | returns `true` if the function is a constructor       |
| `isAsync`                  | returns `true` if the function is `async`             |
| `isPromiseAll`             | Not implemented yet.                                  |
| `getPromiseIndex`          | Not implemented yet.                                  |
| `toString`                 | returns a string representation of the call site      |

If the `Function` object has already been garbage collected, some of these methods return `undefined`.

##### `Error.captureStackTrace(error, startFn)`

`Error.captureStackTrace` captures a stack trace at a specific point in your code, rather than at the point where the error was thrown.

This helps when callbacks or asynchronous code make it hard to tell where an error originated. The 2nd argument to `Error.captureStackTrace` is the function where you want the stack trace to start.

In the following example, `err.stack` points to the code calling `fn()`, even though the error was thrown at `myInner`.

```ts index.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
const fn = () => {
  function myInner() {
    throw new Error("here!");
  }

  try {
    myInner();
  } catch (err) {
    console.log(err.stack);
    console.log("");
    console.log("-- captureStackTrace --");
    console.log("");
    Error.captureStackTrace(err, fn);
    console.log(err.stack);
  }
};

fn();
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
Error: here!
    at myInner (/path/to/index.ts:3:15)
    at fn (/path/to/index.ts:7:5)
    at /path/to/index.ts:18

-- captureStackTrace --

Error: here!
    at /path/to/index.ts:18
```


# Environment Variables
Source: https://bun.com/docs/runtime/environment-variables

Read and configure environment variables in Bun, including automatic .env file support

Bun reads your `.env` files automatically and provides idiomatic ways to read and write your environment variables programmatically. You can also configure parts of Bun's runtime behavior with Bun-specific environment variables.

## Setting environment variables

Bun reads the following files automatically (listed in order of increasing precedence).

* `.env`
* `.env.production`, `.env.development`, `.env.test` (depending on the value of `NODE_ENV`)
* `.env.local`
* `.env.production.local`, `.env.development.local`, `.env.test.local` (depending on the value of `NODE_ENV`)

```ini .env icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
FOO=hello
BAR=world
```

You can also set variables on the command line.

<CodeGroup>
  ```sh Linux/macOS icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
  FOO=helloworld bun run dev
  ```

  ```sh Windows icon="windows" theme={"theme":{"light":"github-light","dark":"dracula"}}
  # Using CMD
  set FOO=helloworld && bun run dev

  # Using PowerShell
  $env:FOO="helloworld"; bun run dev
  ```
</CodeGroup>

<Accordion title="Cross-platform solution with Windows">
  For a cross-platform solution, use [Bun Shell](/docs/runtime/shell), for example through `bun exec`.

  ```sh theme={"theme":{"light":"github-light","dark":"dracula"}}
  bun exec 'FOO=helloworld bun run dev'
  ```

  On Windows, `package.json` scripts called with `bun run` automatically use the **Bun Shell**, so the following is also cross-platform.

  ```json package.json icon="file-json" theme={"theme":{"light":"github-light","dark":"dracula"}}
  "scripts": {
    "dev": "NODE_ENV=development bun --watch app.ts",
  },
  ```
</Accordion>

Or set them programmatically by assigning a property to `process.env`.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
process.env.FOO = "hello";
```

***

## Manually specifying `.env` files

The `--env-file` flag overrides which `.env` files Bun loads. It works both when running a file with `bun` and when running `package.json` scripts.

```sh theme={"theme":{"light":"github-light","dark":"dracula"}}
bun --env-file=.env.1 src/index.ts

bun --env-file=.env.abc --env-file=.env.def run build
```

## Disabling automatic `.env` loading

Use `--no-env-file` to disable Bun's automatic `.env` file loading, for example in production or CI/CD pipelines where you want to rely solely on system environment variables.

```sh theme={"theme":{"light":"github-light","dark":"dracula"}}
bun run --no-env-file index.ts
```

You can also disable it in `bunfig.toml`:

```toml bunfig.toml icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
# Disable loading .env files
env = false
```

Files passed with `--env-file` still load even when default loading is disabled.

When invoked as `node` (for example via `bun --bun`, `bunx --bun`, or a `node` symlink pointing at Bun), Bun disables automatic `.env` loading to match Node.js. This lets tools with their own mode-aware `.env` resolution, such as Vite's `loadEnv`, pick the correct `.env.{mode}` file instead of seeing Bun's pre-populated values as shell-set overrides. Bun still honors explicit `--env-file` arguments.

***

## Quotation marks

Bun supports double quotes, single quotes, and template literal backticks:

```ini .env icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
FOO='hello'
FOO="hello"
FOO=`hello`
```

### Expansion

Bun automatically *expands* environment variables, so you can reference previously-defined variables.

```ini .env icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
FOO=world
BAR=hello$FOO
```

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
process.env.BAR; // => "helloworld"
```

Expansion is useful for constructing connection strings or other compound values.

```ini .env icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
DB_USER=postgres
DB_PASSWORD=secret
DB_HOST=localhost
DB_PORT=5432
DB_URL=postgres://$DB_USER:$DB_PASSWORD@$DB_HOST:$DB_PORT/$DB_NAME
```

To disable expansion, escape the `$` with a backslash.

```ini .env icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
FOO=world
BAR=hello\$FOO
```

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
process.env.BAR; // => "hello$FOO"
```

### `dotenv`

Bun reads `.env` files automatically, so `dotenv` and `dotenv-expand` are unnecessary.

## Reading environment variables

Read the current environment variables from `process.env`.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
process.env.API_TOKEN; // => "secret"
```

Bun also exposes these variables as `Bun.env` and `import.meta.env`, both aliases of `process.env`.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
Bun.env.API_TOKEN; // => "secret"
import.meta.env.API_TOKEN; // => "secret"
```

To print all currently-set environment variables, run `bun --print process.env`.

```sh theme={"theme":{"light":"github-light","dark":"dracula"}}
bun --print process.env
BAZ=stuff
FOOBAR=aaaaaa
<lots more lines>
```

## TypeScript

In TypeScript, all properties of `process.env` are typed as `string | undefined`.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
Bun.env.whatever;
// string | undefined
```

To get autocompletion and tell TypeScript to treat a variable as a non-optional string, use [interface merging](https://www.typescriptlang.org/docs/handbook/declaration-merging.html#merging-interfaces).

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
declare module "bun" {
  interface Env {
    AWESOME: string;
  }
}
```

Add this declaration to any file in your project. It globally adds the `AWESOME` property to `process.env` and `Bun.env`.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
process.env.AWESOME; // => string
```

## Configuring Bun

Bun reads these environment variables to configure aspects of its behavior.

| Name                                     | Description                                                                                                                                                                                                                                                                                                                                                                   |
| ---------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `NODE_TLS_REJECT_UNAUTHORIZED`           | `NODE_TLS_REJECT_UNAUTHORIZED=0` disables SSL certificate validation. Useful for testing and debugging, but be very hesitant to use it in production. Node.js introduced this variable; Bun keeps the name for compatibility.                                                                                                                                                 |
| `BUN_CONFIG_VERBOSE_FETCH`               | If `BUN_CONFIG_VERBOSE_FETCH=curl`, then fetch requests log the URL, method, request headers and response headers to the console. This also works with `node:http`. `BUN_CONFIG_VERBOSE_FETCH=1` is equivalent to `BUN_CONFIG_VERBOSE_FETCH=curl` except without the `curl` output.                                                                                           |
| `BUN_RUNTIME_TRANSPILER_CACHE_PATH`      | The runtime transpiler caches the transpiled output of source files larger than 4 KB, which makes CLIs using Bun load faster. If `BUN_RUNTIME_TRANSPILER_CACHE_PATH` is set, Bun writes the cache to that directory. If it is set to an empty string or the string `"0"`, caching is disabled. If it is unset, Bun writes the cache to the platform-specific cache directory. |
| `TMPDIR`                                 | Bun occasionally requires a directory to store intermediate assets during bundling or other operations. If unset, defaults to the platform-specific temporary directory: `/tmp` on Linux, `/private/tmp` on macOS.                                                                                                                                                            |
| `NO_COLOR`                               | If `NO_COLOR=1`, then ANSI color output is [disabled](https://no-color.org/).                                                                                                                                                                                                                                                                                                 |
| `FORCE_COLOR`                            | If `FORCE_COLOR=1`, then ANSI color output is forced on, even if `NO_COLOR` is set.                                                                                                                                                                                                                                                                                           |
| `BUN_CONFIG_MAX_HTTP_REQUESTS`           | Sets the maximum number of concurrent HTTP requests sent by fetch and `bun install`. Defaults to `256`. Lower it if you run into rate limits or connection issues.                                                                                                                                                                                                            |
| `BUN_CONFIG_NO_CLEAR_TERMINAL_ON_RELOAD` | If `BUN_CONFIG_NO_CLEAR_TERMINAL_ON_RELOAD=true`, then `bun --watch` does not clear the console on reload                                                                                                                                                                                                                                                                     |
| `DO_NOT_TRACK`                           | Disable uploading crash reports to `bun.report` on crash. On macOS & Windows, crash report uploads are enabled by default. Bun sends no other telemetry, though we plan to add some. If `DO_NOT_TRACK=1`, then auto-uploading crash reports and telemetry are both [disabled](https://do-not-track.dev/).                                                                     |
| `BUN_OPTIONS`                            | Prepends command-line arguments to any Bun execution. For example, `BUN_OPTIONS="--hot"` makes `bun run dev` behave like `bun --hot run dev`.                                                                                                                                                                                                                                 |

## Runtime transpiler caching

For files larger than 4 KB, Bun caches transpiled output into `$BUN_RUNTIME_TRANSPILER_CACHE_PATH` or the platform-specific cache directory. This makes CLIs using Bun load faster.

The cache is global and shared across all projects. It is content-addressable, so it never contains duplicate entries. It is safe to delete at any time, even while a Bun process is running.

Disable this cache when using ephemeral filesystems like Docker. Bun's Docker images disable it automatically.

### Disable the runtime transpiler cache

To disable the runtime transpiler cache, set `BUN_RUNTIME_TRANSPILER_CACHE_PATH` to an empty string or the string `"0"`.

```sh theme={"theme":{"light":"github-light","dark":"dracula"}}
BUN_RUNTIME_TRANSPILER_CACHE_PATH=0 bun run dev
```

### What does it cache?

It caches:

* The transpiled output of source files larger than 4 KB.
* The sourcemap for the transpiled output of the file

Cached files use the `.pile` extension.


# FFI
Source: https://bun.com/docs/runtime/ffi

Use Bun's FFI module to efficiently call native libraries from JavaScript

<Warning>
  `bun:ffi` is **experimental**, with known bugs and limitations. Do not rely on it in production. The most stable way
  to interact with native code from Bun is to write a [Node-API module](/docs/runtime/node-api).
</Warning>

Use the built-in `bun:ffi` module to efficiently call native libraries from JavaScript. It works with any language that supports the C ABI, including Zig, Rust, C/C++, C#, Nim, and Kotlin.

***

## dlopen usage (`bun:ffi`)

To print the version number of `sqlite3`:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
import { dlopen, FFIType, suffix } from "bun:ffi";

// `suffix` is either "dylib", "so", or "dll" depending on the platform
// you don't have to use "suffix", it's just there for convenience
const path = `libsqlite3.${suffix}`;

const {
  symbols: {
    sqlite3_libversion, // the function to call
  },
} = dlopen(
  path, // a library name or file path
  {
    sqlite3_libversion: {
      // no arguments, returns a string
      args: [],
      returns: FFIType.cstring,
    },
  },
);

console.log(`SQLite 3 version: ${sqlite3_libversion()}`);
```

***

## Performance

According to [our benchmark](https://github.com/oven-sh/bun/tree/main/bench/ffi), `bun:ffi` is roughly 2-6x faster than Node.js FFI through `Node-API`.

<Image />

Bun's JavaScript engine (JavaScriptCore) implements `dlopen`, `linkSymbols`, `CFunction`, and `JSCallback` natively. Argument conversion, arity handling, and result boxing happen in-engine. Hot call sites compile down through the DFG/FTL JIT tiers into direct native calls with no per-argument JavaScript shim. Bun embeds [TinyCC](https://github.com/TinyCC/tinycc), a small and fast C compiler, only for [`cc()`](/docs/runtime/c-compiler), which compiles C source you provide at runtime.

***

## Usage

### Zig

```zig add.zig icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
pub export fn add(a: i32, b: i32) i32 {
  return a + b;
}
```

To compile:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
zig build-lib add.zig -dynamic -OReleaseFast
```

Pass a path to the shared library and a map of symbols to import into `dlopen`:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
import { dlopen, FFIType, suffix } from "bun:ffi";
const { i32 } = FFIType;

const path = `libadd.${suffix}`;

const lib = dlopen(path, {
  add: {
    args: [i32, i32],
    returns: i32,
  },
});

console.log(lib.symbols.add(1, 2));
```

### Rust

```rust theme={"theme":{"light":"github-light","dark":"dracula"}}
// add.rs
#[no_mangle]
pub extern "C" fn add(a: i32, b: i32) -> i32 {
    a + b
}
```

To compile:

```bash theme={"theme":{"light":"github-light","dark":"dracula"}}
rustc --crate-type cdylib add.rs
```

### C++

```c theme={"theme":{"light":"github-light","dark":"dracula"}}
#include <cstdint>

extern "C" int32_t add(int32_t a, int32_t b) {
    return a + b;
}
```

To compile:

```bash theme={"theme":{"light":"github-light","dark":"dracula"}}
# Linux
clang++ -shared -fPIC add.cpp -o libadd.so

# macOS
clang++ -dynamiclib add.cpp -o libadd.dylib
```

***

## FFI types

The following `FFIType` values are supported.

| `FFIType`      | C Type                | Aliases                         |
| -------------- | --------------------- | ------------------------------- |
| buffer         | `char*`               |                                 |
| cstring        | `char*`               |                                 |
| function       | `(void*)(*)()`        | `fn`, `callback`                |
| ptr            | `void*`               | `pointer`, `void*`, `char*`     |
| i8             | `int8_t`              | `int8_t`                        |
| i16            | `int16_t`             | `int16_t`                       |
| i32            | `int32_t`             | `int32_t`, `int`                |
| i64            | `int64_t`             | `int64_t`                       |
| i64\_fast      | `int64_t`             |                                 |
| u8             | `uint8_t`             | `uint8_t`                       |
| u16            | `uint16_t`            | `uint16_t`                      |
| u32            | `uint32_t`            | `uint32_t`                      |
| u64            | `uint64_t`            | `uint64_t`, `usize`             |
| u64\_fast      | `uint64_t`            |                                 |
| f32            | `float`               | `float`                         |
| f64            | `double`              | `double`                        |
| bool           | `bool`                |                                 |
| char           | `char`                |                                 |
| napi\_env      | `napi_env`            | `cc()` only                     |
| napi\_value    | `napi_value`          | `cc()` only                     |
| buffer\_length | `uint64_t` / `size_t` | engine-native only (not `cc()`) |

`buffer` arguments must be a `TypedArray` or `DataView`.

`buffer_length` is `buffer`'s length twin: pass the **same** `TypedArray`/`DataView` you passed
for the `buffer` parameter, and the callee receives that view's **byte length** as an unsigned
64-bit integer. The engine reads the pointer and the length off the same object at the moment of
the call, so the two always agree. You can't get that atomic snapshot by passing `view.byteLength`
yourself, because a length read in JavaScript beforehand can go stale against a resizable,
growable, or transferred buffer. `buffer_length` is argument-only and, like the napi types, not
available inside `cc()`.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const {
  symbols: { write_all },
} = dlopen(path, {
  // C: size_t write_all(int fd, const void *buf, size_t len)
  write_all: { args: ["i32", "buffer", "buffer_length"], returns: "u64" },
});
const chunk = new TextEncoder().encode("hello");
write_all(1, chunk, chunk); // buf and len both come from `chunk`
```

`napi_env` and `napi_value` are only valid in [`cc()`](/docs/runtime/c-compiler) source. There, the
compiled trampoline fills in a `napi_env` parameter with the module's environment; the JavaScript
argument passed at that position is a placeholder and is ignored. `napi_value` passes the
JavaScript value through unchanged. Using either type in a `dlopen`, `linkSymbols`, `JSCallback`,
or `CFunction` descriptor throws a `TypeError`.

***

## Strings

JavaScript strings and C-like strings are different, and that complicates using strings with native libraries.

<Accordion title="How are JavaScript strings and C strings different?">
  JavaScript strings:

  * UTF16 (2 bytes per letter) or potentially latin1, depending on the JavaScript engine & what characters are used
  * `length` stored separately
  * Immutable

  C strings:

  * UTF8 (1 byte per letter), usually
  * The length is not stored. Instead, the string is null-terminated: its length is the index of the first `\0`
  * Mutable
</Accordion>

To solve this, `bun:ffi` exports `CString`, which reads a UTF-8 C string at a pointer and returns a plain JavaScript string:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
CString(ptr: number, byteOffset?: number, byteLength?: number): string;
```

To convert from a null-terminated string pointer to a JavaScript string:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const myString = new CString(ptr);
```

To convert from a pointer with a known length to a JavaScript string:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const myString = new CString(ptr, 0, byteLength);
```

`new CString()` returns a normal string (`typeof myString === "string"`, `myString === "hello"` works). The string is a clone of the C string, so you can safely keep using it after `ptr` has been freed.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const myString = new CString(ptr);
my_library_free(ptr);

// this is safe because myString is a clone
console.log(myString);
```

When used in `returns`, `FFIType.cstring` coerces the pointer to a JavaScript `string`. When used in `args`, `FFIType.cstring` accepts everything `ptr` does **and** additionally accepts a JavaScript string directly. The engine transcodes the string to a null-terminated UTF-8 buffer that lives for the duration of the call, so you don't need to encode it into a `Buffer` yourself:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
symbols.puts("Hello, world!"); // args: ["cstring"] — pass the string directly
```

**Lifetime of a `cstring` return.** The pointer is whatever the C function returned: memory
owned by the native side (a static, a buffer it manages, or heap it allocated). The engine copies
nothing on return, and the JavaScript string is cloned out of that memory. The one aliasing case is
a C function that hands back a pointer *derived from a `cstring` argument you passed as a JavaScript
string*. The engine transcodes that argument into its call-scoped buffer, so treat such a returned
pointer as valid only until your next FFI call reuses that buffer (the usual C rule for functions
that return their input). Clone it (via the returned string, or `new CString`) rather than holding
the raw address.

***

## Function pointers

<Note>Async functions are not supported</Note>

To call a function pointer from JavaScript, use `CFunction`, for example with a pointer you got from a Node-API (napi) module you've already loaded.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
import { CFunction } from "bun:ffi";

let myNativeLibraryGetVersion = /* somehow, you got this pointer */

const getVersion = new CFunction({
  returns: "cstring",
  args: [],
  ptr: myNativeLibraryGetVersion,
});
getVersion();
```

To define multiple function pointers at once, use `linkSymbols`:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
import { linkSymbols } from "bun:ffi";

// getVersionPtrs defined elsewhere
const [majorPtr, minorPtr, patchPtr] = getVersionPtrs();

const lib = linkSymbols({
  // Unlike with dlopen(), the names here can be whatever you want
  getMajor: {
    returns: "cstring",
    args: [],

    // Since this doesn't use dlsym(), you have to provide a valid ptr
    // That ptr could be a number or a bigint
    // An invalid pointer will crash your program.
    ptr: majorPtr,
  },
  getMinor: {
    returns: "cstring",
    args: [],
    ptr: minorPtr,
  },
  getPatch: {
    returns: "cstring",
    args: [],
    ptr: patchPtr,
  },
});

const [major, minor, patch] = [lib.symbols.getMajor(), lib.symbols.getMinor(), lib.symbols.getPatch()];
```

***

## Callbacks

Use `JSCallback` to create JavaScript callback functions that you can pass to C/FFI functions, so native code can call back into your JavaScript or TypeScript. This is useful for asynchronous code.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
import { dlopen, JSCallback, ptr, CString } from "bun:ffi";

const {
  symbols: { search },
  close,
} = dlopen("libmylib", {
  search: {
    returns: "usize",
    args: ["cstring", "callback"],
  },
});

const searchIterator = new JSCallback((ptr, length) => /hello/.test(new CString(ptr, 0, Number(length))), {
  returns: "bool",
  args: ["ptr", "usize"],
});

const str = Buffer.from("wwutwutwutwutwutwutwutwutwutwutut\0", "utf8");
if (search(ptr(str), searchIterator)) {
  // found a match!
}

// Sometime later:
setTimeout(() => {
  searchIterator.close();
  close();
}, 5000);
```

When you're done with a `JSCallback`, call `close()` to free the memory.

### Experimental thread-safe callbacks

`JSCallback` has experimental support for thread-safe callbacks. You need this if you pass a callback function into a different thread from the one that created it. Enable it with the optional `threadsafe` parameter.

Thread-safe callbacks can be invoked from **any thread** — including threads spawned by your native library that Bun is not otherwise aware of. The engine copies the C arguments on the calling thread and marshals the invocation onto the JavaScript thread. There it converts the arguments (64-bit integers and pointers arrive as exact BigInts) and runs your function. Because the invocation is asynchronous from C's point of view, the value returned to the C caller is unspecified. You may declare a non-`void` `returns` (the example below uses `"bool"`), but the C side must treat a thread-safe callback as returning `void` and ignore its return value.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const searchIterator = new JSCallback((ptr, length) => /hello/.test(new CString(ptr, 0, Number(length))), {
  returns: "bool",
  args: ["ptr", "usize"],
  threadsafe: true, // Optional. Defaults to `false`
});
```

<Note>
  **⚡️ Performance tip**: For a slight performance boost, pass `JSCallback.prototype.ptr` directly instead of the `JSCallback` object:

  ```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
  const onResolve = new JSCallback(arg => arg === 42, {
    returns: "bool",
    args: ["i32"],
  });
  const setOnResolve = new CFunction({
    returns: "bool",
    args: ["function"],
    ptr: myNativeLibrarySetOnResolve,
  });

  // This code runs slightly faster:
  setOnResolve(onResolve.ptr);

  // Compared to this:
  setOnResolve(onResolve);
  ```
</Note>

***

## Pointers

Bun represents [pointers](https://en.wikipedia.org/wiki/Pointer_\(computer_programming\)) as a `number` in JavaScript.

<Accordion title="How does a 64 bit pointer fit in a JavaScript number?">
  64-bit processors support up to [52 bits of addressable space](https://en.wikipedia.org/wiki/64-bit_computing#Limits_of_processors). [JavaScript numbers](https://en.wikipedia.org/wiki/Double-precision_floating-point_format#IEEE_754_double-precision_binary_floating-point_format:_binary64) support 53 bits of usable space, which leaves 1 bit of extra space.

  **Why not `BigInt`?** `BigInt` is slower. JavaScript engines allocate `BigInt`s separately, so they can't fit into a regular JavaScript value. If you pass a `BigInt` to a function, Bun converts it to a `number`.

  **Windows Note**: The Windows API type HANDLE does not represent a virtual address, and using `ptr` for it does *not* work as expected. Use `u64` to safely represent HANDLE values.
</Accordion>

To convert from a `TypedArray` to a pointer:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
import { ptr } from "bun:ffi";
let myTypedArray = new Uint8Array(32);
const myPtr = ptr(myTypedArray);
```

To convert from a pointer to an `ArrayBuffer`:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
import { ptr, toArrayBuffer } from "bun:ffi";
let myTypedArray = new Uint8Array(32);
const myPtr = ptr(myTypedArray);

// toArrayBuffer accepts a `byteOffset` and `byteLength`
// if `byteLength` is not provided, it is assumed to be a null-terminated pointer
myTypedArray = new Uint8Array(toArrayBuffer(myPtr, 0, 32), 0, 32);
```

To read data from a pointer, you have two options. For long-lived pointers, use a `DataView`:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
import { toArrayBuffer } from "bun:ffi";
let myDataView = new DataView(toArrayBuffer(myPtr, 0, 32));

console.log(
  myDataView.getUint8(0, true),
  myDataView.getUint8(1, true),
  myDataView.getUint8(2, true),
  myDataView.getUint8(3, true),
);
```

For short-lived pointers, use `read`:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
import { read } from "bun:ffi";

console.log(
  // ptr, byteOffset
  read.u8(myPtr, 0),
  read.u8(myPtr, 1),
  read.u8(myPtr, 2),
  read.u8(myPtr, 3),
);
```

The `read` function behaves similarly to `DataView`, but it's usually faster because it doesn't need to create a `DataView` or `ArrayBuffer`.

| `FFIType` | `read` function |
| --------- | --------------- |
| ptr       | `read.ptr`      |
| i8        | `read.i8`       |
| i16       | `read.i16`      |
| i32       | `read.i32`      |
| i64       | `read.i64`      |
| u8        | `read.u8`       |
| u16       | `read.u16`      |
| u32       | `read.u32`      |
| u64       | `read.u64`      |
| f32       | `read.f32`      |
| f64       | `read.f64`      |

### Memory management

`bun:ffi` does not manage memory for you. You must free the memory when you're done with it.

#### From JavaScript

To track when a `TypedArray` is no longer in use from JavaScript, use a [FinalizationRegistry](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/FinalizationRegistry).

#### From C, Rust, Zig, etc

To track when a `TypedArray` is no longer in use from C or FFI, pass a callback and an optional context pointer to `toArrayBuffer` or `toBuffer`. Bun calls the callback later, once the garbage collector frees the underlying `ArrayBuffer` JavaScript object.

The expected signature is the same as in [JavaScriptCore's C API](https://developer.apple.com/documentation/javascriptcore/jstypedarraybytesdeallocator?language=objc):

```c theme={"theme":{"light":"github-light","dark":"dracula"}}
typedef void (*JSTypedArrayBytesDeallocator)(void *bytes, void *deallocatorContext);
```

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
import { toArrayBuffer } from "bun:ffi";

// with a deallocatorContext:
toArrayBuffer(
  bytes,
  byteOffset,

  byteLength,

  // this is an optional pointer to a callback
  deallocatorContext,

  // this is a pointer to a function
  jsTypedArrayBytesDeallocator,
);

// without a deallocatorContext:
toArrayBuffer(
  bytes,
  byteOffset,

  byteLength,

  // this is a pointer to a function
  jsTypedArrayBytesDeallocator,
);
```

### Memory safety

Don't use raw pointers outside of FFI. A future version of Bun may add a CLI flag to disable `bun:ffi`.

### Pointer alignment

If an API expects a pointer sized to something other than `char` or `u8`, make sure the `TypedArray` is also that size. A `u64*` is not exactly the same as `[8]u8*` due to alignment.

### Passing a pointer

Where FFI functions expect a pointer, pass a `TypedArray` of equivalent size:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
import { dlopen, FFIType } from "bun:ffi";

const {
  symbols: { encode_png },
} = dlopen(myLibraryPath, {
  encode_png: {
    // FFIType's can be specified as strings too
    args: ["ptr", "u32", "u32"],
    returns: FFIType.ptr,
  },
});

const pixels = new Uint8ClampedArray(128 * 128 * 4);
pixels.fill(254);
pixels.subarray(0, 32 * 32 * 2).fill(0);

const out = encode_png(
  // pixels will be passed as a pointer
  pixels,

  128,
  128,
);
```

The [auto-generated wrapper](https://github.com/oven-sh/bun/blob/6a65631cbdcae75bfa1e64323a6ad613a922cd1a/src/bun.js/ffi.exports.js#L180-L182) converts the `TypedArray` to a pointer.

<Accordion title="Hardmode">
  If you don't want the automatic conversion, or you want a pointer to a specific byte offset within the `TypedArray`, get the pointer to the `TypedArray` directly:

  ```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
  import { dlopen, FFIType, ptr } from "bun:ffi";

  const {
    symbols: { encode_png },
  } = dlopen(myLibraryPath, {
    encode_png: {
      // FFIType's can be specified as strings too
      args: ["ptr", "u32", "u32"],
      returns: FFIType.ptr,
    },
  });

  const pixels = new Uint8ClampedArray(128 * 128 * 4);
  pixels.fill(254);

  // this returns a number! not a BigInt!
  const myPtr = ptr(pixels);

  const out = encode_png(
    myPtr,

    // dimensions:
    128,
    128,
  );
  ```
</Accordion>

### Reading pointers

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const out = encode_png(
  // pixels will be passed as a pointer
  pixels,

  // dimensions:
  128,
  128,
);

// assuming it is 0-terminated, it can be read like this:
let png = new Uint8Array(toArrayBuffer(out));

// save it to disk:
await Bun.write("out.png", png);
```


# File I/O
Source: https://bun.com/docs/runtime/file-io

Bun provides a set of optimized APIs for reading and writing files.

<Note>
  The `Bun.file` and `Bun.write` APIs documented on this page are heavily optimized and are the recommended way to work with files in Bun. For operations they don't cover, such as `mkdir` or `readdir`, use Bun's [nearly complete](/docs/runtime/nodejs-compat#node-fs) implementation of the [`node:fs`](https://nodejs.org/api/fs.html) module.
</Note>

***

## Reading files (`Bun.file()`)

`Bun.file(path): BunFile`

Create a `BunFile` instance with the `Bun.file(path)` function. A `BunFile` represents a lazily-loaded file; initializing it does not read the file from disk.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const foo = Bun.file("foo.txt"); // relative to cwd
foo.size; // number of bytes
foo.type; // MIME type
```

The reference conforms to the [`Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Blob) interface, so you can read the contents in various formats.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const foo = Bun.file("foo.txt");

await foo.text(); // contents as a string
await foo.json(); // contents as a JSON object
foo.stream(); // contents as ReadableStream
await foo.arrayBuffer(); // contents as ArrayBuffer
await foo.bytes(); // contents as Uint8Array
```

You can also create file references from numerical [file descriptors](https://en.wikipedia.org/wiki/File_descriptor) or `file://` URLs.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
Bun.file(1234);
Bun.file(new URL(import.meta.url)); // reference to the current file
```

A `BunFile` can point to a location on disk where a file does not exist.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const notreal = Bun.file("notreal.txt");
notreal.size; // 0
notreal.type; // "text/plain;charset=utf-8"
const exists = await notreal.exists(); // false
```

The default MIME type is `text/plain;charset=utf-8`. Override it by passing a second argument to `Bun.file`.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const notreal = Bun.file("notreal.json", { type: "application/json" });
notreal.type; // => "application/json;charset=utf-8"
```

For convenience, Bun exposes `stdin`, `stdout` and `stderr` as instances of `BunFile`.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
Bun.stdin; // readonly
Bun.stdout;
Bun.stderr;
```

### Deleting files (`file.delete()`)

Call `.delete()` to delete a file.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
await Bun.file("logs.json").delete();
```

***

## Writing files (`Bun.write()`)

`Bun.write(destination, data): Promise<number>`

The `Bun.write` function is a multi-tool for writing payloads of all kinds to disk.

The first argument is the `destination`, which can be any of the following:

* `string`: A path to a location on the file system. Use the `"path"` module to manipulate paths.
* `URL`: A `file://` descriptor.
* `BunFile`: A file reference.

The second argument is the data to write. It can be any of the following:

* `string`
* `Blob` (including `BunFile`)
* `ArrayBuffer` or `SharedArrayBuffer`
* `TypedArray` (`Uint8Array`, et. al.)
* `Response`

Bun handles each combination with the fastest available system call on the current platform.

<Accordion title="See syscalls">
  | Output               | Input          | System call                   | Platform |
  | -------------------- | -------------- | ----------------------------- | -------- |
  | file                 | file           | copy\_file\_range             | Linux    |
  | file                 | pipe           | sendfile                      | Linux    |
  | pipe                 | pipe           | splice                        | Linux    |
  | terminal             | file           | sendfile                      | Linux    |
  | terminal             | terminal       | sendfile                      | Linux    |
  | socket               | file or pipe   | sendfile (if http, not https) | Linux    |
  | file (doesn't exist) | file (path)    | clonefile                     | macOS    |
  | file (exists)        | file           | fcopyfile                     | macOS    |
  | file                 | Blob or string | write                         | macOS    |
  | file                 | Blob or string | write                         | Linux    |
</Accordion>

To write a string to disk:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const data = `It was the best of times, it was the worst of times.`;
await Bun.write("output.txt", data);
```

To copy a file to another location on disk:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const input = Bun.file("input.txt");
const output = Bun.file("output.txt"); // doesn't exist yet!
await Bun.write(output, input);
```

To write a byte array to disk:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const encoder = new TextEncoder();
const data = encoder.encode("datadatadata"); // Uint8Array
await Bun.write("output.txt", data);
```

To write a file to `stdout`:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const input = Bun.file("input.txt");
await Bun.write(Bun.stdout, input);
```

To write the body of an HTTP response to disk:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const response = await fetch("https://bun.com");
await Bun.write("index.html", response);
```

***

## Incremental writing with `FileSink`

Bun provides a native incremental file writing API called `FileSink`. To retrieve a `FileSink` instance from a `BunFile`:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const file = Bun.file("output.txt");
const writer = file.writer();
```

To incrementally write to the file, call `.write()`.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const file = Bun.file("output.txt");
const writer = file.writer();

writer.write("it was the best of times\n");
writer.write("it was the worst of times\n");
```

The `FileSink` buffers these chunks internally. To flush the buffer to disk, use `.flush()`. This returns the number of flushed bytes.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
writer.flush(); // write buffer to disk
```

The buffer also auto-flushes when the `FileSink`'s *high water mark* is reached; that is, when its internal buffer is full. You can configure this value.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const file = Bun.file("output.txt");
const writer = file.writer({ highWaterMark: 1024 * 1024 }); // 1MB
```

To flush the buffer and close the file:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
writer.end();
```

By default, the `bun` process stays alive until you explicitly close this `FileSink` with `.end()`. To opt out of this behavior, "unref" the instance.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
writer.unref();

// to "re-ref" it later
writer.ref();
```

***

## Directories

Bun's implementation of `node:fs` is fast. Use `node:fs` for working with directories in Bun.

### Reading directories (readdir)

To read a directory in Bun, use `readdir` from `node:fs`.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
import { readdir } from "node:fs/promises";

// read all the files in the current directory
const files = await readdir(import.meta.dir);
```

#### Reading directories recursively

To recursively read a directory in Bun, use `readdir` with `recursive: true`.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
import { readdir } from "node:fs/promises";

// read all the files in the current directory, recursively
const files = await readdir("../", { recursive: true });
```

### Creating directories (mkdir)

To recursively create a directory, use `mkdir` in `node:fs`:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
import { mkdir } from "node:fs/promises";

await mkdir("path/to/dir", { recursive: true });
```

***

## Benchmarks

The following is a 3-line implementation of the Linux `cat` command.

```ts cat.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
// Usage
// bun ./cat.ts ./path-to-file

import { resolve } from "path";

const path = resolve(process.argv.at(-1));
await Bun.write(Bun.stdout, Bun.file(path));
```

To run the file:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun ./cat.ts ./path-to-file
```

It runs 2x faster than GNU `cat` for large files on Linux.

<Frame>
  <img alt="Cat screenshot" />
</Frame>

***

## Reference

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
interface Bun {
  stdin: BunFile;
  stdout: BunFile;
  stderr: BunFile;

  file(path: string | number | URL, options?: { type?: string }): BunFile;

  write(
    destination: string | number | BunFile | URL,
    input: string | Blob | ArrayBuffer | SharedArrayBuffer | TypedArray | Response,
  ): Promise<number>;
}

interface BunFile {
  readonly size: number;
  readonly type: string;

  text(): Promise<string>;
  stream(): ReadableStream;
  arrayBuffer(): Promise<ArrayBuffer>;
  json(): Promise<any>;
  bytes(): Promise<Uint8Array>;
  writer(params?: { highWaterMark?: number }): FileSink;
  exists(): Promise<boolean>;
  delete(): Promise<void>;
}

export interface FileSink {
  write(chunk: string | ArrayBufferView | ArrayBuffer | SharedArrayBuffer): number;
  flush(): number | Promise<number>;
  end(error?: Error): number | Promise<number>;
  start(options?: { highWaterMark?: number }): void;
  ref(): void;
  unref(): void;
}
```


# File System Router
Source: https://bun.com/docs/runtime/file-system-router

Bun provides a fast API for resolving routes against file-system paths

This API is intended primarily for library authors. It supports only Next.js-style file-system routing.

## Next.js-style

The `FileSystemRouter` class resolves routes against a `pages` directory. (The Next.js 13 `app` directory is not supported.) Consider the following `pages` directory:

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
pages
├── index.tsx
├── settings.tsx
├── blog
│   ├── [slug].tsx
│   └── index.tsx
└── [[...catchall]].tsx
```

To resolve routes against this directory:

```ts router.ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const router = new Bun.FileSystemRouter({
  style: "nextjs",
  dir: "./pages",
  origin: "https://mydomain.com",
  assetPrefix: "_next/static/"
});

router.match("/");

// =>
{
  filePath: "/path/to/pages/index.tsx",
  kind: "exact",
  name: "/",
  pathname: "/",
  src: "https://mydomain.com/_next/static/index.tsx"
}
```

The router parses query parameters and returns them in the `query` property.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
router.match("/settings?foo=bar");

// =>
{
  filePath: "/Users/colinmcd94/Documents/bun/fun/pages/settings.tsx",
  kind: "exact",
  name: "/settings",
  pathname: "/settings?foo=bar",
  src: "https://mydomain.com/_next/static/settings.tsx",
  query: {
    foo: "bar"
  }
}
```

The router parses URL parameters and returns them in the `params` property:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
router.match("/blog/my-cool-post");

// =>
{
  filePath: "/Users/colinmcd94/Documents/bun/fun/pages/blog/[slug].tsx",
  kind: "dynamic",
  name: "/blog/[slug]",
  pathname: "/blog/my-cool-post",
  src: "https://mydomain.com/_next/static/blog/[slug].tsx",
  params: {
    slug: "my-cool-post"
  }
}
```

The `.match()` method also accepts `Request` and `Response` objects; the router uses their `url` property to resolve the route.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
router.match(new Request("https://example.com/blog/my-cool-post"));
```

The router reads the directory contents on initialization. To re-scan the files, use the `.reload()` method.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
router.reload();
```

## Reference

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
interface Bun {
  class FileSystemRouter {
    constructor(params: {
      dir: string;
      style: "nextjs";
      origin?: string;
      assetPrefix?: string;
      fileExtensions?: string[];
    });

    reload(): void;

    match(path: string | Request | Response): {
      filePath: string;
      kind: "exact" | "catch-all" | "optional-catch-all" | "dynamic";
      name: string;
      pathname: string;
      src: string;
      params?: Record<string, string>;
      query?: Record<string, string>;
    } | null
  }
}
```


# File Types
Source: https://bun.com/docs/runtime/file-types

File types and loaders supported by Bun's bundler and runtime

The Bun bundler implements a set of default loaders. As a rule of thumb, the bundler and the runtime support the same set of file types.

`.js` `.cjs` `.mjs` `.mts` `.cts` `.ts` `.tsx` `.jsx` `.css` `.json` `.jsonc` `.json5` `.toml` `.yaml` `.yml` `.xml` `.txt` `.text` `.md` `.markdown` `.wasm` `.node` `.html` `.sh`

Bun uses the file extension to pick the built-in *loader* that parses the file. Every loader has a name, such as `js`, `tsx`, or `json`. These names are used when building [plugins](/docs/bundler/plugins) that extend Bun with custom loaders.

To specify a loader explicitly, use the `type` import attribute.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
import my_toml from "./my_file" with { type: "toml" };
// or with dynamic imports
const { default: my_toml } = await import("./my_file", { with: { type: "toml" } });
```

***

## Built-in loaders

### `js`

**JavaScript**. Default for `.cjs` and `.mjs`.

Parses the code and applies a set of default transforms like dead-code elimination and tree shaking. Bun does not down-convert syntax.

### `jsx`

**JavaScript + JSX**. Default for `.js` and `.jsx`.

Same as the `js` loader, but JSX syntax is supported. By default, Bun down-converts JSX to plain JavaScript; the details depend on the `jsx*` compiler options in your `tsconfig.json`. Refer to the TypeScript documentation [on JSX](https://www.typescriptlang.org/docs/handbook/jsx.html).

### `ts`

**TypeScript loader**. Default for `.ts`, `.mts`, and `.cts`.

Strips out all TypeScript syntax, then behaves identically to the `js` loader. Bun does not perform typechecking.

### `tsx`

**TypeScript + JSX loader**. Default for `.tsx`. Transpiles both TypeScript and JSX to vanilla JavaScript.

### `json`

**JSON loader**. Default for `.json`.

JSON files can be directly imported.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
import pkg from "./package.json";
pkg.name; // => "my-package"
```

During bundling, Bun inlines the parsed JSON into the bundle as a JavaScript object.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
var pkg = {
  name: "my-package",
  // ... other fields
};
pkg.name;
```

If you pass a `.json` file as an entrypoint to the bundler, Bun converts it to a `.js` module that `export default`s the parsed object.

<CodeGroup>
  ```json Input theme={"theme":{"light":"github-light","dark":"dracula"}}
  {
    "name": "John Doe",
    "age": 35,
    "email": "johndoe@example.com"
  }
  ```

  ```ts Output theme={"theme":{"light":"github-light","dark":"dracula"}}
  export default {
    name: "John Doe",
    age: 35,
    email: "johndoe@example.com",
  };
  ```
</CodeGroup>

### `jsonc`

**JSON with Comments loader**. Default for `.jsonc`.

JSONC (JSON with Comments) files can be directly imported. Bun parses them, stripping out comments and trailing commas.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
import config from "./config.jsonc";
console.log(config);
```

During bundling, Bun inlines the parsed JSONC into the bundle as a JavaScript object, identical to the `json` loader.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
var config = {
  option: "value",
};
```

<Note>
  Bun automatically uses the `jsonc` loader for `tsconfig.json`, `jsconfig.json`, `package.json`, and `bun.lock` files.
</Note>

### `toml`

**TOML loader**. Default for `.toml`.

TOML files can be directly imported. Bun parses them with its fast native TOML parser.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
import config from "./bunfig.toml";
config.logLevel; // => "debug"

// via import attribute:
// import myCustomTOML from './my.config' with {type: "toml"};
```

During bundling, Bun inlines the parsed TOML into the bundle as a JavaScript object.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
var config = {
  logLevel: "debug",
  // ...other fields
};
config.logLevel;
```

If you pass a `.toml` file as an entrypoint, Bun converts it to a `.js` module that `export default`s the parsed object.

<CodeGroup>
  ```toml Input theme={"theme":{"light":"github-light","dark":"dracula"}}
  name = "John Doe"
  age = 35
  email = "johndoe@example.com"
  ```

  ```ts Output theme={"theme":{"light":"github-light","dark":"dracula"}}
  export default {
    name: "John Doe",
    age: 35,
    email: "johndoe@example.com",
  };
  ```
</CodeGroup>

### `yaml`

**YAML loader**. Default for `.yaml` and `.yml`.

YAML files can be directly imported. Bun parses them with its fast native YAML parser.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
import config from "./config.yaml";
console.log(config);

// via import attribute:
import data from "./data.txt" with { type: "yaml" };
```

During bundling, Bun inlines the parsed YAML into the bundle as a JavaScript object.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
var config = {
  name: "my-app",
  version: "1.0.0",
  // ...other fields
};
```

If you pass a `.yaml` or `.yml` file as an entrypoint, Bun converts it to a `.js` module that `export default`s the parsed object.

<CodeGroup>
  ```yaml Input theme={"theme":{"light":"github-light","dark":"dracula"}}
  name: John Doe
  age: 35
  email: johndoe@example.com
  ```

  ```ts Output theme={"theme":{"light":"github-light","dark":"dracula"}}
  export default {
    name: "John Doe",
    age: 35,
    email: "johndoe@example.com",
  };
  ```
</CodeGroup>

### `json5`

**JSON5 loader**. Default for `.json5`.

JSON5 files can be directly imported. Bun parses them with its fast native JSON5 parser. JSON5 is a superset of JSON that adds comments, trailing commas, unquoted keys, single-quoted strings, and more.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
import config from "./config.json5";
console.log(config);

// via import attribute:
import data from "./data.txt" with { type: "json5" };
```

During bundling, Bun inlines the parsed JSON5 into the bundle as a JavaScript object.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
var config = {
  name: "my-app",
  version: "1.0.0",
  // ...other fields
};
```

If you pass a `.json5` file as an entrypoint, Bun converts it to a `.js` module that `export default`s the parsed object.

<CodeGroup>
  ```json5 Input theme={"theme":{"light":"github-light","dark":"dracula"}}
  {
    // Configuration
    name: "John Doe",
    age: 35,
    email: "johndoe@example.com",
  }
  ```

  ```ts Output theme={"theme":{"light":"github-light","dark":"dracula"}}
  export default {
    name: "John Doe",
    age: 35,
    email: "johndoe@example.com",
  };
  ```
</CodeGroup>

### `xml`

**XML loader**. Default for `.xml`.

XML files can be directly imported. Bun parses them with its native XML 1.0 parser into the compact object shape of [`Bun.XML.parse`](/docs/runtime/xml):

* One key for the root element
* `"@name"` keys for attributes
* Arrays for repeated child elements
* `"#text"` for text next to attributes or children
* Every value is a string

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
import doc from "./config.xml";
console.log(doc.config["@version"]);

// via import attribute:
import feed from "./export.rss" with { type: "xml" };
```

During bundling, Bun inlines the parsed XML into the bundle as a JavaScript object.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
var doc = {
  config: {
    "@version": "2",
    // ...other fields
  },
};
```

If you pass a `.xml` file as an entrypoint, Bun converts it to a `.js` module that `export default`s the parsed object.

<CodeGroup>
  ```xml Input theme={"theme":{"light":"github-light","dark":"dracula"}}
  <user id="1">
    <name>John Doe</name>
    <email>johndoe@example.com</email>
    <role>admin</role>
    <role>editor</role>
  </user>
  ```

  ```ts Output theme={"theme":{"light":"github-light","dark":"dracula"}}
  export default {
    user: {
      "@id": "1",
      name: "John Doe",
      email: "johndoe@example.com",
      role: ["admin", "editor"],
    },
  };
  ```
</CodeGroup>

### `text`

**Text loader**. Default for `.txt` and `.text`.

Text files can be directly imported. Bun reads the file and returns it as a string.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
import contents from "./file.txt";
console.log(contents); // => "Hello, world!"

// To import an html file as text
// The "type' attribute can be used to override the default loader.
import html from "./index.html" with { type: "text" };
```

When the file is referenced during a build, Bun inlines the contents into the bundle as a string.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
var contents = `Hello, world!`;
console.log(contents);
```

If you pass a `.txt` file as an entrypoint, Bun converts it to a `.js` module that `export default`s the file contents.

<CodeGroup>
  ```txt Input theme={"theme":{"light":"github-light","dark":"dracula"}}
  Hello, world!
  ```

  ```ts Output theme={"theme":{"light":"github-light","dark":"dracula"}}
  export default "Hello, world!";
  ```
</CodeGroup>

### `napi`

**Native addon loader**. Default for `.node`.

In the runtime, native addons can be directly imported.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
import addon from "./addon.node";
console.log(addon);
```

In the bundler, Bun handles `.node` files using the [`file`](#file) loader.

### `sqlite`

**SQLite loader**. `with { "type": "sqlite" }` import attribute

In the runtime and bundler, SQLite databases can be directly imported. Bun loads the database with [`bun:sqlite`](/docs/runtime/sqlite).

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
import db from "./my.db" with { type: "sqlite" };
```

The `sqlite` loader is only supported when the `target` is `bun`.

By default, the database is external to the bundle: Bun doesn't bundle the on-disk database file into the final output, so you can use a database loaded elsewhere.

You can change this behavior with the `"embed"` attribute:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
// embed the database into the bundle
import db from "./my.db" with { type: "sqlite", embed: "true" };
```

With a [standalone executable](/docs/bundler/executables), Bun embeds the database into the single-file executable.

Otherwise, the database to embed is copied into the `outdir` with a hashed filename.

### `html`

The `html` loader processes HTML files and bundles any referenced assets. It:

* Bundles and hashes referenced JavaScript files (`<script src="...">`)
* Bundles and hashes referenced CSS files (`<link rel="stylesheet" href="...">`)
* Hashes referenced images (`<img src="...">`)
* Preserves external URLs (by default, anything starting with `http://` or `https://`)

For example, given this HTML file:

<CodeGroup>
  ```html src/index.html theme={"theme":{"light":"github-light","dark":"dracula"}}
  <!DOCTYPE html>
  <html>
    <body>
      <img src="./image.jpg" alt="Local image" />
      <img src="https://example.com/image.jpg" alt="External image" />
      <script type="module" src="./script.js"></script>
    </body>
  </html>
  ```
</CodeGroup>

Bun outputs a new HTML file with the bundled assets:

<CodeGroup>
  ```html dist/output.html theme={"theme":{"light":"github-light","dark":"dracula"}}
  <!DOCTYPE html>
  <html>
    <body>
      <img src="./image-HASHED.jpg" alt="Local image" />
      <img src="https://example.com/image.jpg" alt="External image" />
      <script type="module" src="./output-ALSO-HASHED.js"></script>
    </body>
  </html>
  ```
</CodeGroup>

The loader uses [`lol-html`](https://github.com/cloudflare/lol-html) to extract script and link tags as entrypoints, and other assets as external.

The list of selectors is:

* `audio[src]`
* `img[src]`
* `img[srcset]`
* `link[as='font'][href], link[type^='font/'][href]`
* `link[as='image'][href]`
* `link[as='style'][href]`
* `link[as='video'][href], link[as='audio'][href]`
* `link[as='worker'][href]`
* `link[rel='icon'][href], link[rel='apple-touch-icon'][href]`
* `link[rel='manifest'][href]`
* `link[rel='stylesheet'][href]`
* `script[src]`
* `source[src]`
* `source[srcset]`
* `video[poster]`
* `video[src]`

<Note>
  **HTML Loader Behavior in Different Contexts**

  The `html` loader behaves differently depending on how it's used:

  1. **Static Build:** When you run `bun build ./index.html`, Bun produces a static site with all assets bundled and hashed.

  2. **Runtime:** When you run `bun run server.ts` (where `server.ts` imports an HTML file), Bun bundles assets on-the-fly during development, enabling features like hot module replacement.

  3. **Full-stack Build:** When you run `bun build --target=bun server.ts` (where `server.ts` imports an HTML file), the import resolves to a manifest object that `Bun.serve` uses to efficiently serve pre-bundled assets in production.
</Note>

### `css`

**CSS loader**. Default for `.css`.

CSS files can be directly imported. This is primarily useful for [full-stack applications](/docs/bundler/fullstack) where CSS is bundled alongside HTML.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
import "./styles.css";
```

The import returns no value; it's only used for its side effects.

### `sh` loader

**Bun Shell loader**. Default for `.sh` files

This loader parses [Bun Shell](/docs/runtime/shell) scripts. It's only supported when starting Bun itself, so it's not available in the bundler or in the runtime.

```sh theme={"theme":{"light":"github-light","dark":"dracula"}}
bun run ./script.sh
```

### `file`

**File loader**. Default for all unrecognized file types.

The file loader resolves the import as a *path/URL* to the imported file. It's commonly used for referencing media or font assets.

```ts logo.ts theme={"theme":{"light":"github-light","dark":"dracula"}}
import logo from "./logo.svg";
console.log(logo);
```

*In the runtime*, Bun checks that the `logo.svg` file exists and resolves the import to its absolute path on disk.

```bash theme={"theme":{"light":"github-light","dark":"dracula"}}
bun run logo.ts
/path/to/project/logo.svg
```

*In the bundler*, Bun copies the file into `outdir` as-is, and the import resolves to a relative path pointing to the copied file.

```ts Output theme={"theme":{"light":"github-light","dark":"dracula"}}
var logo = "./logo.svg";
console.log(logo);
```

If `publicPath` is set, the import uses its value as a prefix to construct an absolute path/URL.

| Public path                  | Resolved import                    |
| ---------------------------- | ---------------------------------- |
| `""` (default)               | `./logo.svg`                       |
| `"/assets/"`                 | `/assets/logo.svg`                 |
| `"https://cdn.example.com/"` | `https://cdn.example.com/logo.svg` |

<Note>The value of [`naming.asset`](/docs/bundler#naming) determines the location and file name of the copied file.</Note>

<Accordion title="Fixing TypeScript import errors">
  If you're using TypeScript, you may get an error like this:

  ```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
  // TypeScript error
  // Cannot find module './logo.svg' or its corresponding type declarations.
  ```

  To fix this, create a `*.d.ts` file anywhere in your project (any name works) with the following contents:

  ```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
  declare module "*.svg" {
    const content: string;
    export default content;
  }
  ```

  This tells TypeScript to treat any default import from `.svg` as a string.
</Accordion>


# Glob
Source: https://bun.com/docs/runtime/glob

Use Bun's fast native implementation of file globbing

## Quickstart

**Scan a directory for files matching `*.ts`**:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
import { Glob } from "bun";

const glob = new Glob("**/*.ts");

// Scans the current working directory and each of its sub-directories recursively
for await (const file of glob.scan(".")) {
  console.log(file); // => "index.ts"
}
```

**Match a string against a glob pattern**:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
import { Glob } from "bun";

const glob = new Glob("*.ts");

glob.match("index.ts"); // => true
glob.match("index.js"); // => false
```

The `Glob` class implements the following interface:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
class Glob {
  scan(root: string | ScanOptions): AsyncIterable<string>;
  scanSync(root: string | ScanOptions): Iterable<string>;

  match(path: string): boolean;
}

interface ScanOptions {
  /**
   * The root directory to start matching from. Defaults to `process.cwd()`
   */
  cwd?: string;

  /**
   * Allow patterns to match entries that begin with a period (`.`).
   *
   * @default false
   */
  dot?: boolean;

  /**
   * Return the absolute path for entries.
   *
   * @default false
   */
  absolute?: boolean;

  /**
   * Indicates whether to traverse descendants of symbolic link directories.
   *
   * @default false
   */
  followSymlinks?: boolean;

  /**
   * Throw an error when symbolic link is broken
   *
   * @default false
   */
  throwErrorOnBrokenSymlink?: boolean;

  /**
   * Return only files.
   *
   * @default true
   */
  onlyFiles?: boolean;
}
```

## Supported Glob Patterns

Bun supports the following glob patterns:

### `?` - Match any single character

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const glob = new Glob("???.ts");
glob.match("foo.ts"); // => true
glob.match("foobar.ts"); // => false
```

### `*` - Matches zero or more characters, except for path separators (`/` or `\`)

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const glob = new Glob("*.ts");
glob.match("index.ts"); // => true
glob.match("src/index.ts"); // => false
```

### `**` - Match any number of characters including `/`

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const glob = new Glob("**/*.ts");
glob.match("index.ts"); // => true
glob.match("src/index.ts"); // => true
glob.match("src/index.js"); // => false
```

### `[ab]` - Matches one of the characters contained in the brackets, as well as character ranges

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const glob = new Glob("ba[rz].ts");
glob.match("bar.ts"); // => true
glob.match("baz.ts"); // => true
glob.match("bat.ts"); // => false
```

You can use character ranges (for example `[0-9]`, `[a-z]`). The negation operators `^` or `!` match anything *except* the characters in the brackets (for example `[^ab]`, `[!a-z]`).

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const glob = new Glob("ba[a-z][0-9][^4-9].ts");
glob.match("bar01.ts"); // => true
glob.match("baz83.ts"); // => true
glob.match("bat22.ts"); // => true
glob.match("bat24.ts"); // => false
glob.match("ba0a8.ts"); // => false
```

### `{a,b,c}` - Match any of the given patterns

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const glob = new Glob("{a,b,c}.ts");
glob.match("a.ts"); // => true
glob.match("b.ts"); // => true
glob.match("c.ts"); // => true
glob.match("d.ts"); // => false
```

You can nest these patterns up to 10 levels deep, and they can contain any of the earlier wildcards.

### `!` - Negates the result at the start of a pattern

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const glob = new Glob("!index.ts");
glob.match("index.ts"); // => false
glob.match("foo.ts"); // => true
```

### `\` - Escapes any of the special characters above

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const glob = new Glob("\\!index.ts");
glob.match("!index.ts"); // => true
glob.match("index.ts"); // => false
```

## Node.js `fs.glob()` compatibility

Bun also implements Node.js's `fs.glob()` functions:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
import { glob, globSync, promises } from "node:fs";

// Array of patterns
const files = await promises.glob(["**/*.ts", "**/*.js"]);

// Exclude patterns
const filtered = await promises.glob("**/*", {
  exclude: ["node_modules/**", "*.test.*"],
});
```

All three functions (`fs.glob()`, `fs.globSync()`, `fs.promises.glob()`) support:

* Array of patterns as the first argument
* `exclude` option to filter results


# Globals
Source: https://bun.com/docs/runtime/globals

Use Bun's global objects

Bun implements the following globals.

| Global                                                                                                                  | Source         | Notes                                                                                                                                                                                                                                         |
| ----------------------------------------------------------------------------------------------------------------------- | -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [`AbortController`](https://developer.mozilla.org/en-US/docs/Web/API/AbortController)                                   | Web            |                                                                                                                                                                                                                                               |
| [`AbortSignal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal)                                           | Web            |                                                                                                                                                                                                                                               |
| [`alert`](https://developer.mozilla.org/en-US/docs/Web/API/Window/alert)                                                | Web            | Intended for command-line tools                                                                                                                                                                                                               |
| [`Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Blob)                                                         | Web            |                                                                                                                                                                                                                                               |
| [`Buffer`](https://nodejs.org/api/buffer.html#class-buffer)                                                             | Node.js        | See [Node.js > `Buffer`](/docs/runtime/nodejs-compat#node-buffer)                                                                                                                                                                                  |
| `Bun`                                                                                                                   | Bun            | Subject to change as additional APIs are added                                                                                                                                                                                                |
| [`ByteLengthQueuingStrategy`](https://developer.mozilla.org/en-US/docs/Web/API/ByteLengthQueuingStrategy)               | Web            |                                                                                                                                                                                                                                               |
| [`confirm`](https://developer.mozilla.org/en-US/docs/Web/API/Window/confirm)                                            | Web            | Intended for command-line tools                                                                                                                                                                                                               |
| [`__dirname`](https://nodejs.org/api/globals.html#__dirname)                                                            | Node.js        |                                                                                                                                                                                                                                               |
| [`__filename`](https://nodejs.org/api/globals.html#__filename)                                                          | Node.js        |                                                                                                                                                                                                                                               |
| [`atob()`](https://developer.mozilla.org/en-US/docs/Web/API/atob)                                                       | Web            |                                                                                                                                                                                                                                               |
| [`btoa()`](https://developer.mozilla.org/en-US/docs/Web/API/btoa)                                                       | Web            |                                                                                                                                                                                                                                               |
| `BuildMessage`                                                                                                          | Bun            |                                                                                                                                                                                                                                               |
| [`clearImmediate()`](https://developer.mozilla.org/en-US/docs/Web/API/Window/clearImmediate)                            | Web            |                                                                                                                                                                                                                                               |
| [`clearInterval()`](https://developer.mozilla.org/en-US/docs/Web/API/Window/clearInterval)                              | Web            |                                                                                                                                                                                                                                               |
| [`clearTimeout()`](https://developer.mozilla.org/en-US/docs/Web/API/Window/clearTimeout)                                | Web            |                                                                                                                                                                                                                                               |
| [`console`](https://developer.mozilla.org/en-US/docs/Web/API/console)                                                   | Web            |                                                                                                                                                                                                                                               |
| [`CountQueuingStrategy`](https://developer.mozilla.org/en-US/docs/Web/API/CountQueuingStrategy)                         | Web            |                                                                                                                                                                                                                                               |
| [`Crypto`](https://developer.mozilla.org/en-US/docs/Web/API/Crypto)                                                     | Web            |                                                                                                                                                                                                                                               |
| [`crypto`](https://developer.mozilla.org/en-US/docs/Web/API/crypto)                                                     | Web            |                                                                                                                                                                                                                                               |
| [`CryptoKey`](https://developer.mozilla.org/en-US/docs/Web/API/CryptoKey)                                               | Web            |                                                                                                                                                                                                                                               |
| [`CustomEvent`](https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent)                                           | Web            |                                                                                                                                                                                                                                               |
| [`Event`](https://developer.mozilla.org/en-US/docs/Web/API/Event)                                                       | Web            | Also [`ErrorEvent`](https://developer.mozilla.org/en-US/docs/Web/API/ErrorEvent) [`CloseEvent`](https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent) [`MessageEvent`](https://developer.mozilla.org/en-US/docs/Web/API/MessageEvent). |
| [`EventTarget`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget)                                           | Web            |                                                                                                                                                                                                                                               |
| [`exports`](https://nodejs.org/api/globals.html#exports)                                                                | Node.js        |                                                                                                                                                                                                                                               |
| [`fetch`](https://developer.mozilla.org/en-US/docs/Web/API/fetch)                                                       | Web            |                                                                                                                                                                                                                                               |
| [`FormData`](https://developer.mozilla.org/en-US/docs/Web/API/FormData)                                                 | Web            |                                                                                                                                                                                                                                               |
| [`global`](https://nodejs.org/api/globals.html#global)                                                                  | Node.js        | See [Node.js > `global`](/docs/runtime/nodejs-compat#global).                                                                                                                                                                                      |
| [`globalThis`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/globalThis)             | Cross-platform | Aliases to `global`                                                                                                                                                                                                                           |
| [`Headers`](https://developer.mozilla.org/en-US/docs/Web/API/Headers)                                                   | Web            |                                                                                                                                                                                                                                               |
| [`HTMLRewriter`](/docs/runtime/html-rewriter)                                                                                | Cloudflare     |                                                                                                                                                                                                                                               |
| [`JSON`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON)                         | Cross-platform |                                                                                                                                                                                                                                               |
| [`MessageEvent`](https://developer.mozilla.org/en-US/docs/Web/API/MessageEvent)                                         | Web            |                                                                                                                                                                                                                                               |
| [`module`](https://nodejs.org/api/globals.html#module)                                                                  | Node.js        |                                                                                                                                                                                                                                               |
| [`performance`](https://developer.mozilla.org/en-US/docs/Web/API/performance)                                           | Web            |                                                                                                                                                                                                                                               |
| [`process`](https://nodejs.org/api/process.html)                                                                        | Node.js        | See [Node.js > `process`](/docs/runtime/nodejs-compat#node-process)                                                                                                                                                                                |
| [`prompt`](https://developer.mozilla.org/en-US/docs/Web/API/Window/prompt)                                              | Web            | Intended for command-line tools                                                                                                                                                                                                               |
| [`queueMicrotask()`](https://developer.mozilla.org/en-US/docs/Web/API/queueMicrotask)                                   | Web            |                                                                                                                                                                                                                                               |
| [`ReadableByteStreamController`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableByteStreamController)         | Web            |                                                                                                                                                                                                                                               |
| [`ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream)                                     | Web            |                                                                                                                                                                                                                                               |
| [`ReadableStreamDefaultController`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStreamDefaultController)   | Web            |                                                                                                                                                                                                                                               |
| [`ReadableStreamDefaultReader`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStreamDefaultReader)           | Web            |                                                                                                                                                                                                                                               |
| [`reportError`](https://developer.mozilla.org/en-US/docs/Web/API/reportError)                                           | Web            |                                                                                                                                                                                                                                               |
| [`require()`](https://nodejs.org/api/globals.html#require)                                                              | Node.js        |                                                                                                                                                                                                                                               |
| `ResolveMessage`                                                                                                        | Bun            |                                                                                                                                                                                                                                               |
| [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response)                                                 | Web            |                                                                                                                                                                                                                                               |
| [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request)                                                   | Web            |                                                                                                                                                                                                                                               |
| [`setImmediate()`](https://developer.mozilla.org/en-US/docs/Web/API/Window/setImmediate)                                | Web            |                                                                                                                                                                                                                                               |
| [`setInterval()`](https://developer.mozilla.org/en-US/docs/Web/API/Window/setInterval)                                  | Web            |                                                                                                                                                                                                                                               |
| [`setTimeout()`](https://developer.mozilla.org/en-US/docs/Web/API/Window/setTimeout)                                    | Web            |                                                                                                                                                                                                                                               |
| [`ShadowRealm`](https://github.com/tc39/proposal-shadowrealm)                                                           | Cross-platform | Stage 3 proposal                                                                                                                                                                                                                              |
| [`SubtleCrypto`](https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto)                                         | Web            |                                                                                                                                                                                                                                               |
| [`DOMException`](https://developer.mozilla.org/en-US/docs/Web/API/DOMException)                                         | Web            |                                                                                                                                                                                                                                               |
| [`TextDecoder`](https://developer.mozilla.org/en-US/docs/Web/API/TextDecoder)                                           | Web            |                                                                                                                                                                                                                                               |
| [`TextEncoder`](https://developer.mozilla.org/en-US/docs/Web/API/TextEncoder)                                           | Web            |                                                                                                                                                                                                                                               |
| [`TransformStream`](https://developer.mozilla.org/en-US/docs/Web/API/TransformStream)                                   | Web            |                                                                                                                                                                                                                                               |
| [`TransformStreamDefaultController`](https://developer.mozilla.org/en-US/docs/Web/API/TransformStreamDefaultController) | Web            |                                                                                                                                                                                                                                               |
| [`URL`](https://developer.mozilla.org/en-US/docs/Web/API/URL)                                                           | Web            |                                                                                                                                                                                                                                               |
| [`URLSearchParams`](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams)                                   | Web            |                                                                                                                                                                                                                                               |
| [`WebAssembly`](https://nodejs.org/api/globals.html#webassembly)                                                        | Web            |                                                                                                                                                                                                                                               |
| [`WritableStream`](https://developer.mozilla.org/en-US/docs/Web/API/WritableStream)                                     | Web            |                                                                                                                                                                                                                                               |
| [`WritableStreamDefaultController`](https://developer.mozilla.org/en-US/docs/Web/API/WritableStreamDefaultController)   | Web            |                                                                                                                                                                                                                                               |
| [`WritableStreamDefaultWriter`](https://developer.mozilla.org/en-US/docs/Web/API/WritableStreamDefaultWriter)           | Web            |                                                                                                                                                                                                                                               |


# Hashing
Source: https://bun.com/docs/runtime/hashing

Utility functions for hashing and verifying passwords with various cryptographically secure algorithms

<Note>
  Bun implements the `createHash` and `createHmac` functions from [`node:crypto`](https://nodejs.org/api/crypto.html) in
  addition to the Bun-native APIs documented below.
</Note>

***

## `Bun.password`

`Bun.password` is a collection of utility functions for hashing and verifying passwords with various cryptographically secure algorithms.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const password = "super-secure-pa$$word";

const hash = await Bun.password.hash(password);
// => $argon2id$v=19$m=65536,t=2,p=1$tFq+9AVr1bfPxQdh6E8DQRhEXg/M/SqYCNu6gVdRRNs$GzJ8PuBi+K+BVojzPfS5mjnC8OpLGtv8KJqF99eP6a4

const isMatch = await Bun.password.verify(password, hash);
// => true
```

The second argument to `Bun.password.hash` is a params object that selects and configures the hashing algorithm.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const password = "super-secure-pa$$word";

// use argon2 (default)
const argonHash = await Bun.password.hash(password, {
  algorithm: "argon2id", // "argon2id" | "argon2i" | "argon2d"
  memoryCost: 8, // memory usage in kibibytes (minimum 8)
  timeCost: 3, // the number of iterations
});

// use bcrypt
const bcryptHash = await Bun.password.hash(password, {
  algorithm: "bcrypt",
  cost: 4, // number between 4-31
});
```

The algorithm used to create the hash is stored in the hash itself. When using `bcrypt`, Bun encodes the returned hash in [Modular Crypt Format](https://passlib.readthedocs.io/en/stable/modular_crypt_format.html) for compatibility with most existing `bcrypt` implementations. With `argon2`, Bun encodes the result in the newer [PHC format](https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md).

The `verify` function detects the algorithm from the input hash, whether PHC- or MCF-encoded, and uses the matching verification method.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const password = "super-secure-pa$$word";

const hash = await Bun.password.hash(password, {
  /* config */
});

const isMatch = await Bun.password.verify(password, hash);
// => true
```

Synchronous versions of all functions are also available. These functions are computationally expensive, so a blocking API can degrade application performance.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const password = "super-secure-pa$$word";

const hash = Bun.password.hashSync(password, {
  /* config */
});

const isMatch = Bun.password.verifySync(password, hash);
// => true
```

### Salt

`Bun.password.hash` generates a salt automatically and includes it in the hash.

### bcrypt - Modular Crypt Format

In the following [Modular Crypt Format](https://passlib.readthedocs.io/en/stable/modular_crypt_format.html) hash (used by `bcrypt`):

Input:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
await Bun.password.hash("hello", {
  algorithm: "bcrypt",
});
```

Output:

```sh theme={"theme":{"light":"github-light","dark":"dracula"}}
$2b$10$Lyj9kHYZtiyfxh2G60TEfeqs7xkkGiEFFDi3iJGc50ZG/XJ1sxIFi
```

The format is composed of:

* `bcrypt`: `$2b`
* `rounds`: `$10` - rounds (log2 of the actual number of rounds)
* `salt`: `Lyj9kHYZtiyfxh2G60TEfe`
* `hash`: `qs7xkkGiEFFDi3iJGc50ZG/XJ1sxIFi`

By default, the bcrypt library truncates passwords longer than 72 bytes. Instead of silently truncating, `Bun.password.hash` with the `bcrypt` algorithm hashes any password longer than 72 bytes with SHA-512 before passing it to bcrypt.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
await Bun.password.hash("hello".repeat(100), {
  algorithm: "bcrypt",
});
```

### argon2 - PHC format

In the following [PHC format](https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md) hash (used by `argon2`):

Input:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
await Bun.password.hash("hello", {
  algorithm: "argon2id",
});
```

Output:

```sh theme={"theme":{"light":"github-light","dark":"dracula"}}
$argon2id$v=19$m=65536,t=2,p=1$xXnlSvPh4ym5KYmxKAuuHVlDvy2QGHBNuI6bJJrRDOs$2YY6M48XmHn+s5NoBaL+ficzXajq2Yj8wut3r0vnrwI
```

The format is composed of:

* `algorithm`: `$argon2id`
* `version`: `$v=19`
* `memory cost`: `65536`
* `iterations`: `t=2`
* `parallelism`: `p=1`
* `salt`: `$xXnlSvPh4ym5KYmxKAuuHVlDvy2QGHBNuI6bJJrRDOs`
* `hash`: `$2YY6M48XmHn+s5NoBaL+ficzXajq2Yj8wut3r0vnrwI`

***

## `Bun.hash`

`Bun.hash` is a collection of utilities for *non-cryptographic* hashing. Non-cryptographic hashing algorithms are optimized for speed of computation over collision-resistance or security.

The standard `Bun.hash` function uses [Wyhash](https://github.com/wangyi-fudan/wyhash) to generate a 64-bit hash from an input of arbitrary size.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
Bun.hash("some data here");
// 11562320457524636935n
```

The input can be a string, `TypedArray`, `DataView`, `ArrayBuffer`, or `SharedArrayBuffer`.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const arr = new Uint8Array([1, 2, 3, 4]);

Bun.hash("some data here");
Bun.hash(arr);
Bun.hash(arr.buffer);
Bun.hash(new DataView(arr.buffer));
```

The second parameter is an optional integer seed. For 64-bit hashes, pass seeds above `Number.MAX_SAFE_INTEGER` as BigInt to avoid loss of precision.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
Bun.hash("some data here", 1234);
// 15724820720172937558n
```

Additional hashing algorithms are available as properties on `Bun.hash`. The API is the same for each; 32-bit hashes return a number and 64-bit hashes return a bigint.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
Bun.hash.wyhash("data", 1234); // equivalent to Bun.hash()
Bun.hash.crc32("data", 1234);
Bun.hash.adler32("data", 1234);
Bun.hash.cityHash32("data", 1234);
Bun.hash.cityHash64("data", 1234);
Bun.hash.xxHash32("data", 1234);
Bun.hash.xxHash64("data", 1234);
Bun.hash.xxHash3("data", 1234);
Bun.hash.murmur32v3("data", 1234);
Bun.hash.murmur32v2("data", 1234);
Bun.hash.murmur64v2("data", 1234);
Bun.hash.rapidhash("data", 1234);
```

***

## `Bun.CryptoHasher`

`Bun.CryptoHasher` incrementally computes a hash of string or binary data with a cryptographic hash algorithm. It supports the following algorithms:

* `"blake2b256"`
* `"blake2b512"`
* `"blake2s256"`
* `"md4"`
* `"md5"`
* `"ripemd160"`
* `"sha1"`
* `"sha224"`
* `"sha256"`
* `"sha384"`
* `"sha512"`
* `"sha512-224"`
* `"sha512-256"`
* `"sha3-224"`
* `"sha3-256"`
* `"sha3-384"`
* `"sha3-512"`
* `"shake128"`
* `"shake256"`

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const hasher = new Bun.CryptoHasher("sha256");
hasher.update("hello world");
hasher.digest();
// Uint8Array(32) [ <byte>, <byte>, ... ]
```

Feed data to the hasher incrementally with `.update()`, which accepts `string`, `TypedArray`, and `ArrayBuffer`.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const hasher = new Bun.CryptoHasher("sha256");

hasher.update("hello world");
hasher.update(new Uint8Array([1, 2, 3]));
hasher.update(new ArrayBuffer(10));
```

For strings, an optional second parameter specifies the encoding (default `'utf-8'`). The following encodings are supported:

| Category                   | Encodings                                 |
| -------------------------- | ----------------------------------------- |
| Binary encodings           | `"base64"` `"base64url"` `"hex"`          |
| Character encodings        | `"utf8"` `"utf-8"` `"utf16le"` `"latin1"` |
| Legacy character encodings | `"ascii"` `"binary"` `"ucs2"` `"ucs-2"`   |

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
hasher.update("hello world"); // defaults to utf8
hasher.update("hello world", "hex");
hasher.update("hello world", "base64");
hasher.update("hello world", "latin1");
```

Once you have fed in all the data, compute the final hash with `.digest()`. By default, this method returns a `Uint8Array` containing the hash.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const hasher = new Bun.CryptoHasher("sha256");
hasher.update("hello world");

hasher.digest();
// => Uint8Array(32) [ 185, 77, 39, 185, 147, ... ]
```

To get the hash as a string, pass an encoding to `.digest()`:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
hasher.digest("base64");
// => "uU0nuZNNPgilLlLX2n2r+sSE7+N6U4DukIj3rOLvzek="

hasher.digest("hex");
// => "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9"
```

Alternatively, `.digest()` can write the hash into an existing `TypedArray` instead of allocating a new one.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const arr = new Uint8Array(32);

hasher.digest(arr);

console.log(arr);
// => Uint8Array(32) [ 185, 77, 39, 185, 147, ... ]
```

### HMAC in `Bun.CryptoHasher`

`Bun.CryptoHasher` can compute HMAC digests. Pass the key to the constructor.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const hasher = new Bun.CryptoHasher("sha256", "secret-key");
hasher.update("hello world");
console.log(hasher.digest("hex"));
// => "095d5a21fe6d0646db223fdf3de6436bb8dfb2fab0b51677ecf6441fcf5f2a67"
```

HMAC supports a more limited set of algorithms:

* `"blake2b256"`
* `"blake2b512"`
* `"md4"`
* `"md5"`
* `"ripemd160"`
* `"sha1"`
* `"sha224"`
* `"sha256"`
* `"sha384"`
* `"sha512"`
* `"sha512-224"`
* `"sha512-256"`
* `"sha3-224"`
* `"sha3-256"`
* `"sha3-384"`
* `"sha3-512"`

Unlike the non-HMAC `Bun.CryptoHasher`, the HMAC `Bun.CryptoHasher` instance does not reset after you call `.digest()`. Using the same instance again throws an error.

Other methods like `.copy()` and `.update()` are supported as long as you call them before `.digest()`.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const hasher = new Bun.CryptoHasher("sha256", "secret-key");
hasher.update("hello world");

const copy = hasher.copy();
copy.update("!");
console.log(copy.digest("hex"));
// => "3840176c3d8923f59ac402b7550404b28ab11cb0ef1fa199130a5c37864b5497"

console.log(hasher.digest("hex"));
// => "095d5a21fe6d0646db223fdf3de6436bb8dfb2fab0b51677ecf6441fcf5f2a67"
```


# HTMLRewriter
Source: https://bun.com/docs/runtime/html-rewriter

Use Bun's HTMLRewriter to transform HTML documents with CSS selectors

HTMLRewriter transforms HTML documents with CSS selectors. It works with `Response`, `string`, and `ArrayBuffer` inputs. Bun's implementation is based on Cloudflare's [lol-html](https://github.com/cloudflare/lol-html).

***

## Usage

A common use case is rewriting URLs in HTML content:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
// Replace all images with a rickroll
const rewriter = new HTMLRewriter().on("img", {
  element(img) {
    // Famous rickroll video thumbnail
    img.setAttribute("src", "https://img.youtube.com/vi/dQw4w9WgXcQ/maxresdefault.jpg");

    // Wrap the image in a link to the video
    img.before('<a href="https://www.youtube.com/watch?v=dQw4w9WgXcQ" target="_blank">', {
      html: true,
    });
    img.after("</a>", { html: true });

    // Add some fun alt text
    img.setAttribute("alt", "Definitely not a rickroll");
  },
});

// An example HTML document
const html = `
<html>
<body>
  <img src="/cat.jpg">
  <img src="dog.png">
  <img src="https://example.com/bird.webp">
</body>
</html>
`;

const result = rewriter.transform(html);
console.log(result);
```

The rewriter replaces every image with a thumbnail of Rick Astley and wraps each `<img>` in a link, producing a diff like this:

```html theme={"theme":{"light":"github-light","dark":"dracula"}}
<html>
  <body>
    <img src="/cat.jpg" /> <!-- [!code --] -->
    <img src="dog.png" /> <!-- [!code --] -->
    <img src="https://example.com/bird.webp" /> <!-- [!code --] -->
    <a href="https://www.youtube.com/watch?v=dQw4w9WgXcQ" target="_blank"> <!-- [!code ++] -->
      <img src="https://img.youtube.com/vi/dQw4w9WgXcQ/maxresdefault.jpg" alt="Definitely not a rickroll" /> <!-- [!code ++] -->
    </a> <!-- [!code ++] -->
    <a href="https://www.youtube.com/watch?v=dQw4w9WgXcQ" target="_blank"> <!-- [!code ++] -->
      <img src="https://img.youtube.com/vi/dQw4w9WgXcQ/maxresdefault.jpg" alt="Definitely not a rickroll" /> <!-- [!code ++] -->
    </a> <!-- [!code ++] -->
    <a href="https://www.youtube.com/watch?v=dQw4w9WgXcQ" target="_blank"> <!-- [!code ++] -->
      <img src="https://img.youtube.com/vi/dQw4w9WgXcQ/maxresdefault.jpg" alt="Definitely not a rickroll" /> <!-- [!code ++] -->
    </a> <!-- [!code ++] -->
  </body>
</html>
```

Clicking any image now leads to [a very famous video](https://www.youtube.com/watch?v=dQw4w9WgXcQ).

### Input types

HTMLRewriter can transform HTML from several input types:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
// From Response
rewriter.transform(new Response("<div>content</div>"));

// From string
rewriter.transform("<div>content</div>");

// From ArrayBuffer
rewriter.transform(new TextEncoder().encode("<div>content</div>").buffer);

// From Blob (wrap in a Response)
rewriter.transform(new Response(new Blob(["<div>content</div>"])));

// From File (wrap in a Response)
rewriter.transform(new Response(Bun.file("index.html")));
```

The Cloudflare Workers implementation of HTMLRewriter only supports `Response` objects.

### Element Handlers

The `on(selector, handlers)` method registers handlers for HTML elements that match a CSS selector. The handlers run for each matching element during parsing:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
rewriter.on("div.content", {
  // Handle elements
  element(element) {
    element.setAttribute("class", "new-content");
    element.append("<p>New content</p>", { html: true });
  },
  // Handle text nodes
  text(text) {
    text.replace("new text");
  },
  // Handle comments
  comments(comment) {
    comment.remove();
  },
});
```

Handlers can be asynchronous and return a Promise. The transformation pauses on
that element until the Promise settles, so handlers still run one at a time, in
document order:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
rewriter.on("div", {
  async element(element) {
    const fragment = await fetch("https://example.com/fragment").then(r => r.text());
    element.setInnerContent(fragment, { html: true });
  },
});
```

`transform(response)` returns immediately. The rewrite continues in the
background, and you read the result off the returned `Response`. Reading it
paces the rewrite: a streamed input (a file, a `fetch()` response, a
`ReadableStream`) is pulled through only as fast as the returned body is
consumed, so a slow reader does not accumulate the whole document in memory. If
nothing reads the body, the rewrite still runs every handler to the end of the
document and buffers the output until it is read. Because the rewrite outlives
`transform()`, an error thrown by an async handler (or a Promise it returns that
rejects) rejects the response body instead of throwing from `transform()`:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
try {
  const output = await rewriter.transform(new Response(html)).text();
} catch (error) {
  console.error("a handler failed:", error);
}
```

`transform()` on a `string` or `ArrayBuffer` has to return its result
synchronously, so it cannot wait for a handler that needs the event loop to turn
(a timer, I/O, a `fetch`). Such a handler makes `transform()` throw a
`TypeError`, and the rewrite fails without running any further handlers:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
new HTMLRewriter()
  .on("div", {
    async element(element) {
      await Bun.sleep(1000); // needs a timer
    },
  })
  .transform("<div></div>");
// TypeError: HTMLRewriter.transform() cannot synchronously return a string
// because a content handler returned a Promise that did not resolve within a
// microtask. Pass a Response instead and await its body
```

A handler whose Promise settles within a microtask checkpoint still works with
`transform(string)`. Anything that does not need the event loop qualifies,
including `process.nextTick` and already-resolved Promises. Pass a `Response`
whenever a handler might await real work.

### CSS Selector Support

The `on()` method supports a wide range of CSS selectors:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
// Tag selectors
rewriter.on("p", handler);

// Class selectors
rewriter.on("p.red", handler);

// ID selectors
rewriter.on("h1#header", handler);

// Attribute selectors
rewriter.on("p[data-test]", handler); // Has attribute
rewriter.on('p[data-test="one"]', handler); // Exact match
rewriter.on('p[data-test="one" i]', handler); // Case-insensitive
rewriter.on('p[data-test="one" s]', handler); // Case-sensitive
rewriter.on('p[data-test~="two"]', handler); // Word match
rewriter.on('p[data-test^="a"]', handler); // Starts with
rewriter.on('p[data-test$="1"]', handler); // Ends with
rewriter.on('p[data-test*="b"]', handler); // Contains
rewriter.on('p[data-test|="a"]', handler); // Dash-separated

// Combinators
rewriter.on("div span", handler); // Descendant
rewriter.on("div > span", handler); // Direct child

// Pseudo-classes
rewriter.on("p:nth-child(2)", handler);
rewriter.on("p:first-child", handler);
rewriter.on("p:nth-of-type(2)", handler);
rewriter.on("p:first-of-type", handler);
rewriter.on("p:not(:first-child)", handler);

// Universal selector
rewriter.on("*", handler);
```

### Element Operations

All element modification methods return the element instance, so you can chain calls:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
rewriter.on("div", {
  element(el) {
    // Attributes
    el.setAttribute("class", "new-class").setAttribute("data-id", "123");

    const classAttr = el.getAttribute("class"); // "new-class"
    const hasId = el.hasAttribute("id"); // boolean
    el.removeAttribute("class");

    // Content manipulation
    el.setInnerContent("New content"); // Escapes HTML by default
    el.setInnerContent("<p>HTML content</p>", { html: true }); // Parses HTML
    el.setInnerContent(""); // Clear content

    // Position manipulation
    el.before("Content before").after("Content after").prepend("First child").append("Last child");

    // HTML content insertion
    el.before("<span>before</span>", { html: true })
      .after("<span>after</span>", { html: true })
      .prepend("<span>first</span>", { html: true })
      .append("<span>last</span>", { html: true });

    // Removal
    el.remove(); // Remove element and contents
    el.removeAndKeepContent(); // Remove only the element tags

    // Properties
    console.log(el.tagName); // Lowercase tag name
    console.log(el.namespaceURI); // Element's namespace URI
    console.log(el.selfClosing); // Whether element is self-closing (e.g. <div />)
    console.log(el.canHaveContent); // Whether element can contain content (false for void elements like <br>)
    console.log(el.removed); // Whether element was removed

    // Attributes iteration
    for (const [name, value] of el.attributes) {
      console.log(name, value);
    }

    // End tag handling
    el.onEndTag(endTag => {
      endTag.before("Before end tag");
      endTag.after("After end tag");
      endTag.remove(); // Remove the end tag
      console.log(endTag.name); // Tag name in lowercase
    });
  },
});
```

### Text Operations

Text chunks represent portions of text content and report their position in the text node:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
rewriter.on("p", {
  text(text) {
    // Content
    console.log(text.text); // Text content
    console.log(text.lastInTextNode); // Whether this is the last chunk
    console.log(text.removed); // Whether text was removed

    // Manipulation
    text.before("Before text").after("After text").replace("New text").remove();

    // HTML content insertion
    text
      .before("<span>before</span>", { html: true })
      .after("<span>after</span>", { html: true })
      .replace("<span>replace</span>", { html: true });
  },
});
```

### Comment Operations

Comments support similar methods to text nodes:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
rewriter.on("*", {
  comments(comment) {
    // Content
    console.log(comment.text); // Comment text
    comment.text = "New comment text"; // Set comment text
    console.log(comment.removed); // Whether comment was removed

    // Manipulation
    comment.before("Before comment").after("After comment").replace("New comment").remove();

    // HTML content insertion
    comment
      .before("<span>before</span>", { html: true })
      .after("<span>after</span>", { html: true })
      .replace("<span>replace</span>", { html: true });
  },
});
```

### Document Handlers

The `onDocument(handlers)` method registers handlers for events at the document level rather than within specific elements:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
rewriter.onDocument({
  // Handle doctype
  doctype(doctype) {
    console.log(doctype.name); // "html"
    console.log(doctype.publicId); // public identifier if present
    console.log(doctype.systemId); // system identifier if present
  },
  // Handle text nodes
  text(text) {
    console.log(text.text);
  },
  // Handle comments
  comments(comment) {
    console.log(comment.text);
  },
  // Handle document end
  end(end) {
    end.append("<!-- Footer -->", { html: true });
  },
});
```

### Response Handling

When transforming a Response, HTMLRewriter:

* Preserves the status code, headers, and other response properties
* Transforms the body while maintaining streaming capabilities
* Handles content-encoding (like gzip) automatically
* Marks the original response body as used after transformation
* Clones headers to the new response

## Error Handling

The overload you called decides which channel an error takes. Timing never
does. `transform()` itself throws for:

* Invalid selector syntax in the `on()` method
* Invalid input types (for example, passing a Symbol)
* Body already used errors, and input bodies that have already failed or aborted
* Anything a content handler raises on a `string` / `ArrayBuffer` input, since
  those have to produce their result before `transform()` returns. The same goes
  for a handler that needs the event loop (see [Element Handlers](#element-handlers))

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
try {
  const result = rewriter.transform("<div></div>");
} catch (error) {
  console.error("HTMLRewriter error:", error);
}
```

For a `Response` input, `transform()` returns before the rewrite finishes, so
everything the rewrite discovers surfaces on the output body instead:

* An error thrown by a content handler, or a rejected Promise one returned
* Malformed or truncated input
* Stream errors reading the input body
* Memory allocation failures

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
try {
  const output = await rewriter.transform(new Response(html)).text();
} catch (error) {
  console.error("the rewrite failed:", error);
}
```

If a handler creates a Promise but neither returns nor awaits it, a rejection
from that Promise reaches neither channel. Like any detached rejection, it goes
to the process-global `unhandledRejection` path. Earlier versions of Bun could
surface it from `transform()` itself.

***

## See also

You can also read the [Cloudflare documentation](https://developers.cloudflare.com/workers/runtime-apis/html-rewriter/), which this API is intended to be compatible with.


# Cookies
Source: https://bun.com/docs/runtime/http/cookies

Work with cookies in HTTP requests and responses using Bun's built-in Cookie API.

Bun has a built-in API for working with cookies in HTTP requests and responses. The `BunRequest` object exposes a `cookies` property, a `CookieMap` for reading and modifying cookies. When using `routes`, `Bun.serve()` automatically tracks calls to `request.cookies.set` and applies them to the response.

## Reading cookies

Read cookies from incoming requests using the `cookies` property on the `BunRequest` object:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
Bun.serve({
  routes: {
    "/profile": req => {
      // Access cookies from the request
      const userId = req.cookies.get("user_id");
      const theme = req.cookies.get("theme") || "light";

      return Response.json({
        userId,
        theme,
        message: "Profile page",
      });
    },
  },
});
```

## Setting cookies

To set cookies, use the `set` method on the `CookieMap` from the `BunRequest` object.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
Bun.serve({
  routes: {
    "/login": req => {
      const cookies = req.cookies;

      // Set a cookie with various options
      cookies.set("user_id", "12345", {
        maxAge: 60 * 60 * 24 * 7, // 1 week
        httpOnly: true,
        secure: true,
        path: "/",
      });

      // Add a theme preference cookie
      cookies.set("theme", "dark");

      // Modified cookies from the request are automatically applied to the response
      return new Response("Login successful");
    },
  },
});
```

## Deleting cookies

To delete a cookie, use the `delete` method on the `request.cookies` (`CookieMap`) object:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
Bun.serve({
  routes: {
    "/logout": req => {
      // Delete the user_id cookie
      req.cookies.delete("user_id", {
        path: "/",
      });

      return new Response("Logged out successfully");
    },
  },
});
```

Deleted cookies become a `Set-Cookie` header on the response with the `Expires` attribute set to a date in the past and an empty `value`.


# Error Handling
Source: https://bun.com/docs/runtime/http/error-handling

Learn how to handle errors in Bun's development server

To activate development mode, set `development: true`.

```ts title="server.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
Bun.serve({
  development: true, // [!code ++]
  fetch(req) {
    throw new Error("woops!");
  },
});
```

In development mode, Bun surfaces errors in-browser with a built-in error page.

<Frame>
  <img alt="Bun's built-in 500 page" />
</Frame>

### `error` callback

To handle server-side errors, implement an `error` handler. Return a `Response` to serve to the client when an error occurs. In `development` mode, this response replaces Bun's default error page.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
Bun.serve({
  fetch(req) {
    throw new Error("woops!");
  },
  error(error) {
    return new Response(`<pre>${error}\n${error.stack}</pre>`, {
      headers: {
        "Content-Type": "text/html",
      },
    });
  },
});
```

<Info>[Learn more about debugging in Bun](/docs/runtime/debugger)</Info>


# Metrics
Source: https://bun.com/docs/runtime/http/metrics

Monitor server activity with built-in metrics

### `server.pendingRequests` and `server.pendingWebSockets`

Monitor server activity with built-in counters:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const server = Bun.serve({
  fetch(req, server) {
    return new Response(
      `Active requests: ${server.pendingRequests}\n` + `Active WebSockets: ${server.pendingWebSockets}`,
    );
  },
});
```

### `server.subscriberCount(topic)`

Get the number of subscribers for a WebSocket topic:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const server = Bun.serve({
  fetch(req, server) {
    const chatUsers = server.subscriberCount("chat");
    return new Response(`${chatUsers} users in chat`);
  },
  websocket: {
    message(ws) {
      ws.subscribe("chat");
    },
  },
});
```


# Routing
Source: https://bun.com/docs/runtime/http/routing

Define routes in `Bun.serve` using static paths, parameters, and wildcards

Add routes to `Bun.serve()` with the `routes` property (static paths, parameters, and wildcards), or handle unmatched requests with the [`fetch`](#fetch-request-handler) method.

`Bun.serve()`'s router builds on top of uWebSocket's [tree-based approach](https://github.com/oven-sh/bun/blob/0d1a00fa0f7830f8ecd99c027fce8096c9d459b6/packages/bun-uws/src/HttpRouter.h#L57-L64). The router adds [SIMD-accelerated route parameter decoding](https://github.com/oven-sh/bun/blob/main/src/jsc/bindings/decodeURIComponentSIMD.cpp#L21-L271) and [JavaScriptCore structure caching](https://github.com/oven-sh/bun/blob/main/src/jsc/bindings/ServerRouteList.cpp#L100-L101) to push the performance limits of what modern hardware allows.

## Basic Setup

```ts title="server.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
Bun.serve({
  routes: {
    "/": () => new Response("Home"),
    "/api": () => Response.json({ success: true }),
    "/users": async () => Response.json({ users: [] }),
  },
  fetch() {
    return new Response("Unmatched route");
  },
});
```

Routes in `Bun.serve()` receive a `BunRequest` (which extends [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request)) and return a [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response) or `Promise<Response>`. Because routes use these `Request` and `Response` types, it is easier to use the same code for both sending and receiving HTTP requests.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
// Simplified for brevity
interface BunRequest<T extends string> extends Request {
  params: Record<T, string>;
  readonly cookies: CookieMap;
}
```

## Asynchronous Routes

### Async/await

Use async/await in route handlers to return a `Promise<Response>`.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
import { sql, serve } from "bun";

serve({
  port: 3001,
  routes: {
    "/api/version": async () => {
      const [version] = await sql`SELECT version()`;
      return Response.json(version);
    },
  },
});
```

### Promise

You can also return a `Promise<Response>` from a route handler.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
import { sql, serve } from "bun";

serve({
  routes: {
    "/api/version": () => {
      return new Promise(resolve => {
        setTimeout(async () => {
          const [version] = await sql`SELECT version()`;
          resolve(Response.json(version));
        }, 100);
      });
    },
  },
});
```

***

## Route precedence

Bun matches routes in order of specificity:

1. Exact routes (`/users/all`)
2. Parameter routes (`/users/:id`)
3. Wildcard routes (`/users/*`)
4. Global catch-all (`/*`)

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
Bun.serve({
  routes: {
    // Most specific first
    "/api/users/me": () => new Response("Current user"),
    "/api/users/:id": req => new Response(`User ${req.params.id}`),
    "/api/*": () => new Response("API catch-all"),
    "/*": () => new Response("Global catch-all"),
  },
});
```

***

## Type-safe route parameters

TypeScript parses route parameters when passed as a string literal, so your editor shows autocomplete when accessing `request.params`.

```ts title="index.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import type { BunRequest } from "bun";

Bun.serve({
  routes: {
    // TypeScript knows the shape of params when passed as a string literal
    "/orgs/:orgId/repos/:repoId": req => {
      const { orgId, repoId } = req.params;
      return Response.json({ orgId, repoId });
    },

    "/orgs/:orgId/repos/:repoId/settings": (
      // optional: you can explicitly pass a type to BunRequest:
      req: BunRequest<"/orgs/:orgId/repos/:repoId/settings">,
    ) => {
      const { orgId, repoId } = req.params;
      return Response.json({ orgId, repoId });
    },
  },
});
```

Bun automatically decodes percent-encoded route parameter values, including Unicode characters. Bun replaces invalid Unicode with the Unicode replacement character (`\uFFFD`).

### Static responses

Routes can also be `Response` objects (without the handler function). `Bun.serve()` optimizes them for zero-allocation dispatch, which suits health checks, redirects, and fixed content:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
Bun.serve({
  routes: {
    // Health checks
    "/health": new Response("OK"),
    "/ready": new Response("Ready", {
      headers: {
        // Pass custom headers
        "X-Ready": "1",
      },
    }),

    // Redirects
    "/blog": Response.redirect("https://bun.com/blog"),

    // API responses
    "/api/config": Response.json({
      version: "1.0.0",
      env: "production",
    }),
  },
});
```

Static responses do not allocate additional memory after initialization. You can generally expect at least a 15% performance improvement over manually returning a `Response` object.

Bun caches static route responses for the lifetime of the server object. To reload static routes, call `server.reload(options)`.

### File Responses vs Static Responses

Serving a file from a route behaves differently depending on whether you buffer the file content or serve it directly:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
Bun.serve({
  routes: {
    // Static route - content is buffered in memory at startup
    "/logo.png": new Response(await Bun.file("./logo.png").bytes()),

    // File route - content is read from filesystem on each request
    "/download.zip": new Response(Bun.file("./download.zip")),
  },
});
```

**Static routes** (`new Response(await file.bytes())`) buffer content in memory at startup:

* **Zero filesystem I/O** during requests - content served entirely from memory
* **ETag support** - Automatically generates and validates ETags for caching
* **If-None-Match** - Returns `304 Not Modified` when client ETag matches
* **No 404 handling** - Missing files cause startup errors, not runtime 404s
* **Memory usage** - Full file content stored in RAM
* **Best for**: Small static assets, API responses, frequently accessed files

**File routes** (`new Response(Bun.file(path))`) read from filesystem per request:

* **Filesystem reads** on each request - checks file existence and reads content
* **Built-in 404 handling** - Returns `404 Not Found` if file doesn't exist or becomes inaccessible
* **Last-Modified support** - Uses file modification time for `If-Modified-Since` headers
* **If-Modified-Since** - Returns `304 Not Modified` when file hasn't changed since client's cached version
* **Range request support** - Automatically handles partial content requests with `Content-Range` headers
* **Streaming transfers** - Uses buffered reader with backpressure handling for efficient memory usage
* **Memory efficient** - Only buffers small chunks during transfer, not entire file
* **Best for**: Large files, dynamic content, user uploads, files that change frequently

### Directory routes

To serve an entire directory tree at a URL prefix, pass `{ dir }` as the route value. The route path must end in `/*`.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
Bun.serve({
  routes: {
    "/static/*": { dir: "./public" },
  },
});
```

Bun percent-decodes the part of the request URL after the prefix once and opens it relative to `dir`. Bun rejects non-canonical paths with `404`, so the served path is always the path the router matched. A path is non-canonical if it contains `.`, `..`, empty segments, `%2F`, or a `%XX` sequence encoding a character that may appear literally in a path segment. On Linux the open uses `openat2(RESOLVE_IN_ROOT)`, so the kernel clamps symlinks that would escape `dir`.

<Info>
  Routing is case-sensitive, but filesystems on macOS and Windows are case-insensitive by default. As a result, a
  case-varied URL (`/static/Admin/secret.txt`) routes to the directory wildcard rather than a sibling `/static/admin/*`
  handler and still opens `admin/secret.txt`. As with nginx, Caddy, and other static file servers, keep
  access-controlled content outside `dir` rather than relying on an overlapping route to gate it.
</Info>

Directory routes share the response path with file routes:

* **Content-Type** is set from the file extension.
* **Last-Modified** and a weak `ETag` (`W/"<size>-<mtime>"`) are sent on every response, and `If-Modified-Since` / `If-None-Match` are honored with `304 Not Modified`.
* **Range requests** are supported with `Accept-Ranges: bytes` and `Content-Range`.
* A request that resolves to a directory without a trailing `/` receives a `301` redirect to the trailing-slash URL. With the trailing slash, Bun serves `index.html` from that directory.
* Missing files return `404`.

Pass `statCache: false` to disable the per-path `Last-Modified` cache (saves roughly 20 KB per route).

***

## Streaming files

To stream a file, return a `Response` object with a `BunFile` object as the body.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
Bun.serve({
  fetch(req) {
    return new Response(Bun.file("./hello.txt"));
  },
});
```

<Info>
  ⚡️ **Speed** — Bun automatically uses the [`sendfile(2)`](https://man7.org/linux/man-pages/man2/sendfile.2.html)
  system call when possible, enabling zero-copy file transfers in the kernel—the fastest way to send files.
</Info>

To send part of a file, use the [`slice(start, end)`](https://developer.mozilla.org/en-US/docs/Web/API/Blob/slice) method on the `Bun.file` object. Bun sets the `Content-Range` and `Content-Length` headers on the `Response` object automatically.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
Bun.serve({
  fetch(req) {
    // parse `Range` header
    const [start = 0, end = Infinity] = req.headers
      .get("Range") // Range: bytes=0-100
      .split("=") // ["Range: bytes", "0-100"]
      .at(-1) // "0-100"
      .split("-") // ["0", "100"]
      .map(Number); // [0, 100]

    // return a slice of the file
    const bigFile = Bun.file("./big-video.mp4");
    return new Response(bigFile.slice(start, end));
  },
});
```

***

## `fetch` request handler

The `fetch` handler runs for incoming requests that no route matched. It receives a [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request) object and returns a [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response) or [`Promise<Response>`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise).

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
Bun.serve({
  fetch(req) {
    const url = new URL(req.url);
    if (url.pathname === "/") return new Response("Home page!");
    if (url.pathname === "/blog") return new Response("Blog!");
    return new Response("404!");
  },
});
```

The `fetch` handler supports async/await:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
import { sleep, serve } from "bun";

serve({
  async fetch(req) {
    const start = performance.now();
    await sleep(10);
    const end = performance.now();
    return new Response(`Slept for ${end - start}ms`);
  },
});
```

Promise-based responses are also supported:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
Bun.serve({
  fetch(req) {
    // Forward the request to another server.
    return fetch("https://example.com");
  },
});
```

The `fetch` handler also receives the `Server` object as its second argument.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
// `server` is passed in as the second argument to `fetch`.
const server = Bun.serve({
  fetch(req, server) {
    const ip = server.requestIP(req);
    return new Response(`Your IP is ${ip.address}`);
  },
});
```


# Server
Source: https://bun.com/docs/runtime/http/server

Use `Bun.serve` to start a high-performance HTTP server in Bun

## Basic Setup

```ts title="index.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
const server = Bun.serve({
  // `routes` requires Bun v1.2.3+
  routes: {
    // Static routes
    "/api/status": new Response("OK"),

    // Dynamic routes
    "/users/:id": req => {
      return new Response(`Hello User ${req.params.id}!`);
    },

    // Per-HTTP method handlers
    "/api/posts": {
      GET: () => new Response("List posts"),
      POST: async req => {
        const body = await req.json();
        return Response.json({ created: true, ...body });
      },
    },

    // Wildcard route for all routes that start with "/api/" and aren't otherwise matched
    "/api/*": Response.json({ message: "Not found" }, { status: 404 }),

    // Redirect from /blog/hello to /blog/hello/world
    "/blog/hello": Response.redirect("/blog/hello/world"),

    // Serve a file by lazily loading it into memory
    "/favicon.ico": Bun.file("./favicon.ico"),
  },

  // (optional) fallback for unmatched routes:
  // Required if Bun's version < 1.2.3
  fetch(req) {
    return new Response("Not Found", { status: 404 });
  },
});

console.log(`Server running at ${server.url}`);
```

***

## HTML imports

Import HTML files directly into your server code to build full-stack applications with both server-side and client-side code. HTML imports work in two modes:

**Development (`bun --hot`):** Bun bundles assets on demand at runtime and enables hot module replacement (HMR): when you change your frontend code, the browser updates without a full page reload.

**Production (`bun build`):** When you build with `bun build --target=bun`, the `import index from "./index.html"` statement resolves to a pre-built manifest object containing all bundled client assets. `Bun.serve` serves the assets from this manifest with no bundling at runtime.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
import myReactSinglePageApp from "./index.html";

Bun.serve({
  routes: {
    "/": myReactSinglePageApp,
  },
});
```

HTML imports do more than serve HTML: they run Bun's [bundler](/docs/bundler), JavaScript transpiler, and CSS parser, so you can build frontends with React, TypeScript, and Tailwind CSS.

For a complete guide to building full-stack applications with HTML imports, see [fullstack dev server](/docs/bundler/fullstack).

***

## Configuration

### Changing the `port` and `hostname`

To configure which port and hostname the server listens on, set `port` and `hostname` in the options object.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
Bun.serve({
  port: 8080, // defaults to $BUN_PORT, $PORT, $NODE_PORT otherwise 3000 // [!code ++]
  hostname: "mydomain.com", // defaults to "0.0.0.0" // [!code ++]
  fetch(req) {
    return new Response("404!");
  },
});
```

To randomly select an available port, set `port` to `0`.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const server = Bun.serve({
  port: 0, // random port // [!code ++]
  fetch(req) {
    return new Response("404!");
  },
});

// server.port is the randomly selected port
console.log(server.port);
```

Read the chosen port from the server's `port` or `url` property.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
console.log(server.port); // 3000
console.log(server.url); // http://localhost:3000
```

### Configuring a default port

Several flags and environment variables set the default port, which Bun uses when the `port` option is not set.

* `--port` CLI flag

```sh theme={"theme":{"light":"github-light","dark":"dracula"}}
bun --port=4002 server.ts
```

* `BUN_PORT` environment variable

```sh theme={"theme":{"light":"github-light","dark":"dracula"}}
BUN_PORT=4002 bun server.ts
```

* `PORT` environment variable

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
PORT=4002 bun server.ts
```

* `NODE_PORT` environment variable

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
NODE_PORT=4002 bun server.ts
```

***

## Unix domain sockets

To listen on a [unix domain socket](https://en.wikipedia.org/wiki/Unix_domain_socket), pass the `unix` option with the path to the socket.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
Bun.serve({
  unix: "/tmp/my-socket.sock", // path to socket
  fetch(req) {
    return new Response(`404!`);
  },
});
```

### Abstract namespace sockets

On Linux, Bun also supports abstract namespace sockets: prefix the `unix` path with a null byte.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
Bun.serve({
  unix: "\0my-abstract-socket", // abstract namespace socket
  fetch(req) {
    return new Response(`404!`);
  },
});
```

Unlike unix domain sockets, abstract namespace sockets are not bound to the filesystem and are automatically removed when the last reference to the socket is closed.

***

## HTTP/3 (QUIC)

<Note>HTTP/3 support in `Bun.serve` is **experimental** and may change in future releases.</Note>

`Bun.serve` can also listen for HTTP/3 over QUIC. Set `http3: true` together with [`tls`](./tls); HTTP/3 requires TLS.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
Bun.serve({
  tls: {
    key: Bun.file("./key.pem"),
    cert: Bun.file("./cert.pem"),
  },
  http3: true, // [!code ++]
  fetch(req) {
    return new Response("Hello over HTTP/3!");
  },
});
```

When `http3` is enabled, the server listens on the same port over both TCP (HTTP/1.1) and UDP (HTTP/3). HTTP/1.1 responses include an `Alt-Svc` header advertising the HTTP/3 endpoint so capable clients can upgrade automatically.

To serve HTTP/3 only, with no TCP listener at all, set `http1: false`:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
Bun.serve({
  tls: {
    key: Bun.file("./key.pem"),
    cert: Bun.file("./cert.pem"),
  },
  http3: true,
  http1: false, // [!code ++]
  fetch(req) {
    return new Response("HTTP/3 only");
  },
});
```

<Note>
  `http3` is not supported with unix domain sockets: QUIC requires a UDP port. `http1: false` requires `http3: true`.
</Note>

***

## idleTimeout

By default, `Bun.serve` closes connections after **10 seconds** of inactivity. A connection is idle when no data is being sent or received. That includes in-flight requests where your handler is still running but hasn't written any bytes to the response yet. Browsers and `fetch()` clients see this as a connection reset.

To configure this, set the `idleTimeout` field (in seconds). The maximum value is `255`, and `0` disables the timeout entirely.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
Bun.serve({
  // 30 seconds (default is 10)
  idleTimeout: 30,

  fetch(req) {
    return new Response("Bun!");
  },
});
```

<Note>
  **Streaming & Server-Sent Events** — The idle timer applies while a response is being streamed. If your stream goes
  quiet for longer than `idleTimeout`, Bun closes the connection mid-response. For long-lived streams, disable the
  timeout for that request with [`server.timeout(req, 0)`](#server-timeout-request-seconds).
</Note>

***

## export default syntax

Instead of passing the server options into `Bun.serve`, you can `export default` them.

```ts server.ts theme={"theme":{"light":"github-light","dark":"dracula"}}
import type { Serve } from "bun";

export default {
  fetch(req) {
    return new Response("Bun!");
  },
} satisfies Serve.Options<undefined>;
```

The type parameter `<undefined>` is the WebSocket data type. If you add a `websocket` handler that attaches custom data with `server.upgrade(req, { data: ... })`, replace `undefined` with your data type.

You can run this file as-is: when Bun sees a file with a `default` export containing a `fetch` handler, it passes the export into `Bun.serve`.

***

## Hot Route Reloading

Update routes without server restarts using `server.reload()`:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const server = Bun.serve({
  routes: {
    "/api/version": () => Response.json({ version: "1.0.0" }),
  },
});

// Deploy new routes without downtime
server.reload({
  routes: {
    "/api/version": () => Response.json({ version: "2.0.0" }),
  },
});
```

***

## Server Lifecycle Methods

### `server.stop()`

To stop the server from accepting new connections:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const server = Bun.serve({
  fetch(req) {
    return new Response("Hello!");
  },
});

// Gracefully stop the server (waits for in-flight requests)
await server.stop();

// Force stop and close all active connections
await server.stop(true);
```

By default, `stop()` allows in-flight requests and WebSocket connections to complete. The server closes idle keep-alive connections immediately. Connections with a request in flight close once the server has sent their response. Pass `true` to immediately terminate all connections instead. The returned promise resolves once every connection has closed.

### `server.closeIdleConnections()`

To close keep-alive connections that are not currently serving a request, without stopping the server:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const closed = server.closeIdleConnections();
console.log(`closed ${closed} idle connections`);
```

It returns the number of connections it closed. Connections with a request in flight and open WebSockets are untouched, and the server keeps accepting new connections. This method mirrors `node:http`'s `server.closeIdleConnections()`, which returns nothing.

### `server.ref()` and `server.unref()`

Control whether the server keeps the Bun process alive:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
// Don't keep process alive if server is the only thing running
server.unref();

// Restore default behavior - keep process alive
server.ref();
```

### `server.reload()`

Update the server's handlers without restarting:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const server = Bun.serve({
  routes: {
    "/api/version": Response.json({ version: "v1" }),
  },
  fetch(req) {
    return new Response("v1");
  },
});

// Update to new handler
server.reload({
  routes: {
    "/api/version": Response.json({ version: "v2" }),
  },
  fetch(req) {
    return new Response("v2");
  },
});
```

Use this for development and hot reloading. You can update only `fetch`, `error`, `routes`, and `websocket`.

***

## Per-Request Controls

### `server.timeout(Request, seconds)`

Override the idle timeout for an individual request. Pass `0` to disable the timeout entirely for that request.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const server = Bun.serve({
  async fetch(req, server) {
    // Give this request up to 60 seconds of inactivity instead of the default 10
    server.timeout(req, 60);

    // If they take longer than 60 seconds to send the body, the request will be aborted
    await req.text();

    return new Response("Done!");
  },
});
```

Use `server.timeout(req, 0)` to keep a long-lived streaming response (like Server-Sent Events) alive without raising the global `idleTimeout` for every request:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
Bun.serve({
  routes: {
    "/events": (req, server) => {
      // Disable the idle timeout for this streaming response.
      // Otherwise the connection will be closed if no bytes
      // are sent for 10 seconds (the default idleTimeout).
      server.timeout(req, 0);

      return new Response(
        async function* () {
          yield "data: hello\n\n";
          // events can arrive sporadically without the connection being killed
        },
        { headers: { "Content-Type": "text/event-stream" } },
      );
    },
  },
});
```

### `server.requestIP(Request)`

Get client IP and port information:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const server = Bun.serve({
  fetch(req, server) {
    const address = server.requestIP(req);
    if (address) {
      return new Response(`Client IP: ${address.address}, Port: ${address.port}`);
    }
    return new Response("Unknown client");
  },
});
```

Returns `null` for closed requests or Unix domain sockets.

***

## Server Metrics

### `server.pendingRequests` and `server.pendingWebSockets`

Monitor server activity with built-in counters:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const server = Bun.serve({
  fetch(req, server) {
    return new Response(
      `Active requests: ${server.pendingRequests}\n` + `Active WebSockets: ${server.pendingWebSockets}`,
    );
  },
});
```

### `server.subscriberCount(topic)`

Get count of subscribers for a WebSocket topic:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const server = Bun.serve({
  fetch(req, server) {
    const chatUsers = server.subscriberCount("chat");
    return new Response(`${chatUsers} users in chat`);
  },
  websocket: {
    message(ws) {
      ws.subscribe("chat");
    },
  },
});
```

***

## Benchmarks

The following Bun and Node.js servers respond `Bun!` to each incoming `Request`.

```ts Bun theme={"theme":{"light":"github-light","dark":"dracula"}}
Bun.serve({
  fetch(req: Request) {
    return new Response("Bun!");
  },
  port: 3000,
});
```

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
require("http")
  .createServer((req, res) => res.end("Bun!"))
  .listen(8080);
```

The `Bun.serve` server can handle roughly 2.5x more requests per second than Node.js on Linux.

| Runtime | Requests per second |
| ------- | ------------------- |
| Node 16 | \~64,000            |
| Bun     | \~160,000           |

<Frame>
  ![image](https://user-images.githubusercontent.com/709451/162389032-fc302444-9d03-46be-ba87-c12bd8ce89a0.png)
</Frame>

***

## Practical example: REST API

Here's a basic database-backed REST API using Bun's router with zero dependencies:

<CodeGroup>
  ```ts server.ts expandable icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
  import type { Post } from "./types.ts";
  import { Database } from "bun:sqlite";

  const db = new Database("posts.db");
  db.exec(`
    CREATE TABLE IF NOT EXISTS posts (
      id TEXT PRIMARY KEY,
      title TEXT NOT NULL,
      content TEXT NOT NULL,
      created_at TEXT NOT NULL
    )
  `);

  Bun.serve({
    routes: {
      // List posts
      "/api/posts": {
        GET: () => {
          const posts = db.query("SELECT * FROM posts").all();
          return Response.json(posts);
        },

        // Create post
        POST: async req => {
          const post: Omit<Post, "id" | "created_at"> = await req.json();
          const id = crypto.randomUUID();

          db.query(
            `INSERT INTO posts (id, title, content, created_at)
             VALUES (?, ?, ?, ?)`,
          ).run(id, post.title, post.content, new Date().toISOString());

          return Response.json({ id, ...post }, { status: 201 });
        },
      },

      // Get post by ID
      "/api/posts/:id": req => {
        const post = db.query("SELECT * FROM posts WHERE id = ?").get(req.params.id);

        if (!post) {
          return new Response("Not Found", { status: 404 });
        }

        return Response.json(post);
      },
    },

    error(error) {
      console.error(error);
      return new Response("Internal Server Error", { status: 500 });
    },
  });
  ```

  ```ts types.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
  export interface Post {
    id: string;
    title: string;
    content: string;
    created_at: string;
  }
  ```
</CodeGroup>

***

## Reference

```ts expandable See TypeScript Definitions theme={"theme":{"light":"github-light","dark":"dracula"}}
interface Server extends Disposable {
  /**
   * Stop the server from accepting new connections.
   * @param closeActiveConnections If true, immediately terminates all connections
   * @returns Promise that resolves when the server has stopped
   */
  stop(closeActiveConnections?: boolean): Promise<void>;

  /**
   * Close idle keep-alive connections without stopping the server.
   * @returns The number of connections closed
   */
  closeIdleConnections(): number;

  /**
   * Update handlers without restarting the server.
   * Only fetch, error, routes, and websocket can be updated.
   */
  reload(options: Serve.Options<undefined>): Server;

  /**
   * Make a request to the running server.
   * Useful for testing or internal routing.
   */
  fetch(request: Request | string): Response | Promise<Response>;

  /**
   * Upgrade an HTTP request to a WebSocket connection.
   * @returns true if upgrade successful, false if failed
   */
  upgrade<T = undefined>(
    request: Request,
    options?: {
      headers?: Bun.HeadersInit;
      data?: T;
    },
  ): boolean;

  /**
   * Publish a message to all WebSocket clients subscribed to a topic.
   * @returns Bytes sent, 0 if dropped, -1 if backpressure applied
   */
  publish(
    topic: string,
    data: string | ArrayBufferView | ArrayBuffer | SharedArrayBuffer,
    compress?: boolean,
  ): ServerWebSocketSendStatus;

  /**
   * Get count of WebSocket clients subscribed to a topic.
   */
  subscriberCount(topic: string): number;

  /**
   * Get client IP address and port.
   * @returns null for closed requests or Unix sockets
   */
  requestIP(request: Request): SocketAddress | null;

  /**
   * Set custom idle timeout for a request.
   * @param seconds Timeout in seconds, 0 to disable
   */
  timeout(request: Request, seconds: number): void;

  /**
   * Keep process alive while server is running.
   */
  ref(): void;

  /**
   * Allow process to exit if server is only thing running.
   */
  unref(): void;

  /** Number of in-flight HTTP requests */
  readonly pendingRequests: number;

  /** Number of active WebSocket connections */
  readonly pendingWebSockets: number;

  /** Server URL including protocol, hostname and port */
  readonly url: URL;

  /** Port server is listening on */
  readonly port: number;

  /** Hostname server is bound to */
  readonly hostname: string;

  /** Whether server is in development mode */
  readonly development: boolean;

  /** Server instance identifier */
  readonly id: string;
}

interface WebSocketHandler<T = undefined> {
  /** Maximum WebSocket message size in bytes */
  maxPayloadLength?: number;

  /** Bytes of queued messages before applying backpressure */
  backpressureLimit?: number;

  /** Whether to close connection when backpressure limit hit */
  closeOnBackpressureLimit?: boolean;

  /** Called when backpressure is relieved */
  drain?(ws: ServerWebSocket<T>): void | Promise<void>;

  /** Seconds before idle timeout */
  idleTimeout?: number;

  /** Enable per-message deflate compression */
  perMessageDeflate?:
    | boolean
    | {
        compress?: WebSocketCompressor | boolean;
        decompress?: WebSocketCompressor | boolean;
      };

  /** Send ping frames to keep connection alive */
  sendPings?: boolean;

  /** Whether ws.publish() also sends the message to the socket that published it, if subscribed */
  publishToSelf?: boolean;

  /** Called when connection opened */
  open?(ws: ServerWebSocket<T>): void | Promise<void>;

  /** Called when message received */
  message(ws: ServerWebSocket<T>, message: string | Buffer): void | Promise<void>;

  /** Called when connection closed */
  close?(ws: ServerWebSocket<T>, code: number, reason: string): void | Promise<void>;

  /** Called when ping frame received */
  ping?(ws: ServerWebSocket<T>, data: Buffer): void | Promise<void>;

  /** Called when pong frame received */
  pong?(ws: ServerWebSocket<T>, data: Buffer): void | Promise<void>;
}

interface TLSOptions {
  /** Certificate authority chain */
  ca?: string | Buffer | BunFile | Array<string | Buffer | BunFile>;

  /** Server certificate */
  cert?: string | Buffer | BunFile | Array<string | Buffer | BunFile>;

  /** Path to DH parameters file */
  dhParamsFile?: string;

  /** Private key */
  key?: string | Buffer | BunFile | Array<string | Buffer | BunFile>;

  /** Reduce TLS memory usage */
  lowMemoryMode?: boolean;

  /** Private key passphrase */
  passphrase?: string;

  /** OpenSSL options flags */
  secureOptions?: number;

  /** Server name for SNI */
  serverName?: string;
}
```


# TLS
Source: https://bun.com/docs/runtime/http/tls

Enable TLS in Bun.serve

Bun's TLS support is built in, powered by [BoringSSL](https://boringssl.googlesource.com/boringssl). To enable TLS, pass both `key` and `cert`.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
Bun.serve({
  tls: {
    key: Bun.file("./key.pem"), // [!code ++]
    cert: Bun.file("./cert.pem"), // [!code ++]
  },
});
```

The `key` and `cert` fields expect the *contents* of your TLS key and certificate, *not a path to it*. Each can be a string, `BunFile`, `TypedArray`, `Buffer`, or an array of those.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
Bun.serve({
  tls: {
    key: Bun.file("./key.pem"), // BunFile
    key: fs.readFileSync("./key.pem"), // Buffer
    key: fs.readFileSync("./key.pem", "utf8"), // string
    key: [Bun.file("./key1.pem"), Bun.file("./key2.pem")], // array of above
  },
});
```

### Passphrase

If your private key is encrypted with a passphrase, provide a value for `passphrase` to decrypt it.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
Bun.serve({
  tls: {
    key: Bun.file("./key.pem"),
    cert: Bun.file("./cert.pem"),
    passphrase: "my-secret-passphrase", // [!code ++]
  },
});
```

### CA Certificates

Pass `ca` to override the trusted CA certificates. By default, the server trusts the list of well-known CAs curated by Mozilla; setting `ca` replaces that list.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
Bun.serve({
  tls: {
    key: Bun.file("./key.pem"), // path to TLS key
    cert: Bun.file("./cert.pem"), // path to TLS cert
    ca: Bun.file("./ca.pem"), // path to root CA certificate  // [!code ++]
  },
});
```

### Diffie-Hellman

To override Diffie-Hellman parameters:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
Bun.serve({
  tls: {
    dhParamsFile: "/path/to/dhparams.pem", // path to Diffie Hellman parameters // [!code ++]
  },
});
```

***

## Server name indication (SNI)

To configure the server name indication (SNI) for the server, set the `serverName` field in the `tls` object.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
Bun.serve({
  tls: {
    serverName: "my-server.com", // SNI // [!code ++]
  },
});
```

To allow multiple server names, pass an array of objects to `tls`, each with a `serverName` field.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
Bun.serve({
  tls: [
    {
      key: Bun.file("./key1.pem"),
      cert: Bun.file("./cert1.pem"),
      serverName: "my-server1.com", // [!code ++]
    },
    {
      key: Bun.file("./key2.pem"),
      cert: Bun.file("./cert2.pem"),
      serverName: "my-server2.com", // [!code ++]
    },
  ],
});
```


# WebSockets
Source: https://bun.com/docs/runtime/http/websockets

Server-side WebSockets in Bun

`Bun.serve()` supports server-side WebSockets, with on-the-fly compression, TLS support, and a Bun-native publish-subscribe API.

<Info>
  **⚡️ 7x more throughput**

  Bun's WebSockets are fast. For a [simple chatroom](https://github.com/oven-sh/bun/tree/main/bench/websocket-server/README.md) on Linux x64, Bun can handle 7x more messages per second than Node.js + [`"ws"`](https://github.com/websockets/ws).

  | **Messages sent per second** | **Runtime**                    | **Clients** |
  | ---------------------------- | ------------------------------ | ----------- |
  | \~700,000                    | (`Bun.serve`) Bun v0.2.1 (x64) | 16          |
  | \~100,000                    | (`ws`) Node v18.10.0 (x64)     | 16          |

  Internally Bun's WebSocket implementation is built on [uWebSockets](https://github.com/uNetworking/uWebSockets).
</Info>

***

## Start a WebSocket server

The following server, built with `Bun.serve`, [upgrades](https://developer.mozilla.org/en-US/docs/Web/HTTP/Protocol_upgrade_mechanism) every incoming request to a WebSocket connection in the `fetch` handler. You declare the socket handlers in the `websocket` parameter.

```ts server.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
Bun.serve({
  fetch(req, server) {
    // upgrade the request to a WebSocket
    if (server.upgrade(req)) {
      return; // do not return a Response
    }
    return new Response("Upgrade failed", { status: 500 });
  },
  websocket: {}, // handlers
});
```

Bun supports these WebSocket event handlers:

```ts server.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
Bun.serve({
  fetch(req, server) {}, // upgrade logic
  websocket: {
    message(ws, message) {}, // a message is received
    open(ws) {}, // a socket is opened
    close(ws, code, message) {}, // a socket is closed
    drain(ws) {}, // the socket is ready to receive more data
  },
});
```

<Accordion title="An API designed for speed">
  In Bun, you declare handlers once per server, instead of per socket.

  You pass a single `WebSocketHandler` object to `Bun.serve()` with methods for `open`, `message`, `close`, `drain`, and `error`. This design differs from the client-side `WebSocket` class, which extends `EventTarget` (`onmessage`, `onopen`, `onclose`).

  Clients tend to have few socket connections open, so an event-based API makes sense there.

  But servers tend to have **many** socket connections open, which means:

  * Time spent adding/removing event listeners for each connection adds up
  * Extra memory spent on storing references to callback functions for each connection
  * Usually, people create new functions for each connection, which also means more memory

  Reusing one handler object across every connection avoids both costs.
</Accordion>

The first argument to each handler is the `ServerWebSocket` instance handling the event. The `ServerWebSocket` class is a fast, Bun-native implementation of [`WebSocket`](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket) with some additional features.

```ts server.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
Bun.serve({
  fetch(req, server) {}, // upgrade logic
  websocket: {
    message(ws, message) {
      ws.send(message); // echo back the message
    },
  },
});
```

### Sending messages

Each `ServerWebSocket` instance has a `.send()` method for sending messages to the client. It supports a range of input types.

```ts server.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" focus={4-6} theme={"theme":{"light":"github-light","dark":"dracula"}}
Bun.serve({
  fetch(req, server) {}, // upgrade logic
  websocket: {
    message(ws, message) {
      ws.send("Hello world"); // string
      ws.send(response.arrayBuffer()); // ArrayBuffer
      ws.send(new Uint8Array([1, 2, 3])); // TypedArray | DataView
      ws.send(new Blob(["binary"])); // Blob
    },
  },
});
```

### Headers

Once the upgrade succeeds, Bun sends a `101 Switching Protocols` response per the [spec](https://developer.mozilla.org/en-US/docs/Web/HTTP/Protocol_upgrade_mechanism). To attach additional `headers` to this `Response`, pass them to `server.upgrade()`.

```ts server.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
Bun.serve({
  fetch(req, server) {
    const sessionId = await generateSessionId();
    server.upgrade(req, {
      headers: { // [!code ++]
        "Set-Cookie": `SessionId=${sessionId}`, // [!code ++]
      }, // [!code ++]
    });
  },
  websocket: {}, // handlers
});
```

### Contextual data

Attach contextual `data` to a new WebSocket in the `.upgrade()` call. It is available on the `ws.data` property inside the WebSocket handlers.

To strongly type `ws.data`, add a `data` property to the `websocket` handler object. This types `ws.data` across all lifecycle hooks.

```ts server.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
type WebSocketData = {
  createdAt: number;
  channelId: string;
  authToken: string;
};

Bun.serve({
  fetch(req, server) {
    const cookies = new Bun.CookieMap(req.headers.get("cookie")!);

    server.upgrade(req, {
      // this object must conform to WebSocketData
      data: {
        createdAt: Date.now(),
        channelId: new URL(req.url).searchParams.get("channelId"),
        authToken: cookies.get("X-Token"),
      },
    });

    return undefined;
  },
  websocket: {
    // TypeScript: specify the type of ws.data like this
    data: {} as WebSocketData,
    // handler called when a message is received
    async message(ws, message) {
      // ws.data is now properly typed as WebSocketData
      const user = getUserFromToken(ws.data.authToken);

      await saveMessageToDatabase({
        channel: ws.data.channelId,
        message: String(message),
        userId: user.id,
      });
    },
  },
});
```

<Info>
  Previously, you could specify the type of `ws.data` with a type parameter on `Bun.serve`, like `Bun.serve<MyData>({...})`. Bun removed this pattern in favor of the `data` property because of [a limitation in TypeScript](https://github.com/microsoft/TypeScript/issues/26242).
</Info>

To connect to this server from the browser, create a new `WebSocket`.

```js browser.js icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
const socket = new WebSocket("ws://localhost:3000/chat");

socket.addEventListener("message", event => {
  console.log(event.data);
});
```

<Info>
  **Identifying users**

  The browser sends cookies set on the page along with the WebSocket upgrade request. They are available on `req.headers` in the `fetch` handler. Parse them to identify the connecting user and set `data` accordingly.
</Info>

### Pub/Sub

Bun's `ServerWebSocket` includes a native publish-subscribe API for topic-based broadcasting. You specify a topic with a string identifier. An individual socket can `.subscribe()` to a topic and `.publish()` messages to all other subscribers to that topic (excluding itself). This topic-based broadcast API is similar to [MQTT](https://en.wikipedia.org/wiki/MQTT) and [Redis Pub/Sub](https://redis.io/topics/pubsub).

```ts server.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
const server = Bun.serve({
  fetch(req, server) {
    const url = new URL(req.url);
    if (url.pathname === "/chat") {
      console.log(`upgrade!`);
      const username = getUsernameFromReq(req);
      const success = server.upgrade(req, { data: { username } });
      return success ? undefined : new Response("WebSocket upgrade error", { status: 400 });
    }

    return new Response("Hello world");
  },
  websocket: {
    // TypeScript: specify the type of ws.data like this
    data: {} as { username: string },
    open(ws) {
      const msg = `${ws.data.username} has entered the chat`;
      ws.subscribe("the-group-chat");
      server.publish("the-group-chat", msg);
    },
    message(ws, message) {
      // this is a group chat
      // so the server re-broadcasts incoming message to everyone
      server.publish("the-group-chat", `${ws.data.username}: ${message}`);

      // inspect current subscriptions
      console.log(ws.subscriptions); // ["the-group-chat"]
    },
    close(ws) {
      const msg = `${ws.data.username} has left the chat`;
      ws.unsubscribe("the-group-chat");
      server.publish("the-group-chat", msg);
    },
  },
});

console.log(`Listening on ${server.hostname}:${server.port}`);
```

Calling `.publish(topic, data)` sends the message to all subscribers of a topic *except* the socket that called `.publish()`. To send a message to all subscribers of a topic, use the `.publish()` method on the `Server` instance.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const server = Bun.serve({
  websocket: {
    // ...
  },
});

// listen for some external event
server.publish("the-group-chat", "Hello world");
```

### Compression

Enable per-message [compression](https://websockets.readthedocs.io/en/stable/topics/compression.html) with the `perMessageDeflate` parameter.

```ts server.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
Bun.serve({
  websocket: {
    perMessageDeflate: true, // [!code ++]
  },
});
```

To compress an individual message, pass a `boolean` as the second argument to `.send()`.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
ws.send("Hello world", true);
```

For fine-grained control over compression characteristics, refer to the [Reference](#reference).

### Backpressure

The `.send(message)` method of `ServerWebSocket` returns a `number` indicating the result of the operation.

* `-1` — The message was enqueued but there is backpressure
* `0` — The message was dropped due to a connection issue
* `1+` — The number of bytes sent

### Timeouts and limits

By default, Bun closes a WebSocket connection that has been idle for 120 seconds. Configure this with the `idleTimeout` parameter.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
Bun.serve({
  fetch(req, server) {}, // upgrade logic
  websocket: {
    idleTimeout: 60, // 60 seconds  // [!code ++]
  },
});
```

Bun also closes a WebSocket connection if it receives a message larger than 16 MB. Configure this with the `maxPayloadLength` parameter.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
Bun.serve({
  fetch(req, server) {}, // upgrade logic
  websocket: {
    maxPayloadLength: 1024 * 1024, // 1 MB  // [!code ++]
  },
});
```

***

## Connect to a `Websocket` server

Bun implements the `WebSocket` class. To create a WebSocket client that connects to a `ws://` or `wss://` server, create an instance of `WebSocket`, as you would in the browser.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const socket = new WebSocket("ws://localhost:3000");

// With subprotocol negotiation
const socket2 = new WebSocket("ws://localhost:3000", ["soap", "wamp"]);
```

Browsers send cookies set on the page along with the WebSocket upgrade request. This is a standard feature of the `WebSocket` API.

In Bun, you can also set custom headers directly in the constructor. This is a Bun-specific extension of the `WebSocket` standard. *It does not work in browsers.*

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const socket = new WebSocket("ws://localhost:3000", {
  headers: {
    /* custom headers */
  }, // [!code ++]
});
```

To add event listeners to the socket:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
// message is received
socket.addEventListener("message", event => {});

// socket opened
socket.addEventListener("open", event => {});

// socket closed
socket.addEventListener("close", event => {});

// error handler
socket.addEventListener("error", event => {});
```

***

## Reference

```ts See Typescript Definitions expandable theme={"theme":{"light":"github-light","dark":"dracula"}}
namespace Bun {
  export function serve(params: {
    fetch: (req: Request, server: Server) => Response | Promise<Response>;
    websocket?: {
      message: (ws: ServerWebSocket, message: string | ArrayBuffer | Uint8Array) => void;
      open?: (ws: ServerWebSocket) => void;
      close?: (ws: ServerWebSocket, code: number, reason: string) => void;
      error?: (ws: ServerWebSocket, error: Error) => void;
      drain?: (ws: ServerWebSocket) => void;

      maxPayloadLength?: number; // default: 16 * 1024 * 1024 = 16 MB
      idleTimeout?: number; // default: 120 (seconds)
      backpressureLimit?: number; // default: 16 * 1024 * 1024 = 16 MB
      closeOnBackpressureLimit?: boolean; // default: false
      sendPings?: boolean; // default: true
      publishToSelf?: boolean; // default: false

      perMessageDeflate?:
        | boolean
        | {
            compress?: boolean | Compressor;
            decompress?: boolean | Compressor;
          };
    };
  }): Server;
}

type Compressor =
  | `"disable"`
  | `"shared"`
  | `"dedicated"`
  | `"3KB"`
  | `"4KB"`
  | `"8KB"`
  | `"16KB"`
  | `"32KB"`
  | `"64KB"`
  | `"128KB"`
  | `"256KB"`;

interface Server {
  pendingWebSockets: number;
  publish(topic: string, data: string | ArrayBufferView | ArrayBuffer | Blob, compress?: boolean): number;
  upgrade(
    req: Request,
    options?: {
      headers?: HeadersInit;
      data?: any;
    },
  ): boolean;
}

interface ServerWebSocket {
  readonly data: any;
  readonly readyState: number;
  readonly remoteAddress: string;
  readonly subscriptions: string[];
  send(message: string | ArrayBuffer | Uint8Array | Blob, compress?: boolean): number;
  close(code?: number, reason?: string): void;
  subscribe(topic: string): boolean;
  unsubscribe(topic: string): boolean;
  publish(topic: string, message: string | ArrayBuffer | Uint8Array | Blob): void;
  isSubscribed(topic: string): boolean;
  cork(cb: (ws: ServerWebSocket) => void): void;
}
```


# Image
Source: https://bun.com/docs/runtime/image

Decode, transform, and encode images with a fast native pipeline

`Bun.Image` is a chainable image pipeline for decoding, resizing, rotating, and re-encoding JPEG, PNG, WebP, HEIC, and AVIF. It is built on libjpeg-turbo, spng, libwebp, and SIMD geometry kernels, with zero npm dependencies and no native addon build step.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
await Bun.file("photo.jpg").image().resize(400, 400, { fit: "inside" }).webp({ quality: 80 }).write("thumb.webp");
```

The API is shaped after [Sharp](https://sharp.pixelplumbing.com/): construct from an input, chain transforms, pick an output format, then `await` a terminal method. Nothing runs until you await the terminal, and the work executes off the JavaScript thread.

## Input

The constructor accepts a path, bytes, or a `Blob` — including `Bun.file()` and `Bun.s3.file()`. `Blob#image()` is shorthand for `new Bun.Image(blob)`:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
new Bun.Image("./photo.jpg"); // file path
new Bun.Image(buffer); // Buffer / ArrayBuffer / TypedArray
new Bun.Image(Bun.file("photo.jpg")); // BunFile (read lazily, off-thread)
Bun.file("photo.jpg").image(); // same as above
Bun.s3.file("bucket/photo.jpg").image(); // S3File
```

Bun sniffs the format from the bytes and ignores extensions and `Content-Type`.

**Path strings are filesystem paths.** Don't pass user-controlled strings directly to the constructor — that's an arbitrary-file-read primitive. Read untrusted input into a `Buffer` (with `fetch` or `Bun.file` and your own validation) and pass the bytes.

When passing a `TypedArray`/`ArrayBuffer`, don't mutate it while a terminal is pending — decode runs off-thread and borrows the bytes. Bun refuses `SharedArrayBuffer` and resizable buffers; use `buf.slice()` to pass a fixed view.

A second `options` argument guards against decompression bombs and controls EXIF handling:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
new Bun.Image(input, {
  // Reject if width*height > this. Checked after reading the header,
  // before allocating the pixel buffer. Default matches Sharp (~268 MP).
  maxPixels: 4096 * 4096,
  // Apply JPEG EXIF Orientation before any other op. Default: true.
  autoOrient: true,
});
```

## Metadata

Read `width`, `height`, and `format` without decoding pixel data:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const { width, height, format } = await new Bun.Image(input).metadata();
// => { width: 1920, height: 1080, format: "jpeg" }
```

## Resize

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
img.resize(800); // width 800, keep aspect ratio
img.resize(800, 600); // exactly 800×600 (stretch)
img.resize(800, 600, { fit: "inside" }); // fit within 800×600
img.resize(800, 600, { withoutEnlargement: true }); // never upscale
img.resize(800, 600, { filter: "mitchell" });
```

| `fit`              | Behavior                                            |
| ------------------ | --------------------------------------------------- |
| `"fill"` (default) | Stretch to exactly `width × height`                 |
| `"inside"`         | Preserve aspect ratio; result fits *within* the box |

`filter` selects the resampling kernel. The default `"lanczos3"` is the right choice for photographs.

| Filter                    | Use when                                         |
| ------------------------- | ------------------------------------------------ |
| `"lanczos3"` *(default)*  | General-purpose, sharpest for photos             |
| `"lanczos2"`              | Slightly softer, fewer ringing artifacts         |
| `"mitchell"`              | Smooth gradients; the classic bicubic compromise |
| `"cubic"`                 | Catmull-Rom — sharper than Mitchell, can ring    |
| `"mks2013"` / `"mks2021"` | "Magic Kernel Sharp"; used by Facebook/Instagram |
| `"bilinear"` / `"linear"` | Fast, soft                                       |
| `"box"`                   | Area-average; good for large integer downscales  |
| `"nearest"`               | Pixel art / hard edges                           |

When the source is a JPEG and the target is at most half the source size, decode skips straight to the nearest M/8 IDCT scale. As a result, generating a thumbnail from a 24 MP photo never materializes the full-resolution buffer.

## Rotate · flip

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
img.rotate(90); // 90° clockwise (multiples of 90 only)
img.flip(); // mirror vertically (about the x-axis)
img.flop(); // mirror horizontally (about the y-axis)
```

## Modulate

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
img.modulate({
  brightness: 1.2, // 1 = unchanged
  saturation: 0, // 0 = greyscale, 1 = unchanged, >1 = boost
});
```

## Output formats

Calling a format method sets the encode target; without one, Bun reuses the source format.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
img.jpeg({ quality: 85 }); // 1–100, default 80
img.png({ compressionLevel: 6 }); // zlib level 0–9
img.png({ palette: true, colors: 64, dither: true }); // indexed PNG
img.webp({ quality: 80 });
img.webp({ lossless: true });
img.heic({ quality: 80 }); // macOS / Windows only
img.avif({ quality: 60 }); // macOS / Windows only
```

`palette: true` quantizes to a ≤256-color palette and emits an indexed (color-type 3) PNG, optionally with Floyd–Steinberg `dither`. The indexed PNG is typically 3–5× smaller than truecolor for screenshots and UI assets.

## Terminals

A pipeline does no work until you await one of these:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
await img.bytes(); // Uint8Array
await img.buffer(); // Buffer
await img.blob(); // Blob with .type set to the output MIME
await img.toBase64(); // string
await img.dataurl(); // "data:image/png;base64,…"
await img.write("out.webp"); // number (bytes written)
await img.write(Bun.s3.file("bucket/out.webp"));
```

`.write()` accepts the same destinations as `Bun.write` — a path string, `Bun.file()`, `Bun.s3.file()`, or an fd. If you didn't chain a format method and the destination is a path string, the extension picks one (`.jpg`/`.png`/`.webp`/`.heic`/`.avif`).

## Placeholders

`.placeholder()` returns a low-quality placeholder to inline in HTML before the real image loads. The placeholder is a [ThumbHash](https://evanw.github.io/thumbhash/)-rendered ≤32px blur as a `data:` URL. It is \~400–700 bytes and needs no client-side decoder:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const lqip = await Bun.file("hero.jpg").image().placeholder();
// <img src={lqip} … /> — then swap to the real URL on load.
```

For coarse-to-fine rendering of the image *itself*, encode a progressive JPEG:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
img.jpeg({ progressive: true });
```

After the first terminal resolves, `img.width` and `img.height` reflect the *output* dimensions (they're `-1` before).

## `Bun.serve` integration

A `Bun.Image` pipeline is a valid `Response` body and sets `Content-Type` automatically. To keep the encode off the JS thread in a server handler, await a terminal first:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
Bun.serve({
  routes: {
    "/avatar/:id": async req => {
      // Validate before touching the filesystem (see the Input note above).
      if (!/^[a-z0-9]+$/.test(req.params.id)) return new Response(null, { status: 400 });
      const out = await Bun.file(`avatars/${req.params.id}.png`).image().resize(128, 128).webp().blob();
      return new Response(out);
    },
  },
});
```

Passing the pipeline directly (`new Response(img)`) also works, but runs the encode synchronously during body init.

## Clipboard

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const img = Bun.Image.fromClipboard();
if (img) {
  const png = await img.resize(800, 800, { fit: "inside" }).png().bytes();
}
```

`fromClipboard()` reads PNG, TIFF, HEIC, JPEG, WebP, GIF, or BMP from the system pasteboard on macOS and Windows; the regular decode pipeline takes it from there. Returns `null` if there's no image, and always `null` on Linux — call `wl-paste`/`xclip` yourself and pass the bytes to the constructor.

For a passive "image in clipboard, press ⌘V" hint, poll `clipboardChangeCount()` (a single integer read) and call `hasClipboardImage()` only when it moves. Polling is the documented pattern because macOS has no clipboard-change notification.

## Platform backends

|                        | Linux                            | macOS               | Windows      |
| ---------------------- | -------------------------------- | ------------------- | ------------ |
| JPEG / PNG / WebP      | libjpeg-turbo · spng · libwebp   | same                | same         |
| BMP / GIF (decode)     | built-in                         | ImageIO             | WIC          |
| TIFF (decode)          | ❌                                | ImageIO             | WIC          |
| Resize / rotate / flip | Highway SIMD                     | Accelerate vImage ³ | Highway SIMD |
| HEIC / AVIF            | ❌ `ERR_IMAGE_FORMAT_UNSUPPORTED` | ImageIO ²           | WIC ¹        |
| Clipboard              | ❌ returns `null`                 | NSPasteboard        | Win32        |

¹ Windows requires the **HEIF Image Extensions** / **AV1 Video Extension** from the Microsoft Store.
² AVIF *encode* needs an OS AV1 encoder — Apple Silicon M3+ only. Intel Mac and M1/M2 reject with `ERR_IMAGE_FORMAT_UNSUPPORTED`; AVIF *decode* works everywhere ImageIO does (macOS 13+).
³ Resize uses vImage only with the default `lanczos3` filter; every other `filter` uses Highway SIMD on macOS too.

When a system-backend format isn't available on the current machine, the terminal rejects with `error.code === "ERR_IMAGE_FORMAT_UNSUPPORTED"`. Branch on that error code to fall back to a portable format:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const out = await img
  .avif({ quality: 50 })
  .bytes()
  .catch(e => {
    if (e.code === "ERR_IMAGE_FORMAT_UNSUPPORTED") return img.webp({ quality: 80 }).bytes();
    throw e;
  });
```

Formats handled by the system backend (TIFF, HEIC, AVIF, clipboard) inherit the **OS's** patch level, so keep macOS / Windows updated. JPEG, PNG, and WebP go through the same statically-linked codecs on every platform, so encoded output is byte-identical across Linux, macOS, and Windows. To force the portable Highway path for geometry too (e.g. for golden-image tests), set the process-global backend:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
Bun.Image.backend = "bun"; // default is "system" on macOS/Windows
```


# Bun Runtime
Source: https://bun.com/docs/runtime/index

Execute JavaScript/TypeScript files, package.json scripts, and executable packages with Bun's fast runtime.

The Bun Runtime is designed to start fast and run fast.

Bun uses the [JavaScriptCore engine](https://developer.apple.com/documentation/javascriptcore), developed by Apple for Safari. JavaScriptCore usually starts and runs faster than V8, the engine used by Node.js and Chromium-based browsers. Bun's transpiler and runtime are written in Rust. On Linux, Bun starts [4x faster](https://twitter.com/jarredsumner/status/1499225725492076544) than Node.js.

| Command         | Time     |
| --------------- | -------- |
| `bun hello.js`  | `5.2ms`  |
| `node hello.js` | `25.1ms` |

The benchmark runs a Hello World script on Linux.

## Run a file

Use `bun run` to execute a source file.

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun run index.js
```

Bun supports TypeScript and JSX with no configuration. Bun transpiles every file on the fly with its native [transpiler](/docs/runtime/transpiler) before running it.

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun run index.js
bun run index.jsx
bun run index.ts
bun run index.tsx
```

Alternatively, you can omit the `run` keyword and use the "naked" command; it behaves identically.

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun index.tsx
bun index.js
```

### `--watch`

To run a file in watch mode, use the `--watch` flag.

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun --watch run index.tsx
```

<Note>
  When using `bun run`, put Bun flags like `--watch` immediately after `bun`.

  ```bash theme={"theme":{"light":"github-light","dark":"dracula"}}
  bun --watch run dev # ✔️ do this
  bun run dev --watch # ❌ don't do this
  ```

  `bun` ignores flags at the end of the command and passes them through to the `"dev"` script itself.
</Note>

## Run a `package.json` script

<Note>
  Compare to `npm run <script>` or `yarn <script>`
</Note>

```sh theme={"theme":{"light":"github-light","dark":"dracula"}}
bun [bun flags] run <script> [script flags]
```

Your `package.json` can define named `"scripts"` that correspond to shell commands.

```json package.json icon="file-json" theme={"theme":{"light":"github-light","dark":"dracula"}}
{
  // ... other fields
  "scripts": {
    "clean": "rm -rf dist && echo 'Done.'",
    "dev": "bun server.ts"
  }
}
```

Use `bun run <script>` to execute these scripts.

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun run clean
rm -rf dist && echo 'Done.'
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
Done.
```

Bun executes the script command in a subshell. On Linux & macOS, it checks for the following shells in order, using the first one it finds: `bash`, `sh`, `zsh`. On Windows, it uses the [Bun Shell](/docs/runtime/shell) to support bash-like syntax and many common commands.

<Note>⚡️ The startup time for `npm run` on Linux is roughly 170ms; with Bun it is `6ms`.</Note>

You can also run scripts with the shorter command `bun <script>`. If a built-in `bun` command has the same name, the built-in command takes precedence; use the explicit `bun run <script>` to run your package script instead.

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun run dev
```

To see a list of available scripts, run `bun run` without any arguments.

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun run
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
...
package.json scripts (2 found):
  $ bun run clean
    rm -rf dist && echo 'Done.'

  $ bun run dev
    bun server.ts
```

Bun respects lifecycle hooks. For instance, `bun run clean` runs `preclean` and `postclean`, if defined. If the `pre<script>` fails, Bun does not run the script itself.

### `--bun`

It's common for `package.json` scripts to reference locally-installed CLIs like `vite` or `next`. These CLIs are often JavaScript files marked with a [shebang](https://en.wikipedia.org/wiki/Shebang_\(Unix\)) to indicate that they should be executed with `node`.

```js cli.js icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/javascript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=5148f41bbc784f9828f1363dab67340f" theme={"theme":{"light":"github-light","dark":"dracula"}}
#!/usr/bin/env node

// do stuff
```

By default, Bun respects this shebang and executes the script with `node`. The `--bun` flag overrides it: the CLI runs with Bun instead of Node.js.

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun run --bun vite
```

### Filtering

In a monorepo, the `--filter` argument runs a script in many packages at once.

`bun run --filter <pattern> <script>` executes `<script>` in every package selected by `<pattern>`. The pattern can be a package name glob, a `./path`, a `{dir}` directory or a dependency relation like `foo...`.
For example, if you have subdirectories containing packages named `foo`, `bar` and `baz`, running

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun run --filter 'ba*' <script>
```

executes `<script>` in both `bar` and `baz`, but not in `foo`.

See [`--filter`](/docs/pm/filter#running-scripts-with-filter).

## `bun run -` to pipe code from stdin

`bun run -` reads JavaScript, TypeScript, TSX, or JSX from stdin and executes it without writing to a temporary file first.

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
echo "console.log('Hello')" | bun run -
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
Hello
```

You can also use `bun run -` to redirect files into Bun. For example, to run a `.js` file as if it were a `.ts` file:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
echo "console.log!('This is TypeScript!' as any)" > secretly-typescript.js
bun run - < secretly-typescript.js
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
This is TypeScript!
```

`bun run -` treats all input as TypeScript with JSX support.

## `bun run --console-depth`

Control the depth of object inspection in console output with the `--console-depth` flag.

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun --console-depth 5 run index.tsx
```

`--console-depth` sets how deeply Bun displays nested objects in `console.log()` output. The default depth is `2`. Higher values show more nested properties but may produce verbose output for complex objects.

```ts console.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
const nested = { a: { b: { c: { d: "deep" } } } };
console.log(nested);
// With --console-depth 2 (default): { a: { b: { c: [Object] } } }
// With --console-depth 4: { a: { b: { c: { d: 'deep' } } } }
```

## `bun run --smol`

In memory-constrained environments, use the `--smol` flag to reduce memory usage at a cost to performance.

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun --smol run index.tsx
```

`--smol` makes the garbage collector run more frequently, which can slow down execution. Bun adjusts the garbage collector's heap size based on the available memory (accounting for cgroups and other memory limits) with and without the `--smol` flag. The flag is therefore mostly useful when you want the heap to grow more slowly.

## Resolution order

Bun always executes absolute paths and paths starting with `./` or `.\\` as source files. Unless you use `bun run`, a name with an allowed extension resolves to the file rather than a `package.json` script.

When a `package.json` script and a file have the same name, `bun run` prefers the script. The full resolution order is:

1. `package.json` scripts: `bun run build`
2. Source files: `bun run src/main.js`
3. Binaries from project packages: `bun add eslint && bun run eslint`
4. (`bun run` only) System commands: `bun run ls`

***

# CLI Usage

```bash theme={"theme":{"light":"github-light","dark":"dracula"}}
bun run <file or script>
```

### General Execution Options

<ParamField type="boolean">
  Don't print the script command
</ParamField>

<ParamField type="boolean">
  Exit without an error if the entrypoint does not exist
</ParamField>

<ParamField type="string">
  Evaluate argument as a script. Alias: <code>-e</code>
</ParamField>

<ParamField type="string">
  Evaluate argument as a script and print the result. Alias: <code>-p</code>
</ParamField>

<ParamField type="boolean">
  Display this menu and exit. Alias: <code>-h</code>
</ParamField>

### Workspace Management

<ParamField type="number">
  Number of lines of script output shown when using --filter (default: 10). Set to 0 to show all lines
</ParamField>

<ParamField type="string">
  Run a script in all workspace packages matching the pattern. Alias: <code>-F</code>
</ParamField>

<ParamField type="boolean">
  Run a script in all workspace packages (from the <code>workspaces</code> field in <code>package.json</code>)
</ParamField>

<ParamField type="boolean">
  Run multiple scripts or workspace scripts concurrently with prefixed output
</ParamField>

<ParamField type="boolean">
  Run multiple scripts or workspace scripts one after another with prefixed output
</ParamField>

<ParamField type="boolean">
  When using <code>--parallel</code> or <code>--sequential</code>, continue running other scripts when one fails
</ParamField>

### Runtime & Process Control

<ParamField type="boolean">
  Force a script or package to use Bun's runtime instead of Node.js (via symlinking node). Alias: <code>-b</code>
</ParamField>

<ParamField type="string">
  Control the shell used for <code>package.json</code> scripts. Supports either <code>bun</code> or <code>system</code>
</ParamField>

<ParamField type="boolean">
  Open the Node.js-compatible REPL (<code>node:repl</code>). When combined with <code>-e</code>, starts the REPL and
  then evaluates the script. Under <code>--interactive</code>, <code>-e</code> is raw JavaScript (matching
  <code>node -i -e</code>). Use <code>bun repl</code> for TypeScript. Distinct from <code>bun repl</code>, which is
  Bun's native REPL.
</ParamField>

<ParamField type="boolean">
  Use less memory, but run garbage collection more often
</ParamField>

<ParamField type="boolean">
  Expose <code>gc()</code> on the global object. Has no effect on <code>Bun.gc()</code>
</ParamField>

<ParamField type="boolean">
  Silence all deprecation warnings
</ParamField>

<ParamField type="boolean">
  Throw deprecation warnings as errors
</ParamField>

<ParamField type="string">
  Set the process title
</ParamField>

<ParamField type="boolean">
  Force <code>Buffer.allocUnsafe(size)</code> to be zero-filled
</ParamField>

<ParamField type="boolean">
  Throw an error if <code>process.dlopen</code> is called, and disable export condition <code>node-addons</code>
</ParamField>

<ParamField type="string">
  One of <code>strict</code>, <code>throw</code>, <code>warn</code>, <code>none</code>, or
  <code>warn-with-error-code</code>
</ParamField>

<ParamField type="number">
  Set the default depth for <code>console.log</code> object inspection (default: 2)
</ParamField>

### Development Workflow

<ParamField type="boolean">
  Automatically restart the process on file change
</ParamField>

<ParamField type="string">
  Signal whose handlers run when --watch restarts the process
</ParamField>

<ParamField type="boolean">
  Enable auto reload in the Bun runtime, test runner, or bundler
</ParamField>

<ParamField type="boolean">
  Disable clearing the terminal screen on reload when --hot or --watch is enabled
</ParamField>

### Debugging

<ParamField type="string">
  Activate Bun's debugger
</ParamField>

<ParamField type="string">
  Activate Bun's debugger, wait for a connection before executing
</ParamField>

<ParamField type="string">
  Activate Bun's debugger, set breakpoint on first line of code and wait
</ParamField>

### Dependency & Module Resolution

<ParamField type="string">
  Import a module before Bun loads other modules. Alias: <code>-r</code>
</ParamField>

<ParamField type="string">
  Alias of --preload, for Node.js compatibility
</ParamField>

<ParamField type="string">
  Alias of --preload, for Node.js compatibility
</ParamField>

<ParamField type="boolean">
  Disable auto install in the Bun runtime
</ParamField>

<ParamField type="string">
  Configure auto-install behavior. One of <code>auto</code> (default, auto-installs when no node\_modules),
  <code>fallback</code> (missing packages only), <code>force</code> (always)
</ParamField>

<ParamField type="boolean">
  Auto-install dependencies during execution. Equivalent to --install=fallback
</ParamField>

<ParamField type="boolean">
  Skip staleness checks for packages in the Bun runtime and resolve from disk
</ParamField>

<ParamField type="boolean">
  Use the latest matching versions of packages in the Bun runtime, always checking npm
</ParamField>

<ParamField type="string">
  Pass custom conditions to resolve
</ParamField>

<ParamField type="string">
  Main fields to lookup in <code>package.json</code>
</ParamField>

<ParamField type="boolean">
  Preserve symlinks when resolving files
</ParamField>

<ParamField type="boolean">
  Preserve symlinks when resolving the main entry point
</ParamField>

<ParamField type="string">
  Defaults to: <code>.tsx,.ts,.jsx,.cts,.cjs,.js,.mjs,.mts,.json,.node</code>
</ParamField>

### Transpilation & Language Features

<ParamField type="string">
  Specify custom <code>tsconfig.json</code>. Default <code>\$cwd/tsconfig.json</code>
</ParamField>

<ParamField type="string">
  Substitute K:V while parsing, e.g. <code>--define process.env.NODE\_ENV:"development"</code>. Bun parses values as
  JSON. Alias: <code>-d</code>
</ParamField>

<ParamField type="string">
  Remove function calls, e.g. <code>--drop=console</code> removes all <code>console.\*</code> calls
</ParamField>

<ParamField type="string">
  Parse files with <code>.ext:loader</code>, e.g. <code>--loader .js:jsx</code>. Valid loaders: <code>js</code>,
  <code>jsx</code>, <code>ts</code>, <code>tsx</code>, <code>json</code>, <code>toml</code>, <code>text</code>,
  <code>file</code>, <code>wasm</code>, <code>napi</code>. Alias: <code>-l</code>
</ParamField>

<ParamField type="boolean">
  Disable macro execution in the bundler, transpiler and runtime
</ParamField>

<ParamField type="string">
  Changes the function called when compiling JSX elements using the classic JSX runtime
</ParamField>

<ParamField type="string">
  Changes the function called when compiling JSX fragments
</ParamField>

<ParamField type="string">
  Declares the module specifier used to import the jsx and jsxs factory functions. Default: <code>react</code>
</ParamField>

<ParamField type="string">
  <code>automatic</code> (default) or <code>classic</code>
</ParamField>

<ParamField type="boolean">
  Treat JSX elements as having side effects (disable pure annotations)
</ParamField>

<ParamField type="boolean">
  Ignore tree-shaking annotations such as <code>@**PURE**</code>
</ParamField>

### Networking & Security

<ParamField type="number">
  Set the default port for <code>Bun.serve</code>
</ParamField>

<ParamField type="string">
  Preconnect to a URL while code is loading
</ParamField>

<ParamField type="number">
  Set the maximum size of HTTP headers in bytes. Default is 16KiB
</ParamField>

<ParamField type="string">
  Set the default order of DNS lookup results. Valid orders: <code>verbatim</code> (default), <code>ipv4first</code>,
  <code>ipv6first</code>
</ParamField>

<ParamField type="boolean">
  Use the system's trusted certificate authorities
</ParamField>

<ParamField type="boolean">
  Use OpenSSL's default CA store
</ParamField>

<ParamField type="boolean">
  Use bundled CA store
</ParamField>

<ParamField type="boolean">
  Preconnect to <code>\$REDIS\_URL</code> at startup
</ParamField>

<ParamField type="boolean">
  Preconnect to PostgreSQL at startup
</ParamField>

<ParamField type="string">
  Set the default User-Agent header for HTTP requests
</ParamField>

### Global Configuration & Context

<ParamField type="string">
  Load environment variables from the specified file(s)
</ParamField>

<ParamField type="string">
  Absolute path to resolve files & entrypoints from. This only changes the process' cwd
</ParamField>

<ParamField type="string">
  Specify path to Bun config file. Default <code>\$cwd/bunfig.toml</code>. Alias: <code>-c</code>
</ParamField>

## Examples

Run a JavaScript or TypeScript file:

```bash theme={"theme":{"light":"github-light","dark":"dracula"}}
bun run ./index.js
bun run ./index.tsx
```

Run a package.json script:

```bash theme={"theme":{"light":"github-light","dark":"dracula"}}
bun run dev
bun run lint
```


# JSON5
Source: https://bun.com/docs/runtime/json5

Use Bun's built-in support for JSON5 files through both runtime APIs and bundler integration

In Bun, JSON5 is a first-class citizen alongside JSON, TOML, and YAML. You can:

* Parse and stringify JSON5 with `Bun.JSON5.parse` and `Bun.JSON5.stringify`
* `import` & `require` JSON5 files as modules at runtime (including hot reloading & watch mode support)
* `import` & `require` JSON5 files in frontend apps with Bun's bundler

***

## Conformance

Bun's JSON5 parser is written in Rust and passes 100% of the [official JSON5 test suite](https://github.com/json5/json5-tests). The [translated test suite](https://github.com/oven-sh/bun/blob/main/test/js/bun/json5/json5-test-suite.test.ts) lists every test case.

***

## Runtime API

### `Bun.JSON5.parse()`

Parse a JSON5 string into a JavaScript value.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
import { JSON5 } from "bun";

const data = JSON5.parse(`{
  // JSON5 supports comments
  name: 'my-app',
  version: '1.0.0',
  debug: true,

  // trailing commas are allowed
  tags: ['web', 'api',],
}`);

console.log(data);
// {
//   name: "my-app",
//   version: "1.0.0",
//   debug: true,
//   tags: ["web", "api"]
// }
```

#### Supported JSON5 Features

JSON5 is a superset of JSON based on ECMAScript 5.1 syntax. It supports:

* **Comments**: single-line (`//`) and multi-line (`/* */`)
* **Trailing commas**: in objects and arrays
* **Unquoted keys**: any valid ECMAScript 5.1 identifier
* **Single-quoted strings**: in addition to double-quoted strings
* **Multi-line strings**: using backslash line continuations
* **Hex numbers**: `0xFF`
* **Leading & trailing decimal points**: `.5` and `5.`
* **Infinity and NaN**: positive and negative
* **Explicit plus sign**: `+42`

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const data = JSON5.parse(`{
  // Unquoted keys
  unquoted: 'keys work',

  // Single and double quotes
  single: 'single-quoted',
  double: "double-quoted",

  // Trailing commas
  trailing: 'comma',

  // Special numbers
  hex: 0xDEADbeef,
  half: .5,
  to: Infinity,
  nan: NaN,

  // Multi-line strings
  multiline: 'line 1 \
line 2',
}`);
```

#### Error Handling

`Bun.JSON5.parse()` throws a `SyntaxError` if the input is invalid JSON5:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
try {
  JSON5.parse("{invalid}");
} catch (error) {
  console.error("Failed to parse JSON5:", error.message);
}
```

### `Bun.JSON5.stringify()`

Stringify a JavaScript value to a JSON5 string.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
import { JSON5 } from "bun";

const str = JSON5.stringify({ name: "my-app", version: "1.0.0" });
console.log(str);
// {name:'my-app',version:'1.0.0'}
```

#### Pretty Printing

Pass a `space` argument to format the output with indentation:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const pretty = JSON5.stringify(
  {
    name: "my-app",
    debug: true,
    tags: ["web", "api"],
  },
  null,
  2,
);

console.log(pretty);
// {
//   name: 'my-app',
//   debug: true,
//   tags: [
//     'web',
//     'api',
//   ],
// }
```

The `space` argument can be a number (number of spaces) or a string (used as the indent string for each level):

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
// Tab indentation
JSON5.stringify(data, null, "\t");
```

#### Special Values

Unlike `JSON.stringify`, `JSON5.stringify` preserves special numeric values:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
JSON5.stringify({ inf: Infinity, ninf: -Infinity, nan: NaN });
// {inf:Infinity,ninf:-Infinity,nan:NaN}
```

***

## Module Import

### ES Modules

You can import JSON5 files directly as ES modules:

```json5 config.json5 theme={"theme":{"light":"github-light","dark":"dracula"}}
{
  // Database configuration
  database: {
    host: "localhost",
    port: 5432,
    name: "myapp",
  },

  features: {
    auth: true,
    rateLimit: true,
    analytics: false,
  },
}
```

#### Default Import

```ts app.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import config from "./config.json5";

console.log(config.database.host); // "localhost"
console.log(config.features.auth); // true
```

#### Named Imports

You can destructure top-level properties as named imports:

```ts app.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { database, features } from "./config.json5";

console.log(database.host); // "localhost"
console.log(features.rateLimit); // true
```

### CommonJS

You can also `require` JSON5 files in CommonJS:

```ts app.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
const config = require("./config.json5");
console.log(config.database.name); // "myapp"

// Destructuring also works
const { database, features } = require("./config.json5");
```

***

## Hot Reloading with JSON5

When you run your application with `bun --hot`, Bun reloads JSON5 files when they change:

```json5 config.json5 theme={"theme":{"light":"github-light","dark":"dracula"}}
{
  server: {
    port: 3000,
    host: "localhost",
  },
  features: {
    debug: true,
    verbose: false,
  },
}
```

```ts server.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { server, features } from "./config.json5";

Bun.serve({
  port: server.port,
  hostname: server.host,
  fetch(req) {
    if (features.verbose) {
      console.log(`${req.method} ${req.url}`);
    }
    return new Response("Hello World");
  },
});
```

Run with hot reloading:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun --hot server.ts
```

***

## Bundler Integration

When you bundle with Bun, the bundler parses imported JSON5 files at build time and includes them as JavaScript modules:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun build app.ts --outdir=dist
```

Parsing at build time means:

* Zero runtime JSON5 parsing overhead in production
* Smaller bundle sizes
* Tree shaking of unused properties (named imports)

### Dynamic Imports

You can import JSON5 files dynamically:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const { default: config } = await import("./config.json5");
```


# JSONL
Source: https://bun.com/docs/runtime/jsonl

Parse newline-delimited JSON (JSONL) with Bun's built-in streaming parser

Bun has built-in support for parsing [JSONL](https://jsonlines.org/) (newline-delimited JSON), where each line is a separate JSON value. The parser is implemented in C++ using JavaScriptCore's optimized JSON parser and supports streaming.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const results = Bun.JSONL.parse('{"name":"Alice"}\n{"name":"Bob"}\n');
// [{ name: "Alice" }, { name: "Bob" }]
```

***

## `Bun.JSONL.parse()`

Parse a complete JSONL input and return an array of all parsed values.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
import { JSONL } from "bun";

const input = '{"id":1,"name":"Alice"}\n{"id":2,"name":"Bob"}\n{"id":3,"name":"Charlie"}\n';
const records = JSONL.parse(input);
console.log(records);
// [
//   { id: 1, name: "Alice" },
//   { id: 2, name: "Bob" },
//   { id: 3, name: "Charlie" }
// ]
```

Input can be a string or a `Uint8Array`:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const buffer = new TextEncoder().encode('{"a":1}\n{"b":2}\n');
const results = Bun.JSONL.parse(buffer);
// [{ a: 1 }, { b: 2 }]
```

With `Uint8Array` input, Bun skips a UTF-8 BOM at the start of the buffer.

### Error handling

If the input contains invalid JSON and no values were successfully parsed, `Bun.JSONL.parse()` throws a `SyntaxError`. If at least one value was parsed before the error, it returns the parsed values without throwing.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
try {
  Bun.JSONL.parse("{invalid}\n");
} catch (error) {
  console.error(error); // SyntaxError: Failed to parse JSONL
}
```

***

## `Bun.JSONL.parseChunk()`

For streaming, `parseChunk` parses as many complete values as it can from the input and reports how far it got. That way you know where to resume when data arrives incrementally (for example, from a network stream).

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const chunk = '{"id":1}\n{"id":2}\n{"id":3';

const result = Bun.JSONL.parseChunk(chunk);
console.log(result.values); // [{ id: 1 }, { id: 2 }]
console.log(result.read); // 17 — characters consumed
console.log(result.done); // false — incomplete value remains
console.log(result.error); // null — no parse error
```

### Return value

`parseChunk` returns an object with four properties:

| Property | Type                  | Description                                                             |
| -------- | --------------------- | ----------------------------------------------------------------------- |
| `values` | `any[]`               | Array of successfully parsed JSON values                                |
| `read`   | `number`              | Number of bytes (for `Uint8Array`) or characters (for strings) consumed |
| `done`   | `boolean`             | `true` if the entire input was consumed with no remaining data          |
| `error`  | `SyntaxError \| null` | Parse error, or `null` if no error occurred                             |

### Streaming example

Use `read` to slice off consumed input and carry forward the remainder:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
let buffer = "";

async function processStream(stream: ReadableStream<string>) {
  for await (const chunk of stream) {
    buffer += chunk;
    const result = Bun.JSONL.parseChunk(buffer);

    for (const value of result.values) {
      handleRecord(value);
    }

    // Keep only the unconsumed portion
    buffer = buffer.slice(result.read);
  }

  // Handle any remaining data
  if (buffer.length > 0) {
    const final = Bun.JSONL.parseChunk(buffer);
    for (const value of final.values) {
      handleRecord(value);
    }
    if (final.error) {
      console.error("Parse error in final chunk:", final.error.message);
    }
  }
}
```

### Byte offsets with `Uint8Array`

When the input is a `Uint8Array`, you can pass optional `start` and `end` byte offsets:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const buf = new TextEncoder().encode('{"a":1}\n{"b":2}\n{"c":3}\n');

// Parse starting from byte 8
const result = Bun.JSONL.parseChunk(buf, 8);
console.log(result.values); // [{ b: 2 }, { c: 3 }]
console.log(result.read); // 23

// Parse a specific range
const partial = Bun.JSONL.parseChunk(buf, 0, 8);
console.log(partial.values); // [{ a: 1 }]
```

The `read` value is always a byte offset into the original buffer. Use it with `TypedArray.subarray()` for zero-copy streaming:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
let buf = new Uint8Array(0);

async function processBinaryStream(stream: ReadableStream<Uint8Array>) {
  for await (const chunk of stream) {
    // Append chunk to buffer
    const newBuf = new Uint8Array(buf.length + chunk.length);
    newBuf.set(buf);
    newBuf.set(chunk, buf.length);
    buf = newBuf;

    const result = Bun.JSONL.parseChunk(buf);

    for (const value of result.values) {
      handleRecord(value);
    }

    // Keep unconsumed bytes
    buf = buf.subarray(result.read);
  }
}
```

### Error recovery

Unlike `parse()`, `parseChunk()` does not throw on invalid JSON. Instead, it returns the error in the `error` property, along with any values that were successfully parsed before the error:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const input = '{"a":1}\n{invalid}\n{"b":2}\n';
const result = Bun.JSONL.parseChunk(input);

console.log(result.values); // [{ a: 1 }] — values parsed before the error
console.log(result.error); // SyntaxError
console.log(result.read); // 7 — position up to last successful parse
```

***

## Supported value types

Each line can be any valid JSON value, not just objects:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const input = '42\n"hello"\ntrue\nnull\n[1,2,3]\n{"key":"value"}\n';
const values = Bun.JSONL.parse(input);
// [42, "hello", true, null, [1, 2, 3], { key: "value" }]
```

***

## Performance notes

* **ASCII fast path**: Bun parses pure ASCII input directly without copying, using a zero-allocation `StringView`.
* **UTF-8 support**: Bun decodes non-ASCII `Uint8Array` input to UTF-16.
* **BOM handling**: Bun automatically skips a UTF-8 BOM (`0xEF 0xBB 0xBF`) at the start of a `Uint8Array`.
* **Pre-built object shape**: The result object from `parseChunk` uses a cached structure for fast property access.


# JSX
Source: https://bun.com/docs/runtime/jsx

Built-in JSX and TSX support in Bun with configurable transpilation options

Bun supports `.jsx` and `.tsx` files. Bun's internal transpiler converts JSX syntax into vanilla JavaScript before execution.

```ts react.tsx icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
function Component(props: {message: string}) {
  return (
    <body>
      <h1 style={{color: 'red'}}>{props.message}</h1>
    </body>
  );
}

console.log(<Component message="Hello world!" />);
```

## Configuration

Bun reads your `tsconfig.json` or `jsconfig.json` to determine how to perform the JSX transform internally. If you'd rather not use either, you can set the same options in [`bunfig.toml`](/docs/runtime/bunfig).

Bun respects the following compiler options.

### [`jsx`](https://www.typescriptlang.org/tsconfig#jsx)

How Bun transforms JSX constructs into vanilla JavaScript internally. The following table lists the possible values of `jsx`, along with how each transpiles this JSX component:

```tsx theme={"theme":{"light":"github-light","dark":"dracula"}}
<Box width={5}>Hello</Box>
```

| Compiler options                                    | Transpiled output                                                                                                                                                                                                                                                                                                                                                                                                       |
| --------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `json<br/>{<br/>  "jsx": "react"<br/>}<br/>`        | `tsx<br/>React.createElement(Box, { width: 5 }, "Hello");<br/>`                                                                                                                                                                                                                                                                                                                                                         |
| `json<br/>{<br/>  "jsx": "react-jsx"<br/>}<br/>`    | `tsx<br/>import { jsx } from "react/jsx-runtime";<br/>jsx(Box, { width: 5, children: "Hello" });<br/>`                                                                                                                                                                                                                                                                                                                  |
| `json<br/>{<br/>  "jsx": "react-jsxdev"<br/>}<br/>` | `tsx<br/>import { jsxDEV } from "react/jsx-dev-runtime";<br/>jsxDEV(<br/>  Box,<br/>  { width: 5, children: "Hello" },<br/>  undefined,<br/>  false,<br/>  undefined,<br/>  this,<br/>);<br/>`<br /><br />The `jsxDEV` variable name is a React convention. The `DEV` suffix marks code intended for development. The development version of React is slower and includes additional validity checks & debugging tools. |
| `json<br/>{<br/>  "jsx": "preserve"<br/>}<br/>`     | `tsx<br/>// JSX is not transpiled<br/>// "preserve" is not supported by Bun currently<br/><Box width={5}>Hello</Box><br/>`                                                                                                                                                                                                                                                                                              |

### [`jsxFactory`](https://www.typescriptlang.org/tsconfig#jsxFactory)

<Note>Only applicable when `jsx` is `react`.</Note>

The function name used to represent JSX constructs. Default value is `"React.createElement"`. Set this for libraries like [Preact](https://preactjs.com/) that use a different function name (`"h"`).

| Compiler options                                                      | Transpiled output                             |
| --------------------------------------------------------------------- | --------------------------------------------- |
| `json<br/>{<br/>  "jsx": "react",<br/>  "jsxFactory": "h"<br/>}<br/>` | `tsx<br/>h(Box, { width: 5 }, "Hello");<br/>` |

### [`jsxFragmentFactory`](https://www.typescriptlang.org/tsconfig#jsxFragmentFactory)

<Note>Only applicable when `jsx` is `react`.</Note>

The function name used to represent [JSX fragments](https://react.dev/reference/react/Fragment) such as `<>Hello</>`. Default value is `"React.Fragment"`.

| Compiler options                                                                                                    | Transpiled output                                                                                |
| ------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ |
| `json<br/>{<br/>  "jsx": "react",<br/>  "jsxFactory": "myjsx",<br/>  "jsxFragmentFactory": "MyFragment"<br/>}<br/>` | `tsx<br/>// input<br/><>Hello</>;<br/><br/>// output<br/>myjsx(MyFragment, null, "Hello");<br/>` |

### [`jsxImportSource`](https://www.typescriptlang.org/tsconfig#jsxImportSource)

<Note>Only applicable when `jsx` is `react-jsx` or `react-jsxdev`.</Note>

The module the component factory function (such as `createElement`, `jsx`, or `jsxDEV`) is imported from. Default value is `"react"`. You typically need this when using a component library like Preact.

| Compiler options                                                                                                       | Transpiled output                                                                                                                                                                                                                                 |
| ---------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `jsonc<br/>{<br/>  "jsx": "react-jsx",<br/>  // jsxImportSource is not defined<br/>  // default to "react"<br/>}<br/>` | `tsx<br/>import { jsx } from "react/jsx-runtime";<br/>jsx(Box, { width: 5, children: "Hello" });<br/>`                                                                                                                                            |
| `jsonc<br/>{<br/>  "jsx": "react-jsx",<br/>  "jsxImportSource": "preact",<br/>}<br/>`                                  | `tsx<br/>import { jsx } from "preact/jsx-runtime";<br/>jsx(Box, { width: 5, children: "Hello" });<br/>`                                                                                                                                           |
| `jsonc<br/>{<br/>  "jsx": "react-jsxdev",<br/>  "jsxImportSource": "preact",<br/>}<br/>`                               | `tsx<br/>// /jsx-dev-runtime is automatically appended<br/>import { jsxDEV } from "preact/jsx-dev-runtime";<br/>jsxDEV(<br/>  Box,<br/>  { width: 5, children: "Hello" },<br/>  undefined,<br/>  false,<br/>  undefined,<br/>  this,<br/>);<br/>` |

### JSX pragma

You can set any of these values per file with a *pragma*, a comment that sets a compiler option in a particular file.

| Pragma                                   | Equivalent config                                                  |
| ---------------------------------------- | ------------------------------------------------------------------ |
| `ts<br/>// @jsx h<br/>`                  | `jsonc<br/>{<br/>  "jsxFactory": "h",<br/>}<br/>`                  |
| `ts<br/>// @jsxFrag MyFragment<br/>`     | `jsonc<br/>{<br/>  "jsxFragmentFactory": "MyFragment",<br/>}<br/>` |
| `ts<br/>// @jsxImportSource preact<br/>` | `jsonc<br/>{<br/>  "jsxImportSource": "preact",<br/>}<br/>`        |

## Logging

Bun implements special logging for JSX to help with debugging. Given the following file:

```tsx index.tsx icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { Stack, UserCard } from "./components";

console.log(
  <Stack>
    <UserCard name="Dom" bio="Street racer and Corona lover" />
    <UserCard name="Jakob" bio="Super spy and Dom's secret brother" />
  </Stack>,
);
```

Bun pretty-prints the component tree:

<Frame>![JSX logging output](https://github.com/oven-sh/bun/assets/3084745/d29db51d-6837-44e2-b8be-84fc1b9e9d97)</Frame>

## Prop punning

The Bun runtime also supports "prop punning" for JSX: a shorthand for assigning a variable to a prop with the same name.

```tsx react.tsx icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
function Div(props: {className: string;}) {
  const {className} = props;

  // without punning
  return <div className={className} />;
  // with punning
  return <div {className} />;
}
```


# Markdown
Source: https://bun.com/docs/runtime/markdown

Parse and render Markdown with Bun's built-in Markdown API, supporting GFM extensions and custom rendering callbacks

<Callout type="note">
  **Unstable API** — This API is under active development and may change in future versions of Bun.
</Callout>

Bun includes a fast, built-in Markdown parser written in Rust. It supports GitHub Flavored Markdown (GFM) extensions and provides three APIs:

* `Bun.markdown.html()` — render Markdown to an HTML string
* `Bun.markdown.render()` — render Markdown with custom callbacks for each element
* `Bun.markdown.react()` — render Markdown to React JSX elements

***

## `Bun.markdown.html()`

Convert a Markdown string to HTML.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const html = Bun.markdown.html("# Hello **world**");
// "<h1>Hello <strong>world</strong></h1>\n"
```

GFM extensions like tables, strikethrough, and task lists are enabled by default:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const html = Bun.markdown.html(`
| Feature      | Status |
|-------------|--------|
| Tables       | ~~done~~ |
| Strikethrough| ~~done~~ |
| Task lists   | done |
`);
```

### Options

Pass an options object as the second argument to configure the parser:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const html = Bun.markdown.html("some markdown", {
  tables: true, // GFM tables (default: true)
  strikethrough: true, // GFM strikethrough (default: true)
  tasklists: true, // GFM task lists (default: true)
  tagFilter: true, // GFM tag filter for disallowed HTML tags
  autolinks: true, // Autolink URLs, emails, and www. links
});
```

All available options:

| Option                 | Default | Description                                                 |
| ---------------------- | ------- | ----------------------------------------------------------- |
| `tables`               | `true`  | GFM tables                                                  |
| `strikethrough`        | `true`  | GFM strikethrough (`~~text~~`)                              |
| `tasklists`            | `true`  | GFM task lists (`- [x] item`)                               |
| `autolinks`            | `false` | Enable autolinks — see [Autolinks](#autolinks)              |
| `headings`             | `false` | Heading IDs and autolinks — see [Heading IDs](#heading-ids) |
| `hardSoftBreaks`       | `false` | Treat soft line breaks as hard breaks                       |
| `wikiLinks`            | `false` | Enable `[[wiki links]]`                                     |
| `underline`            | `false` | `__text__` renders as `<u>` instead of `<strong>`           |
| `latexMath`            | `false` | Enable `$inline$` and `$$display$$` math                    |
| `collapseWhitespace`   | `false` | Collapse whitespace in text                                 |
| `permissiveAtxHeaders` | `false` | ATX headers without space after `#`                         |
| `noIndentedCodeBlocks` | `false` | Disable indented code blocks                                |
| `noHtmlBlocks`         | `false` | Disable HTML blocks                                         |
| `noHtmlSpans`          | `false` | Disable inline HTML                                         |
| `tagFilter`            | `false` | GFM tag filter for disallowed HTML tags                     |

#### Autolinks

Pass `true` to enable all autolink types, or an object for granular control:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
// Enable all autolinks (URL, WWW, email)
Bun.markdown.html("Visit www.example.com", { autolinks: true });

// Enable only specific types
Bun.markdown.html("Visit www.example.com", {
  autolinks: { url: true, www: true },
});
```

#### Heading IDs

Pass `true` to enable both heading IDs and autolink headings, or an object for granular control:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
// Enable heading IDs and autolink headings
Bun.markdown.html("## Hello World", { headings: true });
// '<h2 id="hello-world"><a href="#hello-world">Hello World</a></h2>\n'

// Enable only heading IDs (no autolink)
Bun.markdown.html("## Hello World", { headings: { ids: true } });
// '<h2 id="hello-world">Hello World</h2>\n'
```

***

## `Bun.markdown.render()`

Parse Markdown and render it using custom JavaScript callbacks. The callbacks give you full control over the output format. You can generate HTML with custom classes, ANSI terminal output, or any other string format.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const result = Bun.markdown.render("# Hello **world**", {
  heading: (children, { level }) => `<h${level} class="title">${children}</h${level}>`,
  strong: children => `<b>${children}</b>`,
  paragraph: children => `<p>${children}</p>`,
});
// '<h1 class="title">Hello <b>world</b></h1>'
```

### Callback signature

Each callback receives:

1. **`children`** — the accumulated content of the element as a string
2. **`meta`** (optional) — an object with element-specific metadata

Return a string to replace the element's rendering. Return `null` or `undefined` to omit the element from the output entirely. If an element has no callback, its children pass through unchanged.

### Block callbacks

| Callback     | Meta                                          | Description                                                                              |
| ------------ | --------------------------------------------- | ---------------------------------------------------------------------------------------- |
| `heading`    | `{ level, id? }`                              | Heading level 1–6. `id` is set when `headings: { ids: true }` is enabled                 |
| `paragraph`  | —                                             | Paragraph block                                                                          |
| `blockquote` | —                                             | Blockquote block                                                                         |
| `code`       | `{ language? }`                               | Fenced or indented code block. `language` is the info-string when specified on the fence |
| `list`       | `{ ordered, start?, depth }`                  | `depth` is nesting level (0 = top-level). `start` is set for ordered lists               |
| `listItem`   | `{ index, depth, ordered, start?, checked? }` | See [List item meta](#list-item-meta) below                                              |
| `hr`         | —                                             | Horizontal rule                                                                          |
| `table`      | —                                             | Table block                                                                              |
| `thead`      | —                                             | Table head                                                                               |
| `tbody`      | —                                             | Table body                                                                               |
| `tr`         | —                                             | Table row                                                                                |
| `th`         | `{ align? }`                                  | Table header cell. `align` is `"left"`, `"center"`, `"right"`, or absent                 |
| `td`         | `{ align? }`                                  | Table data cell                                                                          |
| `html`       | —                                             | Raw HTML content                                                                         |

#### List item meta

The `listItem` callback receives everything needed to render markers directly:

* `index` — 0-based position within the parent list
* `depth` — the parent list's nesting level (0 = top-level)
* `ordered` — whether the parent list is ordered
* `start` — the parent list's start number (only when `ordered` is true)
* `checked` — task list state (only for `- [x]` / `- [ ]` items)

### Inline callbacks

| Callback        | Meta                               | Description                  |
| --------------- | ---------------------------------- | ---------------------------- |
| `strong`        | —                                  | Strong emphasis (`**text**`) |
| `emphasis`      | —                                  | Emphasis (`*text*`)          |
| `link`          | `{ href: string, title?: string }` | Link                         |
| `image`         | `{ src: string, title?: string }`  | Image                        |
| `codespan`      | —                                  | Inline code (`` `code` ``)   |
| `strikethrough` | —                                  | Strikethrough (`~~text~~`)   |
| `text`          | —                                  | Plain text content           |

### Examples

#### Custom HTML with classes

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const html = Bun.markdown.render("# Title\n\nHello **world**", {
  heading: (children, { level }) => `<h${level} class="heading heading-${level}">${children}</h${level}>`,
  paragraph: children => `<p class="body">${children}</p>`,
  strong: children => `<strong class="bold">${children}</strong>`,
});
```

#### Stripping all formatting

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const plaintext = Bun.markdown.render("# Hello **world**", {
  heading: children => children,
  paragraph: children => children,
  strong: children => children,
  emphasis: children => children,
  link: children => children,
  image: () => "",
  code: children => children,
  codespan: children => children,
});
// "Hello world"
```

#### Omitting elements

Return `null` or `undefined` to remove an element from the output:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const result = Bun.markdown.render("# Title\n\n![logo](img.png)\n\nHello", {
  image: () => null, // Remove all images
  heading: children => children,
  paragraph: children => children + "\n",
});
// "Title\nHello\n"
```

#### ANSI terminal output

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const ansi = Bun.markdown.render("# Hello\n\nThis is **bold** and *italic*", {
  heading: (children, { level }) => `\x1b[1;4m${children}\x1b[0m\n`,
  paragraph: children => children + "\n",
  strong: children => `\x1b[1m${children}\x1b[22m`,
  emphasis: children => `\x1b[3m${children}\x1b[23m`,
});
```

#### Nested list numbering

The `listItem` callback receives everything needed to render markers directly — no post-processing:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const result = Bun.markdown.render("1. first\n   1. sub-a\n   2. sub-b\n2. second", {
  listItem: (children, { index, depth, ordered, start }) => {
    const n = (start ?? 1) + index;
    // 1. 2. 3. at depth 0, a. b. c. at depth 1, i. ii. iii. at depth 2
    const marker = !ordered
      ? "-"
      : depth === 0
        ? `${n}.`
        : depth === 1
          ? `${String.fromCharCode(96 + n)}.`
          : `${toRoman(n)}.`;
    return "  ".repeat(depth) + marker + " " + children.trimEnd() + "\n";
  },
  // Prepend a newline so nested lists are separated from their parent item's text
  list: children => "\n" + children,
});
// 1. first
//   a. sub-a
//   b. sub-b
// 2. second
```

#### Code block syntax highlighting

````ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const result = Bun.markdown.render("```js\nconsole.log('hi')\n```", {
  code: (children, meta) => {
    const lang = meta?.language ?? "";
    return `<pre><code class="language-${lang}">${children}</code></pre>`;
  },
});
````

### Parser options

Pass parser options as a separate third argument:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const result = Bun.markdown.render(
  "Visit www.example.com",
  {
    link: (children, { href }) => `[${children}](${href})`,
    paragraph: children => children,
  },
  { autolinks: true },
);
```

***

## `Bun.markdown.react()`

Render Markdown directly to React elements. Returns a `<Fragment>` that you can use as a component return value.

```tsx theme={"theme":{"light":"github-light","dark":"dracula"}}
function Markdown({ text }: { text: string }) {
  return Bun.markdown.react(text);
}
```

### Server-side rendering

Works with `renderToString()` and React Server Components:

```tsx theme={"theme":{"light":"github-light","dark":"dracula"}}
import { renderToString } from "react-dom/server";

const html = renderToString(Bun.markdown.react("# Hello **world**"));
// "<h1>Hello <strong>world</strong></h1>"
```

### Component overrides

Replace any HTML element with a custom React component by passing it in the second argument, keyed by tag name:

```tsx theme={"theme":{"light":"github-light","dark":"dracula"}}
function Code({ language, children }) {
  return (
    <pre data-language={language}>
      <code>{children}</code>
    </pre>
  );
}

function Link({ href, title, children }) {
  return (
    <a href={href} title={title} target="_blank" rel="noopener noreferrer">
      {children}
    </a>
  );
}

function Heading({ id, children }) {
  return (
    <h2 id={id}>
      <a href={`#${id}`}>{children}</a>
    </h2>
  );
}

const el = Bun.markdown.react(
  content,
  {
    pre: Code,
    a: Link,
    h2: Heading,
  },
  { headings: { ids: true } },
);
```

#### Available overrides

You can override every HTML tag the parser produces:

| Option       | Props                        | Description                                                     |
| ------------ | ---------------------------- | --------------------------------------------------------------- |
| `h1`–`h6`    | `{ id?, children }`          | Headings. `id` is set when `headings: { ids: true }` is enabled |
| `p`          | `{ children }`               | Paragraph                                                       |
| `blockquote` | `{ children }`               | Blockquote                                                      |
| `pre`        | `{ language?, children }`    | Code block. `language` is the info string (e.g. `"js"`)         |
| `hr`         | `{}`                         | Horizontal rule (no children)                                   |
| `ul`         | `{ children }`               | Unordered list                                                  |
| `ol`         | `{ start, children }`        | Ordered list. `start` is the first item number                  |
| `li`         | `{ checked?, children }`     | List item. `checked` is set for task list items                 |
| `table`      | `{ children }`               | Table                                                           |
| `thead`      | `{ children }`               | Table head                                                      |
| `tbody`      | `{ children }`               | Table body                                                      |
| `tr`         | `{ children }`               | Table row                                                       |
| `th`         | `{ align?, children }`       | Table header cell                                               |
| `td`         | `{ align?, children }`       | Table data cell                                                 |
| `em`         | `{ children }`               | Emphasis (`*text*`)                                             |
| `strong`     | `{ children }`               | Strong (`**text**`)                                             |
| `a`          | `{ href, title?, children }` | Link                                                            |
| `img`        | `{ src, alt?, title? }`      | Image (no children)                                             |
| `code`       | `{ children }`               | Inline code                                                     |
| `del`        | `{ children }`               | Strikethrough (`~~text~~`)                                      |
| `br`         | `{}`                         | Hard line break (no children)                                   |

### React 18 and older

By default, elements use `Symbol.for('react.transitional.element')` as the `$$typeof` symbol. For React 18 and older, pass `reactVersion: 18` in the options (third argument):

```tsx theme={"theme":{"light":"github-light","dark":"dracula"}}
function Markdown({ text }: { text: string }) {
  return Bun.markdown.react(text, undefined, { reactVersion: 18 });
}
```

### Parser options

Pass any of the [parser options](#options) as the third argument:

```tsx theme={"theme":{"light":"github-light","dark":"dracula"}}
const el = Bun.markdown.react("## Hello World", undefined, {
  headings: { ids: true },
  autolinks: true,
});
```


# Module Resolution
Source: https://bun.com/docs/runtime/module-resolution

How Bun resolves modules and handles imports in JavaScript and TypeScript

The JavaScript ecosystem is in a years-long transition from CommonJS modules to native ES modules (ESM). Different runtimes and build tools have historically disagreed on how import specifiers map to files on disk. Bun aims to provide a consistent and predictable module resolution system that works without configuration.

## Syntax

Consider the following files.

<CodeGroup>
  ```ts index.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
  import { hello } from "./hello";

  hello();
  ```

  ```ts hello.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
  export function hello() {
    console.log("Hello world!");
  }
  ```
</CodeGroup>

Running `index.ts` prints "Hello world!".

```bash icon="terminal" terminal theme={"theme":{"light":"github-light","dark":"dracula"}}
bun index.ts
Hello world!
```

Here `./hello` is a relative path with no extension. **Extensioned imports are optional but supported.** To resolve this import, Bun checks for the following files in order:

* `./hello.tsx`
* `./hello.jsx`
* `./hello.mts`
* `./hello.ts`
* `./hello.mjs`
* `./hello.js`
* `./hello.cts`
* `./hello.cjs`
* `./hello.json`
* `./hello/index.tsx`
* `./hello/index.jsx`
* `./hello/index.mts`
* `./hello/index.ts`
* `./hello/index.mjs`
* `./hello/index.js`
* `./hello/index.cts`
* `./hello/index.cjs`
* `./hello/index.json`

<Note>
  The exact order varies by context: `require()` tries CommonJS extensions (`.cts`, `.cjs`) before ESM ones (`.mts`,
  `.mjs`). Imports inside `node_modules` try JavaScript extensions before TypeScript ones. The list above shows the
  order for a local ESM `import`.
</Note>

If the import path includes an extension, Bun checks for that exact file first. If no exact match exists, Bun falls back to trying the extension list above appended to the full path (so `./hello.world` can resolve to `./hello.world.ts`).

```ts index.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { hello } from "./hello";
import { hello } from "./hello.ts"; // this works
```

There's one additional rule for TypeScript compatibility. If you import `from "*.js"` or `from "*.jsx"`, Bun also checks for a matching `*.ts` or `*.tsx` file. Outside `node_modules`, `from "*.mjs"` also matches `*.mts`. This rule follows the TypeScript compiler's [file extension substitution](https://www.typescriptlang.org/docs/handbook/modules/reference.html#file-extension-substitution), which lets source files reference each other by their compiled output paths. Unlike TypeScript, Bun doesn't rewrite `.cjs` to `.cts`.

```ts index.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { hello } from "./hello";
import { hello } from "./hello.ts"; // this works
import { hello } from "./hello.js"; // this also works
```

Bun supports both ES modules (`import`/`export` syntax) and CommonJS modules (`require()`/`module.exports`). The following CommonJS version also works in Bun.

<CodeGroup>
  ```js index.js icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/javascript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=5148f41bbc784f9828f1363dab67340f" theme={"theme":{"light":"github-light","dark":"dracula"}}
  const { hello } = require("./hello");

  hello();
  ```

  ```js hello.js icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/javascript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=5148f41bbc784f9828f1363dab67340f" theme={"theme":{"light":"github-light","dark":"dracula"}}
  function hello() {
    console.log("Hello world!");
  }

  exports.hello = hello;
  ```
</CodeGroup>

That said, using CommonJS is discouraged in new projects.

***

## Module systems

Bun has native support for CommonJS and ES modules. ES modules are the recommended module format for new projects, but CommonJS modules are still widely used in the Node.js ecosystem.

In Bun's JavaScript runtime, both ES modules and CommonJS modules can use `require`. If the target module is an ES module, `require` returns the module namespace object (equivalent to `import * as`). If the target module is a CommonJS module, `require` returns the `module.exports` object (as in Node.js).

| Module Type | `require()`      | `import * as`                                                           |
| ----------- | ---------------- | ----------------------------------------------------------------------- |
| ES Module   | Module Namespace | Module Namespace                                                        |
| CommonJS    | module.exports   | `default` is `module.exports`, keys of module.exports are named exports |

### Using `require()`

You can `require()` any file or package, even `.ts` or `.mjs` files.

```ts title="index.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
const { foo } = require("./foo"); // extensions are optional
const { bar } = require("./bar.mjs");
const { baz } = require("./baz.tsx");
```

<Accordion title="What is a CommonJS module?">
  In 2016, ECMAScript added support for [ES modules](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules). ES modules are the standard for JavaScript modules. However, millions of npm packages still use CommonJS modules.

  CommonJS modules use `module.exports` to export values and are typically imported with `require`.

  ```ts my-commonjs.cjs icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/javascript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=5148f41bbc784f9828f1363dab67340f" theme={"theme":{"light":"github-light","dark":"dracula"}}
  const stuff = require("./stuff");
  module.exports = { stuff };
  ```

  The biggest difference between CommonJS and ES modules is that CommonJS modules are synchronous, while ES modules are asynchronous. Other differences:

  * ES modules support top-level `await` and CommonJS modules don't.
  * ES modules are always in [strict mode](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Strict_mode), while CommonJS modules are not.
  * Browsers do not have native support for CommonJS modules, but they do have native support for ES modules through `<script type="module">`.
  * CommonJS modules are not statically analyzable, while ES modules only allow static imports and exports.
  * Static `import` statements run synchronously, just like CommonJS `require`. You can also load ES modules on the fly with the asynchronous `import()` function, called a "dynamic import".
</Accordion>

### Using `import`

You can `import` any file or package, even `.cjs` files.

```ts title="index.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { foo } from "./foo"; // extensions are optional
import bar from "./bar.ts";
import { stuff } from "./my-commonjs.cjs";
```

### Using `import` and `require()` together

In Bun, you can use `import` or `require` in the same file—they both work, all the time.

```ts title="index.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { stuff } from "./my-commonjs.cjs";
import Stuff from "./my-commonjs.cjs";

const myStuff = require("./my-commonjs.cjs");
```

### Top level await

The only exception to this rule is top-level await. You can't `require()` a file that uses top-level await, since the `require()` function is inherently synchronous.

Fortunately, very few libraries use top-level await, so this is rarely a problem. But if you're using top-level await in your application code, make sure that file isn't `require()`'d from elsewhere in your application. Use `import` or [dynamic `import()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import) instead.

***

## Importing packages

Bun implements the Node.js module resolution algorithm, so you can import packages from `node_modules` with a bare specifier.

```ts title="index.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { stuff } from "foo";
```

The full algorithm is specified in the [Node.js documentation](https://nodejs.org/api/modules.html). Briefly: if you import `from "foo"`, Bun scans up the file system for a `node_modules` directory containing the package `foo`.

Once it finds the `foo` package, Bun reads its `package.json` to determine the package's entrypoint. Bun first reads the `exports` field and checks for the following conditions.

```json package.json icon="file-json" theme={"theme":{"light":"github-light","dark":"dracula"}}
{
  "name": "foo",
  "exports": {
    "bun": "./index.js",
    "node-addons": "./index.js", // unless --no-addons was passed
    "node": "./index.js",
    "require": "./index.js", // if the importer uses require()
    "import": "./index.mjs", // if the importer uses import
    "default": "./index.js"
  }
}
```

Whichever of these conditions occurs *first* in the `package.json` determines the package's entrypoint.

Bun respects subpath [`"exports"`](https://nodejs.org/api/packages.html#subpath-exports) and [`"imports"`](https://nodejs.org/api/packages.html#imports).

```json package.json icon="file-json" theme={"theme":{"light":"github-light","dark":"dracula"}}
{
  "name": "foo",
  "exports": {
    ".": "./index.js"
  }
}
```

Subpath exports and conditional exports work together.

```json package.json icon="file-json" theme={"theme":{"light":"github-light","dark":"dracula"}}
{
  "name": "foo",
  "exports": {
    ".": {
      "import": "./index.mjs",
      "require": "./index.js"
    }
  }
}
```

As in Node.js, specifying any subpath in the `"exports"` map prevents other subpaths from being importable. You can only import files that are explicitly exported. Given the preceding `package.json`:

```ts index.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import stuff from "foo"; // this works
import stuff from "foo/index.mjs"; // this doesn't
```

<Note>
  **Shipping TypeScript** — Bun supports the special `"bun"` export condition. If your library is written in TypeScript,
  you can publish your un-transpiled TypeScript files to `npm` directly. If you specify your package's `*.ts` entrypoint
  in the `"bun"` condition, Bun imports and executes your TypeScript source files directly.
</Note>

If `exports` is not defined, Bun falls back to the legacy top-level entrypoint fields. At runtime Bun prefers [`"main"`](https://nodejs.org/api/packages.html#main) (or an implicit `index.*` file) when present, and uses `"module"` otherwise.

```json package.json icon="file-json" theme={"theme":{"light":"github-light","dark":"dracula"}}
{
  "name": "foo",
  "module": "./index.js",
  "main": "./index.js"
}
```

### NODE\_PATH

Bun supports `NODE_PATH` for additional module resolution directories:

```bash theme={"theme":{"light":"github-light","dark":"dracula"}}
NODE_PATH=./packages bun run src/index.js
```

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
// packages/foo/index.js
export const hello = "world";

// src/index.js
import { hello } from "foo";
```

Multiple paths use the platform's delimiter (`:` on Unix, `;` on Windows):

```bash theme={"theme":{"light":"github-light","dark":"dracula"}}
NODE_PATH=./packages:./lib bun run src/index.js  # Unix/macOS
NODE_PATH=./packages;./lib bun run src/index.js  # Windows
```

### Custom conditions

The `--conditions` flag specifies the conditions to use when resolving packages from `package.json` `"exports"`.

Both `bun build` and Bun's runtime support this flag.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
# Use it with bun build:
bun build --conditions="react-server" --target=bun ./app/foo/route.js

# Use it with bun's runtime:
bun --conditions="react-server" ./app/foo/route.js
```

You can also use `conditions` programmatically with `Bun.build`:

```ts build.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
await Bun.build({
  conditions: ["react-server"],
  target: "bun",
  entryPoints: ["./app/foo/route.js"],
});
```

***

## Path re-mapping

Bun supports import path re-mapping through TypeScript's [`compilerOptions.paths`](https://www.typescriptlang.org/tsconfig#paths) in `tsconfig.json`, which works well with editors. If you aren't a TypeScript user, use a [`jsconfig.json`](https://code.visualstudio.com/docs/languages/jsconfig) in your project root for the same behavior.

```json tsconfig.json icon="file-json" theme={"theme":{"light":"github-light","dark":"dracula"}}
{
  "compilerOptions": {
    "paths": {
      "config": ["./config.ts"], // map specifier to file
      "components/*": ["components/*"] // wildcard matching
    }
  }
}
```

Bun also supports [Node.js-style subpath imports](https://nodejs.org/api/packages.html#subpath-imports) in `package.json`, where mapped paths must start with `#`. TypeScript and editors resolve these too, and you can use both mechanisms together.

```json package.json icon="file-json" theme={"theme":{"light":"github-light","dark":"dracula"}}
{
  "imports": {
    "#config": "./config.ts", // map specifier to file
    "#components/*": "./components/*" // wildcard matching
  }
}
```

<Accordion title="Low-level details of CommonJS interop in Bun">
  Bun's JavaScript runtime has native support for CommonJS. When Bun's JavaScript transpiler detects usages of `module.exports`, it treats the file as CommonJS. The module loader then wraps the transpiled module in a function shaped like this:

  ```js theme={"theme":{"light":"github-light","dark":"dracula"}}
  (function (module, exports, require) {
    // transpiled module
  })(module, exports, require);
  ```

  `module`, `exports`, and `require` are very much like the `module`, `exports`, and `require` in Node.js. Bun assigns these through a [`with scope`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/with) in C++. An internal `Map` stores the `exports` object to handle cyclical `require` calls before the module is fully loaded.

  Once the CommonJS module is successfully evaluated, Bun creates a Synthetic Module Record with the `default` ES Module [export set to `module.exports`](https://github.com/oven-sh/bun/blob/9b6913e1a674ceb7f670f917fc355bb8758c6c72/src/bun.js/bindings/CommonJSModuleRecord.cpp#L212-L213). If `module.exports` is an object, Bun also re-exports its keys as named exports.

  Bun's bundler works differently: it wraps the CommonJS module in a `require_${moduleName}` function which returns the `module.exports` object.
</Accordion>

***

## `import.meta`

The `import.meta` object exposes information about the current module. It's part of the JavaScript language, but its contents are not standardized: each "host" (browser or runtime) implements its own properties on the `import.meta` object.

Bun implements the following properties.

```ts /path/to/project/file.ts theme={"theme":{"light":"github-light","dark":"dracula"}}
import.meta.dir; // => "/path/to/project"
import.meta.file; // => "file.ts"
import.meta.path; // => "/path/to/project/file.ts"
import.meta.url; // => "file:///path/to/project/file.ts"

import.meta.main; // `true` if this file is directly executed by `bun run`
// `false` otherwise

import.meta.resolve("zod"); // => "file:///path/to/project/node_modules/zod/index.js"
```

| Property               | Description                                                                                                                                                                                                                                                                                                                   |
| ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `import.meta.dir`      | Absolute path to the directory containing the current file, e.g. `/path/to/project`. Equivalent to `__dirname` in CommonJS modules (and Node.js)                                                                                                                                                                              |
| `import.meta.dirname`  | An alias to `import.meta.dir`, for Node.js compatibility                                                                                                                                                                                                                                                                      |
| `import.meta.env`      | An alias to `process.env`.                                                                                                                                                                                                                                                                                                    |
| `import.meta.file`     | The name of the current file, e.g. `index.tsx`                                                                                                                                                                                                                                                                                |
| `import.meta.path`     | Absolute path to the current file, e.g. `/path/to/project/index.ts`. Equivalent to `__filename` in CommonJS modules (and Node.js)                                                                                                                                                                                             |
| `import.meta.filename` | An alias to `import.meta.path`, for Node.js compatibility                                                                                                                                                                                                                                                                     |
| `import.meta.main`     | Indicates whether the current file is the entrypoint to the current `bun` process: `true` if `bun run` executes it directly, `false` if it's imported                                                                                                                                                                         |
| `import.meta.resolve`  | Resolve a module specifier (e.g. `"zod"` or `"./file.tsx"`) to a url. Equivalent to [`import.meta.resolve` in browsers](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import.meta#resolve). Example: `import.meta.resolve("zod")` returns `"file:///path/to/project/node_modules/zod/index.js"` |
| `import.meta.url`      | A `string` url to the current file, e.g. `file:///path/to/project/index.ts`. Equivalent to [`import.meta.url` in browsers](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import.meta#url)                                                                                                       |


# DNS
Source: https://bun.com/docs/runtime/networking/dns

Use Bun's DNS module to resolve DNS records

Bun implements its own `dns` module and the `node:dns` module.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
import * as dns from "node:dns";

const addrs = await dns.promises.resolve4("bun.com", { ttl: true });
console.log(addrs);
// => [{ address: "172.67.161.226", ttl: 0 }, ...]
```

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
import { dns } from "bun";

dns.prefetch("bun.com", 443);
```

***

## Choosing a resolver backend

`Bun.dns.lookup()` accepts a `backend` option that selects the resolver implementation:

* `"c-ares"`: the [c-ares](https://c-ares.org/) asynchronous resolver. It reads `/etc/resolv.conf` directly, so it does not consult NSS modules such as `systemd-resolved`. This backend is the default for `Bun.dns.lookup()` on Linux.
* `"system"`: the platform's own resolver (the non-blocking system API on macOS, `getaddrinfo` on a thread pool everywhere else). This backend is the default on macOS, Windows, and Android.
* `"getaddrinfo"` (alias `"libc"`): the POSIX `getaddrinfo(3)` function on a thread pool.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
import { dns } from "bun";

const [{ address }] = await dns.lookup("example.com", { backend: "system" });
```

`node:dns.lookup()` always uses the `"system"` backend to match Node.js, whose `dns.lookup()` is documented as calling `getaddrinfo(3)`. The `node:dns.resolve*()` functions use c-ares, as they do in Node.js.

## DNS caching in Bun

Bun caches DNS lookups, which makes repeated connections to the same hosts faster.

The cache holds up to 256 entries for a maximum of 30 seconds each. If a connection to a host fails, Bun removes that host's entry from the cache. Simultaneous connections to the same host share one DNS lookup.

Bun uses this cache automatically in:

* `bun install`
* `fetch()`
* `node:http` (client)
* `Bun.connect`
* `node:net`
* `node:tls`

### When should I prefetch a DNS entry?

Web browsers expose [`<link rel="dns-prefetch">`](https://developer.mozilla.org/en-US/docs/Web/Performance/dns-prefetch) to resolve a hostname before it's needed. In Bun, `dns.prefetch` does the same thing: use it when you know you'll connect to a host soon and want to avoid the initial DNS lookup.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
import { dns } from "bun";

dns.prefetch("my.database-host.com", 5432);
```

A database driver is a good example: prefetch the database host's DNS entry when your application starts. By the time the rest of the application has loaded, the lookup may already be complete.

### `dns.prefetch`

<Warning>This API is experimental and may change in the future.</Warning>

`dns.prefetch` resolves a hostname before you need it.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
dns.prefetch(hostname: string, port?: number): void;
```

Here's an example:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
import { dns } from "bun";

dns.prefetch("bun.com", 443);
//
// ... sometime later ...
await fetch("https://bun.com");
```

### `dns.getCacheStats()`

<Warning>This API is experimental and may change in the future.</Warning>

`dns.getCacheStats()` returns the current cache stats as an object with the following properties:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
{
  cacheHitsCompleted: number; // Cache hits completed
  cacheHitsInflight: number; // Cache hits in flight
  cacheMisses: number; // Cache misses
  size: number; // Number of items in the DNS cache
  errors: number; // Number of times a connection failed
  totalCount: number; // Number of times a connection was requested at all (including cache hits and misses)
}
```

Example:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
import { dns } from "bun";

const stats = dns.getCacheStats();
console.log(stats);
// => { cacheHitsCompleted: 0, cacheHitsInflight: 0, cacheMisses: 0, size: 0, errors: 0, totalCount: 0 }
```

### Configuring DNS cache TTL

Bun caches DNS entries for 30 seconds by default. To change the TTL, set the `$BUN_CONFIG_DNS_TIME_TO_LIVE_SECONDS` environment variable. For example, to set it to 5 seconds:

```sh theme={"theme":{"light":"github-light","dark":"dracula"}}
BUN_CONFIG_DNS_TIME_TO_LIVE_SECONDS=5 bun run my-script.ts
```

#### Why is 30 seconds the default?

The system API underneath (`getaddrinfo`) does not expose the TTL of a DNS entry, so Bun has to pick a number. We chose 30 seconds because it's long enough to see the benefits of caching and short enough to be unlikely to cause issues if a DNS entry changes. [Amazon Web Services recommends 5 seconds](https://docs.aws.amazon.com/sdk-for-java/v1/developer-guide/jvm-ttl-dns.html) for the Java Virtual Machine, though the JVM's default is to cache indefinitely.


# Fetch
Source: https://bun.com/docs/runtime/networking/fetch

Send HTTP requests with Bun's fetch API

Bun implements the WHATWG `fetch` standard, with some extensions to meet the needs of server-side JavaScript.

Bun also implements `node:http`, but we generally recommend `fetch` instead.

## Sending an HTTP request

To send an HTTP request, use `fetch`:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const response = await fetch("http://example.com");

console.log(response.status); // => 200

const text = await response.text(); // or response.json(), response.formData(), etc.
```

`fetch` also works with HTTPS URLs.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const response = await fetch("https://example.com");
```

You can also pass `fetch` a [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request) object.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const request = new Request("http://example.com", {
  method: "POST",
  body: "Hello, world!",
});

const response = await fetch(request);
```

### Sending a POST request

To send a POST request, pass an object with the `method` property set to `"POST"`.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const response = await fetch("http://example.com", {
  method: "POST",
  body: "Hello, world!",
});
```

`body` can be a string, a `FormData` object, an `ArrayBuffer`, a `Blob`, or another of the body types listed in the [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch#setting_a_body).

### Proxying requests

To proxy a request, pass an object with the `proxy` property set to a URL string, a `URL` instance, or to an object whose `url` is a string or a `URL`:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const response = await fetch("http://example.com", {
  proxy: "http://proxy.com",
});
```

To send custom headers to the proxy server, pass an object instead:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const response = await fetch("http://example.com", {
  proxy: {
    url: "http://proxy.com",
    headers: {
      "Proxy-Authorization": "Bearer my-token",
      "X-Custom-Proxy-Header": "value",
    },
  },
});
```

Bun sends the `headers` directly to the proxy in `CONNECT` requests (for HTTPS targets) or in the proxy request (for HTTP targets). If you provide a `Proxy-Authorization` header, it overrides any credentials in the proxy URL.

### Custom headers

To set custom headers, pass an object with the `headers` property set to an object.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const response = await fetch("http://example.com", {
  headers: {
    "X-Custom-Header": "value",
  },
});
```

You can also set headers using the [Headers](https://developer.mozilla.org/en-US/docs/Web/API/Headers) object.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const headers = new Headers();
headers.append("X-Custom-Header", "value");

const response = await fetch("http://example.com", {
  headers,
});
```

### Response bodies

To read the response body, use one of the following methods:

* `response.text(): Promise<string>`: Returns a promise that resolves with the response body as a string.
* `response.json(): Promise<any>`: Returns a promise that resolves with the response body as a JSON object.
* `response.formData(): Promise<FormData>`: Returns a promise that resolves with the response body as a `FormData` object.
* `response.bytes(): Promise<Uint8Array>`: Returns a promise that resolves with the response body as a `Uint8Array`.
* `response.arrayBuffer(): Promise<ArrayBuffer>`: Returns a promise that resolves with the response body as an `ArrayBuffer`.
* `response.blob(): Promise<Blob>`: Returns a promise that resolves with the response body as a `Blob`.

#### Streaming response bodies

You can use async iterators to stream the response body.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const response = await fetch("http://example.com");

for await (const chunk of response.body) {
  console.log(chunk);
}
```

You can also access the `ReadableStream` directly.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const response = await fetch("http://example.com");

const stream = response.body;

const reader = stream.getReader();
const { value, done } = await reader.read();
```

### Streaming request bodies

You can also stream data in request bodies using a `ReadableStream`:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const stream = new ReadableStream({
  start(controller) {
    controller.enqueue("Hello");
    controller.enqueue(" ");
    controller.enqueue("World");
    controller.close();
  },
});

const response = await fetch("http://example.com", {
  method: "POST",
  body: stream,
});
```

When using streams with HTTP(S):

* Bun streams the data directly to the network without buffering the entire body in memory
* If the connection is lost, Bun cancels the stream
* Bun sets the `Content-Length` header automatically only when the stream has a known size

When using streams with S3:

* For PUT/POST requests, Bun automatically uses multipart upload
* Bun consumes the stream in chunks and uploads the chunks in parallel
* You can monitor progress through the S3 options

### Fetching a URL with a timeout

To fetch a URL with a timeout, use `AbortSignal.timeout`:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const response = await fetch("http://example.com", {
  signal: AbortSignal.timeout(1000),
});
```

#### Canceling a request

To cancel a request, use an `AbortController`:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const controller = new AbortController();

const response = await fetch("http://example.com", {
  signal: controller.signal,
});

controller.abort();
```

### Unix domain sockets

To fetch a URL using a Unix domain socket, use the `unix: string` option:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const response = await fetch("https://hostname/a/path", {
  unix: "/var/run/path/to/unix.sock",
  method: "POST",
  body: JSON.stringify({ message: "Hello from Bun!" }),
  headers: {
    "Content-Type": "application/json",
  },
});
```

### TLS

To use a client certificate, use the `tls` option:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
await fetch("https://example.com", {
  tls: {
    key: Bun.file("/path/to/key.pem"),
    cert: Bun.file("/path/to/cert.pem"),
    // ca: [Bun.file("/path/to/ca.pem")],
  },
});
```

#### Custom TLS Validation

To customize TLS validation, use the `checkServerIdentity` option in `tls`:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
await fetch("https://example.com", {
  tls: {
    checkServerIdentity: (hostname, peerCertificate) => {
      // Return an Error if the certificate is invalid
    },
  },
});
```

This option is similar to the one in Node's `tls` module.

#### Disable TLS validation

To disable TLS validation, set `rejectUnauthorized` to `false`:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
await fetch("https://example.com", {
  tls: {
    rejectUnauthorized: false,
  },
});
```

This avoids SSL errors with self-signed certificates, but it disables TLS validation, so use it with caution.

### Request options

In addition to the standard fetch options, Bun provides several extensions:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const response = await fetch("http://example.com", {
  // Control automatic response decompression (default: true)
  // Supports gzip, deflate, brotli (br), and zstd
  decompress: true,

  // Disable connection reuse for this request
  keepalive: false,

  // Debug logging level
  verbose: true, // or "curl" for more detailed output
});
```

### Protocol support

Beyond HTTP(S), Bun's fetch supports several additional protocols:

#### S3 URLs - `s3://`

Bun supports fetching from S3 buckets directly.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
// Using environment variables for credentials
const response = await fetch("s3://my-bucket/path/to/object");

// Or passing credentials explicitly
const response = await fetch("s3://my-bucket/path/to/object", {
  s3: {
    accessKeyId: "YOUR_ACCESS_KEY",
    secretAccessKey: "YOUR_SECRET_KEY",
    region: "us-east-1",
  },
});
```

Only PUT and POST methods support request bodies when using S3. For uploads, Bun automatically uses multipart upload for streaming bodies.

See the [S3](/docs/runtime/s3) documentation.

#### File URLs - `file://`

You can fetch local files using the `file:` protocol:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const response = await fetch("file:///path/to/file.txt");
const text = await response.text();
```

On Windows, Bun normalizes paths automatically:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
// Both work on Windows
const response = await fetch("file:///C:/path/to/file.txt");
const response2 = await fetch("file:///c:/path\\to/file.txt");
```

#### Data URLs - `data:`

Bun supports the `data:` URL scheme:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const response = await fetch("data:text/plain;base64,SGVsbG8sIFdvcmxkIQ==");
const text = await response.text(); // "Hello, World!"
```

#### Blob URLs - `blob:`

You can fetch blobs using URLs created by `URL.createObjectURL()`:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const blob = new Blob(["Hello, World!"], { type: "text/plain" });
const url = URL.createObjectURL(blob);
const response = await fetch(url);
```

### Error handling

Bun's fetch implementation includes several specific error cases:

* Using a request body with GET/HEAD methods throws an error (which is expected for the fetch API)
* Using the `proxy` and `unix` options together throws an error
* TLS certificate validation failures when `rejectUnauthorized` is true (or undefined)
* S3 operations may throw specific errors related to authentication or permissions

### Content-Type handling

Bun automatically sets the `Content-Type` header for request bodies when not explicitly provided:

* For `Blob` objects, uses the blob's `type`
* For `FormData`, sets appropriate multipart boundary

## Debugging

For debugging, pass `verbose: true` to `fetch`:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const response = await fetch("http://example.com", {
  verbose: true,
});
```

This prints the request and response headers to your terminal:

```sh theme={"theme":{"light":"github-light","dark":"dracula"}}
[fetch] > HTTP/1.1 GET http://example.com/
[fetch] > Connection: keep-alive
[fetch] > User-Agent: Bun/1.3.3
[fetch] > Accept: */*
[fetch] > Host: example.com
[fetch] > Accept-Encoding: gzip, deflate, br, zstd

[fetch] < 200 OK
[fetch] < Content-Encoding: gzip
[fetch] < Age: 201555
[fetch] < Cache-Control: max-age=604800
[fetch] < Content-Type: text/html; charset=UTF-8
[fetch] < Date: Sun, 21 Jul 2024 02:41:14 GMT
[fetch] < Etag: "3147526947+gzip"
[fetch] < Expires: Sun, 28 Jul 2024 02:41:14 GMT
[fetch] < Last-Modified: Thu, 17 Oct 2019 07:18:26 GMT
[fetch] < Server: ECAcc (sac/254F)
[fetch] < Vary: Accept-Encoding
[fetch] < X-Cache: HIT
[fetch] < Content-Length: 648
```

`verbose: boolean` is a Bun-specific extension, not part of the Web standard `fetch` API.

## Performance

Before an HTTP request can be sent, Bun has to resolve DNS, connect the TCP socket, and sometimes complete a TLS handshake. Each step takes time, especially over a slow DNS server or a poor network connection. After the request completes, consuming the response body also takes time and memory.

Bun provides APIs to optimize each of these steps.

### DNS prefetching

Use `dns.prefetch` when you know you'll connect to a host soon and want to avoid the initial DNS lookup.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
import { dns } from "bun";

dns.prefetch("bun.com");
```

#### DNS caching

By default, Bun caches and deduplicates DNS queries in-memory for up to 30 seconds. `dns.getCacheStats()` returns the cache stats.

See [DNS caching](/docs/runtime/networking/dns).

### Preconnect to a host

`fetch.preconnect` starts the DNS lookup, TCP socket connection, and TLS handshake for a host before you're ready to send a request to it.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
import { fetch } from "bun";

fetch.preconnect("https://bun.com");
```

Calling `fetch` immediately after `fetch.preconnect` does not make your request faster. Preconnecting only helps when there's a gap between knowing the host and sending the request.

#### Preconnect at startup

To preconnect to a host at startup, pass `--fetch-preconnect`:

```sh theme={"theme":{"light":"github-light","dark":"dracula"}}
bun --fetch-preconnect https://bun.com ./my-script.ts
```

`--fetch-preconnect` is similar to `<link rel="preconnect">` in HTML. It is not implemented on Windows; if you need it there, file an issue.

### Connection pooling & HTTP keep-alive

Bun automatically reuses connections to the same host. This is called **connection pooling**, and it can significantly reduce the time spent establishing connections.

#### Simultaneous connection limit

By default, Bun limits the number of simultaneous `fetch` requests to 256, for two reasons:

* It improves overall system stability. Operating systems have an upper limit on the number of simultaneous open TCP sockets, usually in the low thousands. Nearing this limit causes your entire computer to behave strangely. Applications hang and crash.
* It encourages HTTP Keep-Alive connection reuse. For short-lived HTTP requests, the slowest step is often the initial connection setup. Reusing connections can save a lot of time.

When the limit is exceeded, Bun queues requests and sends them as soon as the next request ends.

To raise the limit, set the `BUN_CONFIG_MAX_HTTP_REQUESTS` environment variable:

```sh theme={"theme":{"light":"github-light","dark":"dracula"}}
BUN_CONFIG_MAX_HTTP_REQUESTS=512 bun ./my-script.ts
```

The max value for this limit is 65,535. The maximum port number is 65,535, so it's quite difficult for any one computer to exceed this limit.

### Response buffering

The fastest way to read the response body is to use one of these methods:

* `response.text(): Promise<string>`
* `response.json(): Promise<any>`
* `response.formData(): Promise<FormData>`
* `response.bytes(): Promise<Uint8Array>`
* `response.arrayBuffer(): Promise<ArrayBuffer>`
* `response.blob(): Promise<Blob>`

You can also use `Bun.write` to write the response body to a file on disk:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
import { write } from "bun";

await write("output.txt", response);
```

### Implementation details

* Connection pooling is enabled by default. You can disable it per-request with `keepalive: false` or the `"Connection: close"` header.
* Bun optimizes large file uploads using the operating system's `sendfile` syscall under specific conditions:
  * The file must be larger than 32KB
  * The request must not be using a proxy or the `compress` option
  * On macOS, only regular files (not pipes, sockets, or devices) can use `sendfile`
  * When these conditions aren't met, or when using S3/streaming uploads, Bun falls back to reading the file into memory
  * Bun only uses this optimization for HTTP (not HTTPS) requests, where the file can be sent directly from the kernel to the network stack; Bun does not use it on Windows
* S3 operations automatically handle signing requests and merging authentication headers

Many of these features are Bun-specific extensions to the standard fetch API.


# TCP
Source: https://bun.com/docs/runtime/networking/tcp

Use Bun's native TCP API to implement performance-sensitive systems like database clients, game servers, or anything that needs to communicate over TCP (instead of HTTP)

Bun's TCP API is low-level, intended for library authors and advanced use cases.

## Start a server (`Bun.listen()`)

Start a TCP server with `Bun.listen`:

```ts server.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
Bun.listen({
  hostname: "localhost",
  port: 8080,
  socket: {
    data(socket, data) {}, // message received from client
    open(socket) {}, // socket opened
    close(socket, error) {}, // socket closed
    drain(socket) {}, // socket ready for more data
    error(socket, error) {}, // error handler
  },
});
```

<Accordion title="An API designed for speed">
  In Bun, you declare one set of handlers per server instead of assigning callbacks to each socket, as with Node.js `EventEmitters` or the web-standard `WebSocket` API.

  ```ts server.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
  Bun.listen({
    hostname: "localhost",
    port: 8080,
    socket: {
      open(socket) {},
      data(socket, data) {},
      drain(socket) {},
      close(socket, error) {},
      error(socket, error) {},
    },
  });
  ```

  For performance-sensitive servers, assigning listeners to each socket can cause significant garbage collector pressure and increase memory usage. By contrast, Bun only allocates one handler function for each event and shares it among all sockets. This is a small optimization, but it adds up.
</Accordion>

Attach contextual data to a socket in the `open` handler.

```ts server.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
type SocketData = { sessionId: string };

Bun.listen<SocketData>({
  hostname: "localhost",
  port: 8080,
  socket: {
    data(socket, data) {
      socket.write(`${socket.data.sessionId}: ack`); // [!code ++]
    },
    open(socket) {
      socket.data = { sessionId: "abcd" }; // [!code ++]
    },
  },
});
```

To enable TLS, pass a `tls` object containing `key` and `cert` fields.

```ts server.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
Bun.listen({
  hostname: "localhost",
  port: 8080,
  socket: {
    data(socket, data) {},
  },
  tls: {
    // can be string, BunFile, TypedArray, Buffer, or array thereof
    key: Bun.file("./key.pem"), // [!code ++]
    cert: Bun.file("./cert.pem"), // [!code ++]
  },
});
```

The `key` and `cert` fields expect the *contents* of your TLS key and certificate. This can be a string, `BunFile`, `TypedArray`, `Buffer`, or an array of these.

```ts server.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
Bun.listen({
  // ...
  tls: {
    key: Bun.file("./key.pem"), // BunFile
    key: fs.readFileSync("./key.pem"), // Buffer
    key: fs.readFileSync("./key.pem", "utf8"), // string
    key: [Bun.file("./key1.pem"), Bun.file("./key2.pem")], // array of above
  },
});
```

`Bun.listen` returns a server that conforms to the `TCPSocketListener` interface.

```ts server.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
const server = Bun.listen({
  /* config*/
});

// stop listening
// parameter determines whether active connections are closed
server.stop(true);

// let Bun process exit even if server is still listening
server.unref();
```

***

## Create a connection (`Bun.connect()`)

Use `Bun.connect` to connect to a TCP server. Specify the server with `hostname` and `port`. TCP clients can define the same set of handlers as `Bun.listen`, plus a few client-specific handlers.

```ts server.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
// The client
const socket = await Bun.connect({
  hostname: "localhost",
  port: 8080,

  socket: {
    data(socket, data) {},
    open(socket) {},
    close(socket, error) {},
    drain(socket) {},
    error(socket, error) {},

    // client-specific handlers
    connectError(socket, error) {}, // connection failed
    end(socket) {}, // connection closed by server
    timeout(socket) {}, // connection timed out
  },
});
```

To require TLS, specify `tls: true`.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
// The client
const socket = await Bun.connect({
  // ... config
  tls: true, // [!code ++]
});
```

***

## Hot reloading

You can hot reload both TCP servers and sockets with new handlers.

<CodeGroup>
  ```ts server.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
  const server = Bun.listen({
    /* config */
  });

  // reloads handlers for all active server-side sockets
  server.reload({
    socket: {
      data() {
        // new 'data' handler
      },
    },
  });
  ```

  ```ts client.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
  const socket = await Bun.connect({
    /* config */
  });

  socket.reload({
    data() {
      // new 'data' handler
    },
  });
  ```
</CodeGroup>

***

## Buffering

TCP sockets in Bun do not buffer data, so performance-sensitive code should buffer writes itself. For example, this:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
socket.write("h");
socket.write("e");
socket.write("l");
socket.write("l");
socket.write("o");
```

...performs significantly worse than this:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
socket.write("hello");
```

To buffer writes, use Bun's `ArrayBufferSink` with the `{stream: true}` option:

```ts server.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { ArrayBufferSink } from "bun";

const sink = new ArrayBufferSink();
sink.start({
  stream: true, // [!code ++]
  highWaterMark: 1024,
});

sink.write("h");
sink.write("e");
sink.write("l");
sink.write("l");
sink.write("o");

queueMicrotask(() => {
  const data = sink.flush();
  const wrote = socket.write(data);
  if (wrote < data.byteLength) {
    // put it back in the sink if the socket is full
    sink.write(data.subarray(wrote));
  }
});
```

<Note>
  **Corking**

  Support for corking is planned. In the meantime, you must manage backpressure manually with the `drain` handler.
</Note>


# UDP
Source: https://bun.com/docs/runtime/networking/udp

Use Bun's UDP API to implement services with advanced real-time requirements, such as voice chat.

## Bind a UDP socket (`Bun.udpSocket()`)

To create a new (bound) UDP socket:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const socket = await Bun.udpSocket({});
console.log(socket.port); // assigned by the operating system
```

Specify a port:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const socket = await Bun.udpSocket({
  port: 41234, // [!code ++]
});

console.log(socket.port); // 41234
```

### Send a datagram

Specify the data to send, the destination port, and the destination address.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
socket.send("Hello, world!", 41234, "127.0.0.1");
```

The address must be a valid IP address. `send` does not perform DNS
resolution, as it is intended for low-latency operations.

### Receive datagrams

When creating your socket, add a `data` callback to handle incoming packets:

```ts server.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
const server = await Bun.udpSocket({
  socket: {
    data(socket, buf, port, addr) {
      console.log(`message from ${addr}:${port}:`);
      console.log(buf.toString());
    },
  },
});

const client = await Bun.udpSocket({});
client.send("Hello!", server.port, "127.0.0.1");
```

### Connections

UDP has no concept of a connection, but many UDP exchanges (especially as a client) involve only one peer.
In that case you can connect the socket to that peer, which sets the destination address for every packet you send
and restricts incoming packets to that peer.

```ts server.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
const server = await Bun.udpSocket({
  socket: {
    data(socket, buf, port, addr) {
      console.log(`message from ${addr}:${port}:`);
      console.log(buf.toString());
    },
  },
});

const client = await Bun.udpSocket({
  connect: {
    port: server.port,
    hostname: "127.0.0.1",
  },
});

client.send("Hello");
```

Connections are implemented at the operating system level, so they can also improve performance.

### Send many packets at once using `sendMany()`

To send a large volume of packets without the overhead of a system call for each, batch them with `sendMany()`.

For an unconnected socket, `sendMany` takes an array as its only argument. Each set of three array elements describes a packet:
the data to send, the target port, and the target address.

```ts server.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
const socket = await Bun.udpSocket({});

// sends 'Hello' to 127.0.0.1:41234, and 'foo' to 1.1.1.1:53 in a single operation
socket.sendMany(["Hello", 41234, "127.0.0.1", "foo", 53, "1.1.1.1"]);
```

With a connected socket, `sendMany` takes an array where each element is the data to send to the peer.

```ts server.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
const socket = await Bun.udpSocket({
  connect: {
    port: 41234,
    hostname: "localhost",
  },
});

socket.sendMany(["foo", "bar", "baz"]);
```

`sendMany` returns the number of packets successfully sent. Like `send`, it only takes valid IP addresses
as destinations and does not perform DNS resolution.

### Handle backpressure

A packet you send may not fit into the operating system's packet buffer. You can detect that this
has happened when:

* `send` returns `false`
* `sendMany` returns a number smaller than the number of packets you specified

In either case, Bun calls the `drain` socket handler once the socket becomes writable again:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const socket = await Bun.udpSocket({
  socket: {
    drain(socket) {
      // continue sending data
    },
  },
});
```

### Socket options

UDP sockets support setting various socket options:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const socket = await Bun.udpSocket({});

// Enable broadcasting to send packets to a broadcast address
socket.setBroadcast(true);

// Set the IP TTL (time to live) for outgoing packets
socket.setTTL(64);
```

### Multicast

Bun supports multicast operations for UDP sockets. Use `addMembership` and `dropMembership` to join and leave multicast groups:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const socket = await Bun.udpSocket({});

// Join a multicast group
socket.addMembership("224.0.0.1");

// Join with a specific interface
socket.addMembership("224.0.0.1", "192.168.1.100");

// Leave a multicast group
socket.dropMembership("224.0.0.1");
```

Additional multicast options:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
// Set TTL for multicast packets (number of network hops)
socket.setMulticastTTL(2);

// Control whether multicast packets loop back to the local socket
socket.setMulticastLoopback(true);

// Specify which interface to use for outgoing multicast packets
socket.setMulticastInterface("192.168.1.100");
```

For source-specific multicast (SSM), use `addSourceSpecificMembership` and `dropSourceSpecificMembership`:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
socket.addSourceSpecificMembership("10.0.0.1", "232.0.0.1");
socket.dropSourceSpecificMembership("10.0.0.1", "232.0.0.1");
```


# Node-API
Source: https://bun.com/docs/runtime/node-api

Use Bun's Node-API module to build native add-ons to Node.js

Node-API is an interface for building native add-ons to Node.js. Bun implements this interface from scratch, so most existing Node-API extensions work with Bun out of the box.

As in Node.js, you can `require()` `.node` files (Node-API modules) directly.

```js theme={"theme":{"light":"github-light","dark":"dracula"}}
const napi = require("./my-node-module.node");
```

Alternatively, use `process.dlopen`:

```js theme={"theme":{"light":"github-light","dark":"dracula"}}
let mod = { exports: {} };
process.dlopen(mod, "./my-node-module.node");
```


# Node.js Compatibility
Source: https://bun.com/docs/runtime/nodejs-compat

Bun's compatibility status with Node.js APIs, modules, and globals

Every day, Bun gets closer to 100% Node.js API compatibility. Popular frameworks like Next.js, Express, and millions of `npm` packages intended for Node.js work with Bun. To ensure compatibility, we run thousands of tests from Node.js' test suite before every release of Bun.

**If a package works in Node.js but doesn't work in Bun, we consider it a bug in Bun.** [Open an issue](https://bun.com/issues) and we'll fix it.

We update this page regularly. It reflects the latest version of Bun's compatibility with *Node.js v26*.

## Built-in Node.js modules

### [`node:assert`](https://nodejs.org/api/assert.html)

🟢 Fully implemented. Legacy-mode `deepEqual` uses `Bun.deepEquals` semantics rather than Node's loose `==` comparison, and function-valued or `printf`-style `message` arguments are not formatted.

### [`node:buffer`](https://nodejs.org/api/buffer.html)

🟢 Fully implemented. A single `Buffer` is capped at 4 GiB (`buffer.constants.MAX_LENGTH` is `2**32`).

### [`node:console`](https://nodejs.org/api/console.html)

🟢 Fully implemented. Bun writes console output directly to the stdout/stderr file descriptors and formats it with its own inspector. As a result, replacing `process.stdout.write` does not capture the output, and object layout differs from `util.inspect`. `console.trace()` writes to stdout and `console.time*()` to stderr.

### [`node:dgram`](https://nodejs.org/api/dgram.html)

🟢 Fully implemented. 99% of Node.js's test suite passes. `addMembership()` does not implicitly bind an unbound socket; call `bind()` first.

### [`node:diagnostics_channel`](https://nodejs.org/api/diagnostics_channel.html)

🟡 `channel()`, `subscribe()`, `tracingChannel()` and the `http` client, `http2` and `dgram` built-in channels are implemented. Missing `boundedChannel()` and the `http.server.*`, `net`, `module`, `console`, `child_process` and `worker_threads` built-in channels. Subscribers do not keep a `Channel` alive, so hold a reference to it.

### [`node:dns`](https://nodejs.org/api/dns.html)

🟢 Fully implemented. Missing `resolveTlsa`. Bun ignores the `Resolver` `maxTimeout` option, and the callback-style `Resolver` class cannot be subclassed (`dns.promises.Resolver` can).

### [`node:events`](https://nodejs.org/api/events.html)

🟢 Fully implemented. 95% of Node.js's test suite passes. `EventEmitterAsyncResource` uses `AsyncResource` underneath, so its `asyncId` is always `0`.

### [`node:fs`](https://nodejs.org/api/fs.html)

🟢 Fully implemented. 98% of Node.js's test suite passes. `Stats` objects lack the `Temporal.Instant` getters (`atimeInstant` and friends).

### [`node:http`](https://nodejs.org/api/http.html)

🟢 Fully implemented. `http.Server` does not extend `net.Server`. Bun ignores `listen(handle)` and the `fd`, `ipv6Only` and `signal` options of `listen()`. `keepAlive`/`keepAliveInitialDelay` on the server are no-ops.

### [`node:https`](https://nodejs.org/api/https.html)

🟡 `request`, `get`, `Agent` and `globalAgent` are implemented, including connection pooling. `https.Server` is `http.Server` with TLS options rather than a `tls.Server`. Request sockets are not `tls.TLSSocket`s: `encrypted`, `authorized` and `servername` work, but `getPeerCertificate()` and `getCipher()` are missing. `setSecureContext()`, `addContext()`, `SNICallback` and `handshakeTimeout` are not supported.

### [`node:os`](https://nodejs.org/api/os.html)

🟢 Fully implemented. `userInfo()` reads `username`, `shell` and `homedir` from the environment (`USER`, `SHELL`, `HOME`) rather than the passwd database. `machine()` returns `"arm64"` instead of `"aarch64"` on Linux arm64.

### [`node:path`](https://nodejs.org/api/path.html)

🟢 Fully implemented. `matchesGlob()` uses `Bun.Glob` semantics rather than minimatch (`*` matches dotfiles, no extglobs). `path.win32` differs from Node in a few edge cases involving device paths and reserved names.

### [`node:punycode`](https://nodejs.org/api/punycode.html)

🟢 Fully implemented. 100% of Node.js's test suite passes. *Deprecated by Node.js*.

### [`node:querystring`](https://nodejs.org/api/querystring.html)

🟢 Fully implemented. 100% of Node.js's test suite passes.

### [`node:readline`](https://nodejs.org/api/readline.html)

🟢 Fully implemented.

### [`node:stream`](https://nodejs.org/api/stream.html)

🟢 Fully implemented. `isReadable`, `isWritable`, `isErrored` and `Readable.isDisturbed` only understand Node.js streams, not web streams.

### [`node:string_decoder`](https://nodejs.org/api/string_decoder.html)

🟢 Fully implemented. 100% of Node.js's test suite passes. `end()` does not accept a string argument, and `StringDecoder` cannot be subclassed with `class extends`.

### [`node:timers`](https://nodejs.org/api/timers.html)

🟢 Fully implemented. The exports are the same functions as the globals. `node:timers/promises` (including `scheduler.wait()` and `scheduler.yield()`) is also implemented.

### [`node:tty`](https://nodejs.org/api/tty.html)

🟢 Fully implemented. `ReadStream` and `WriteStream` extend the `fs` streams rather than `net.Socket`, and constructing them on a non-TTY fd returns a stream with `isTTY` set to `false` instead of throwing.

### [`node:url`](https://nodejs.org/api/url.html)

🟢 Fully implemented.

### [`node:zlib`](https://nodejs.org/api/zlib.html)

🟢 Fully implemented. 98% of Node.js's test suite passes.

### [`node:async_hooks`](https://nodejs.org/api/async_hooks.html)

🟡 `AsyncLocalStorage` and `AsyncResource` are implemented. `createHook`, `executionAsyncId`, `triggerAsyncId` and `executionAsyncResource` are stubs: Bun does not invoke hooks, apart from `init` for `process.nextTick`, and async ids are always `0`. Node.js [strongly discourages](https://nodejs.org/docs/latest/api/async_hooks.html#async-hooks) these APIs in favor of `AsyncLocalStorage`. Bun does not propagate `AsyncLocalStorage` context into `MessagePort`, `BroadcastChannel` or `Worker` events.

### [`node:child_process`](https://nodejs.org/api/child_process.html)

🟡 IPC can send `net.Socket`, `net.Server` and `dgram.Socket` handles (including to and from Node.js processes), but not `http` server sockets. `serialization: "advanced"` only works between Bun processes, so use JSON serialization for Node.js ↔ Bun IPC. Missing `subprocess.channel.ref()`/`unref()`. You cannot pass a child's `stdout`/`stderr` as another child's `stdio`, and `spawnSync` does not return extra `stdio` pipes in `output`.

### [`node:cluster`](https://nodejs.org/api/cluster.html)

🟡 `net` and `dgram` servers in workers are shared through the primary as in Node.js (`SCHED_RR` and `SCHED_NONE`), and handles can be passed with `worker.send()`. `node:http`/`node:https` servers in workers each bind their own socket instead, so load-balancing HTTP requests across processes is only supported on Linux (through `SO_REUSEPORT`). Otherwise, implemented but not battle-tested.

### [`node:crypto`](https://nodejs.org/api/crypto.html)

🟡 Missing `encapsulate`/`decapsulate` (you can use ML-KEM keys through `crypto.subtle`). `argon2()` and custom engines (`setEngine()`) throw, `setFips()` is a no-op and `secureHeapUsed()` returns `undefined`. Bun's crypto is backed by BoringSSL, which lacks the `ed448`, `x448`, `rsa-pss`, `dsa` and `dh` key types, EC curves other than P-224/256/384/521 (no `secp256k1`), and the CCM, OCB, XTS and `chacha20-poly1305` ciphers.

### [`node:domain`](https://nodejs.org/api/domain.html)

🟡 Missing `Domain` `members`. A domain only catches errors thrown synchronously inside `run()`/`bind()` or emitted by emitters passed to `add()`. Bun does not route errors from timers, `process.nextTick`, promises and other async callbacks to the domain.

### [`node:http2`](https://nodejs.org/api/http2.html)

🟢 Client & server are implemented. 94% of Node.js's test suite passes. The `maxDeflateDynamicTableSize`, `peerMaxConcurrentStreams`, `streamResetBurst`/`streamResetRate` and `maxOriginSetSize` options are accepted but ignored.

### [`node:module`](https://nodejs.org/api/module.html)

🟡 Missing `Module#load()`, `registerHooks`, `findPackageJSON`, `stripTypeScriptTypes`, `getSourceMapsSupport`/`setSourceMapsSupport`. Overriding `require.cache`, `require.extensions` and `module._resolveFilename` is supported. `syncBuiltinESMExports`, `module._load`, `module._pathCache` and `module.register` are no-ops (we recommend [`Bun.plugin`](/docs/runtime/plugins) instead). `findSourceMap` always returns `undefined`.

### [`node:net`](https://nodejs.org/api/net.html)

🟢 Fully implemented, including `BlockList`, `SocketAddress`, `autoSelectFamily`, Unix domain sockets and `server.listen({ fd })`. `new net.Socket({ fd })` cannot read from an existing file descriptor (only write-only wrapping works). `server.listen(handle)` only accepts `{ fd }`. Missing `blockList.toJSON()`/`fromJSON()`.

### [`node:perf_hooks`](https://nodejs.org/api/perf_hooks.html)

🟡 `monitorEventLoopDelay()`, `createHistogram()`, `timerify()` and `PerformanceObserver` (`mark`, `measure`, `function`, `net`, `http` and `http2` entries) are implemented. Bun never emits `gc`, `dns` or `resource` entries. `eventLoopUtilization()` always returns zeros, and `performance.nodeTiming` holds placeholder values. The Node-specific additions to the global `performance` object only appear once `node:perf_hooks` has been imported.

### [`node:process`](https://nodejs.org/api/process.html)

🟡 See [`process`](#process) Global.

### [`node:sys`](https://nodejs.org/api/util.html)

🟡 See [`node:util`](#node-util).

### [`node:tls`](https://nodejs.org/api/tls.html)

🟡 Missing `pskCallback`, OCSP stapling (`requestOCSP`), the server `'newSession'`/`'resumeSession'` events and session ticket keys (`ticketKeys` is ignored). As a result, session resumption does not work across processes. Bun uses BoringSSL, so `tlsSocket.renegotiate()` always fails and `getEphemeralKeyInfo()`/`getSharedSigalgs()` return no information.

### [`node:util`](https://nodejs.org/api/util.html)

🟡 Missing `diff`, `transferableAbortSignal` and `transferableAbortController`. `debuglog()` ignores its `callback` argument and the returned function has no `enabled` property.

### [`node:v8`](https://nodejs.org/api/v8.html)

🟡 `writeHeapSnapshot`, `getHeapSnapshot`, `getHeapStatistics`, `getHeapSpaceStatistics`, `GCProfiler` and `startupSnapshot` are implemented. The heap statistics describe JavaScriptCore's single heap, and `setFlagsFromString` ignores the flags it is given. `serialize` and `deserialize` use JavaScriptCore's wire format instead of V8's. Missing `queryObjects`, `startCpuProfile`, `startHeapProfile`, `Serializer`/`Deserializer`, `takeCoverage`/`stopCoverage` and `promiseHooks`. For profiling, use [`bun:jsc`](/docs/project/benchmarking#javascript-heap-stats) instead.

### [`node:vm`](https://nodejs.org/api/vm.html)

🟡 Core functionality and ES modules are implemented, including `vm.Script`, `vm.createContext`, `vm.runInContext`, `vm.runInNewContext`, `vm.runInThisContext`, `vm.compileFunction`, `vm.isContext`, `vm.Module`, `vm.SourceTextModule`, `vm.SyntheticModule` (exported without `--experimental-vm-modules`), and `importModuleDynamically` support. The `timeout`, `breakOnSigint`, `cachedData`, `microtaskMode` and `codeGeneration` options are supported. An `importModuleDynamically` callback that returns a promise for a `vm.Module` resolves `import()` to the module object rather than its namespace. `vm.measureMemory()` reports whole-heap figures for every context.

### [`node:wasi`](https://nodejs.org/api/wasi.html)

🟡 Partially implemented. `WASI` supports `args`, `env`, `preopens`, `wasiImport` and `start()`, and `bun ./program.wasm` runs a WASI command directly. Missing `getImportObject()` (use `wasiImport`), `initialize()` and the `sock_accept` import. Bun ignores the `version`, `returnOnExit`, `stdin`, `stdout` and `stderr` options, so `proc_exit` exits the Bun process.

### [`node:worker_threads`](https://nodejs.org/api/worker_threads.html)

🟡 `Worker` ignores the `resourceLimits` and `trackUnmanagedFds` options, and `execArgv` only sets `process.execArgv` in the worker. `worker.performance.eventLoopUtilization()` is a stub. Missing `moveMessagePortToContext` and `locks`.

### [`node:inspector`](https://nodejs.org/api/inspector.html)

🟡 Partially implemented. `Session` supports the `Profiler` domain (including precise coverage), `Runtime.enable` and `NodeTracing`, from both `node:inspector` and `node:inspector/promises`. Other `Session` commands such as `Runtime.evaluate` and the `HeapProfiler` domain are not implemented. `open()`, `url()`, `close()` and `waitForDebugger()` are implemented. `open()` serves the `Debugger` and `Runtime` domains and throws in workers. Missing `Network`.

### [`node:repl`](https://nodejs.org/api/repl.html)

🟡 Mostly implemented. `bun --interactive` starts a Node.js-compatible REPL. The REPL does not show result previews (they need V8's inspector-based side-effect-free eval). Tab-completion skips `let`/`const`/`class` bindings, and some V8-specific error-message and stack-frame wording differs.

### [`node:sqlite`](https://nodejs.org/api/sqlite.html)

🟢 Fully implemented. `backup()` runs synchronously and blocks the event loop for the duration of the copy (Node runs it on a worker thread). A `Buffer`/`Uint8Array` database path must be valid UTF-8 (Node passes the raw bytes through; Bun rejects non-UTF-8 with `ERR_INVALID_ARG_VALUE`). On macOS, Bun uses the system `libsqlite3.dylib`. `loadExtension()` requires a full SQLite build, and so do `createSession()`/`applyChangeset()` on older macOS releases. To use a full SQLite build, call `require("bun:sqlite").Database.setCustomSQLite(path)` before opening a database.

### [`node:test`](https://nodejs.org/api/test.html)

🟡 Partially implemented. The in-process API works when test files run under `bun test`: tests, suites, subtests, hooks, `t.plan()`, `t.assert`, `assert.register()`, `t.waitFor()`, `getTestContext()`, `expectFailure`, and `t.mock` (function/method/getter/setter/property mocks and mock timers). `run()` requires an explicit `files` list and runs each file in a `bun test` child process. Most of its options (`globPatterns`, `watch`, `coverage`, `shard`, `only`, `testNamePatterns`, ...) throw `ERR_NOT_IMPLEMENTED`. Missing `node:test/reporters`, snapshot testing, `mock.module()`, `t.runOnly()`, code coverage, `--test-only`, test-level `signal` abort, and Node's `--test` CLI runner mode. `test.only()` / `{only: true}` are accepted but do not filter. `concurrency` is validated but subtests always run serially. Use [`bun:test`](/docs/test) instead.

### [`node:trace_events`](https://nodejs.org/api/tracing.html)

🟢 Fully implemented. `createTracing()`, `getEnabledCategories()` and the `--trace-events-enabled`, `--trace-event-categories` and `--trace-event-file-pattern` flags are supported. Bun writes the trace at exit. Some categories record less than in Node.js. For example, `node.async_hooks` only records timers, and the `v8` category is a placeholder, since JavaScriptCore has no V8 GC or compile events.

### [`node:quic`](https://github.com/nodejs/node/blob/main/doc/api/quic.md)

🟢 Implemented: `listen()`, `connect()`, `QuicEndpoint`, `QuicSession` and `QuicStream`. 99% of Node.js's test suite passes. The API is experimental in Node.js, and importing it emits an `ExperimentalWarning` in Bun too.

### [`node:sea`](https://nodejs.org/api/single-executable-applications.html)

🔴 Not implemented. Use [`bun build --compile`](/docs/bundler/executables) to build single-file executables instead.

## Node.js globals

The following list covers the globals implemented by Node.js and Bun's compatibility status for each.

### [`AbortController`](https://developer.mozilla.org/en-US/docs/Web/API/AbortController)

🟢 Fully implemented.

### [`AbortSignal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal)

🟢 Fully implemented.

### [`Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Blob)

🟢 Fully implemented. The `endings` constructor option is ignored, and `blob.stream()` does not support BYOB readers.

### [`Buffer`](https://nodejs.org/api/buffer.html#class-buffer)

🟢 Fully implemented. A single `Buffer` is capped at 4 GiB (`buffer.constants.MAX_LENGTH` is `2**32`).

### [`ByteLengthQueuingStrategy`](https://developer.mozilla.org/en-US/docs/Web/API/ByteLengthQueuingStrategy)

🟢 Fully implemented.

### [`__dirname`](https://nodejs.org/api/globals.html#__dirname)

🟢 Fully implemented.

### [`__filename`](https://nodejs.org/api/globals.html#__filename)

🟢 Fully implemented.

### [`atob()`](https://developer.mozilla.org/en-US/docs/Web/API/atob)

🟢 Fully implemented.

### [`Atomics`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Atomics)

🟢 Fully implemented.

### [`BroadcastChannel`](https://developer.mozilla.org/en-US/docs/Web/API/BroadcastChannel)

🟢 Fully implemented.

### [`btoa()`](https://developer.mozilla.org/en-US/docs/Web/API/btoa)

🟢 Fully implemented.

### [`clearImmediate()`](https://developer.mozilla.org/en-US/docs/Web/API/Window/clearImmediate)

🟢 Fully implemented.

### [`clearInterval()`](https://developer.mozilla.org/en-US/docs/Web/API/Window/clearInterval)

🟢 Fully implemented.

### [`clearTimeout()`](https://developer.mozilla.org/en-US/docs/Web/API/Window/clearTimeout)

🟢 Fully implemented.

### [`CompressionStream`](https://developer.mozilla.org/en-US/docs/Web/API/CompressionStream)

🟢 Fully implemented.

### [`console`](https://developer.mozilla.org/en-US/docs/Web/API/console)

🟢 Fully implemented. See [`node:console`](#node-console) for the differences in how output is written.

### [`CountQueuingStrategy`](https://developer.mozilla.org/en-US/docs/Web/API/CountQueuingStrategy)

🟢 Fully implemented.

### [`Crypto`](https://developer.mozilla.org/en-US/docs/Web/API/Crypto)

🟢 Fully implemented.

### [`SubtleCrypto (crypto)`](https://developer.mozilla.org/en-US/docs/Web/API/crypto)

🟢 Fully implemented. See [`SubtleCrypto`](#subtlecrypto) for the algorithms Bun does not support.

### [`CryptoKey`](https://developer.mozilla.org/en-US/docs/Web/API/CryptoKey)

🟢 Fully implemented.

### [`CustomEvent`](https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent)

🟢 Fully implemented.

### [`DecompressionStream`](https://developer.mozilla.org/en-US/docs/Web/API/DecompressionStream)

🟢 Fully implemented.

### [`Event`](https://developer.mozilla.org/en-US/docs/Web/API/Event)

🟢 Fully implemented.

### [`EventTarget`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget)

🟢 Fully implemented.

### [`exports`](https://nodejs.org/api/globals.html#exports)

🟢 Fully implemented.

### [`fetch`](https://developer.mozilla.org/en-US/docs/Web/API/fetch)

🟢 Fully implemented. The `integrity` option is ignored.

### [`FormData`](https://developer.mozilla.org/en-US/docs/Web/API/FormData)

🟢 Fully implemented.

### [`global`](https://nodejs.org/api/globals.html#global)

🟢 Implemented. `global` is an object containing all objects in the global namespace. It's rarely referenced directly, as its contents are available without a prefix, for example `console` instead of `global.console`.

### [`globalThis`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/globalThis)

🟢 Aliases to `global`.

### [`Headers`](https://developer.mozilla.org/en-US/docs/Web/API/Headers)

🟢 Fully implemented.

### [`MessageChannel`](https://developer.mozilla.org/en-US/docs/Web/API/MessageChannel)

🟢 Fully implemented.

### [`MessageEvent`](https://developer.mozilla.org/en-US/docs/Web/API/MessageEvent)

🟢 Fully implemented.

### [`MessagePort`](https://developer.mozilla.org/en-US/docs/Web/API/MessagePort)

🟢 Fully implemented. The EventEmitter-style methods Node.js adds (`on()`, `once()`, `off()`, ...) are only installed once `node:worker_threads` has been loaded.

### [`module`](https://nodejs.org/api/globals.html#module)

🟢 Fully implemented. Missing `module.isPreloading`.

### [`PerformanceEntry`](https://developer.mozilla.org/en-US/docs/Web/API/PerformanceEntry)

🟢 Fully implemented.

### [`PerformanceMark`](https://developer.mozilla.org/en-US/docs/Web/API/PerformanceMark)

🟢 Fully implemented.

### [`PerformanceMeasure`](https://developer.mozilla.org/en-US/docs/Web/API/PerformanceMeasure)

🟢 Fully implemented.

### [`PerformanceObserver`](https://developer.mozilla.org/en-US/docs/Web/API/PerformanceObserver)

🟡 Observing `mark` and `measure` entries works. Bun only delivers Node-only entry types (`function`, `http`, `net`, ...) to the `node:perf_hooks` `PerformanceObserver`, and never emits `gc`, `dns` or `resource` entries.

### [`PerformanceObserverEntryList`](https://developer.mozilla.org/en-US/docs/Web/API/PerformanceObserverEntryList)

🟢 Fully implemented.

### [`PerformanceResourceTiming`](https://developer.mozilla.org/en-US/docs/Web/API/PerformanceResourceTiming)

🟡 The class exists, but no entries are ever created: `fetch()` does not record resource timing and `performance.markResourceTiming()` is a no-op.

### [`performance`](https://developer.mozilla.org/en-US/docs/Web/API/performance)

🟡 `now()`, `timeOrigin`, `mark()`, `measure()` and `getEntries()` are implemented. The Node.js additions (`eventLoopUtilization()`, `nodeTiming`, `timerify()`) only exist once `node:perf_hooks` has been loaded. `eventLoopUtilization()` always returns zeros and `nodeTiming` holds placeholder values.

### [`process`](https://nodejs.org/api/process.html)

🟡 Mostly implemented. `process.binding` (internal Node.js bindings some packages rely on) is partially implemented: `buffer`, `config`, `constants`, `fs`, `natives`, `tty_wrap`, `util` and `uv` are available, the rest throw. Setting `process.title` is a no-op on macOS & Linux. `getActiveResourcesInfo()`, `_getActiveHandles()` and `_getActiveRequests()` always return an empty array, `setSourceMapsEnabled()` is a no-op, and `process.report.writeReport()` writes nothing. Missing `sourceMapsEnabled` and `addUncaughtExceptionCaptureCallback`.

### [`queueMicrotask()`](https://developer.mozilla.org/en-US/docs/Web/API/queueMicrotask)

🟢 Fully implemented.

### [`ReadableByteStreamController`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableByteStreamController)

🟢 Fully implemented.

### [`ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream)

🟢 Fully implemented. Streams cannot be transferred with `postMessage()` or `structuredClone()`.

### [`ReadableStreamBYOBReader`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStreamBYOBReader)

🟢 Fully implemented.

### [`ReadableStreamBYOBRequest`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStreamBYOBRequest)

🟢 Fully implemented.

### [`ReadableStreamDefaultController`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStreamDefaultController)

🟢 Fully implemented.

### [`ReadableStreamDefaultReader`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStreamDefaultReader)

🟢 Fully implemented.

### [`require()`](https://nodejs.org/api/globals.html#require)

🟢 Fully implemented, including [`require.main`](https://nodejs.org/api/modules.html#requiremain), [`require.cache`](https://nodejs.org/api/modules.html#requirecache), [`require.resolve`](https://nodejs.org/api/modules.html#requireresolverequest-options).

### [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response)

🟢 Fully implemented. A `Response` constructed from a string does not expose the default `content-type` header in `headers` (`Bun.serve()` still sends it).

### [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request)

🟡 Missing `keepalive` and `duplex`. The `credentials`, `integrity`, `referrer` and `referrerPolicy` options are accepted but ignored.

### [`setImmediate()`](https://developer.mozilla.org/en-US/docs/Web/API/Window/setImmediate)

🟢 Fully implemented.

### [`setInterval()`](https://developer.mozilla.org/en-US/docs/Web/API/Window/setInterval)

🟢 Fully implemented.

### [`setTimeout()`](https://developer.mozilla.org/en-US/docs/Web/API/Window/setTimeout)

🟢 Fully implemented.

### [`structuredClone()`](https://developer.mozilla.org/en-US/docs/Web/API/structuredClone)

🟢 Fully implemented. Only `ArrayBuffer` and `MessagePort` can be transferred, and cloned `Error`s lose their `cause`.

### [`SubtleCrypto`](https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto)

🟢 Fully implemented, including `supports()`, `getPublicKey()`, the `encapsulate*()`/`decapsulate*()` methods, `ML-DSA`, `ML-KEM-768`/`ML-KEM-1024`, `SHA3-*` and `ChaCha20-Poly1305`. Missing the `Ed448`, `X448`, `AES-OCB`, `Argon2*`, `cSHAKE*`, `KMAC*`, `KT128`/`KT256`, `TurboSHAKE*` and `ML-KEM-512` algorithms (all experimental in Node.js).

### [`DOMException`](https://developer.mozilla.org/en-US/docs/Web/API/DOMException)

🟢 Fully implemented. Instances are not native errors (`Error.isError()` returns `false`).

### [`TextDecoder`](https://developer.mozilla.org/en-US/docs/Web/API/TextDecoder)

🟢 Fully implemented.

### [`TextDecoderStream`](https://developer.mozilla.org/en-US/docs/Web/API/TextDecoderStream)

🟢 Fully implemented.

### [`TextEncoder`](https://developer.mozilla.org/en-US/docs/Web/API/TextEncoder)

🟢 Fully implemented.

### [`TextEncoderStream`](https://developer.mozilla.org/en-US/docs/Web/API/TextEncoderStream)

🟢 Fully implemented.

### [`TransformStream`](https://developer.mozilla.org/en-US/docs/Web/API/TransformStream)

🟢 Fully implemented. Cannot be transferred with `postMessage()` or `structuredClone()`.

### [`TransformStreamDefaultController`](https://developer.mozilla.org/en-US/docs/Web/API/TransformStreamDefaultController)

🟢 Fully implemented.

### [`URL`](https://developer.mozilla.org/en-US/docs/Web/API/URL)

🟢 Fully implemented.

### [`URLSearchParams`](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams)

🟢 Fully implemented.

### [`WebAssembly`](https://nodejs.org/api/globals.html#webassembly)

🟢 Fully implemented. Memory64 is disabled by default (set `BUN_JSC_useWasmMemory64=1` to enable it).

### [`WritableStream`](https://developer.mozilla.org/en-US/docs/Web/API/WritableStream)

🟢 Fully implemented. Cannot be transferred with `postMessage()` or `structuredClone()`.

### [`WritableStreamDefaultController`](https://developer.mozilla.org/en-US/docs/Web/API/WritableStreamDefaultController)

🟢 Fully implemented.

### [`WritableStreamDefaultWriter`](https://developer.mozilla.org/en-US/docs/Web/API/WritableStreamDefaultWriter)

🟢 Fully implemented.


# Plugins
Source: https://bun.com/docs/runtime/plugins

Universal plugin API for extending Bun's runtime and bundler

Bun has a universal plugin API that extends both the *runtime* and the *bundler*.

Plugins intercept imports and perform custom loading logic, like reading files or transpiling code. Use them to add support for additional file types, such as `.scss`. In Bun's bundler, plugins can implement framework-level features like CSS extraction, macros, and client-server code co-location.

## Lifecycle hooks

Plugins register callbacks that run at various points in the lifecycle of a bundle:

* [`onStart()`](#onstart): Run once the bundler has started a bundle
* [`onResolve()`](#onresolve): Run before the bundler resolves a module
* [`onLoad()`](#onload): Run before the bundler loads a module
* [`onBeforeParse()`](#onbeforeparse): Run zero-copy native addons in the parser thread before the bundler parses a file

### Reference

A rough overview of the types (see Bun's `bun.d.ts` for the full type definitions):

```ts Plugin Types icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
type PluginBuilder = {
  onStart(callback: () => void | Promise<void>): void;
  onResolve: (
    args: { filter: RegExp; namespace?: string },
    callback: (args: { path: string; importer: string }) => {
      path: string;
      namespace?: string;
    } | void,
  ) => void;
  onLoad: (
    args: { filter: RegExp; namespace?: string },
    callback: (args: { path: string; loader: Loader; namespace: string; defer: () => Promise<void> }) => {
      loader?: Loader;
      contents?: string;
      exports?: Record<string, any>;
    },
  ) => void;
  config: BuildConfig;
};

type Loader =
  | "js"
  | "jsx"
  | "ts"
  | "tsx"
  | "json"
  | "jsonc"
  | "toml"
  | "yaml"
  | "file"
  | "napi"
  | "wasm"
  | "text"
  | "css"
  | "html";
```

## Usage

A plugin is a JavaScript object containing a `name` property and a `setup` function.

```tsx myPlugin.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import type { BunPlugin } from "bun";

const myPlugin: BunPlugin = {
  name: "Custom loader",
  setup(build) {
    // implementation
  },
};
```

Pass the plugin in the `plugins` array when calling `Bun.build`.

```ts index.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
await Bun.build({
  entrypoints: ["./app.ts"],
  outdir: "./out",
  plugins: [myPlugin],
});
```

## Plugin lifecycle

### Namespaces

`onLoad` and `onResolve` accept an optional `namespace` string. Every module has a namespace, which prefixes the import in transpiled code. For instance, a loader with a `filter: /\.yaml$/` and `namespace: "yaml:"` transforms an import from `./myfile.yaml` into `yaml:./myfile.yaml`.

The default namespace is `"file"` and you don't need to specify it: `import myModule from "./my-module.ts"` is the same as `import myModule from "file:./my-module.ts"`.

Other common namespaces are:

* `"bun"`: for Bun-specific modules (`"bun:test"`, `"bun:sqlite"`)
* `"node"`: for Node.js modules (`"node:fs"`, `"node:path"`)

### `onStart`

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
onStart(callback: () => void | Promise<void>): void;
```

Registers a callback that runs when the bundler starts a new bundle.

```ts index.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { plugin } from "bun";

plugin({
  name: "onStart example",

  setup(build) {
    build.onStart(() => {
      console.log("Bundle started!");
    });
  },
});
```

The callback can return a `Promise`. After the bundle process has initialized, the bundler waits until all `onStart()` callbacks have completed before continuing.

For example:

```ts index.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
const result = await Bun.build({
  entrypoints: ["./app.ts"],
  outdir: "./dist",
  sourcemap: "external",
  plugins: [
    {
      name: "Sleep for 10 seconds",
      setup(build) {
        build.onStart(async () => {
          await Bun.sleep(10_000);
        });
      },
    },
    {
      name: "Log bundle time to a file",
      setup(build) {
        build.onStart(async () => {
          const now = Date.now();
          await Bun.$`echo ${now} > bundle-time.txt`;
        });
      },
    },
  ],
});
```

Here, Bun waits for both `onStart()` callbacks to complete: the first sleeps for 10 seconds and the second writes the bundle time to a file.

`onStart()` callbacks (like every other lifecycle callback) cannot modify the `build.config` object. To mutate `build.config`, do so directly in the `setup()` function.

### `onResolve`

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
onResolve(
  args: { filter: RegExp; namespace?: string },
  callback: (args: { path: string; importer: string }) => {
    path: string;
    namespace?: string;
  } | void,
): void;
```

To bundle your project, Bun walks down the dependency tree of all modules in your project. For each imported module, Bun has to find and read that module. The "finding" part is known as "resolving" a module.

The `onResolve()` lifecycle callback customizes how Bun resolves a module.

The first argument to `onResolve()` is an object with a `filter` and [`namespace`](#namespaces) property. The filter is a regular expression run on the import string. Together they determine which modules your custom resolution logic applies to.

The second argument to `onResolve()` is a callback that runs for each module import Bun finds that matches the `filter` and `namespace` defined in the first argument.

The callback receives the *path* to the matching module and can return a *new path* for it. Bun reads the contents of the *new path* and parses it as a module.

For example, redirecting all imports to `images/` to `./public/images/`:

```ts index.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { plugin } from "bun";

plugin({
  name: "onResolve example",
  setup(build) {
    build.onResolve({ filter: /.*/, namespace: "file" }, args => {
      if (args.path.startsWith("images/")) {
        return {
          path: args.path.replace("images/", "./public/images/"),
        };
      }
    });
  },
});
```

### `onLoad`

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
onLoad(
  args: { filter: RegExp; namespace?: string },
  callback: (args: { path: string; namespace: string; loader: Loader; defer: () => Promise<void> }) => {
    loader?: Loader;
    contents?: string;
    exports?: Record<string, any>;
  },
): void;
```

After Bun's bundler has resolved a module, it needs to read and parse the module's contents.

Use the `onLoad()` lifecycle callback to modify the *contents* of a module before Bun reads and parses it.

Like `onResolve()`, the first argument to `onLoad()` filters which modules this invocation of `onLoad()` applies to.

The second argument to `onLoad()` is a callback that runs for each matching module *before* Bun loads its contents into memory.

The callback receives the matching module's *path*, its *namespace*, the default *loader* for that file, and a *defer* function.

The callback can return a new `contents` string for the module as well as a new `loader`.

For example:

```ts index.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import type { BunPlugin } from "bun";

const envPlugin: BunPlugin = {
  name: "env plugin",
  setup(build) {
    build.onLoad({ filter: /env/, namespace: "file" }, args => {
      return {
        contents: `export default ${JSON.stringify(process.env)}`,
        loader: "js",
      };
    });
  },
};

Bun.build({
  entrypoints: ["./app.ts"],
  outdir: "./dist",
  plugins: [envPlugin],
});

// import env from "env"
// env.FOO === "bar"
```

This plugin transforms all imports of the form `import env from "env"` into a JavaScript module that exports the current environment variables.

#### `.defer()`

The `onLoad` callback receives a `defer` function, which returns a `Promise` that resolves once Bun has loaded all *other* modules. Await it when a module's contents depend on other modules.

##### Example: tracking and reporting unused exports

```ts index.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { plugin } from "bun";

plugin({
  name: "track imports",
  setup(build) {
    const transpiler = new Bun.Transpiler();

    let trackedImports: Record<string, number> = {};

    // Each module that goes through this onLoad callback
    // will record its imports in `trackedImports`
    build.onLoad({ filter: /\.ts/ }, async ({ path }) => {
      const contents = await Bun.file(path).arrayBuffer();

      const imports = transpiler.scanImports(contents);

      for (const i of imports) {
        trackedImports[i.path] = (trackedImports[i.path] || 0) + 1;
      }

      return undefined;
    });

    build.onLoad({ filter: /stats\.json/ }, async ({ defer }) => {
      // Wait for all files to be loaded, ensuring
      // that every file goes through the above `onLoad()` function
      // and their imports tracked
      await defer();

      // Emit JSON containing the stats of each import
      return {
        contents: `export default ${JSON.stringify(trackedImports)}`,
        loader: "json",
      };
    });
  },
});
```

You can call the `.defer()` function only once per `onLoad` callback.

## Native plugins

Bun's bundler is fast partly because it is written in native code and uses multiple threads to load and parse modules in parallel. Plugins written in JavaScript cannot take advantage of this, because JavaScript itself is single-threaded.

Native plugins are written as [NAPI](/docs/runtime/node-api) modules and can run on multiple threads, so they run much faster than JavaScript plugins. They can also skip unnecessary work such as the UTF-8 -> UTF-16 conversion needed to pass strings to JavaScript.

The following lifecycle hooks are available to native plugins:

* [`onBeforeParse()`](#onbeforeparse): Called on any thread before Bun's bundler parses a file.

Native plugins are NAPI modules which expose lifecycle hooks as C ABI functions. To create one, export a C ABI function that matches the signature of the lifecycle hook you want to implement.

### Creating a native plugin in Rust

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun add -g @napi-rs/cli
napi new
```

Then install this crate:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
cargo add bun-native-plugin
```

Inside `lib.rs`, use the `bun_native_plugin::bun` proc macro to define a function that implements your native plugin.

Here's an example implementing the `onBeforeParse` hook:

```rs lib.rs icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/rust.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=c48e2f9ffc38d0c1d77ef723c617aca8" theme={"theme":{"light":"github-light","dark":"dracula"}}
use bun_native_plugin::{define_bun_plugin, OnBeforeParse, bun, Result, anyhow, BunLoader};
use napi_derive::napi;

/// Define the plugin and its name
define_bun_plugin!("replace-foo-with-bar");

/// Here we'll implement `onBeforeParse` with code that replaces all occurrences of
/// `foo` with `bar`.
///
/// We use the #[bun] macro to generate some of the boilerplate code.
///
/// The argument of the function (`handle: &mut OnBeforeParse`) tells
/// the macro that this function implements the `onBeforeParse` hook.
#[bun]
pub fn replace_foo_with_bar(handle: &mut OnBeforeParse) -> Result<()> {
  // Fetch the input source code.
  let input_source_code = handle.input_source_code()?;

  // Get the Loader for the file
  let loader = handle.output_loader();


  let output_source_code = input_source_code.replace("foo", "bar");

  handle.set_output_source_code(output_source_code, BunLoader::BUN_LOADER_JSX);

  Ok(())
}
```

To use it in `Bun.build()`:

```typescript theme={"theme":{"light":"github-light","dark":"dracula"}}
import myNativeAddon from "./my-native-addon";
Bun.build({
  entrypoints: ["./app.tsx"],
  plugins: [
    {
      name: "my-plugin",

      setup(build) {
        build.onBeforeParse(
          {
            namespace: "file",
            filter: /\.tsx$/,
          },
          {
            napiModule: myNativeAddon,
            symbol: "replace_foo_with_bar",
            // external: myNativeAddon.getSharedState()
          },
        );
      },
    },
  ],
});
```

### `onBeforeParse`

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
onBeforeParse(
  args: { filter: RegExp; namespace?: string },
  callback: { napiModule: NapiModule; symbol: string; external?: unknown },
): void;
```

This lifecycle callback runs immediately before Bun's bundler parses a file.

It receives the file's contents and can return new source code.

Bun can call the callback from any thread, so the NAPI module implementation must be thread-safe.


# Redis
Source: https://bun.com/docs/runtime/redis

Use Bun's native Redis client with a Promise-based API

<Note>Bun's Redis client supports Redis server versions 7.2 and up.</Note>

Bun's native Redis client has a Promise-based API with built-in connection management, fully typed responses, and TLS support.

```ts redis.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { redis } from "bun";

// Set a key
await redis.set("greeting", "Hello from Bun!");

// Get a key
const greeting = await redis.get("greeting");
console.log(greeting); // "Hello from Bun!"

// Increment a counter
await redis.set("counter", 0);
await redis.incr("counter");

// Check if a key exists
const exists = await redis.exists("greeting");

// Delete a key
await redis.del("greeting");
```

***

## Getting Started

To use the Redis client, you first need to create a connection:

```ts redis.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { redis, RedisClient } from "bun";

// Using the default client (reads connection info from environment)
// process.env.REDIS_URL is used by default
await redis.set("hello", "world");
const result = await redis.get("hello");

// Creating a custom client
const client = new RedisClient("redis://username:password@localhost:6379");
await client.set("counter", "0");
await client.incr("counter");
```

By default, the client reads connection information from the following environment variables (in order of precedence):

* `REDIS_URL`
* `VALKEY_URL`
* If not set, defaults to `"redis://localhost:6379"`

### Connection Lifecycle

The Redis client automatically handles connections in the background:

```ts redis.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
// No connection is made until a command is executed
const client = new RedisClient();

// First command initiates the connection
await client.set("key", "value");

// Connection remains open for subsequent commands
await client.get("key");

// Explicitly close the connection when done
client.close();
```

You can also manually control the connection lifecycle:

```ts redis.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
const client = new RedisClient();

// Explicitly connect
await client.connect();

// Run commands
await client.set("key", "value");

// Disconnect when done
client.close();
```

***

## Basic Operations

### String Operations

```ts redis.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
// Set a key
await redis.set("user:1:name", "Alice");

// Get a key
const name = await redis.get("user:1:name");

// Get a key as Uint8Array
const buffer = await redis.getBuffer("user:1:name");

// Delete a key
await redis.del("user:1:name");

// Check if a key exists
const exists = await redis.exists("user:1:name");

// Set expiration (in seconds)
await redis.set("session:123", "active");
await redis.expire("session:123", 3600); // expires in 1 hour

// Get time to live (in seconds)
const ttl = await redis.ttl("session:123");
```

### Numeric Operations

```ts redis.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
// Set initial value
await redis.set("counter", "0");

// Increment by 1
await redis.incr("counter");

// Decrement by 1
await redis.decr("counter");
```

### Hash Operations

```ts redis.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
// Set multiple fields in a hash
await redis.hmset("user:123", ["name", "Alice", "email", "alice@example.com", "active", "true"]);

// Get multiple fields from a hash
const userFields = await redis.hmget("user:123", ["name", "email"]);
console.log(userFields); // ["Alice", "alice@example.com"]

// Get single field from hash (returns value directly, null if missing)
const userName = await redis.hget("user:123", "name");
console.log(userName); // "Alice"

// Increment a numeric field in a hash
await redis.hincrby("user:123", "visits", 1);

// Increment a float field in a hash
await redis.hincrbyfloat("user:123", "score", 1.5);
```

### Set Operations

```ts redis.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
// Add member to set
await redis.sadd("tags", "javascript");

// Remove member from set
await redis.srem("tags", "javascript");

// Check if member exists in set
const isMember = await redis.sismember("tags", "javascript");

// Get all members of a set
const allTags = await redis.smembers("tags");

// Get a random member
const randomTag = await redis.srandmember("tags");

// Pop (remove and return) a random member
const poppedTag = await redis.spop("tags");
```

***

## Pub/Sub

Bun provides native bindings for the [Redis Pub/Sub](https://redis.io/docs/latest/develop/pubsub/) protocol, added in Bun 1.2.23.

<Warning>
  Redis Pub/Sub is experimental. We expect it to be stable, but we're still looking for feedback and areas for
  improvement.
</Warning>

### Basic Usage

Create a publisher in `publisher.ts`:

```typescript publisher.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { RedisClient } from "bun";

const writer = new RedisClient("redis://localhost:6379");
await writer.connect();

writer.publish("general", "Hello everyone!");

writer.close();
```

In another file, create the subscriber in `subscriber.ts`:

```typescript subscriber.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { RedisClient } from "bun";

const listener = new RedisClient("redis://localhost:6379");
await listener.connect();

await listener.subscribe("general", (message, channel) => {
  console.log(`Received: ${message}`);
});
```

In one shell, run your subscriber:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun run subscriber.ts
```

and, in another, run your publisher:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun run publisher.ts
```

<Note>
  Subscribing takes over the `RedisClient` connection: a client with
  subscriptions can only call the subscription methods (`subscribe()`,
  `psubscribe()`, `unsubscribe()`, `punsubscribe()`), `pubsub()`, and `ping()`. To
  send other commands to Redis, create a separate connection with `.duplicate()`:

  ```ts redis.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
  import { RedisClient } from "bun";

  const redis = new RedisClient("redis://localhost:6379");
  await redis.connect();
  const subscriber = await redis.duplicate(); // [!code ++]

  await subscriber.subscribe("foo", () => {});
  await redis.set("bar", "baz");
  ```
</Note>

### Publishing

Publish messages with the `publish()` method:

```typescript redis.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
await client.publish(channelName, message);
```

### Subscriptions

Subscribe to channels with the `.subscribe()` method:

```typescript redis.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
await client.subscribe(channel, (message, channel) => {});
```

Unsubscribe with the `.unsubscribe()` method:

```typescript redis.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
await client.unsubscribe(); // Unsubscribe from all channels.
await client.unsubscribe(channel); // Unsubscribe a particular channel.
await client.unsubscribe(channel, listener); // Unsubscribe a particular listener.
```

## Advanced Usage

### Command Execution and Pipelining

The client automatically pipelines commands, improving performance by sending multiple commands in a batch and processing responses as they arrive.

```ts redis.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
// Commands are automatically pipelined by default
const [infoResult, listResult] = await Promise.all([redis.get("user:1:name"), redis.get("user:2:email")]);
```

To disable automatic pipelining, set the `enableAutoPipelining` option to `false`:

```ts redis.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
const client = new RedisClient("redis://localhost:6379", {
  enableAutoPipelining: false, // [!code ++]
});
```

### Raw Commands

Use the `send` method to run any Redis command, including ones without a dedicated method. The first argument is the command name, and the second is an array of string arguments.

```ts redis.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
// Run any Redis command
const info = await redis.send("INFO", []);

// LPUSH to a list
await redis.send("LPUSH", ["mylist", "value1", "value2"]);

// Get list range
const list = await redis.send("LRANGE", ["mylist", "0", "-1"]);
```

### Connection Events

You can register handlers for connection events:

```ts redis.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
const client = new RedisClient();

// Called when successfully connected to Redis server
client.onconnect = () => {
  console.log("Connected to Redis server");
};

// Called when disconnected from Redis server
client.onclose = error => {
  console.error("Disconnected from Redis server:", error);
};

// Manually connect/disconnect
await client.connect();
client.close();
```

### Connection Status and Monitoring

```ts redis.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
// Check if connected
console.log(client.connected); // boolean indicating connection status

// Check amount of data buffered (in bytes)
console.log(client.bufferedAmount);
```

### Type Conversion

The client automatically converts Redis responses to JavaScript values:

* Integer responses are returned as JavaScript numbers
* Bulk strings are returned as JavaScript strings
* Simple strings are returned as JavaScript strings
* Null bulk strings are returned as `null`
* Array responses are returned as JavaScript arrays
* Error responses throw JavaScript errors with appropriate error codes
* Boolean responses (RESP3) are returned as JavaScript booleans
* Map responses (RESP3) are returned as JavaScript objects
* Set responses (RESP3) are returned as JavaScript arrays

Special handling for specific commands:

* `EXISTS` returns a boolean instead of a number (1 becomes true, 0 becomes false)
* `SISMEMBER` returns a boolean (1 becomes true, 0 becomes false)

The following commands disable automatic pipelining:

* `AUTH`
* `INFO`
* `QUIT`
* `EXEC`
* `MULTI`
* `WATCH`
* `SCRIPT`
* `SELECT`
* `CLUSTER`
* `DISCARD`
* `UNWATCH`
* `PIPELINE`
* `SUBSCRIBE`
* `PSUBSCRIBE`
* `UNSUBSCRIBE`
* `UNPSUBSCRIBE`

***

## Connection Options

When creating a client, you can pass options to configure the connection:

```ts redis.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
const client = new RedisClient("redis://localhost:6379", {
  // Connection timeout in milliseconds (default: 10000)
  connectionTimeout: 5000,

  // Idle timeout in milliseconds (default: 0 = no timeout)
  idleTimeout: 30000,

  // Whether to automatically reconnect on disconnection (default: true)
  autoReconnect: true,

  // Maximum number of reconnection attempts (default: 20)
  maxRetries: 10,

  // Whether to queue commands when disconnected (default: true)
  enableOfflineQueue: true,

  // Whether to automatically pipeline commands (default: true)
  enableAutoPipelining: true,

  // TLS options (default: false)
  tls: true,
  // Alternatively, provide custom TLS config:
  // tls: {
  //   rejectUnauthorized: true,
  //   ca: "path/to/ca.pem",
  //   cert: "path/to/cert.pem",
  //   key: "path/to/key.pem",
  // }
});
```

### Reconnection Behavior

When a connection is lost, the client automatically attempts to reconnect with exponential backoff:

1. The client starts with a small delay (50ms) and doubles it with each attempt
2. The client caps the reconnection delay at 2000ms (2 seconds)
3. The client attempts to reconnect up to `maxRetries` times (default: 20)
4. While disconnected, the client:
   * Queues commands if `enableOfflineQueue` is true (default)
   * Rejects commands immediately if `enableOfflineQueue` is false

***

## Supported URL Formats

The Redis client supports various URL formats:

```ts redis.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
// Standard Redis URL
new RedisClient("redis://localhost:6379");
new RedisClient("redis://localhost:6379");

// With authentication
new RedisClient("redis://username:password@localhost:6379");

// With database number
new RedisClient("redis://localhost:6379/0");

// TLS connections
new RedisClient("rediss://localhost:6379");
new RedisClient("rediss://localhost:6379");
new RedisClient("redis+tls://localhost:6379");
new RedisClient("redis+tls://localhost:6379");

// Unix socket connections
new RedisClient("redis+unix:///path/to/socket");
new RedisClient("redis+unix:///path/to/socket");

// TLS over Unix socket
new RedisClient("redis+tls+unix:///path/to/socket");
new RedisClient("redis+tls+unix:///path/to/socket");
```

***

## Error Handling

The Redis client throws typed errors for different scenarios:

```ts redis.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
try {
  await redis.get("non-existent-key");
} catch (error) {
  if (error.code === "ERR_REDIS_CONNECTION_CLOSED") {
    console.error("Connection to Redis server was closed");
  } else if (error.code === "ERR_REDIS_AUTHENTICATION_FAILED") {
    console.error("Authentication failed");
  } else {
    console.error("Unexpected error:", error);
  }
}
```

Common error codes:

* `ERR_REDIS_CONNECTION_CLOSED` - Connection to the server was closed
* `ERR_REDIS_AUTHENTICATION_FAILED` - Failed to authenticate with the server
* `ERR_REDIS_INVALID_RESPONSE` - Received an invalid response from the server

***

## Example Use Cases

### Caching

```ts redis.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
async function getUserWithCache(userId) {
  const cacheKey = `user:${userId}`;

  // Try to get from cache first
  const cachedUser = await redis.get(cacheKey);
  if (cachedUser) {
    return JSON.parse(cachedUser);
  }

  // Not in cache, fetch from database
  const user = await database.getUser(userId);

  // Store in cache for 1 hour
  await redis.set(cacheKey, JSON.stringify(user));
  await redis.expire(cacheKey, 3600);

  return user;
}
```

### Rate Limiting

```ts redis.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
async function rateLimit(ip, limit = 100, windowSecs = 3600) {
  const key = `ratelimit:${ip}`;

  // Increment counter
  const count = await redis.incr(key);

  // Set expiry if this is the first request in window
  if (count === 1) {
    await redis.expire(key, windowSecs);
  }

  // Check if limit exceeded
  return {
    limited: count > limit,
    remaining: Math.max(0, limit - count),
  };
}
```

### Session Storage

```ts redis.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
async function createSession(userId, data) {
  const sessionId = crypto.randomUUID();
  const key = `session:${sessionId}`;

  // Store session with expiration
  await redis.hmset(key, ["userId", userId.toString(), "created", Date.now().toString(), "data", JSON.stringify(data)]);
  await redis.expire(key, 86400); // 24 hours

  return sessionId;
}

async function getSession(sessionId) {
  const key = `session:${sessionId}`;

  // Get session data
  const exists = await redis.exists(key);
  if (!exists) return null;

  const [userId, created, data] = await redis.hmget(key, ["userId", "created", "data"]);

  return {
    userId: Number(userId),
    created: Number(created),
    data: JSON.parse(data),
  };
}
```

***

## Implementation Notes

Bun's Redis client is implemented in Rust and uses the Redis Serialization Protocol (RESP3). It reconnects automatically with exponential backoff. It also pipelines commands, so it can send multiple commands without waiting for replies to previous ones.

## Limitations and Future Plans

Limitations we plan to address in future versions:

* Transactions (MULTI/EXEC) require raw commands

Unsupported features:

* Redis Sentinel
* Redis Cluster


# REPL
Source: https://bun.com/docs/runtime/repl

An interactive JavaScript and TypeScript REPL with syntax highlighting, history, and tab completion

`bun repl` starts an interactive Read-Eval-Print Loop (REPL) for evaluating JavaScript and TypeScript expressions. Use it to test code snippets, explore APIs, and debug.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun repl
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
Welcome to Bun v1.3.3
Type .copy [code] to copy to clipboard. .help for more info.

> 1 + 1
2
> const greeting = "Hello, Bun!"
undefined
> greeting
'Hello, Bun!'
```

***

## Features

* **TypeScript & JSX** — Write TypeScript and JSX directly. Bun transpiles everything on the fly.
* **Top-level `await`** — Await promises directly at the prompt without wrapping in an async function.
* **Syntax highlighting** — The REPL highlights input as you type.
* **Persistent history** — The REPL saves history to `~/.bun_repl_history`. History persists across sessions.
* **Tab completion** — Press `Tab` to complete property names and REPL commands.
* **Multi-line input** — Unclosed brackets, braces, and parentheses automatically continue on the next line.
* **Node.js globals** — `require`, `module`, `__dirname`, and `__filename` are available, resolved relative to your current working directory.

***

## Special variables

The REPL exposes two special variables that update after each evaluation.

| Variable | Description                       |
| -------- | --------------------------------- |
| `_`      | The result of the last expression |
| `_error` | The last error that was thrown    |

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
> 2 + 2
4
> _ * 10
40
> JSON.parse("oops")
SyntaxError: JSON Parse error: Unexpected identifier "oops"
> _error
SyntaxError: JSON Parse error: Unexpected identifier "oops"
```

***

## Top-level `await`

You can `await` any expression directly at the prompt.

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
> await fetch("https://api.github.com/repos/oven-sh/bun").then(r => r.json()).then(r => r.stargazers_count)
81234
> const response = await fetch("https://example.com")
undefined
> response.status
200
```

***

## Importing modules

Like Bun's runtime, the REPL accepts both `require` and `import`: mix ES modules and CommonJS freely at the prompt. Module resolution uses the same rules as `bun run`, so you can import from `node_modules`, relative paths, or `node:` builtins.

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
> import { z } from "zod"
undefined
> const path = require("path")
undefined
> z.string().parse(path.join("/tmp", "file.txt"))
'/tmp/file.txt'
```

Declarations persist for the rest of the session. Unlike in regular scripts, you can redeclare `const`/`let` across evaluations, so you can re-run `import` and `require` statements while iterating.

***

## Multi-line input

When you press `Enter` on a line with unclosed brackets, braces, or parentheses, the REPL automatically continues on the next line. The prompt changes to `...` to indicate continuation.

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
> function add(a, b) {
...   return a + b;
... }
undefined
> add(2, 3)
5
```

For longer multi-line entries, use `.editor` to enter editor mode, which buffers all input until you press `Ctrl+D`.

***

## REPL commands

Type `.help` at the prompt to see all available REPL commands.

| Command    | Description                                                                                      |
| ---------- | ------------------------------------------------------------------------------------------------ |
| `.help`    | Print the help message listing commands and keybindings                                          |
| `.exit`    | Exit the REPL                                                                                    |
| `.clear`   | Clear the screen                                                                                 |
| `.copy`    | Copy the last result to the clipboard. Pass an expression to evaluate and copy it: `.copy 1 + 1` |
| `.load`    | Load a file into the REPL session: `.load ./script.ts`                                           |
| `.save`    | Save the current REPL history to a file: `.save ./session.txt`                                   |
| `.editor`  | Enter multi-line editor mode (press `Ctrl+D` to evaluate, `Ctrl+C` to cancel)                    |
| `.break`   | Cancel the current multi-line input                                                              |
| `.history` | Print the command history                                                                        |

***

## Keybindings

The REPL supports Emacs-style line editing.

| Keybinding          | Action                                                   |
| ------------------- | -------------------------------------------------------- |
| `Ctrl+A`            | Move to start of line                                    |
| `Ctrl+E`            | Move to end of line                                      |
| `Ctrl+B` / `Ctrl+F` | Move backward/forward one character                      |
| `Alt+B` / `Alt+F`   | Move backward/forward one word                           |
| `Ctrl+U`            | Delete to start of line                                  |
| `Ctrl+K`            | Delete to end of line                                    |
| `Ctrl+W`            | Delete word backward                                     |
| `Ctrl+D`            | Delete character (or exit if line is empty)              |
| `Ctrl+L`            | Clear screen                                             |
| `Ctrl+T`            | Swap the two characters before the cursor                |
| `Up` / `Down`       | Navigate history                                         |
| `Tab`               | Auto-complete                                            |
| `Ctrl+C`            | Cancel current input (press twice on empty line to exit) |

***

## History

The REPL automatically saves history to `~/.bun_repl_history` (up to 1000 entries) and loads it at the start of each session. Use `Up`/`Down` to navigate.

To export your history to a different file, use `.save`:

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
> .save ./my-session.txt
```

***

## Non-interactive mode

Use `-e` / `--eval` to evaluate a script with REPL semantics and exit. Use `-p` / `--print` to additionally print the result.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun repl -e "const x: number = 42; console.log(x)"
# 42

bun repl -p "await fetch('https://example.com').then(r => r.status)"
# 200

bun repl -p "{ a: 1, b: 2 }"
# { a: 1, b: 2 }
```

Both flags use the same transforms as the interactive REPL, so Bun treats a bare object literal like `{ a: 1 }` as an object expression instead of a block statement. The process exits after the event loop drains (pending timers and I/O complete first). On error, the process exits with code `1`.


# S3
Source: https://bun.com/docs/runtime/s3

Bun provides fast, native bindings for interacting with S3-compatible object storage services.

Production servers often read, upload, and write files to S3-compatible object storage services instead of the local filesystem. Historically, that means local filesystem APIs you use in development can't be used in production. When you use Bun, things are different.

### Bun's S3 API is fast

<Frame>
  <img alt="Bun's S3 API is fast" />
</Frame>

Bun provides fast, native bindings for interacting with S3-compatible object storage services. Its S3 API resembles fetch's `Response` and `Blob` APIs (like Bun's local filesystem APIs).

```ts s3.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { s3, write, S3Client } from "bun";

// Bun.s3 reads environment variables for credentials
// file() returns a lazy reference to a file on S3
const metadata = s3.file("123.json");

// Download from S3 as JSON
const data = await metadata.json();

// Upload to S3
await write(metadata, JSON.stringify({ name: "John", age: 30 }));

// Presign a URL (synchronous - no network request needed)
const url = metadata.presign({
  acl: "public-read",
  expiresIn: 60 * 60 * 24, // 1 day
});

// Delete the file
await metadata.delete();
```

S3 is the [de facto standard](https://en.wikipedia.org/wiki/De_facto_standard) internet filesystem. Bun's S3 API works with S3-compatible storage services like:

* AWS S3
* Cloudflare R2
* DigitalOcean Spaces
* MinIO
* Backblaze B2
* ...and any other S3-compatible storage service

## Basic Usage

### `Bun.S3Client` & `Bun.s3`

`Bun.s3` is equivalent to `new Bun.S3Client()`, relying on environment variables for credentials.

To explicitly set credentials, pass them to the `Bun.S3Client` constructor.

```ts s3.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { S3Client } from "bun";

const client = new S3Client({
  accessKeyId: "your-access-key",
  secretAccessKey: "your-secret-key",
  bucket: "my-bucket",
  // sessionToken: "..."
  // acl: "public-read",
  // endpoint: "https://s3.us-east-1.amazonaws.com",
  // endpoint: "https://<account-id>.r2.cloudflarestorage.com", // Cloudflare R2
  // endpoint: "https://<region>.digitaloceanspaces.com", // DigitalOcean Spaces
  // endpoint: "http://localhost:9000", // MinIO
});

// Bun.s3 is a global singleton that is equivalent to `new Bun.S3Client()`
```

### Working with S3 Files

The **`file`** method in `S3Client` returns a **lazy reference to a file on S3**.

```ts s3.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
// A lazy reference to a file on S3
const s3file: S3File = client.file("123.json");
```

Like `Bun.file(path)`, the `S3Client`'s `file` method is synchronous. It makes no network requests until you call a method that needs one.

### Reading files from S3

`S3File` extends `Blob`, so the same methods that work on `Blob` also work on `S3File`.

```ts s3.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
// Read an S3File as text
const text = await s3file.text();

// Read an S3File as JSON
const json = await s3file.json();

// Read an S3File as an ArrayBuffer
const buffer = await s3file.arrayBuffer();

// Get only the first 1024 bytes
const partial = await s3file.slice(0, 1024).text();

// Stream the file
const stream = s3file.stream();
for await (const chunk of stream) {
  console.log(chunk);
}
```

#### Memory optimization

Methods like `text()`, `json()`, `bytes()`, or `arrayBuffer()` avoid duplicating the string or bytes in memory when possible.

If the text happens to be ASCII, Bun transfers the string directly to JavaScriptCore (the engine) without transcoding and without duplicating it in memory. `.bytes()` and `.arrayBuffer()` also avoid duplicating the bytes in memory.

### Writing & uploading files to S3

Writing to S3 works the same way.

```ts s3.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
// Write a string (replacing the file)
await s3file.write("Hello World!");

// Write a Buffer (replacing the file)
await s3file.write(Buffer.from("Hello World!"));

// Write a Response (replacing the file)
await s3file.write(new Response("Hello World!"));

// Write with content type
await s3file.write(JSON.stringify({ name: "John", age: 30 }), {
  type: "application/json",
});

// Write with content encoding (e.g. for pre-compressed data)
await s3file.write(compressedData, {
  type: "application/json",
  contentEncoding: "gzip",
});

// Write with content disposition
await s3file.write(pdfData, {
  type: "application/pdf",
  contentDisposition: 'attachment; filename="report.pdf"',
});

// Write using a writer (streaming)
const writer = s3file.writer({ type: "application/json" });
writer.write("Hello");
writer.write(" World!");
await writer.end();

// Write using Bun.write
await Bun.write(s3file, "Hello World!");
```

### Working with large files (streams)

Bun automatically handles multipart uploads for large files and supports streaming. The same API that works for local files also works for S3 files.

```ts s3.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
// Write a large file
const bigFile = Buffer.alloc(10 * 1024 * 1024); // 10MB
const writer = s3file.writer({
  // Automatically retry on network errors up to 3 times
  retry: 3,

  // Queue up to 10 requests at a time
  queueSize: 10,

  // Upload in 5 MB chunks
  partSize: 5 * 1024 * 1024,
});
for (let i = 0; i < 10; i++) {
  writer.write(bigFile);
  await writer.flush();
}
await writer.end();
```

***

## Presigning URLs

When your production service needs to let users upload files to your server, it's often more reliable for the user to upload directly to S3 instead of your server acting as an intermediary.

To let users upload directly to S3, presign URLs for S3 files. Presigning generates a URL with a signature that lets a user upload that specific file to S3, without exposing your credentials or granting them unnecessary access to your bucket.

By default, Bun generates a `GET` URL that expires in 24 hours.

```ts s3.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { s3 } from "bun";

// Generate a presigned URL that expires in 24 hours (default)
const download = s3.presign("my-file.txt"); // GET, expires in 24 hours

const upload = s3.presign("my-file", {
  expiresIn: 3600, // 1 hour
  method: "PUT",
  type: "application/json", // Sets response-content-type in the presigned URL
});

// Presign with content disposition (e.g. force download with a specific filename)
const downloadUrl = s3.presign("report.pdf", {
  expiresIn: 3600,
  contentDisposition: 'attachment; filename="quarterly-report.pdf"',
});

// You can call .presign() if on a file reference, but avoid doing so
// unless you already have a reference (to avoid memory usage).
const myFile = s3.file("my-file.txt");
const presignedFile = myFile.presign({
  expiresIn: 3600, // 1 hour
});
```

### Setting ACLs

To set an ACL (access control list) on a presigned URL, pass the `acl` option:

```ts s3.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
const url = s3file.presign({
  acl: "public-read",
  expiresIn: 3600,
});
```

You can pass any of the following ACLs:

| ACL                           | Explanation                                                         |
| ----------------------------- | ------------------------------------------------------------------- |
| `"public-read"`               | The object is readable by the public.                               |
| `"private"`                   | The object is readable only by the bucket owner.                    |
| `"public-read-write"`         | The object is readable and writable by the public.                  |
| `"authenticated-read"`        | The object is readable by the bucket owner and authenticated users. |
| `"aws-exec-read"`             | The object is readable by the AWS account that made the request.    |
| `"bucket-owner-read"`         | The object is readable by the bucket owner.                         |
| `"bucket-owner-full-control"` | The object is readable and writable by the bucket owner.            |
| `"log-delivery-write"`        | The object is writable by AWS services used for log delivery.       |

### Expiring URLs

To set an expiration time for a presigned URL, pass the `expiresIn` option.

```ts s3.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
const url = s3file.presign({
  // Seconds
  expiresIn: 3600, // 1 hour

  // access control list
  acl: "public-read",

  // HTTP method
  method: "PUT",
});
```

### `method`

To set the HTTP method for a presigned URL, pass the `method` option.

```ts s3.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
const url = s3file.presign({
  method: "PUT",
  // method: "DELETE",
  // method: "GET",
  // method: "HEAD",
  // method: "POST",
  // method: "PUT",
});
```

### `new Response(S3File)`

To redirect users to a presigned URL for an S3 file, pass an `S3File` instance to a `Response` object as the body.

The response redirects the user to a presigned URL for the S3 file. The redirect saves you the memory, time, and bandwidth cost of downloading the file to your server and sending it back to the user.

```ts s3.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
const response = new Response(s3file);
console.log(response);
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
Response (0 KB) {
  ok: false,
  url: "",
  status: 302,
  statusText: "",
  headers: Headers {
    "location": "https://<account-id>.r2.cloudflarestorage.com/...",
  },
  redirected: true,
  bodyUsed: false
}
```

***

## Support for S3-Compatible Services

Bun's S3 implementation works with any S3-compatible storage service. Specify the appropriate endpoint:

### Using Bun's S3Client with AWS S3

AWS S3 is the default. With AWS S3, you can pass a `region` option instead of an `endpoint` option.

```ts s3.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { S3Client } from "bun";

// AWS S3
const s3 = new S3Client({
  accessKeyId: "access-key",
  secretAccessKey: "secret-key",
  bucket: "my-bucket",
  // endpoint: "https://s3.us-east-1.amazonaws.com",
  // region: "us-east-1",
});
```

### Using Bun's S3Client with Google Cloud Storage

To use Bun's S3 client with [Google Cloud Storage](https://cloud.google.com/storage), set `endpoint` to `"https://storage.googleapis.com"` in the `S3Client` constructor.

```ts s3.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" highlight={8} theme={"theme":{"light":"github-light","dark":"dracula"}}
import { S3Client } from "bun";

// Google Cloud Storage
const gcs = new S3Client({
  accessKeyId: "access-key",
  secretAccessKey: "secret-key",
  bucket: "my-bucket",
  endpoint: "https://storage.googleapis.com",
});
```

### Using Bun's S3Client with Cloudflare R2

To use Bun's S3 client with [Cloudflare R2](https://developers.cloudflare.com/r2/), set `endpoint` to the R2 endpoint in the `S3Client` constructor. The R2 endpoint includes your account ID.

```ts s3.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" highlight={8} theme={"theme":{"light":"github-light","dark":"dracula"}}
import { S3Client } from "bun";

// CloudFlare R2
const r2 = new S3Client({
  accessKeyId: "access-key",
  secretAccessKey: "secret-key",
  bucket: "my-bucket",
  endpoint: "https://<account-id>.r2.cloudflarestorage.com",
});
```

### Using Bun's S3Client with DigitalOcean Spaces

To use Bun's S3 client with [DigitalOcean Spaces](https://www.digitalocean.com/products/spaces/), set `endpoint` to the DigitalOcean Spaces endpoint in the `S3Client` constructor.

```ts s3.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" highlight={8} theme={"theme":{"light":"github-light","dark":"dracula"}}
import { S3Client } from "bun";

const spaces = new S3Client({
  accessKeyId: "access-key",
  secretAccessKey: "secret-key",
  bucket: "my-bucket",
  // region: "nyc3",
  endpoint: "https://<region>.digitaloceanspaces.com",
});
```

### Using Bun's S3Client with MinIO

To use Bun's S3 client with [MinIO](https://min.io/), set `endpoint` to the URL that MinIO is running on in the `S3Client` constructor.

```ts s3.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" highlight={10} theme={"theme":{"light":"github-light","dark":"dracula"}}
import { S3Client } from "bun";

const minio = new S3Client({
  accessKeyId: "access-key",
  secretAccessKey: "secret-key",
  bucket: "my-bucket",

  // Make sure to use the correct endpoint URL
  // It might not be localhost in production!
  endpoint: "http://localhost:9000",
});
```

### Using Bun's S3Client with supabase

To use Bun's S3 client with [Supabase](https://supabase.com/), set `endpoint` to the Supabase endpoint in the `S3Client` constructor. The Supabase endpoint includes your account ID and the `/storage/v1/s3` path. In the Supabase dashboard at `https://supabase.com/dashboard/project/<account-id>/settings/storage`, turn on **Enable connection via S3 protocol** and use the region shown in that section.

```ts s3.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" highlight={3-10} theme={"theme":{"light":"github-light","dark":"dracula"}}
import { S3Client } from "bun";

const supabase = new S3Client({
  accessKeyId: "access-key",
  secretAccessKey: "secret-key",
  bucket: "my-bucket",
  region: "us-west-1",
  endpoint: "https://<account-id>.supabase.co/storage/v1/s3",
});
```

### Using Bun's S3Client with S3 Virtual Hosted-Style endpoints

When using a virtual hosted-style endpoint, set the `virtualHostedStyle` option to `true`.

<Note>
  * If you don't specify an endpoint, Bun determines the AWS S3 endpoint from the provided region and bucket. - If you
    don't specify a region, Bun defaults to `us-east-1`. - If you explicitly provide an endpoint, you don't need to
    specify a bucket name.
</Note>

```ts s3.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" highlight={17, 25} theme={"theme":{"light":"github-light","dark":"dracula"}}
import { S3Client } from "bun";

// AWS S3 endpoint inferred from region and bucket
const s3 = new S3Client({
  accessKeyId: "access-key",
  secretAccessKey: "secret-key",
  bucket: "my-bucket",
  virtualHostedStyle: true, // [!code ++]
  // endpoint: "https://my-bucket.s3.us-east-1.amazonaws.com",
  // region: "us-east-1",
});

// AWS S3
const s3WithEndpoint = new S3Client({
  accessKeyId: "access-key",
  secretAccessKey: "secret-key",
  endpoint: "https://<bucket-name>.s3.<region>.amazonaws.com",
  virtualHostedStyle: true, // [!code ++]
});

// Cloudflare R2
const r2WithEndpoint = new S3Client({
  accessKeyId: "access-key",
  secretAccessKey: "secret-key",
  endpoint: "https://<bucket-name>.<account-id>.r2.cloudflarestorage.com",
  virtualHostedStyle: true, // [!code ++]
});
```

***

## Credentials

By default, Bun reads the following environment variables for credentials.

| Option name       | Environment variable   |
| ----------------- | ---------------------- |
| `accessKeyId`     | `S3_ACCESS_KEY_ID`     |
| `secretAccessKey` | `S3_SECRET_ACCESS_KEY` |
| `region`          | `S3_REGION`            |
| `endpoint`        | `S3_ENDPOINT`          |
| `bucket`          | `S3_BUCKET`            |
| `sessionToken`    | `S3_SESSION_TOKEN`     |

For each option, if the `S3_*` environment variable is not set, Bun falls back to the matching `AWS_*` environment variable.

| Option name       | Fallback environment variable |
| ----------------- | ----------------------------- |
| `accessKeyId`     | `AWS_ACCESS_KEY_ID`           |
| `secretAccessKey` | `AWS_SECRET_ACCESS_KEY`       |
| `region`          | `AWS_REGION`                  |
| `endpoint`        | `AWS_ENDPOINT`                |
| `bucket`          | `AWS_BUCKET`                  |
| `sessionToken`    | `AWS_SESSION_TOKEN`           |

Bun reads these environment variables from [`.env` files](/docs/runtime/environment-variables) or from the process environment at initialization time (Bun does not use `process.env` for this).

Options you pass to `s3.file(credentials)`, `new Bun.S3Client(credentials)`, or any of the methods that accept credentials override these defaults. So if you use the same credentials for different buckets, you can set the credentials once in your `.env` file and pass only `bucket: "my-bucket"` to `s3.file()`.

### `S3Client` objects

When you're not using environment variables, or you're using multiple buckets, create an `S3Client` object to set credentials explicitly.

```ts s3.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" highlight={3-11} theme={"theme":{"light":"github-light","dark":"dracula"}}
import { S3Client } from "bun";

const client = new S3Client({
  accessKeyId: "your-access-key",
  secretAccessKey: "your-secret-key",
  bucket: "my-bucket",
  // sessionToken: "..."
  endpoint: "https://s3.us-east-1.amazonaws.com",
  // endpoint: "https://<account-id>.r2.cloudflarestorage.com", // Cloudflare R2
  // endpoint: "http://localhost:9000", // MinIO
});

const file = client.file("my-file.txt");

// Write using a Response
await file.write(new Response("Hello World!"));

// Presign a URL
const url = file.presign({
  expiresIn: 60 * 60 * 24, // 1 day
  acl: "public-read",
});

// Delete the file
await file.delete();
```

### `S3Client.prototype.write`

To upload or write a file to S3, call `write` on the `S3Client` instance.

```ts s3.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" highlight={8, 9} theme={"theme":{"light":"github-light","dark":"dracula"}}
const client = new Bun.S3Client({
  accessKeyId: "your-access-key",
  secretAccessKey: "your-secret-key",
  endpoint: "https://s3.us-east-1.amazonaws.com",
  bucket: "my-bucket",
});

await client.write("my-file.txt", "Hello World!");
await client.write("my-file.txt", new Response("Hello World!"));

// equivalent to
// await client.file("my-file.txt").write("Hello World!");
```

### `S3Client.prototype.delete`

To delete a file from S3, call `delete` on the `S3Client` instance.

```ts s3.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" highlight={7} theme={"theme":{"light":"github-light","dark":"dracula"}}
const client = new Bun.S3Client({
  accessKeyId: "your-access-key",
  secretAccessKey: "your-secret-key",
  bucket: "my-bucket",
});

await client.delete("my-file.txt");
// equivalent to
// await client.file("my-file.txt").delete();
```

### `S3Client.prototype.exists`

To check if a file exists in S3, call `exists` on the `S3Client` instance.

```ts s3.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" highlight={7} theme={"theme":{"light":"github-light","dark":"dracula"}}
const client = new Bun.S3Client({
  accessKeyId: "your-access-key",
  secretAccessKey: "your-secret-key",
  bucket: "my-bucket",
});

const exists = await client.exists("my-file.txt");
// equivalent to
// const exists = await client.file("my-file.txt").exists();
```

## `S3File`

Calling `file()` on an `S3Client` instance, or `s3.file()`, returns an `S3File`. Like `Bun.file()`, `S3File` instances are lazy: they don't refer to something that necessarily exists at the time of creation. That's why all the methods that don't involve network requests are fully synchronous.

```ts Type Reference icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" expandable theme={"theme":{"light":"github-light","dark":"dracula"}}
interface S3File extends Blob {
  slice(start: number, end?: number): S3File;
  exists(): Promise<boolean>;
  unlink(): Promise<void>;
  presign(options: S3Options): string;
  text(): Promise<string>;
  json(): Promise<any>;
  bytes(): Promise<Uint8Array>;
  arrayBuffer(): Promise<ArrayBuffer>;
  stream(options: S3Options): ReadableStream;
  write(
    data: string | Uint8Array | ArrayBuffer | Blob | ReadableStream | Response | Request,
    options?: BlobPropertyBag,
  ): Promise<number>;

  exists(options?: S3Options): Promise<boolean>;
  unlink(options?: S3Options): Promise<void>;
  delete(options?: S3Options): Promise<void>;
  presign(options?: S3Options): string;

  stat(options?: S3Options): Promise<S3Stat>;
  /**
   * Size is not synchronously available because it requires a network request.
   *
   * @deprecated Use `stat()` instead.
   */
  size: NaN;

  // ... more omitted for brevity
}
```

Like `Bun.file()`, `S3File` extends [`Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Blob), so all the methods available on `Blob` are also available on `S3File`. The same API that reads data from a local file also reads data from S3.

| Method                       | Output           |
| ---------------------------- | ---------------- |
| `await s3File.text()`        | `string`         |
| `await s3File.bytes()`       | `Uint8Array`     |
| `await s3File.json()`        | `JSON`           |
| `s3File.stream()`            | `ReadableStream` |
| `await s3File.arrayBuffer()` | `ArrayBuffer`    |

Because `S3File` extends `Blob`, `S3File` instances work with `fetch()`, `Response`, and other web APIs that accept `Blob` instances.

### Partial reads with `slice`

To read a partial range of a file, use the `slice` method.

```ts s3.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" highlight={1} theme={"theme":{"light":"github-light","dark":"dracula"}}
const partial = s3file.slice(0, 1024);

// Read the partial range as a Uint8Array
const bytes = await partial.bytes();

// Read the partial range as a string
const text = await partial.text();
```

Internally, Bun uses the HTTP `Range` header to request only the bytes you want. This `slice` method is the same as [`Blob.prototype.slice`](https://developer.mozilla.org/en-US/docs/Web/API/Blob/slice).

### Deleting files from S3

To delete a file from S3, use the `delete` method.

```ts s3.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" highlight={1} theme={"theme":{"light":"github-light","dark":"dracula"}}
await s3file.delete();
// await s3File.unlink();
```

`delete` is the same as `unlink`.

## Error codes

When Bun's S3 API throws an error, the error has a `code` property with one of the following values:

* `ERR_S3_MISSING_CREDENTIALS`
* `ERR_S3_INVALID_METHOD`
* `ERR_S3_INVALID_PATH`
* `ERR_S3_INVALID_ENDPOINT`
* `ERR_S3_INVALID_SIGNATURE`
* `ERR_S3_INVALID_SESSION_TOKEN`

When the S3 service itself returns an error (that is, not Bun), the error is an `S3Error` instance: an `Error` instance with the name `"S3Error"`.

## `S3Client` static methods

The `S3Client` class provides several static methods for interacting with S3.

### `S3Client.write` (static)

To write data directly to a path in the bucket, use the `S3Client.write` static method.

```ts s3.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" highlight={12, 15-18, 22, 25-29} theme={"theme":{"light":"github-light","dark":"dracula"}}
import { S3Client } from "bun";

const credentials = {
  accessKeyId: "your-access-key",
  secretAccessKey: "your-secret-key",
  bucket: "my-bucket",
  // endpoint: "https://s3.us-east-1.amazonaws.com",
  // endpoint: "https://<account-id>.r2.cloudflarestorage.com", // Cloudflare R2
};

// Write string
await S3Client.write("my-file.txt", "Hello World", credentials);

// Write JSON with type
await S3Client.write("data.json", JSON.stringify({ hello: "world" }), {
  ...credentials,
  type: "application/json",
});

// Write from fetch
const res = await fetch("https://example.com/data");
await S3Client.write("data.bin", res, credentials);

// Write with ACL
await S3Client.write("public.html", html, {
  ...credentials,
  acl: "public-read",
  type: "text/html",
});
```

This is equivalent to calling `new S3Client(credentials).write("my-file.txt", "Hello World")`.

### `S3Client.presign` (static)

To generate a presigned URL for an S3 file, use the `S3Client.presign` static method.

```ts s3.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" highlight={11-14} theme={"theme":{"light":"github-light","dark":"dracula"}}
import { S3Client } from "bun";

const credentials = {
  accessKeyId: "your-access-key",
  secretAccessKey: "your-secret-key",
  bucket: "my-bucket",
  // endpoint: "https://s3.us-east-1.amazonaws.com",
  // endpoint: "https://<account-id>.r2.cloudflarestorage.com", // Cloudflare R2
};

const url = S3Client.presign("my-file.txt", {
  ...credentials,
  expiresIn: 3600,
});
```

This is equivalent to calling `new S3Client(credentials).presign("my-file.txt", { expiresIn: 3600 })`.

### `S3Client.list` (static)

To list some or all (up to 1,000) objects in a bucket, use the `S3Client.list` static method.

```ts s3.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" highlight={12, 15-20, 24-29} theme={"theme":{"light":"github-light","dark":"dracula"}}
import { S3Client } from "bun";

const credentials = {
  accessKeyId: "your-access-key",
  secretAccessKey: "your-secret-key",
  bucket: "my-bucket",
  // endpoint: "https://s3.us-east-1.amazonaws.com",
  // endpoint: "https://<account-id>.r2.cloudflarestorage.com", // Cloudflare R2
};

// List (up to) 1000 objects in the bucket
const allObjects = await S3Client.list(null, credentials);

// List (up to) 500 objects under `uploads/` prefix, with owner field for each object
const uploads = await S3Client.list(
  {
    prefix: "uploads/",
    maxKeys: 500,
    fetchOwner: true,
  },
  credentials,
);

// Check if more results are available
if (uploads.isTruncated) {
  // List next batch of objects under `uploads/` prefix
  const moreUploads = await S3Client.list(
    {
      prefix: "uploads/",
      maxKeys: 500,
      startAfter: uploads.contents!.at(-1).key,
      fetchOwner: true,
    },
    credentials,
  );
}
```

This is equivalent to calling `new S3Client(credentials).list()`.

### `S3Client.exists` (static)

To check if an S3 file exists, use the `S3Client.exists` static method.

```ts s3.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" highlight={11} theme={"theme":{"light":"github-light","dark":"dracula"}}
import { S3Client } from "bun";

const credentials = {
  accessKeyId: "your-access-key",
  secretAccessKey: "your-secret-key",
  bucket: "my-bucket",
  // endpoint: "https://s3.us-east-1.amazonaws.com",
  // endpoint: "https://<account-id>.r2.cloudflarestorage.com", // Cloudflare R2
};

const exists = await S3Client.exists("my-file.txt", credentials);
```

The same method also works on `S3File` instances.

```ts s3.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" highlight=7} theme={"theme":{"light":"github-light","dark":"dracula"}}
import { s3 } from "bun";

const s3file = s3.file("my-file.txt", {
  // ...credentials,
});

const exists = await s3file.exists();
```

### `S3Client.size` (static)

To check the size of an S3 file without downloading it, use the `S3Client.size` static method.

```ts s3.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" highlight={11} theme={"theme":{"light":"github-light","dark":"dracula"}}
import { S3Client } from "bun";

const credentials = {
  accessKeyId: "your-access-key",
  secretAccessKey: "your-secret-key",
  bucket: "my-bucket",
  // endpoint: "https://s3.us-east-1.amazonaws.com",
  // endpoint: "https://<account-id>.r2.cloudflarestorage.com", // Cloudflare R2
};

const bytes = await S3Client.size("my-file.txt", credentials);
```

This is equivalent to calling `new S3Client(credentials).size("my-file.txt")`.

### `S3Client.stat` (static)

To get the size, etag, and other metadata of an S3 file, use the `S3Client.stat` static method.

```ts s3.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { S3Client } from "bun";

const credentials = {
  accessKeyId: "your-access-key",
  secretAccessKey: "your-secret-key",
  bucket: "my-bucket",
  // endpoint: "https://s3.us-east-1.amazonaws.com",
  // endpoint: "https://<account-id>.r2.cloudflarestorage.com", // Cloudflare R2
};

const stat = await S3Client.stat("my-file.txt", credentials);
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
{
  etag: "\"7a30b741503c0b461cc14157e2df4ad8\"",
  lastModified: 2025-01-07T00:19:10.000Z,
  size: 1024,
  type: "text/plain;charset=utf-8",
}
```

### `S3Client.delete` (static)

To delete an S3 file, use the `S3Client.delete` static method.

```ts s3.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" highlight={10, 15} theme={"theme":{"light":"github-light","dark":"dracula"}}
import { S3Client } from "bun";

const credentials = {
  accessKeyId: "your-access-key",
  secretAccessKey: "your-secret-key",
  bucket: "my-bucket",
  // endpoint: "https://s3.us-east-1.amazonaws.com",
};

await S3Client.delete("my-file.txt", credentials);
// equivalent to
// await new S3Client(credentials).delete("my-file.txt");

// S3Client.unlink is alias of S3Client.delete
await S3Client.unlink("my-file.txt", credentials);
```

## `s3://` protocol

`fetch` and `Bun.file()` support the `s3://` protocol, so the same code works for local files and S3 files.

```ts s3.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
const response = await fetch("s3://my-bucket/my-file.txt");
const file = Bun.file("s3://my-bucket/my-file.txt");
```

You can also pass `s3` options to `fetch` and `Bun.file`.

```ts s3.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" highlight={2-6} theme={"theme":{"light":"github-light","dark":"dracula"}}
const response = await fetch("s3://my-bucket/my-file.txt", {
  s3: {
    accessKeyId: "your-access-key",
    secretAccessKey: "your-secret-key",
    endpoint: "https://s3.us-east-1.amazonaws.com",
  },
  headers: {
    range: "bytes=0-1023",
  },
});
```

### UTF-8, UTF-16, and BOM (byte order mark)

Like `Response` and `Blob`, `S3File` assumes UTF-8 encoding by default.

When calling `text()` or `json()` on an `S3File`:

* When Bun detects a UTF-16 byte order mark (BOM), it treats the data as UTF-16. JavaScriptCore natively supports UTF-16, so Bun skips the UTF-8 transcoding step (and strips the BOM). One consequence: invalid surrogate pairs in your UTF-16 string pass through to JavaScriptCore (same as source code).
* When Bun detects a UTF-8 BOM, it strips the BOM and replaces invalid UTF-8 codepoints with the Unicode replacement character (`\uFFFD`) before passing the string to JavaScriptCore.
* UTF-32 is not supported.


# Secrets
Source: https://bun.com/docs/runtime/secrets

Use Bun's Secrets API to store and retrieve sensitive credentials securely

Store and retrieve sensitive credentials securely using the operating system's native credential storage APIs.

<Warning>This API is new and experimental. It may change in the future.</Warning>

```typescript index.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { secrets } from "bun";

let githubToken: string | null = await secrets.get({
  service: "my-cli-tool",
  name: "github-token",
});

if (!githubToken) {
  githubToken = prompt("Please enter your GitHub token");

  await secrets.set({
    service: "my-cli-tool",
    name: "github-token",
    value: githubToken,
  });

  console.log("GitHub token stored");
}

const response = await fetch("https://api.github.com/user", {
  headers: { Authorization: `token ${githubToken}` },
});

console.log(`Logged in as ${(await response.json()).login}`);
```

***

## Overview

`Bun.secrets` provides a cross-platform API for managing sensitive credentials that CLI tools and development applications typically store in plaintext files like `~/.npmrc`, `~/.aws/credentials`, or `.env`. It uses:

* **macOS**: Keychain Services
* **Linux**: libsecret (GNOME Keyring, KWallet, and other secret service daemons)
* **Windows**: Windows Credential Manager

All operations are asynchronous and non-blocking, running on Bun's threadpool.

<Note>
  This API is mostly useful for local development tools. We may later add a `provider` option for production deployment
  secrets.
</Note>

***

## API

### `Bun.secrets.get(options)`

Retrieve a stored credential.

```typescript theme={"theme":{"light":"github-light","dark":"dracula"}}
import { secrets } from "bun";

const password = await Bun.secrets.get({
  service: "my-app",
  name: "alice@example.com",
});
// Returns: string | null

// Or if you prefer without an object
const password = await Bun.secrets.get("my-app", "alice@example.com");
```

**Parameters:**

* `options.service` (string, required) - The service or application name
* `options.name` (string, required) - The username or account identifier

**Returns:**

* `Promise<string | null>` - The stored password, or `null` if not found

### `Bun.secrets.set(options)`

Store or update a credential.

```typescript theme={"theme":{"light":"github-light","dark":"dracula"}}
import { secrets } from "bun";

await secrets.set({
  service: "my-app",
  name: "alice@example.com",
  value: "super-secret-password",
});
```

**Parameters:**

* `options.service` (string, required) - The service or application name
* `options.name` (string, required) - The username or account identifier
* `options.value` (string, required) - The password or secret to store

**Notes:**

* If a credential already exists for the given service/name combination, Bun replaces it
* The operating system encrypts the stored value

### `Bun.secrets.delete(options)`

Delete a stored credential.

```typescript theme={"theme":{"light":"github-light","dark":"dracula"}}
const deleted = await Bun.secrets.delete({
  service: "my-app",
  name: "alice@example.com",
});
// Returns: boolean
```

**Parameters:**

* `options.service` (string, required) - The service or application name
* `options.name` (string, required) - The username or account identifier

**Returns:**

* `Promise<boolean>` - `true` if a credential was deleted, `false` if not found

***

## Examples

### Storing CLI Tool Credentials

```javascript theme={"theme":{"light":"github-light","dark":"dracula"}}
// Store GitHub CLI token (instead of ~/.config/gh/hosts.yml)
await Bun.secrets.set({
  service: "my-app.com",
  name: "github-token",
  value: "ghp_xxxxxxxxxxxxxxxxxxxx",
});

// Or if you prefer without an object
await Bun.secrets.set("my-app.com", "github-token", "ghp_xxxxxxxxxxxxxxxxxxxx");

// Store npm registry token (instead of ~/.npmrc)
await Bun.secrets.set({
  service: "npm-registry",
  name: "https://registry.npmjs.org",
  value: "npm_xxxxxxxxxxxxxxxxxxxx",
});

// Retrieve for API calls
const token = await Bun.secrets.get({
  service: "my-app.com",
  name: "github-token",
});

if (token) {
  const response = await fetch("https://api.github.com/user", {
    headers: {
      Authorization: `token ${token}`,
    },
  });
}
```

### Migrating from Plaintext Config Files

```javascript theme={"theme":{"light":"github-light","dark":"dracula"}}
// Instead of storing in ~/.aws/credentials
await Bun.secrets.set({
  service: "aws-cli",
  name: "AWS_SECRET_ACCESS_KEY",
  value: process.env.AWS_SECRET_ACCESS_KEY,
});

// Instead of .env files with sensitive data
await Bun.secrets.set({
  service: "my-app",
  name: "api-key",
  value: "sk_live_xxxxxxxxxxxxxxxxxxxx",
});

// Load at runtime
const apiKey =
  (await Bun.secrets.get({
    service: "my-app",
    name: "api-key",
  })) || process.env.API_KEY; // Fallback for CI/production
```

### Error Handling

```javascript theme={"theme":{"light":"github-light","dark":"dracula"}}
try {
  await Bun.secrets.set({
    service: "my-app",
    name: "alice",
    value: "password123",
  });
} catch (error) {
  console.error("Failed to store credential:", error.message);
}

// Check if a credential exists
const password = await Bun.secrets.get({
  service: "my-app",
  name: "alice",
});

if (password === null) {
  console.log("No credential found");
}
```

### Updating Credentials

```javascript theme={"theme":{"light":"github-light","dark":"dracula"}}
// Initial password
await Bun.secrets.set({
  service: "email-server",
  name: "admin@example.com",
  value: "old-password",
});

// Update to new password
await Bun.secrets.set({
  service: "email-server",
  name: "admin@example.com",
  value: "new-password",
});

// The old password is replaced
```

***

## Platform Behavior

### macOS (Keychain)

* Bun stores credentials in the user's login keychain
* The keychain may prompt for access permission on first use
* Credentials persist across system restarts
* Accessible by the user who stored them

### Linux (libsecret)

* Requires a secret service daemon such as GNOME Keyring or KWallet
* Bun stores credentials in the default collection
* May prompt for unlock if the keyring is locked
* The secret service must be running

### Windows (Credential Manager)

* Bun stores credentials in Windows Credential Manager
* Visible in Control Panel → Credential Manager → Windows Credentials
* Persisted with the `CRED_PERSIST_ENTERPRISE` flag, so they're scoped per user
* Encrypted using Windows Data Protection API

## Security Considerations

1. **Encryption**: The operating system's credential manager encrypts credentials
2. **Access Control**: Only the user who stored the credential can retrieve it
3. **No Plain Text**: Passwords are never stored in plain text
4. **Memory Safety**: Bun zeros out password memory after use
5. **Process Isolation**: Credentials are isolated per user account

## Limitations

* Maximum password length varies by platform (typically 2048-4096 bytes)
* Keep `service` and `name` reasonably short (under 256 characters)
* Some special characters may need escaping depending on the platform
* Requires appropriate system services:
  * Linux: Secret service daemon must be running
  * macOS: Keychain Access must be available
  * Windows: Credential Manager service must be enabled

***

## Comparison with Environment Variables

Unlike environment variables, `Bun.secrets`:

* ✅ Encrypts credentials at rest (thanks to the operating system)
* ✅ Avoids exposing secrets in process memory dumps (Bun zeros the memory after it's no longer needed)
* ✅ Survives application restarts
* ✅ Can be updated without restarting the application
* ✅ Provides user-level access control
* ❌ Requires OS credential service
* ❌ Not very useful for deployment secrets (use environment variables in production)

***

## Best Practices

1. **Use descriptive service names**: Match the tool or application name
   If you're building a CLI for external use, use a UTI (Uniform Type Identifier) for the service name.

   ```javascript theme={"theme":{"light":"github-light","dark":"dracula"}}
   // Good - matches the actual tool
   { service: "com.docker.hub", name: "username" }
   { service: "com.vercel.cli", name: "team-name" }

   // Avoid - too generic
   { service: "api", name: "key" }
   ```

2. **Credentials-only**: Don't store application configuration in this API
   This API is slow; keep non-secret settings in a config file.

3. **Use for local development tools**:
   * ✅ CLI tools (gh, npm, docker, kubectl)
   * ✅ Local development servers
   * ✅ Personal API keys for testing
   * ❌ Production servers (use proper secret management)

***

## TypeScript

```typescript theme={"theme":{"light":"github-light","dark":"dracula"}}
namespace Bun {
  interface SecretsOptions {
    service: string;
    name: string;
  }

  interface Secrets {
    get(options: SecretsOptions): Promise<string | null>;
    set(options: SecretsOptions & { value: string }): Promise<void>;
    delete(options: SecretsOptions): Promise<boolean>;
  }

  const secrets: Secrets;
}
```


# Semver
Source: https://bun.com/docs/runtime/semver

Use Bun's semantic versioning API

`Bun.semver` compares semantic versions and checks whether a version is compatible with a range of versions. Versions and ranges are designed to be compatible with `node-semver`, which npm clients use.

`Bun.semver` is about 20x faster than `node-semver`.

<Frame>![Benchmark](https://github.com/oven-sh/bun/assets/709451/94746adc-8aba-4baf-a143-3c355f8e0f78)</Frame>

The API provides two functions:

## `Bun.semver.satisfies(version: string, range: string): boolean`

Returns `true` if `version` satisfies `range`, otherwise `false`.

Example:

```typescript theme={"theme":{"light":"github-light","dark":"dracula"}}
import { semver } from "bun";

semver.satisfies("1.0.0", "^1.0.0"); // true
semver.satisfies("1.0.0", "^1.0.1"); // false
semver.satisfies("1.0.0", "~1.0.0"); // true
semver.satisfies("1.0.0", "~1.0.1"); // false
semver.satisfies("1.0.0", "1.0.0"); // true
semver.satisfies("1.0.0", "1.0.1"); // false
semver.satisfies("1.0.1", "1.0.0"); // false
semver.satisfies("1.0.0", "1.0.x"); // true
semver.satisfies("1.0.0", "1.x.x"); // true
semver.satisfies("1.0.0", "x.x.x"); // true
semver.satisfies("1.0.0", "1.0.0 - 2.0.0"); // true
semver.satisfies("1.0.0", "1.0.0 - 1.0.1"); // true
```

If `range` or `version` is invalid, it returns `false`.

## `Bun.semver.order(versionA: string, versionB: string): 0 | 1 | -1`

Returns `0` if `versionA` and `versionB` are equal, `1` if `versionA` is greater than `versionB`, and `-1` if `versionA` is less than `versionB`.

Example:

```typescript theme={"theme":{"light":"github-light","dark":"dracula"}}
import { semver } from "bun";

semver.order("1.0.0", "1.0.0"); // 0
semver.order("1.0.0", "1.0.1"); // -1
semver.order("1.0.1", "1.0.0"); // 1

const unsorted = ["1.0.0", "1.0.1", "1.0.0-alpha", "1.0.0-beta", "1.0.0-rc"];
unsorted.sort(semver.order); // ["1.0.0-alpha", "1.0.0-beta", "1.0.0-rc", "1.0.0", "1.0.1"]
console.log(unsorted);
```

If you need other semver functions, open an issue or pull request.


# Shell
Source: https://bun.com/docs/runtime/shell

Use Bun's shell scripting API to run shell commands from JavaScript

Bun Shell makes shell scripting with JavaScript & TypeScript fun. It's a cross-platform bash-like shell with JavaScript interop.

Quickstart:

```ts index.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { $ } from "bun";

const response = await fetch("https://example.com");

// Use Response as stdin.
await $`cat < ${response} | wc -c`; // 1256
```

***

## Features

* **Cross-platform**: works on Windows, Linux & macOS. Instead of installing `rimraf` or `cross-env`, you can use Bun Shell. It implements common shell commands like `ls`, `cd`, and `rm` natively.
* **Familiar**: Bun Shell is a bash-like shell that supports redirection, pipes, and environment variables.
* **Globs**: Bun Shell supports glob patterns natively, including `**`, `*`, and `{expansion}`.
* **Template literals**: Template literals execute shell commands and interpolate variables and expressions.
* **Safety**: Bun Shell escapes all strings by default, preventing shell injection attacks.
* **JavaScript interop**: Use `Response`, `ArrayBuffer`, `Blob`, `Bun.file(path)` and other JavaScript objects as stdin, stdout, and stderr.
* **Shell scripting**: Bun Shell runs shell scripts (`.sh` files).
* **Custom interpreter**: Bun Shell is a small programming language with its own lexer, parser, and interpreter, written in Rust.

***

## Getting started

Start with `echo`. To run it, use the `$` template literal tag:

```js theme={"theme":{"light":"github-light","dark":"dracula"}}
import { $ } from "bun";

await $`echo "Hello World!"`; // Hello World!
```

By default, shell commands print to stdout. To quiet the output, call `.quiet()`:

```js theme={"theme":{"light":"github-light","dark":"dracula"}}
import { $ } from "bun";

await $`echo "Hello World!"`.quiet(); // No output
```

To read the output of the command as text, use `.text()`:

```js theme={"theme":{"light":"github-light","dark":"dracula"}}
import { $ } from "bun";

// .text() automatically calls .quiet() for you
const welcome = await $`echo "Hello World!"`.text();

console.log(welcome); // Hello World!\n
```

By default, `await`ing returns stdout and stderr as `Buffer`s.

```js theme={"theme":{"light":"github-light","dark":"dracula"}}
import { $ } from "bun";

const { stdout, stderr } = await $`echo "Hello!"`.quiet();

console.log(stdout); // Buffer(7) [ 72, 101, 108, 108, 111, 33, 10 ]
console.log(stderr); // Buffer(0) []
```

***

## Error handling

By default, a non-zero exit code throws an error. The `ShellError` contains information about the command that ran.

```js theme={"theme":{"light":"github-light","dark":"dracula"}}
import { $ } from "bun";

try {
  const output = await $`something-that-may-fail`.text();
  console.log(output);
} catch (err) {
  console.log(`Failed with code ${err.exitCode}`);
  console.log(err.stdout.toString());
  console.log(err.stderr.toString());
}
```

`.nothrow()` disables throwing. Check the result's `exitCode` yourself.

```js theme={"theme":{"light":"github-light","dark":"dracula"}}
import { $ } from "bun";

const { stdout, stderr, exitCode } = await $`something-that-may-fail`.nothrow().quiet();

if (exitCode !== 0) {
  console.log(`Non-zero exit code ${exitCode}`);
}

console.log(stdout);
console.log(stderr);
```

To change the default for all commands, call `.nothrow()` or `.throws(boolean)` on the `$` function itself.

```js theme={"theme":{"light":"github-light","dark":"dracula"}}
import { $ } from "bun";
// shell promises will not throw, meaning you will have to
// check for `exitCode` manually on every shell command.
$.nothrow(); // equivalent to $.throws(false)

// default behavior, non-zero exit codes will throw an error
$.throws(true);

// alias for $.nothrow()
$.throws(false);

await $`something-that-may-fail`; // No exception thrown
```

***

## Redirection

Redirect a command's *input* or *output* with the typical Bash operators:

* `<` redirect stdin
* `>` or `1>` redirect stdout
* `2>` redirect stderr
* `&>` redirect both stdout and stderr
* `>>` or `1>>` redirect stdout, *appending* to the destination, instead of overwriting
* `2>>` redirect stderr, *appending* to the destination, instead of overwriting
* `&>>` redirect both stdout and stderr, *appending* to the destination, instead of overwriting
* `1>&2` redirect stdout to stderr (writes to stdout go to stderr instead)
* `2>&1` redirect stderr to stdout (writes to stderr go to stdout instead)

Bun Shell also supports redirecting from and to JavaScript objects.

### Example: Redirect output to JavaScript objects (`>`)

To redirect stdout to a JavaScript object, use the `>` operator:

```js theme={"theme":{"light":"github-light","dark":"dracula"}}
import { $ } from "bun";

const buffer = Buffer.alloc(100);
await $`echo "Hello World!" > ${buffer}`;

console.log(buffer.toString()); // Hello World!\n
```

You can redirect output to these JavaScript objects:

* `Buffer`, `Uint8Array`, `Uint16Array`, `Uint32Array`, `Int8Array`, `Int16Array`, `Int32Array`, `Float32Array`, `Float64Array`, `ArrayBuffer`, `SharedArrayBuffer` (writes to the underlying buffer)
* `Bun.file(path)`, `Bun.file(fd)` (writes to the file)

### Example: Redirect input from JavaScript objects (`<`)

To use a JavaScript object as stdin, use the `<` operator:

```js theme={"theme":{"light":"github-light","dark":"dracula"}}
import { $ } from "bun";

const response = new Response("hello i am a response body");

const result = await $`cat < ${response}`.text();

console.log(result); // hello i am a response body
```

You can redirect from these JavaScript objects:

* `Buffer`, `Uint8Array`, `Uint16Array`, `Uint32Array`, `Int8Array`, `Int16Array`, `Int32Array`, `Float32Array`, `Float64Array`, `ArrayBuffer`, `SharedArrayBuffer` (reads from the underlying buffer)
* `Bun.file(path)`, `Bun.file(fd)` (reads from the file)
* `Response` (reads from the body)

### Example: Redirect stdin -> file

```js theme={"theme":{"light":"github-light","dark":"dracula"}}
import { $ } from "bun";

await $`cat < myfile.txt`;
```

### Example: Redirect stdout -> file

```js theme={"theme":{"light":"github-light","dark":"dracula"}}
import { $ } from "bun";

await $`echo bun! > greeting.txt`;
```

### Example: Redirect stderr -> file

```js theme={"theme":{"light":"github-light","dark":"dracula"}}
import { $ } from "bun";

await $`bun run index.ts 2> errors.txt`;
```

### Example: Redirect stderr -> stdout

```js theme={"theme":{"light":"github-light","dark":"dracula"}}
import { $ } from "bun";

// redirects stderr to stdout, so all output
// will be available on stdout
await $`bun run ./index.ts 2>&1`;
```

### Example: Redirect stdout -> stderr

```js theme={"theme":{"light":"github-light","dark":"dracula"}}
import { $ } from "bun";

// redirects stdout to stderr, so all output
// will be available on stderr
await $`bun run ./index.ts 1>&2`;
```

## Piping (`|`)

Like in bash, you can pipe the output of one command to another:

```js theme={"theme":{"light":"github-light","dark":"dracula"}}
import { $ } from "bun";

const result = await $`echo "Hello World!" | wc -w`.text();

console.log(result); // 2\n
```

You can also pipe with JavaScript objects:

```js theme={"theme":{"light":"github-light","dark":"dracula"}}
import { $ } from "bun";

const response = new Response("hello i am a response body");

const result = await $`cat < ${response} | wc -w`.text();

console.log(result); // 6\n
```

## Command substitution (`$(...)`)

Command substitution inserts the output of another command into the current script:

```js theme={"theme":{"light":"github-light","dark":"dracula"}}
import { $ } from "bun";

// Prints out the hash of the current commit
await $`echo Hash of current commit: $(git rev-parse HEAD)`;
```

Bun Shell inserts the output as text, so you can use it to declare a shell variable:

```js theme={"theme":{"light":"github-light","dark":"dracula"}}
import { $ } from "bun";

await $`
  REV=$(git rev-parse HEAD)
  docker build -t myapp:$REV .
  echo Done building docker image "myapp:$REV"
`;
```

<Note>
  Because Bun internally uses the special [`raw`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#raw_strings) property on the input template literal, using the backtick syntax for command substitution doesn't work:

  ```ts icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
  import { $ } from "bun";

  await $`echo \`echo hi\``;
  ```

  Instead of printing:

  ```
  hi
  ```

  It prints:

  ```
  `echo hi`
  ```

  Use the `$(...)` syntax instead.
</Note>

***

## Environment variables

Set environment variables like in bash:

```js theme={"theme":{"light":"github-light","dark":"dracula"}}
import { $ } from "bun";

await $`FOO=foo bun -e 'console.log(process.env.FOO)'`; // foo\n
```

Use string interpolation to set the value:

```js theme={"theme":{"light":"github-light","dark":"dracula"}}
import { $ } from "bun";

const foo = "bar123";

await $`FOO=${foo + "456"} bun -e 'console.log(process.env.FOO)'`; // bar123456\n
```

Bun Shell escapes input by default, preventing shell injection attacks:

```js theme={"theme":{"light":"github-light","dark":"dracula"}}
import { $ } from "bun";

const foo = "bar123; rm -rf /tmp";

await $`FOO=${foo} bun -e 'console.log(process.env.FOO)'`; // bar123; rm -rf /tmp\n
```

### Changing the environment variables

By default, all commands use `process.env` as their environment variables.

To change the environment variables for a single command, call `.env()`:

```js theme={"theme":{"light":"github-light","dark":"dracula"}}
import { $ } from "bun";

await $`echo $FOO`.env({ ...process.env, FOO: "bar" }); // bar
```

To change the default environment variables for all commands, call `$.env`:

```js theme={"theme":{"light":"github-light","dark":"dracula"}}
import { $ } from "bun";

$.env({ FOO: "bar" });

// the globally-set $FOO
await $`echo $FOO`; // bar

// the locally-set $FOO
await $`echo $FOO`.env({ FOO: "baz" }); // baz
```

To reset the environment variables to the default, call `$.env()` with no arguments:

```js theme={"theme":{"light":"github-light","dark":"dracula"}}
import { $ } from "bun";

$.env({ FOO: "bar" });

// the globally-set $FOO
await $`echo $FOO`; // bar

$.env();

// the default $FOO
await $`echo $FOO`; // ""
```

### Changing the working directory

To change the working directory of a command, pass a string to `.cwd()`:

```js theme={"theme":{"light":"github-light","dark":"dracula"}}
import { $ } from "bun";

await $`pwd`.cwd("/tmp"); // /tmp
```

To change the default working directory for all commands, call `$.cwd`:

```js theme={"theme":{"light":"github-light","dark":"dracula"}}
import { $ } from "bun";

$.cwd("/tmp");

// the globally-set working directory
await $`pwd`; // /tmp

// the locally-set working directory
await $`pwd`.cwd("/"); // /
```

***

## Reading output

To read the output of a command as a string, use `.text()`:

```js theme={"theme":{"light":"github-light","dark":"dracula"}}
import { $ } from "bun";

const result = await $`echo "Hello World!"`.text();

console.log(result); // Hello World!\n
```

### Reading output as JSON

To read the output of a command as JSON, use `.json()`:

```js theme={"theme":{"light":"github-light","dark":"dracula"}}
import { $ } from "bun";

const result = await $`echo '{"foo": "bar"}'`.json();

console.log(result); // { foo: "bar" }
```

### Reading output line-by-line

To read the output of a command line-by-line, use `.lines()`:

```js theme={"theme":{"light":"github-light","dark":"dracula"}}
import { $ } from "bun";

for await (let line of $`echo "Hello World!"`.lines()) {
  console.log(line); // Hello World!
}
```

You can also use `.lines()` on a piped command:

```js theme={"theme":{"light":"github-light","dark":"dracula"}}
import { $ } from "bun";

const search = "bun";

for await (let line of $`cat list.txt | grep ${search}`.lines()) {
  console.log(line);
}
```

### Reading output as a Blob

To read the output of a command as a Blob, use `.blob()`:

```js theme={"theme":{"light":"github-light","dark":"dracula"}}
import { $ } from "bun";

const result = await $`echo "Hello World!"`.blob();

console.log(result); // Blob(13) { size: 13, type: "text/plain" }
```

***

## Builtin Commands

For cross-platform compatibility, Bun Shell implements a set of builtin commands, in addition to reading commands from the `PATH` environment variable.

* `cd`: change the working directory
* `ls`: list files in a directory (supports `-l` for long listing format)
* `rm`: remove files and directories
* `echo`: print text
* `pwd`: print the working directory
* `bun`: run bun in bun
* `cat`
* `touch`
* `mkdir`
* `which`
* `mv`
* `exit`
* `true`
* `false`
* `yes`
* `seq`
* `dirname`
* `basename`

**Not** implemented yet, but planned:

* See [Issue #9716](https://github.com/oven-sh/bun/issues/9716) for the full list.

***

## Utilities

Bun Shell also implements a set of utilities for working with shells.

### `$.braces` (brace expansion)

`$.braces` implements [brace expansion](https://www.gnu.org/software/bash/manual/html_node/Brace-Expansion.html) for shell commands:

```js theme={"theme":{"light":"github-light","dark":"dracula"}}
import { $ } from "bun";

await $.braces(`echo {1,2,3}`);
// => ["echo 1", "echo 2", "echo 3"]
```

### `$.escape` (escape strings)

Exposes Bun Shell's escaping logic as a function:

```js theme={"theme":{"light":"github-light","dark":"dracula"}}
import { $ } from "bun";

console.log($.escape('$(foo) `bar` "baz"'));
// => "\$(foo) \`bar\` \"baz\""
```

To skip escaping, wrap the string in a `{ raw: 'str' }` object:

```js theme={"theme":{"light":"github-light","dark":"dracula"}}
import { $ } from "bun";

await $`echo ${{ raw: '$(foo) `bar` "baz"' }}`;
// => bun: command not found: foo
// => bun: command not found: bar
// => baz
```

***

## `.sh` file loader

For simple shell scripts, you can use Bun Shell instead of `/bin/sh`. Pass a file with the `.sh` extension to `bun`:

```sh script.sh icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
echo "Hello World! pwd=$(pwd)"
```

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun ./script.sh
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
Hello World! pwd=/home/demo
```

Bun Shell scripts are cross-platform, so they work on Windows:

```powershell powershell icon="windows" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun .\script.sh
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
Hello World! pwd=C:\Users\Demo
```

***

## Implementation notes

Bun Shell is a small programming language implemented in Rust, with a handwritten lexer, parser, and interpreter. Unlike bash, zsh, and other shells, Bun Shell runs operations concurrently.

***

## Security in the Bun shell

By design, Bun Shell *does not invoke a system shell* like `/bin/sh`. It's a
re-implementation of bash that runs in the same Bun process.

When parsing command arguments, Bun Shell treats all *interpolated variables* as single, literal strings.

This protects against **command injection**:

```js theme={"theme":{"light":"github-light","dark":"dracula"}}
import { $ } from "bun";

const userInput = "my-file.txt; rm -rf /";

// SAFE: `userInput` is treated as a single quoted string
await $`ls ${userInput}`;
```

Here, Bun Shell treats `userInput` as a single string, so `ls` tries to read the
contents of a single directory named `my-file.txt; rm -rf /`.

### Security considerations

While Bun Shell prevents command injection by default, you are still
responsible for security in certain scenarios.

Similar to the `Bun.spawn` or `node:child_process.exec()` APIs, you can intentionally
execute a command which spawns a new shell (for example, `bash -c`) with arguments.

When you do this, you hand off control, and Bun's built-in protections no
longer apply to the string interpreted by that new shell.

```js theme={"theme":{"light":"github-light","dark":"dracula"}}
import { $ } from "bun";

const userInput = "world; touch /tmp/pwned";

// UNSAFE: You have explicitly started a new shell process with `bash -c`.
// This new shell will execute the `touch` command. Any user input
// passed this way must be rigorously sanitized.
await $`bash -c "echo ${userInput}"`;
```

### Argument injection

Bun Shell cannot know how an external command interprets its own
command-line arguments. An attacker can supply input that the target program
recognizes as one of its own options or flags, leading to unintended behavior.

```js theme={"theme":{"light":"github-light","dark":"dracula"}}
import { $ } from "bun";

// Malicious input formatted as a Git command-line flag
const branch = "--upload-pack=echo pwned";

// UNSAFE: While Bun safely passes the string as a single argument,
// the `git` program itself sees and acts upon the malicious flag.
await $`git ls-remote origin ${branch}`;
```

<Note>
  **Recommendation**: Always sanitize user-provided input before passing it as an argument to an external command.
  Validating arguments is your application's responsibility.
</Note>

***

## Credits

Large parts of this API were inspired by [zx](https://github.com/google/zx), [dax](https://github.com/dsherret/dax), and [bnx](https://github.com/wobsoriano/bnx). Thank you to the authors of those projects.


# SQL
Source: https://bun.com/docs/runtime/sql

Bun provides native bindings for working with SQL databases through a unified Promise-based API that supports PostgreSQL, MySQL, and SQLite.

You write queries as tagged template literals, and the client supports connection pooling, transactions, and prepared statements.

```ts title="db.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { sql, SQL } from "bun";

// PostgreSQL (default)
const users = await sql`
  SELECT * FROM users
  WHERE active = ${true}
  LIMIT ${10}
`;

// With MySQL
const mysql = new SQL("mysql://user:pass@localhost:3306/mydb");
const mysqlResults = await mysql`
  SELECT * FROM users 
  WHERE active = ${true}
`;

// With SQLite
const sqlite = new SQL("sqlite://myapp.db");
const sqliteResults = await sqlite`
  SELECT * FROM users 
  WHERE active = ${1}
`;
```

### Features

* Tagged template literals to protect against SQL injection
* Transactions
* Named & positional parameters
* Connection pooling
* `BigInt` support
* SASL (SCRAM-SHA-256), MD5, and Clear Text authentication
* Connection timeouts
* Returning rows as data objects, arrays of arrays, or Buffer
* Binary protocol support makes it faster
* TLS support (and auth mode)
* Automatic configuration with environment variables
* PostgreSQL `LISTEN` / `NOTIFY`

***

## Database Support

`Bun.SQL` provides a unified API for multiple database systems:

### PostgreSQL

Bun uses PostgreSQL when:

* The connection string doesn't match SQLite or MySQL patterns (it's the fallback adapter)
* The connection string explicitly uses `postgres://` or `postgresql://` protocols
* No connection string is provided and environment variables point to PostgreSQL

```ts title="db.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { sql } from "bun";
// Uses PostgreSQL if DATABASE_URL is not set or is a PostgreSQL URL
await sql`SELECT ...`;

import { SQL } from "bun";
const pg = new SQL("postgres://user:pass@localhost:5432/mydb");
await pg`SELECT ...`;
```

### MySQL

MySQL support is built into `Bun.SQL`, with the same tagged template literal interface, and is compatible with MySQL 5.7+ and MySQL 8.0+:

```ts title="db.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { SQL } from "bun";

// MySQL connection
const mysql = new SQL("mysql://user:password@localhost:3306/database");
const mysql2 = new SQL("mysql2://user:password@localhost:3306/database"); // mysql2 protocol also works

// Using options object
const mysql3 = new SQL({
  adapter: "mysql",
  hostname: "localhost",
  port: 3306,
  database: "myapp",
  username: "dbuser",
  password: "secretpass",
});

// Works with parameters - automatically uses prepared statements
const users = await mysql`SELECT * FROM users WHERE id = ${userId}`;

// Transactions work the same as PostgreSQL
await mysql.begin(async tx => {
  await tx`INSERT INTO users (name) VALUES (${"Alice"})`;
  await tx`UPDATE accounts SET balance = balance - 100 WHERE user_id = ${userId}`;
});

// Bulk inserts
const newUsers = [
  { name: "Alice", email: "alice@example.com" },
  { name: "Bob", email: "bob@example.com" },
];
await mysql`INSERT INTO users ${mysql(newUsers)}`;
```

<Accordion title="MySQL Connection String Formats">
  MySQL accepts various URL formats for connection strings:

  ```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
  // Standard mysql:// protocol
  new SQL("mysql://user:pass@localhost:3306/database");
  new SQL("mysql://user:pass@localhost/database"); // Default port 3306

  // mysql2:// protocol (compatibility with mysql2 npm package)
  new SQL("mysql2://user:pass@localhost:3306/database");

  // With query parameters
  new SQL("mysql://user:pass@localhost/db?ssl=true");

  // Unix socket connection
  new SQL("mysql://user:pass@/database?socket=/var/run/mysqld/mysqld.sock");
  ```
</Accordion>

<Accordion title="MySQL-Specific Features">
  MySQL databases support:

  * **Prepared statements**: Automatically created for parameterized queries with statement caching
  * **Binary protocol**: For better performance with prepared statements and accurate type handling
  * **Multiple result sets**: Support for stored procedures returning multiple result sets
  * **Authentication plugins**: Support for mysql\_native\_password, caching\_sha2\_password (MySQL 8.0 default), and sha256\_password
  * **SSL/TLS connections**: Configurable SSL modes similar to PostgreSQL
  * **Connection attributes**: Client information sent to server for monitoring
  * **Query pipelining**: Execute multiple prepared statements without waiting for responses
</Accordion>

<Accordion title="MySQL 8 authentication over plain TCP (allowPublicKeyRetrieval)">
  MySQL 8 accounts default to the `caching_sha2_password` plugin. The first connection for a user requires full authentication: over an unencrypted connection the client has to download the server's RSA public key to encrypt the password. Bun refuses that by default (the same default as mysql2 and Connector/J), because an attacker on the network path could substitute their own key and recover the password. The connection fails with `ERR_MYSQL_PUBLIC_KEY_RETRIEVAL_NOT_ALLOWED`.

  Connect with TLS, or opt in explicitly if you trust the network (for example a local development database):

  ```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
  const mysql = new SQL({
    adapter: "mysql",
    hostname: "localhost",
    username: "root",
    password: "secret",
    database: "myapp",
    allowPublicKeyRetrieval: true, // permit RSA public key retrieval without TLS
  });
  ```
</Accordion>

### SQLite

SQLite support is built into `Bun.SQL`, with the same tagged template literal interface:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
import { SQL } from "bun";

// In-memory database
const memory = new SQL(":memory:");
const memory2 = new SQL("sqlite://:memory:");

// File-based database
const sql1 = new SQL("sqlite://myapp.db");

// Using options object
const sql2 = new SQL({
  adapter: "sqlite",
  filename: "./data/app.db",
});

// For simple filenames, specify adapter explicitly
const sql3 = new SQL("myapp.db", { adapter: "sqlite" });
```

<Accordion title="SQLite Connection String Formats">
  SQLite accepts various URL formats for connection strings:

  ```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
  // Standard sqlite:// protocol
  new SQL("sqlite://path/to/database.db");
  new SQL("sqlite:path/to/database.db"); // Without slashes

  // file:// protocol (also recognized as SQLite)
  new SQL("file://path/to/database.db");
  new SQL("file:path/to/database.db");

  // Special :memory: database
  new SQL(":memory:");
  new SQL("sqlite://:memory:");
  new SQL("file://:memory:");

  // Relative and absolute paths
  new SQL("sqlite://./local.db"); // Relative to current directory
  new SQL("sqlite://../parent/db.db"); // Parent directory
  new SQL("sqlite:///absolute/path.db"); // Absolute path

  // With query parameters
  new SQL("sqlite://data.db?mode=ro"); // Read-only mode
  new SQL("sqlite://data.db?mode=rw"); // Read-write mode (no create)
  new SQL("sqlite://data.db?mode=rwc"); // Read-write-create mode (default)
  ```

  <Note>
    Simple filenames without a protocol (like `"myapp.db"`) require explicitly specifying `{ adapter: "sqlite" }` to avoid ambiguity with PostgreSQL.
  </Note>
</Accordion>

<Accordion title="SQLite-Specific Options">
  SQLite databases support additional configuration options:

  ```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
  const sql = new SQL({
    adapter: "sqlite",
    filename: "app.db",

    // SQLite-specific options
    readonly: false, // Open in read-only mode
    create: true, // Create database if it doesn't exist
    readwrite: true, // Open for reading and writing

    // Additional Bun:sqlite options
    strict: true, // Enable strict mode
    safeIntegers: false, // Use JavaScript numbers for integers
  });
  ```

  Bun parses query parameters in the URL to set these options:

  * `?mode=ro` → `readonly: true`
  * `?mode=rw` → `readonly: false, create: false`
  * `?mode=rwc` → `readonly: false, create: true` (default)
</Accordion>

## Inserting data

Pass JavaScript values directly to the SQL template literal; Bun handles the escaping.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
import { sql } from "bun";

// Basic insert with direct values
const [user] = await sql`
  INSERT INTO users (name, email) 
  VALUES (${name}, ${email})
  RETURNING *
`;

// Using object helper for cleaner syntax
const userData = {
  name: "Alice",
  email: "alice@example.com",
};

const [newUser] = await sql`
  INSERT INTO users ${sql(userData)}
  RETURNING *
`;
// Expands to: INSERT INTO users (name, email) VALUES ('Alice', 'alice@example.com')
```

### Bulk Insert

You can also pass an array of objects, which Bun expands into an `INSERT INTO ... VALUES ...` statement.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const users = [
  { name: "Alice", email: "alice@example.com" },
  { name: "Bob", email: "bob@example.com" },
  { name: "Charlie", email: "charlie@example.com" },
];

await sql`INSERT INTO users ${sql(users)}`;
```

### Picking columns to insert

Use `sql(object, ...string)` to pick which columns to insert. Each column must be defined on the object.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const user = {
  name: "Alice",
  email: "alice@example.com",
  age: 25,
};

await sql`INSERT INTO users ${sql(user, "name", "email")}`;
// Only inserts name and email columns, ignoring other fields
```

***

## Query Results

By default, Bun's SQL client returns query results as arrays of objects, where each object represents a row with column names as keys. Two other formats are available.

### `sql``.values()` format

The `sql``.values()` method returns each row as an array of values, in the same order as the columns in your query.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const rows = await sql`SELECT * FROM users`.values();
console.log(rows);
```

The rows look like:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
[
  ["Alice", "alice@example.com"],
  ["Bob", "bob@example.com"],
];
```

`sql``.values()` is useful when a query returns duplicate column names. With objects (the default), the last column wins because the column name is the key. With `sql``.values()`, every column is present in the array, so you can read duplicates by index.

### `sql``.raw()` format

The `.raw()` method returns rows as arrays of `Buffer` objects. Use it for binary data or for performance.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const rows = await sql`SELECT * FROM users`.raw();
console.log(rows); // [[Buffer, Buffer], [Buffer, Buffer], [Buffer, Buffer]]
```

***

## SQL Fragments

Bun can build queries dynamically from runtime conditions without risking SQL injection.

### Dynamic Table Names

To reference tables or schemas dynamically, use the `sql()` helper, which escapes them:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
// Safely reference tables dynamically
await sql`SELECT * FROM ${sql("users")}`;

// With schema qualification
await sql`SELECT * FROM ${sql("public.users")}`;
```

### Conditional Queries

Use the `sql()` helper to build queries with conditional clauses:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
// Optional WHERE clauses
const filterAge = true;
const minAge = 21;
const ageFilter = sql`AND age > ${minAge}`;
await sql`
  SELECT * FROM users
  WHERE active = ${true}
  ${filterAge ? ageFilter : sql``}
`;
```

### Dynamic columns in updates

Use `sql(object, ...string)` to pick which columns to update. Each column must be defined on the object. If you don't list any columns, Bun uses all keys on the object.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
await sql`UPDATE users SET ${sql(user, "name", "email")} WHERE id = ${user.id}`;
// uses all keys from the object to update the row
await sql`UPDATE users SET ${sql(user)} WHERE id = ${user.id}`;
```

### Dynamic values and `where in`

Value lists can also be created dynamically, for `WHERE IN` queries. You can also pass an array of objects and name the key to build the list from.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
await sql`SELECT * FROM users WHERE id IN ${sql([1, 2, 3])}`;

const users = [
  { id: 1, name: "Alice" },
  { id: 2, name: "Bob" },
  { id: 3, name: "Charlie" },
];
await sql`SELECT * FROM users WHERE id IN ${sql(users, "id")}`;
```

### `sql.array` helper

The `sql.array` helper creates PostgreSQL array literals from JavaScript arrays:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
// Create array literals for PostgreSQL
await sql`INSERT INTO tags (items) VALUES (${sql.array(["red", "blue", "green"])})`;
// Generates: INSERT INTO tags (items) VALUES (ARRAY['red', 'blue', 'green'])

// Works with numeric arrays too
await sql`SELECT * FROM products WHERE ids = ANY(${sql.array([1, 2, 3])})`;
// Generates: SELECT * FROM products WHERE ids = ANY(ARRAY[1, 2, 3])
```

<Note>`sql.array` is PostgreSQL-only. Multi-dimensional arrays and NULL elements may not be supported yet.</Note>

***

## `sql``.simple()`

The PostgreSQL wire protocol supports two types of queries: "simple" and "extended". Simple queries can contain multiple statements but don't support parameters, while extended queries (the default) support parameters but only allow one statement.

To run multiple statements in a single query, use `sql``.simple()`:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
// Multiple statements in one query
await sql`
  SELECT 1;
  SELECT 2;
`.simple();
```

Simple queries are useful for database migrations and setup scripts.

Simple queries cannot use parameters (`${value}`). If you need parameters, split your query into separate statements.

### Queries in files

`sql.file` reads a query from a file and executes it. If the file uses placeholders like `$1` and `$2`, you can pass parameters to the query. Without parameters, the file can contain multiple commands.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const result = await sql.file("query.sql", [1, 2, 3]);
```

With the SQLite adapter, parameters can also be an object of named parameters (`:name`, `$name`, or `@name` placeholders), the same form `sql.unsafe` accepts below.

### Unsafe Queries

`sql.unsafe` executes raw SQL strings. Use it with caution: it does not escape user input. Without parameters, the string can contain more than one command.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
// Multiple commands without parameters
const result = await sql.unsafe(`
  SELECT ${userColumns} FROM users;
  SELECT ${accountColumns} FROM accounts;
`);

// Using parameters (only one command is allowed)
const result = await sql.unsafe("SELECT " + dangerous + " FROM users WHERE id = $1", [id]);
```

With the SQLite adapter, parameters can also be an object of named parameters using `:name`, `$name`, or `@name` placeholders. Object keys keep the prefix (`{ ":id": 1 }`) unless the connection sets `strict: true`, which allows bare keys:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const sql = new SQL({ adapter: "sqlite", filename: "myapp.db", strict: true });
const result = await sql.unsafe("SELECT * FROM users WHERE id = :id", { id: 1 });
```

### Execute and Cancelling Queries

Queries are lazy: they only start executing when awaited or run with `.execute()`.
To cancel a running query, call `cancel()` on the query object.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const query = sql`SELECT * FROM users`.execute();
setTimeout(() => query.cancel(), 100);
await query;
```

***

## Database Environment Variables

You can configure `sql` connection parameters with environment variables. The client checks them in order of precedence and detects the database type from the connection string format.

### Automatic Database Detection

When you use `Bun.sql()` without arguments, or `new SQL()` with a connection string, Bun detects the adapter from the URL format:

#### MySQL Auto-Detection

Bun selects MySQL when the connection string matches these patterns:

* `mysql://...` - MySQL protocol URLs
* `mysql2://...` - MySQL2 protocol URLs (compatibility alias)

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
// These all use MySQL automatically (no adapter needed)
const sql1 = new SQL("mysql://user:pass@localhost/mydb");
const sql2 = new SQL("mysql2://user:pass@localhost:3306/mydb");

// Works with DATABASE_URL environment variable
DATABASE_URL="mysql://user:pass@localhost/mydb" bun run app.js
DATABASE_URL="mysql2://user:pass@localhost:3306/mydb" bun run app.js
```

#### SQLite Auto-Detection

Bun selects SQLite when the connection string matches these patterns:

* `:memory:` - In-memory database
* `sqlite://...` - SQLite protocol URLs
* `sqlite:...` - SQLite protocol without slashes
* `file://...` - File protocol URLs
* `file:...` - File protocol without slashes

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
// These all use SQLite automatically (no adapter needed)
const sql1 = new SQL(":memory:");
const sql2 = new SQL("sqlite://app.db");
const sql3 = new SQL("file://./database.db");

// Works with DATABASE_URL environment variable
DATABASE_URL=":memory:" bun run app.js
DATABASE_URL="sqlite://myapp.db" bun run app.js
DATABASE_URL="file://./data/app.db" bun run app.js
```

#### PostgreSQL Auto-Detection

PostgreSQL is the default for connection strings that don't match MySQL or SQLite patterns:

```bash theme={"theme":{"light":"github-light","dark":"dracula"}}
# PostgreSQL is detected for these patterns
DATABASE_URL="postgres://user:pass@localhost:5432/mydb" bun run app.js
DATABASE_URL="postgresql://user:pass@localhost:5432/mydb" bun run app.js

# Or any URL that doesn't match MySQL or SQLite patterns
DATABASE_URL="localhost:5432/mydb" bun run app.js
```

### MySQL Environment Variables

You can configure MySQL connections with environment variables:

```bash theme={"theme":{"light":"github-light","dark":"dracula"}}
# Primary connection URL (checked first)
MYSQL_URL="mysql://user:pass@localhost:3306/mydb"

# Alternative: DATABASE_URL with MySQL protocol
DATABASE_URL="mysql://user:pass@localhost:3306/mydb"
DATABASE_URL="mysql2://user:pass@localhost:3306/mydb"
```

Without a connection URL, Bun checks these individual parameters:

| Environment Variable     | Default Value | Description                      |
| ------------------------ | ------------- | -------------------------------- |
| `MYSQL_HOST`             | `localhost`   | Database host                    |
| `MYSQL_PORT`             | `3306`        | Database port                    |
| `MYSQL_USER`             | `root`        | Database user                    |
| `MYSQL_PASSWORD`         | (empty)       | Database password                |
| `MYSQL_DATABASE`         | `mysql`       | Database name                    |
| `MYSQL_URL`              | (empty)       | Primary connection URL for MySQL |
| `TLS_MYSQL_DATABASE_URL` | (empty)       | SSL/TLS-enabled connection URL   |

### PostgreSQL Environment Variables

These environment variables define the PostgreSQL connection:

| Environment Variable        | Description                                |
| --------------------------- | ------------------------------------------ |
| `POSTGRES_URL`              | Primary connection URL for PostgreSQL      |
| `DATABASE_URL`              | Alternative connection URL (auto-detected) |
| `PGURL`                     | Alternative connection URL                 |
| `PG_URL`                    | Alternative connection URL                 |
| `TLS_POSTGRES_DATABASE_URL` | SSL/TLS-enabled connection URL             |
| `TLS_DATABASE_URL`          | Alternative SSL/TLS-enabled connection URL |

Without a connection URL, Bun checks these individual parameters:

| Environment Variable | Fallback Variables           | Default Value | Description                                                                    |
| -------------------- | ---------------------------- | ------------- | ------------------------------------------------------------------------------ |
| `PGHOST`             | -                            | `localhost`   | Database host                                                                  |
| `PGPORT`             | -                            | `5432`        | Database port                                                                  |
| `PGUSERNAME`         | `PGUSER`, `USER`, `USERNAME` | `postgres`    | Database user                                                                  |
| `PGPASSWORD`         | -                            | (empty)       | Database password                                                              |
| `PGDATABASE`         | -                            | username      | Database name                                                                  |
| `PGSSLMODE`          | -                            | `disable`     | SSL mode (`disable`, `allow`, `prefer`, `require`, `verify-ca`, `verify-full`) |

### SQLite Environment Variables

You can configure SQLite connections with `DATABASE_URL` when it contains a SQLite-compatible URL:

```bash theme={"theme":{"light":"github-light","dark":"dracula"}}
# These are all recognized as SQLite
DATABASE_URL=":memory:"
DATABASE_URL="sqlite://./app.db"
DATABASE_URL="file:///absolute/path/to/db.sqlite"
```

**Note:** Bun ignores PostgreSQL-specific environment variables such as `POSTGRES_URL` and `PGHOST` when you use SQLite.

***

## Runtime Preconnection

Bun can preconnect to PostgreSQL at startup, before your application code runs, so the first query doesn't pay the connection latency.

```bash theme={"theme":{"light":"github-light","dark":"dracula"}}
# Enable PostgreSQL preconnection
bun --sql-preconnect index.js

# Works with DATABASE_URL environment variable
DATABASE_URL=postgres://user:pass@localhost:5432/db bun --sql-preconnect index.js

# Can be combined with other runtime flags
bun --sql-preconnect --hot index.js
```

The `--sql-preconnect` flag establishes a PostgreSQL connection at startup using your configured environment variables. If the connection fails, Bun handles the error without crashing your application.

***

## Connection Options

You can configure the connection manually by passing options to the `SQL` constructor. Options vary by adapter:

### MySQL Options

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
import { SQL } from "bun";

const sql = new SQL({
  // Required for MySQL when using options object
  adapter: "mysql",

  // Connection details
  hostname: "localhost",
  port: 3306,
  database: "myapp",
  username: "dbuser",
  password: "secretpass",

  // Unix socket connection (alternative to hostname/port)
  // socket: "/var/run/mysqld/mysqld.sock",

  // Connection pool settings
  max: 20, // Maximum connections in pool (default: 10)
  idleTimeout: 30, // Close idle connections after 30s
  maxLifetime: 0, // Connection lifetime in seconds (0 = forever)
  connectionTimeout: 30, // Timeout when establishing new connections

  // SSL/TLS options
  ssl: "prefer", // or "disable", "require", "verify-ca", "verify-full"
  // tls: {
  //   rejectUnauthorized: true,
  //   ca: "path/to/ca.pem",
  //   key: "path/to/key.pem",
  //   cert: "path/to/cert.pem",
  // },

  // Callbacks
  onconnect: client => {
    console.log("Connected to MySQL");
  },
  onclose: (client, err) => {
    if (err) {
      console.error("MySQL connection error:", err);
    } else {
      console.log("MySQL connection closed");
    }
  },
});
```

### PostgreSQL Options

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
import { SQL } from "bun";

const sql = new SQL({
  // Connection details (adapter is auto-detected as PostgreSQL)
  url: "postgres://user:pass@localhost:5432/dbname",

  // Alternative connection parameters
  hostname: "localhost",
  port: 5432,
  database: "myapp",
  username: "dbuser",
  password: "secretpass",

  // Connection pool settings
  max: 20, // Maximum connections in pool
  idleTimeout: 30, // Close idle connections after 30s
  maxLifetime: 0, // Connection lifetime in seconds (0 = forever)
  connectionTimeout: 30, // Timeout when establishing new connections

  // SSL/TLS options
  tls: true,
  // tls: {
  //   rejectUnauthorized: true,
  //   requestCert: true,
  //   ca: "path/to/ca.pem",
  //   key: "path/to/key.pem",
  //   cert: "path/to/cert.pem",
  //   checkServerIdentity(hostname, cert) {
  //     ...
  //   },
  // },

  // Callbacks
  onconnect: client => {
    console.log("Connected to PostgreSQL");
  },
  onclose: client => {
    console.log("PostgreSQL connection closed");
  },
});
```

### SQLite Options

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
import { SQL } from "bun";

const sql = new SQL({
  // Required for SQLite
  adapter: "sqlite",
  filename: "./data/app.db", // or ":memory:" for in-memory database

  // SQLite-specific access modes
  readonly: false, // Open in read-only mode
  create: true, // Create database if it doesn't exist
  readwrite: true, // Allow read and write operations

  // SQLite data handling
  strict: true, // Enable strict mode for better type safety
  safeIntegers: false, // Use BigInt for integers exceeding JS number range

  // Callbacks
  onconnect: client => {
    console.log("SQLite database opened");
  },
  onclose: client => {
    console.log("SQLite database closed");
  },
});
```

<Accordion title="SQLite Connection Notes">
  * **Connection Pooling**: SQLite doesn't use connection pooling as it's a file-based database. Each `SQL` instance represents a single connection.
  * **Transactions**: SQLite supports nested transactions through savepoints, similar to PostgreSQL.
  * **Concurrent Access**: SQLite handles concurrent access through file locking. Use WAL mode for better concurrency.
  * **Memory Databases**: Using `:memory:` creates a temporary database that exists only for the connection lifetime.
</Accordion>

***

## Dynamic passwords

For alternative authentication schemes such as access tokens, or databases with rotating passwords, set `password` to a synchronous or asynchronous function. Bun calls it at connection time to resolve the password.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
import { SQL } from "bun";

const sql = new SQL(url, {
  // Other connection config
  ...
  // Password function for the database user
  password: async () => await signer.getAuthToken(),
});
```

***

## SQLite-Specific Features

### Query Execution

SQLite executes queries synchronously, unlike PostgreSQL, which uses asynchronous I/O. The API still returns Promises:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const sqlite = new SQL("sqlite://app.db");

// Works the same as PostgreSQL, but executes synchronously under the hood
const users = await sqlite`SELECT * FROM users`;

// Parameters work identically
const user = await sqlite`SELECT * FROM users WHERE id = ${userId}`;
```

### SQLite Pragmas

Use `PRAGMA` statements to configure SQLite behavior:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const sqlite = new SQL("sqlite://app.db");

// Enable foreign keys
await sqlite`PRAGMA foreign_keys = ON`;

// Set journal mode to WAL for better concurrency
await sqlite`PRAGMA journal_mode = WAL`;

// Check integrity
const integrity = await sqlite`PRAGMA integrity_check`;
```

### Data Type Differences

SQLite has a more flexible type system than PostgreSQL:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
// SQLite stores data in 5 storage classes: NULL, INTEGER, REAL, TEXT, BLOB
const sqlite = new SQL("sqlite://app.db");

// SQLite is more lenient with types
await sqlite`
  CREATE TABLE flexible (
    id INTEGER PRIMARY KEY,
    data TEXT,        -- Can store numbers as strings
    value NUMERIC,    -- Can store integers, reals, or text
    blob BLOB         -- Binary data
  )
`;

// JavaScript values are automatically converted
await sqlite`INSERT INTO flexible VALUES (${1}, ${"text"}, ${123.45}, ${Buffer.from("binary")})`;
```

***

## Transactions

To start a new transaction, use `sql.begin`. This method works for both PostgreSQL and SQLite. For PostgreSQL, it reserves a dedicated connection from the pool. For SQLite, it begins a transaction on the single connection.

Bun sends the `BEGIN` command automatically, including any optional configurations you specify. If an error occurs during the transaction, Bun issues a `ROLLBACK`.

### Basic Transactions

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
await sql.begin(async tx => {
  // All queries in this function run in a transaction
  await tx`INSERT INTO users (name) VALUES (${"Alice"})`;
  await tx`UPDATE accounts SET balance = balance - 100 WHERE user_id = 1`;

  // Transaction automatically commits if no errors are thrown
  // Rolls back if any error occurs
});
```

To pipeline the queries in a transaction, return an array of queries from the callback:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
await sql.begin(async tx => {
  return [
    tx`INSERT INTO users (name) VALUES (${"Alice"})`,
    tx`UPDATE accounts SET balance = balance - 100 WHERE user_id = 1`,
  ];
});
```

### Savepoints

Savepoints create intermediate checkpoints within a transaction, so part of it can roll back without aborting the whole thing.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
await sql.begin(async tx => {
  await tx`INSERT INTO users (name) VALUES (${"Alice"})`;

  await tx.savepoint(async sp => {
    // This part can be rolled back separately
    await sp`UPDATE users SET status = 'active'`;
    if (someCondition) {
      throw new Error("Rollback to savepoint");
    }
  });

  // Continue with transaction even if savepoint rolled back
  await tx`INSERT INTO audit_log (action) VALUES ('user_created')`;
});
```

### Distributed Transactions

Two-Phase Commit (2PC) is a distributed transaction protocol. In phase 1, the coordinator prepares each node, making sure its data is written and ready to commit. In phase 2, the nodes commit or roll back based on the coordinator's decision.

In PostgreSQL and MySQL, distributed transactions persist beyond their original session, so privileged users or coordinators can commit or roll them back later. PostgreSQL implements them as prepared transactions; MySQL uses XA Transactions.

An uncaught exception during the distributed transaction rolls back all changes. Otherwise, you can commit or roll back the transaction later.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
// Begin a distributed transaction
await sql.beginDistributed("tx1", async tx => {
  await tx`INSERT INTO users (name) VALUES (${"Alice"})`;
});

// Later, commit or rollback
await sql.commitDistributed("tx1");
// or
await sql.rollbackDistributed("tx1");
```

***

## Authentication

Bun supports SCRAM-SHA-256 (SASL), MD5, and Clear Text authentication. We recommend SASL for better security. See [Postgres SASL Authentication](https://www.postgresql.org/docs/current/sasl-authentication.html).

### SSL Modes Overview

PostgreSQL's SSL/TLS modes control whether a secure connection is required and how much certificate verification is performed.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const sql = new SQL({
  hostname: "localhost",
  username: "user",
  password: "password",
  ssl: "disable", // | "prefer" | "require" | "verify-ca" | "verify-full"
});
```

| SSL Mode      | Description                                                                                                          |
| ------------- | -------------------------------------------------------------------------------------------------------------------- |
| `disable`     | No SSL/TLS used. Connections fail if server requires SSL. Default mode if none specified.                            |
| `prefer`      | Tries SSL first, falls back to non-SSL if SSL fails.                                                                 |
| `require`     | Requires SSL without certificate verification. Fails if SSL cannot be established.                                   |
| `verify-ca`   | Verifies server certificate is signed by trusted CA. Fails if verification fails.                                    |
| `verify-full` | Most secure mode. Verifies certificate and hostname match. Protects against untrusted certificates and MITM attacks. |

### Using With Connection Strings

You can also set the SSL mode in the connection string:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
// Using prefer mode
const sql = new SQL("postgres://user:password@localhost/mydb?sslmode=prefer");

// Using verify-full mode
const sql = new SQL("postgres://user:password@localhost/mydb?sslmode=verify-full");
```

***

## Connection Pooling

Bun's SQL client manages a connection pool. The pool reuses database connections across queries instead of opening and closing one for each query, and it caps the number of concurrent connections.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const sql = new SQL({
  // Pool configuration
  max: 20, // Maximum 20 concurrent connections
  idleTimeout: 30, // Close idle connections after 30s
  maxLifetime: 3600, // Max connection lifetime 1 hour
  connectionTimeout: 10, // Connection timeout 10s
});
```

Bun doesn't open a connection until you run a query.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const sql = Bun.SQL(); // no connection are created

await sql`...`; // pool is started until max is reached (if possible), first available connection is used
await sql`...`; // previous connection is reused

// two connections are used now at the same time
await Promise.all([
  sql`INSERT INTO users ${sql({ name: "Alice" })}`,
  sql`UPDATE users SET name = ${user.name} WHERE id = ${user.id}`,
]);

await sql.close(); // await all queries to finish and close all connections from the pool
await sql.close({ timeout: 5 }); // wait 5 seconds and close all connections from the pool
await sql.close({ timeout: 0 }); // close all connections from the pool immediately
```

***

## Reserved Connections

`sql.reserve()` takes a connection from the pool and returns a client that wraps it, so you can run queries on an isolated connection.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
// Get exclusive connection from pool
const reserved = await sql.reserve();

try {
  await reserved`INSERT INTO users (name) VALUES (${"Alice"})`;
} finally {
  // Important: Release connection back to pool
  reserved.release();
}

// Or using Symbol.dispose
{
  using reserved = await sql.reserve();
  await reserved`SELECT 1`;
} // Automatically released
```

***

## LISTEN / NOTIFY (PostgreSQL)

`sql.listen()` subscribes to a PostgreSQL notification channel and `sql.notify()` publishes to one. Processes sharing a database can use this as a lightweight message bus: cache invalidation, waking workers when rows are inserted (typically from a trigger calling `pg_notify`), fan-out of small events.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
import { sql } from "bun";

const subscription = await sql.listen("orders", payload => {
  console.log("new order", JSON.parse(payload));
});

await sql.notify("orders", JSON.stringify({ id: 42 }));

await subscription.unlisten();
```

`listen()` resolves once PostgreSQL has acknowledged the `LISTEN`, so a `notify()` issued after it resolves is delivered. The subscription it resolves to is also an async disposable:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
{
  await using subscription = await sql.listen("orders", handleOrder);
  await doWork();
} // unlistened here
```

### How it works

* All subscriptions on a client share one dedicated connection. The first `listen()` opens it, and removing the last subscription closes it. As a result, a client that never listens never pays for the connection, and unlistening everything lets the process exit without `sql.close()`.
* While anything is subscribed, that connection keeps the process alive, like a listening server.
* If the connection drops, Bun re-establishes it with exponential backoff (250ms doubling to 32s, with jitter) and re-subscribes every channel. PostgreSQL only delivers to connected listeners, so notifications sent in between are lost. The optional third argument to `listen()` runs on the initial subscribe and after every reconnect, which is the place to catch up:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
await sql.listen("orders", handleOrder, async () => {
  for (const order of await sql`SELECT * FROM orders WHERE processed = false`) handleOrder(order);
});
```

* Every `listen()` call is its own subscription. Several on one channel share a single server-side `LISTEN`. Each callback receives every notification, and each handle's `unlisten()` removes only what its own call registered. A callback (either argument) that throws is reported as an uncaught exception and stays subscribed.
* Bun quotes channel names as identifiers for you. Like any PostgreSQL identifier, they are limited to 63 bytes, and Bun rejects longer names rather than silently truncating them. PostgreSQL limits payloads to 8000 bytes by default.

### `notify()`

`notify()` is an ordinary query (`SELECT pg_notify($1, $2)`) on whichever handle you call it through. On `sql` it uses the pool. Inside `sql.begin()` it runs in the transaction, so PostgreSQL delivers it on `COMMIT` and drops it on `ROLLBACK`. Calling `notify()` inside a transaction is how to announce a change only once it is visible:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
await sql.begin(async tx => {
  const [order] = await tx`INSERT INTO orders ${sql(data)} RETURNING id`;
  await tx.notify("orders", String(order.id)); // delivered when the transaction commits
});
```

The payload is optional: `sql.notify("cache-invalidated")` is PostgreSQL's bare `NOTIFY`. Reserved and transaction handles also have `listen()`; it always uses the client's shared listen connection.

`LISTEN`/`NOTIFY` is PostgreSQL-only; on MySQL and SQLite these methods reject.

***

## Prepared Statements

By default, Bun's SQL client creates named prepared statements for queries it can infer are static, which is faster. To disable named prepared statements, set `prepare: false` in the connection options:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const sql = new SQL({
  // ... other options ...
  prepare: false, // Disable persisting named prepared statements on the server
});
```

When `prepare: false` is set:

Queries still use the "extended" protocol, but run as [unnamed prepared statements](https://www.postgresql.org/docs/current/protocol-flow.html#PROTOCOL-FLOW-EXT-QUERY). An unnamed prepared statement lasts only until the next Parse statement specifying the unnamed statement as destination is issued.

* Parameter binding is still safe against SQL injection
* The server parses and plans each query from scratch
* Queries are not [pipelined](https://www.postgresql.org/docs/current/protocol-flow.html#PROTOCOL-FLOW-PIPELINING)
* Only one command per query is supported (unless you use `sql``.simple()`)

You might want to use `prepare: false` when:

* Using PGBouncer in transaction mode (though since PGBouncer 1.21.0, protocol-level named prepared statements are supported when configured properly)
* Debugging query execution plans
* Working with dynamic SQL where query plans need to be regenerated frequently

Disabling prepared statements can slow down queries that run frequently with different parameters, since the server parses and plans each one from scratch.

***

## Error Handling

The client provides typed errors for different failure scenarios. Errors are database-specific and extend a base error class:

### Error Classes

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
import { SQL } from "bun";

try {
  await sql`SELECT * FROM users`;
} catch (error) {
  if (error instanceof SQL.PostgresError) {
    // PostgreSQL-specific error
    console.log(error.code); // PostgreSQL error code
    console.log(error.detail); // Detailed error message
    console.log(error.hint); // Helpful hint from PostgreSQL
  } else if (error instanceof SQL.SQLiteError) {
    // SQLite-specific error
    console.log(error.code); // SQLite error code (e.g., "SQLITE_CONSTRAINT")
    console.log(error.errno); // SQLite error number
    console.log(error.byteOffset); // Byte offset in SQL statement (if available)
  } else if (error instanceof SQL.SQLError) {
    // Generic SQL error (base class)
    console.log(error.message);
  }
}
```

<Accordion title="PostgreSQL-Specific Error Codes">
  ### PostgreSQL Connection Errors

  | Connection Errors                 | Description                                                                                                                                                                                                                                                                            |
  | --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
  | `ERR_POSTGRES_CONNECTION_CLOSED`  | An established connection was terminated                                                                                                                                                                                                                                               |
  | `ERR_POSTGRES_CONNECTION_FAILED`  | Connection was accepted but closed before the handshake completed (e.g. the server is still starting up). Retried with backoff until `connectionTimeout` while queries are waiting. Note: errors the server sends during startup, like `57P03`, surface as `ERR_POSTGRES_SERVER_ERROR` |
  | `ERR_POSTGRES_CONNECTION_REFUSED` | Connection was refused because nothing is listening at the address. Fails immediately and is not retried                                                                                                                                                                               |
  | `ERR_POSTGRES_CONNECTION_TIMEOUT` | Failed to establish connection within timeout period                                                                                                                                                                                                                                   |
  | `ERR_POSTGRES_IDLE_TIMEOUT`       | Connection closed due to inactivity                                                                                                                                                                                                                                                    |
  | `ERR_POSTGRES_LIFETIME_TIMEOUT`   | Connection exceeded maximum lifetime                                                                                                                                                                                                                                                   |
  | `ERR_POSTGRES_TLS_NOT_AVAILABLE`  | SSL/TLS connection not available                                                                                                                                                                                                                                                       |
  | `ERR_POSTGRES_TLS_UPGRADE_FAILED` | Failed to upgrade connection to SSL/TLS                                                                                                                                                                                                                                                |

  ### Authentication Errors

  | Authentication Errors                            | Description                              |
  | ------------------------------------------------ | ---------------------------------------- |
  | `ERR_POSTGRES_AUTHENTICATION_FAILED_PBKDF2`      | Password authentication failed           |
  | `ERR_POSTGRES_UNKNOWN_AUTHENTICATION_METHOD`     | Server requested unknown auth method     |
  | `ERR_POSTGRES_UNSUPPORTED_AUTHENTICATION_METHOD` | Server requested unsupported auth method |
  | `ERR_POSTGRES_INVALID_SERVER_KEY`                | Invalid server key during authentication |
  | `ERR_POSTGRES_INVALID_SERVER_SIGNATURE`          | Invalid server signature                 |
  | `ERR_POSTGRES_SASL_SIGNATURE_INVALID_BASE64`     | Invalid SASL signature encoding          |
  | `ERR_POSTGRES_SASL_SIGNATURE_MISMATCH`           | SASL signature verification failed       |

  ### Query Errors

  | Query Errors                         | Description                                |
  | ------------------------------------ | ------------------------------------------ |
  | `ERR_POSTGRES_SYNTAX_ERROR`          | Invalid SQL syntax (extends `SyntaxError`) |
  | `ERR_POSTGRES_SERVER_ERROR`          | General error from PostgreSQL server       |
  | `ERR_POSTGRES_INVALID_QUERY_BINDING` | Invalid parameter binding                  |
  | `ERR_POSTGRES_QUERY_CANCELLED`       | Query was cancelled                        |
  | `ERR_POSTGRES_NOT_TAGGED_CALL`       | Query was called without a tagged call     |

  ### Data Type Errors

  | Data Type Errors                                        | Description                           |
  | ------------------------------------------------------- | ------------------------------------- |
  | `ERR_POSTGRES_INVALID_BINARY_DATA`                      | Invalid binary data format            |
  | `ERR_POSTGRES_INVALID_BYTE_SEQUENCE`                    | Invalid byte sequence                 |
  | `ERR_POSTGRES_INVALID_BYTE_SEQUENCE_FOR_ENCODING`       | Encoding error                        |
  | `ERR_POSTGRES_INVALID_CHARACTER`                        | Invalid character in data             |
  | `ERR_POSTGRES_OVERFLOW`                                 | Numeric overflow                      |
  | `ERR_POSTGRES_UNSUPPORTED_BYTEA_FORMAT`                 | Unsupported binary format             |
  | `ERR_POSTGRES_UNSUPPORTED_INTEGER_SIZE`                 | Integer size not supported            |
  | `ERR_POSTGRES_MULTIDIMENSIONAL_ARRAY_NOT_SUPPORTED_YET` | Multidimensional arrays not supported |
  | `ERR_POSTGRES_NULLS_IN_ARRAY_NOT_SUPPORTED_YET`         | NULL values in arrays not supported   |

  ### Protocol Errors

  | Protocol Errors                         | Description                 |
  | --------------------------------------- | --------------------------- |
  | `ERR_POSTGRES_EXPECTED_REQUEST`         | Expected client request     |
  | `ERR_POSTGRES_EXPECTED_STATEMENT`       | Expected prepared statement |
  | `ERR_POSTGRES_INVALID_BACKEND_KEY_DATA` | Invalid backend key data    |
  | `ERR_POSTGRES_INVALID_MESSAGE`          | Invalid protocol message    |
  | `ERR_POSTGRES_INVALID_MESSAGE_LENGTH`   | Invalid message length      |
  | `ERR_POSTGRES_UNEXPECTED_MESSAGE`       | Unexpected message type     |

  ### Transaction Errors

  | Transaction Errors                       | Description                           |
  | ---------------------------------------- | ------------------------------------- |
  | `ERR_POSTGRES_UNSAFE_TRANSACTION`        | Unsafe transaction operation detected |
  | `ERR_POSTGRES_INVALID_TRANSACTION_STATE` | Invalid transaction state             |
</Accordion>

### SQLite-Specific Errors

SQLite errors carry SQLite's standard error codes and numbers:

<Accordion title="Common SQLite Error Codes">
  | Error Code          | errno | Description                                          |
  | ------------------- | ----- | ---------------------------------------------------- |
  | `SQLITE_CONSTRAINT` | 19    | Constraint violation (UNIQUE, CHECK, NOT NULL, etc.) |
  | `SQLITE_BUSY`       | 5     | Database is locked                                   |
  | `SQLITE_LOCKED`     | 6     | Table in the database is locked                      |
  | `SQLITE_READONLY`   | 8     | Attempt to write to a readonly database              |
  | `SQLITE_IOERR`      | 10    | Disk I/O error                                       |
  | `SQLITE_CORRUPT`    | 11    | Database disk image is malformed                     |
  | `SQLITE_FULL`       | 13    | Database or disk is full                             |
  | `SQLITE_CANTOPEN`   | 14    | Unable to open database file                         |
  | `SQLITE_PROTOCOL`   | 15    | Database lock protocol error                         |
  | `SQLITE_SCHEMA`     | 17    | Database schema has changed                          |
  | `SQLITE_TOOBIG`     | 18    | String or BLOB exceeds size limit                    |
  | `SQLITE_MISMATCH`   | 20    | Data type mismatch                                   |
  | `SQLITE_MISUSE`     | 21    | Library used incorrectly                             |
  | `SQLITE_AUTH`       | 23    | Authorization denied                                 |

  Example error handling:

  ```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
  const sqlite = new SQL("sqlite://app.db");

  try {
    await sqlite`INSERT INTO users (id, name) VALUES (1, 'Alice')`;
    await sqlite`INSERT INTO users (id, name) VALUES (1, 'Bob')`; // Duplicate ID
  } catch (error) {
    if (error instanceof SQL.SQLiteError) {
      if (error.code === "SQLITE_CONSTRAINT") {
        console.log("Constraint violation:", error.message);
        // Handle unique constraint violation
      }
    }
  }
  ```
</Accordion>

***

## Numbers and BigInt

Bun returns PostgreSQL `bigint` (`int8`) values, and MySQL `BIGINT` values outside the 32-bit range, as strings:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
import { sql } from "bun";

const [{ x, y }] = await sql`SELECT 9223372036854777 as x, 12345 as y`;

console.log(typeof x, x); // "string" "9223372036854777"
console.log(typeof y, y); // "number" 12345
```

***

## BigInt Instead of Strings

To get large numbers as `BigInt` instead of strings, set the `bigint` option to `true` when creating the SQL client:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const sql = new SQL({
  bigint: true,
});

const [{ x }] = await sql`SELECT 9223372036854777 as x`;

console.log(typeof x, x); // "bigint" 9223372036854777n
```

***

## Roadmap

Things we haven't finished yet:

* Column name transforms (for example, `snake_case` to `camelCase`). This is mostly blocked on a unicode-aware implementation of changing the case in C++ using WebKit's `WTF::String`.
* Column type transforms

***

## Database-Specific Features

#### Authentication Methods

MySQL supports multiple authentication plugins, which the client negotiates automatically:

* **`mysql_native_password`** - Traditional MySQL authentication, widely compatible
* **`caching_sha2_password`** - Default in MySQL 8.0+, more secure with RSA key exchange
* **`sha256_password`** - SHA-256 based authentication

The client automatically handles authentication plugin switching when the server requests it.

#### Prepared Statements & Performance

MySQL uses server-side prepared statements for all parameterized queries:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
// This automatically creates a prepared statement on the server
const user = await mysql`SELECT * FROM users WHERE id = ${userId}`;

// Prepared statements are cached and reused for identical queries
for (const id of userIds) {
  // Same prepared statement is reused
  await mysql`SELECT * FROM users WHERE id = ${id}`;
}

// Query pipelining - multiple statements sent without waiting
const [users, orders, products] = await Promise.all([
  mysql`SELECT * FROM users WHERE active = ${true}`,
  mysql`SELECT * FROM orders WHERE status = ${"pending"}`,
  mysql`SELECT * FROM products WHERE in_stock = ${true}`,
]);
```

#### Multiple Result Sets

MySQL can return multiple result sets from multi-statement queries:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const mysql = new SQL("mysql://user:pass@localhost/mydb");

// Multi-statement queries with simple() method
const multiResults = await mysql`
  SELECT * FROM users WHERE id = 1;
  SELECT * FROM orders WHERE user_id = 1;
`.simple();
```

#### Character Sets & Collations

`Bun.SQL` uses the `utf8mb4` character set for MySQL connections, which covers all of Unicode, including emoji.

#### Connection Attributes

Bun sends client information to MySQL for monitoring:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
// These attributes are sent automatically:
// _client_name: "Bun"
// _client_version: <bun version>
// You can see these in MySQL's performance_schema.session_connect_attrs
```

#### Type Handling

Bun converts MySQL types to JavaScript types:

| MySQL Type                              | JavaScript Type          | Notes                                                                                            |
| --------------------------------------- | ------------------------ | ------------------------------------------------------------------------------------------------ |
| INT, TINYINT, MEDIUMINT                 | number                   | Within safe integer range                                                                        |
| BIGINT                                  | string, number or BigInt | number if the value fits in i32/u32, otherwise string or BigInt depending on the `bigint` option |
| DECIMAL, NUMERIC                        | string                   | To preserve precision                                                                            |
| FLOAT, DOUBLE                           | number                   |                                                                                                  |
| DATE                                    | Date                     | JavaScript Date object                                                                           |
| DATETIME, TIMESTAMP                     | Date                     | Decoded as UTC (see note below); `0000-00-00` becomes an Invalid Date                            |
| TIME                                    | number                   | Total of microseconds                                                                            |
| YEAR                                    | number                   |                                                                                                  |
| CHAR, VARCHAR, VARSTRING, STRING        | string                   |                                                                                                  |
| TINY TEXT, MEDIUM TEXT, TEXT, LONG TEXT | string                   |                                                                                                  |
| TINY BLOB, MEDIUM BLOB, BLOB, LONG BLOB | Buffer                   | Same wire types as TEXT; Bun returns a Buffer when the column uses the binary character set      |
| JSON                                    | object/array             | Automatically parsed                                                                             |
| BIT(1)                                  | boolean                  | BIT(1) in MySQL                                                                                  |
| GEOMETRY                                | string                   | Geometry data                                                                                    |

`DATETIME` and `TIMESTAMP` values have no timezone on the wire, so Bun reads them back as **UTC**. The `Date` you get has the same UTC wall-clock that was stored, regardless of the machine's timezone. Reading as UTC matches how Bun writes values (a bound `Date` stores its UTC components). The same applies to PostgreSQL's `timestamp` (without time zone); `timestamptz` carries an explicit offset and is unaffected.

#### Differences from PostgreSQL

The API is unified, but behavior differs:

1. **Parameter placeholders**: MySQL uses `?` internally but Bun converts `$1, $2` style automatically
2. **RETURNING clause**: MySQL doesn't support RETURNING; use `result.lastInsertRowid` or a separate SELECT
3. **Array types**: MySQL doesn't have native array types like PostgreSQL

### MySQL-Specific Features

We haven't implemented `LOAD DATA INFILE` support yet.

### PostgreSQL-Specific Features

We haven't implemented these yet:

* `COPY` support

We also haven't implemented some of the more uncommon features like:

* GSSAPI authentication
* `SCRAM-SHA-256-PLUS` support
* Point & PostGIS types
* All the multi-dimensional integer array types (only a couple of the types are supported)

***

## Common Patterns & Best Practices

### Working with MySQL Result Sets

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
// Getting insert ID after INSERT
const result = await mysql`INSERT INTO users (name) VALUES (${"Alice"})`;
console.log(result.lastInsertRowid); // MySQL's LAST_INSERT_ID()

// Handling affected rows
const updated = await mysql`UPDATE users SET active = ${false} WHERE age < ${18}`;
console.log(updated.affectedRows); // Number of rows updated

// Using MySQL-specific functions
const now = await mysql`SELECT NOW() as current_time`;
const uuid = await mysql`SELECT UUID() as id`;
```

### MySQL Error Handling

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
try {
  await mysql`INSERT INTO users (email) VALUES (${"duplicate@email.com"})`;
} catch (error) {
  if (error.code === "ER_DUP_ENTRY") {
    console.log("Duplicate entry detected");
  } else if (error.code === "ER_ACCESS_DENIED_ERROR") {
    console.log("Access denied");
  } else if (error.code === "ER_BAD_DB_ERROR") {
    console.log("Database does not exist");
  }
  // MySQL error codes are compatible with mysql/mysql2 packages
}
```

### Performance Tips for MySQL

1. **Use connection pooling**: Set appropriate `max` pool size based on your workload
2. **Enable prepared statements**: They're enabled by default and improve performance
3. **Use transactions for bulk operations**: Group related queries in transactions
4. **Index properly**: MySQL relies heavily on indexes for query performance
5. **Use `utf8mb4` charset**: It's set by default and handles all Unicode characters

***

## Frequently Asked Questions

<AccordionGroup>
  <Accordion title="Why is this `Bun.sql` and not `Bun.postgres`?">
    The plan was to add more database drivers. The unified API now supports PostgreSQL, MySQL, and SQLite.
  </Accordion>

  <Accordion title="How do I know which database adapter is being used?">
    Bun detects the adapter automatically from the connection string:

    * URLs starting with `mysql://` or `mysql2://` use MySQL
    * URLs matching SQLite patterns (`:memory:`, `sqlite://`, `file://`) use SQLite
    * Everything else defaults to PostgreSQL
  </Accordion>

  <Accordion title="Are MySQL stored procedures supported?">
    Yes, stored procedures are supported, including OUT parameters and multiple result sets:

    ```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
    // Call stored procedure
    const results = await mysql`CALL GetUserStats(${userId}, @total_orders)`;

    // Get OUT parameter
    const outParam = await mysql`SELECT @total_orders as total`;
    ```
  </Accordion>

  <Accordion title="Can I use MySQL-specific SQL syntax?">
    Yes, you can use any MySQL-specific syntax:

    ```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
    // MySQL-specific syntax works fine
    await mysql`SET @user_id = ${userId}`;
    await mysql`SHOW TABLES`;
    await mysql`DESCRIBE users`;
    await mysql`EXPLAIN SELECT * FROM users WHERE id = ${id}`;
    ```
  </Accordion>
</AccordionGroup>

***

## Why not just use an existing library?

You can use npm packages like postgres.js, pg, and node-postgres in Bun too. They're great options.

Two reasons why:

1. We think it's simpler for developers to have a database driver built into Bun. The time you spend library shopping is time you could be building your app.
2. We use some JavaScriptCore engine internals to create objects faster, in ways that would be difficult to implement in a library.

## Credits

Huge thanks to [@porsager](https://github.com/porsager)'s [postgres.js](https://github.com/porsager/postgres) for the inspiration for the API interface.


# SQLite
Source: https://bun.com/docs/runtime/sqlite

Bun natively implements a high-performance SQLite3 driver.

Bun natively implements a high-performance [SQLite3](https://www.sqlite.org/) driver. To use it, import from the built-in `bun:sqlite` module.

```ts db.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { Database } from "bun:sqlite";

const db = new Database(":memory:");
const query = db.query("select 'Hello world' as message;");
query.get();
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
{ message: "Hello world" }
```

The API is synchronous and fast. Credit to [better-sqlite3](https://github.com/JoshuaWise/better-sqlite3) and its contributors for inspiring the API of `bun:sqlite`.

Features include:

* Transactions
* Parameters (named & positional)
* Prepared statements
* Datatype conversions (`BLOB` becomes `Uint8Array`)
* Map query results to classes without an ORM - `query.as(MyClass)`
* The fastest performance of any SQLite driver for JavaScript
* `bigint` support
* Multi-query statements (for example `SELECT 1; SELECT 2;`) in a single call to `database.run(query)`

The `bun:sqlite` module is roughly 3-6x faster than `better-sqlite3` and 8-9x faster than `deno.land/x/sqlite` for read queries. Each driver was benchmarked against the [Northwind Traders](https://github.com/jpwhite3/northwind-SQLite3/blob/46d5f8a64f396f87cd374d1600dbf521523980e8/Northwind_large.sqlite.zip) dataset. View and run the [benchmark source](https://github.com/oven-sh/bun/tree/main/bench/sqlite).

<Frame>
  ![SQLite benchmarks for Bun, better-sqlite3, and
  deno.land/x/sqlite](https://user-images.githubusercontent.com/709451/168459263-8cd51ca3-a924-41e9-908d-cf3478a3b7f3.png)
</Frame>

***

## Database

To open or create a SQLite3 database:

```ts db.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { Database } from "bun:sqlite";

const db = new Database("mydb.sqlite");
```

To open an in-memory database:

```ts db.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { Database } from "bun:sqlite";

// all of these do the same thing
const db = new Database(":memory:");
const db = new Database();
const db = new Database("");
```

To open in `readonly` mode:

```ts db.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" highlight={2} theme={"theme":{"light":"github-light","dark":"dracula"}}
import { Database } from "bun:sqlite";
const db = new Database("mydb.sqlite", { readonly: true });
```

To create the database if the file doesn't exist:

```ts db.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" highlight={2} theme={"theme":{"light":"github-light","dark":"dracula"}}
import { Database } from "bun:sqlite";
const db = new Database("mydb.sqlite", { create: true });
```

### Strict mode

By default, `bun:sqlite` requires binding parameters to include the `$`, `:`, or `@` prefix, and does not throw an error if a parameter is missing.

To instead throw an error when a parameter is missing and allow binding without a prefix, set `strict: true` on the `Database` constructor:

```ts db.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" highlight={3} theme={"theme":{"light":"github-light","dark":"dracula"}}
import { Database } from "bun:sqlite";

const strict = new Database(":memory:", { strict: true });

// throws error because of the typo:
const query = strict.query("SELECT $message;").all({ messag: "Hello world" });

const notStrict = new Database(":memory:");
// does not throw error:
notStrict.query("SELECT $message;").all({ messag: "Hello world" });
```

### Load via ES module import

You can also load a database with an import attribute.

```ts db.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" highlight={1} theme={"theme":{"light":"github-light","dark":"dracula"}}
import db from "./mydb.sqlite" with { type: "sqlite" };

console.log(db.query("select * from users LIMIT 1").get());
```

This is equivalent to the following:

```ts db.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { Database } from "bun:sqlite";
const db = new Database("./mydb.sqlite");
```

### `.close(throwOnError: boolean = false)`

To close a database connection but let statements created with `.prepare()` keep working until they are finalized or garbage collected, call `.close(false)`:

```ts db.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" highlight={3} theme={"theme":{"light":"github-light","dark":"dracula"}}
const db = new Database();
// ... do stuff
db.close(false);
```

The `Database` owns statements created with `.query()` and finalizes them immediately either way. Bun releases the underlying connection (and the database file handle) once the last outstanding `.prepare()` statement is finalized.

To finalize **every** outstanding statement, release the connection immediately, and throw if SQLite reports an error while closing, call `.close(true)`:

```ts db.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" highlight={3} theme={"theme":{"light":"github-light","dark":"dracula"}}
const db = new Database();
// ... do stuff
db.close(true);
```

Using a statement that `close()` finalized throws `Database has closed`. Two exceptions: `toString()` returns an empty string, and `finalize()` stays safe to call.

<Note>
  `close()` is safe to call multiple times but has no effect after the first. One exception: `close(true)` after
  `close(false)` still finalizes any remaining `.prepare()` statements. If a `Database` is garbage collected without
  being closed, Bun releases the connection once every statement created from it has also been finalized or collected.
  The `using` statement calls `close(true)`.
</Note>

### `using` statement

The `using` statement closes the database connection when the block exits.

```ts db.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" highlight={4, 5} theme={"theme":{"light":"github-light","dark":"dracula"}}
import { Database } from "bun:sqlite";

{
  using db = new Database("mydb.sqlite");
  using query = db.query("select 'Hello world' as message;");
  console.log(query.get());
}
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
{ message: "Hello world" }
```

### `.serialize()`

`bun:sqlite` supports SQLite's built-in mechanism for [serializing](https://www.sqlite.org/c3ref/serialize.html) and [deserializing](https://www.sqlite.org/c3ref/deserialize.html) databases to and from memory.

```ts db.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" highlight={2} theme={"theme":{"light":"github-light","dark":"dracula"}}
const olddb = new Database("mydb.sqlite");
const contents = olddb.serialize(); // => Uint8Array
const newdb = Database.deserialize(contents);
```

Internally, `.serialize()` calls [`sqlite3_serialize`](https://www.sqlite.org/c3ref/serialize.html).

### `.query()`

Use the `db.query()` method on your `Database` instance to [prepare](https://www.sqlite.org/c3ref/prepare.html) a SQL query. The result is a `Statement` instance that is cached on the `Database` instance. *The query is not executed.*

```ts db.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
const query = db.query(`select "Hello world" as message`);
```

<Note>
  **What does "cached" mean?**

  The caching refers to the **compiled prepared statement** (the SQL bytecode), not the query results. When you call `db.query()` with the same SQL string multiple times, Bun returns the same cached `Statement` object instead of recompiling the SQL. The cache holds the `Database.MAX_QUERY_CACHE_SIZE` (default 20) most recently used SQL strings. Evicted statements keep working, but a later `db.query()` with the same string compiles a new one.

  It is safe to reuse a cached statement with different parameter values:

  ```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
  const query = db.query("SELECT * FROM users WHERE id = ?");
  query.get(1); // ✓ Works
  query.get(2); // ✓ Also works - parameters are bound fresh each time
  query.get(3); // ✓ Still works
  ```

  Use `.prepare()` instead of `.query()` when you want a fresh `Statement` instance that isn't cached, for example if you're dynamically generating SQL and don't want to fill the cache with one-off queries.

  ```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
  // compile the prepared statement without caching
  const query = db.prepare("SELECT * FROM foo WHERE bar = ?");
  ```
</Note>

***

## WAL mode

SQLite supports [write-ahead log mode](https://www.sqlite.org/wal.html) (WAL), which dramatically improves performance, especially with many concurrent readers and a single writer. We recommend enabling WAL mode for most applications.

To enable WAL mode, run this pragma query at the beginning of your application:

```ts db.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
db.run("PRAGMA journal_mode = WAL;");
```

<Accordion title="What is WAL mode?">
  In WAL mode, writes to the database go directly to a separate file called the "WAL file" (`-wal`). SQLite also creates
  a shared-memory index file (`-shm`) for read coordination. SQLite later integrates the WAL file into the main database
  file. Think of the WAL file as a buffer for pending writes. Refer to the [SQLite
  docs](https://www.sqlite.org/wal.html) for a more detailed overview.
</Accordion>

### WAL sidecar file cleanup

When using WAL mode with a file-based database, SQLite creates two sidecar files alongside your database: a write-ahead log (`-wal`) and a shared-memory index (`-shm`). Whether SQLite removes these files automatically after `.close()` depends on your platform:

* **macOS**: Bun uses the system-provided SQLite, which Apple builds with persistent WAL enabled. The `-wal` and `-shm` files **persist** after close. This is not a bug — it is how Apple configured the system SQLite.
* **Linux** and **Windows**: Bun statically links its own SQLite build, which follows upstream defaults. SQLite **typically removes** the sidecar files after close when no other connections are open.

To ensure sidecar files are cleaned up on all platforms, disable WAL persistence and run a truncating checkpoint before closing:

```ts db.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { Database, constants } from "bun:sqlite";

const db = new Database("mydb.sqlite");
db.run("PRAGMA journal_mode = WAL;");

// ... use the database ...

// Disable persistent WAL (needed on macOS)
db.fileControl(constants.SQLITE_FCNTL_PERSIST_WAL, 0);
// Checkpoint and truncate the WAL file
db.run("PRAGMA wal_checkpoint(TRUNCATE);");
db.close();
// Only mydb.sqlite remains — no -wal or -shm files
```

***

## Statements

A `Statement` is a *prepared query*, which means it's been parsed and compiled into an efficient binary form. It can be executed multiple times.

Create a statement with the `.query` method on your `Database` instance.

```ts db.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
const query = db.query(`select "Hello world" as message`);
```

Queries can contain parameters. These can be numerical (`?1`) or named (`$param` or `:param` or `@param`).

```ts db.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
const query = db.query(`SELECT ?1, ?2;`);
const query = db.query(`SELECT $param1, $param2;`);
```

You bind values to these parameters when you execute the query. You can execute a `Statement` with several different methods, each returning the results in a different form.

### Binding values

To bind values to a statement, pass an object to the `.all()`, `.get()`, `.run()`, or `.values()` method.

```ts db.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" highlight={2} theme={"theme":{"light":"github-light","dark":"dracula"}}
const query = db.query(`select $message;`);
query.all({ $message: "Hello world" });
```

You can bind using positional parameters too:

```ts db.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
const query = db.query(`select ?1;`);
query.all("Hello world");
```

#### `strict: true` lets you bind values without prefixes

By default, the `$`, `:`, and `@` prefixes are **included** when binding values to named parameters. To bind without these prefixes, use the `strict` option in the `Database` constructor.

```ts db.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { Database } from "bun:sqlite";

const db = new Database(":memory:", {
  // bind values without prefixes
  strict: true, // [!code ++]
});

const query = db.query(`select $message;`);

// strict: true
query.all({ message: "Hello world" });

// strict: false
// query.all({ $message: "Hello world" });
```

### `.all()`

Use `.all()` to run a query and get back the results as an array of objects.

```ts db.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" highlight={2} theme={"theme":{"light":"github-light","dark":"dracula"}}
const query = db.query(`select $message;`);
query.all({ $message: "Hello world" });
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
[{ $message: "Hello world" }]
```

Internally, this calls [`sqlite3_reset`](https://www.sqlite.org/capi3ref.html#sqlite3_reset) and repeatedly calls [`sqlite3_step`](https://www.sqlite.org/capi3ref.html#sqlite3_step) until it returns `SQLITE_DONE`.

### `.get()`

Use `.get()` to run a query and get back the first result as an object.

```ts db.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" highlight={2} theme={"theme":{"light":"github-light","dark":"dracula"}}
const query = db.query(`select $message;`);
query.get({ $message: "Hello world" });
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
{ $message: "Hello world" }
```

Internally, this calls [`sqlite3_reset`](https://www.sqlite.org/capi3ref.html#sqlite3_reset) followed by [`sqlite3_step`](https://www.sqlite.org/capi3ref.html#sqlite3_step) until it no longer returns `SQLITE_ROW`. If the query returns no rows, the result is `null`.

### `.run()`

Use `.run()` to run a query and get back an object with execution metadata. This is useful for schema-modifying queries (such as `CREATE TABLE`) or bulk write operations.

```ts db.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" highlight={2} theme={"theme":{"light":"github-light","dark":"dracula"}}
const query = db.query(`create table foo (bar text);`);
query.run();
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
{
  lastInsertRowid: 0,
  changes: 0,
}
```

Internally, this calls [`sqlite3_reset`](https://www.sqlite.org/capi3ref.html#sqlite3_reset) and calls [`sqlite3_step`](https://www.sqlite.org/capi3ref.html#sqlite3_step) once. Stepping through all the rows is not necessary when you don't care about the results.

The `lastInsertRowid` property is the ID of the last row inserted into the database. The `changes` property is the number of rows affected by the query.

### `.as(Class)` - Map query results to a class

Use `.as(Class)` to run a query and get back the results as instances of a class. The class's methods, getters, and setters are available on each row.

```ts db.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" highlight={10} theme={"theme":{"light":"github-light","dark":"dracula"}}
class Movie {
  title: string;
  year: number;

  get isMarvel() {
    return this.title.includes("Marvel");
  }
}

const query = db.query("SELECT title, year FROM movies").as(Movie);
const movies = query.all();
const first = query.get();

console.log(movies[0].isMarvel);
console.log(first.isMarvel);
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
true
true
```

As a performance optimization, Bun does not call the class constructor or run default initializers, and private fields are not accessible. This is more like `Object.create` than `new`: Bun assigns the class's prototype to the object, so its methods, getters, and setters work.

Bun sets the database columns as properties on the class instance.

### `.iterate()` (`@@iterator`)

Use `.iterate()` to run a query and incrementally return results. This is useful for large result sets that you want to process one row at a time without loading all the results into memory.

```ts db.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" highlight={2} theme={"theme":{"light":"github-light","dark":"dracula"}}
const query = db.query("SELECT * FROM foo");
for (const row of query.iterate()) {
  console.log(row);
}
```

You can also use the `@@iterator` protocol:

```ts db.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" highlight={2} theme={"theme":{"light":"github-light","dark":"dracula"}}
const query = db.query("SELECT * FROM foo");
for (const row of query) {
  console.log(row);
}
```

### `.values()`

Use `values()` to run a query and get back all results as an array of arrays.

```ts db.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" highlight={3, 4} theme={"theme":{"light":"github-light","dark":"dracula"}}
const query = db.query(`select $message;`);

query.values({ $message: "Hello world" });
query.values(2);
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
[ [ "Hello world" ] ]
[ [ 2 ] ]
```

Internally, this calls [`sqlite3_reset`](https://www.sqlite.org/capi3ref.html#sqlite3_reset) and repeatedly calls [`sqlite3_step`](https://www.sqlite.org/capi3ref.html#sqlite3_step) until it returns `SQLITE_DONE`.

### `.finalize()`

Use `.finalize()` to destroy a `Statement` and free any resources associated with it. Once finalized, a `Statement` cannot be executed again. Typically, the garbage collector does this for you, but explicit finalization may be useful in performance-sensitive applications.

```ts db.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" highlight={3} theme={"theme":{"light":"github-light","dark":"dracula"}}
const query = db.query("SELECT title, year FROM movies");
const movies = query.all();
query.finalize();
```

### `.toString()`

Calling `toString()` on a `Statement` instance prints the expanded SQL query. This is useful for debugging.

```ts db.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" highlight={6, 9, 12} theme={"theme":{"light":"github-light","dark":"dracula"}}
import { Database } from "bun:sqlite";

// setup
const query = db.query("SELECT $param;");

console.log(query.toString()); // => "SELECT NULL"

query.run(42);
console.log(query.toString()); // => "SELECT 42"

query.run(365);
console.log(query.toString()); // => "SELECT 365"
```

Internally, this calls [`sqlite3_expanded_sql`](https://www.sqlite.org/capi3ref.html#sqlite3_expanded_sql). The parameters are expanded using the most recently bound values.

## Parameters

Queries can contain parameters. These can be numerical (`?1`) or named (`$param` or `:param` or `@param`). Bind values to these parameters when executing the query:

```ts title="query.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
const query = db.query("SELECT * FROM foo WHERE bar = $bar");
const results = query.all({
  $bar: "bar",
});
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
[{ "bar": "bar" }]
```

Numbered (positional) parameters work too:

```ts db.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
const query = db.query("SELECT ?1, ?2");
const results = query.all("hello", "goodbye");
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
[
	{
		"?1": "hello",
		"?2": "goodbye",
	},
];
```

***

## Integers

SQLite supports signed 64-bit integers, but JavaScript only supports signed 53-bit integers or arbitrary-precision integers with `bigint`.

`bigint` input is supported everywhere, but by default `bun:sqlite` returns integers as `number` types. If you need to handle integers larger than 2^53, set the `safeIntegers` option to `true` when creating a `Database` instance. The option also validates that `bigint` values passed to `bun:sqlite` do not exceed 64 bits.

### `safeIntegers: true`

When `safeIntegers` is `true`, `bun:sqlite` returns integers as `bigint` types:

```ts db.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" highlight={3} theme={"theme":{"light":"github-light","dark":"dracula"}}
import { Database } from "bun:sqlite";

const db = new Database(":memory:", { safeIntegers: true });
const query = db.query(`SELECT ${BigInt(Number.MAX_SAFE_INTEGER) + 102n} as max_int`);
const result = query.get();

console.log(result.max_int);
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
9007199254741093n
```

When `safeIntegers` is `true`, `bun:sqlite` throws an error if a `bigint` value in a bound parameter exceeds 64 bits:

```ts db.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" highlight={3} theme={"theme":{"light":"github-light","dark":"dracula"}}
import { Database } from "bun:sqlite";

const db = new Database(":memory:", { safeIntegers: true });
db.run("CREATE TABLE test (id INTEGER PRIMARY KEY, value INTEGER)");

const query = db.query("INSERT INTO test (value) VALUES ($value)");

try {
  query.run({ $value: BigInt(Number.MAX_SAFE_INTEGER) ** 2n });
} catch (e) {
  console.log(e.message);
}
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
BigInt value '81129638414606663681390495662081' is out of range
```

### `safeIntegers: false` (default)

When `safeIntegers` is `false`, `bun:sqlite` returns integers as `number` types and rounds integers beyond 53 bits to the nearest representable `number`:

```ts db.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" highlight={3} theme={"theme":{"light":"github-light","dark":"dracula"}}
import { Database } from "bun:sqlite";

const db = new Database(":memory:", { safeIntegers: false });
const query = db.query(`SELECT ${BigInt(Number.MAX_SAFE_INTEGER) + 102n} as max_int`);
const result = query.get();
console.log(result.max_int);
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
9007199254741092
```

***

## Transactions

A transaction executes multiple queries *atomically*: either all of them succeed or none do. Create a transaction with the `db.transaction()` method:

```ts db.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" highlight={2} theme={"theme":{"light":"github-light","dark":"dracula"}}
const insertCat = db.prepare("INSERT INTO cats (name) VALUES ($name)");
const insertCats = db.transaction(cats => {
  for (const cat of cats) insertCat.run(cat);
});
```

No cats have been inserted yet. `db.transaction()` returns a new function (`insertCats`) that *wraps* the function that executes the queries.

To execute the transaction, call this function. The transaction function passes its arguments through to the wrapped function and returns the wrapped function's return value. The wrapped function also has access to the `this` context as defined where the transaction is executed.

```ts db.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" highlight={3} theme={"theme":{"light":"github-light","dark":"dracula"}}
const insert = db.prepare("INSERT INTO cats (name) VALUES ($name)");
const insertCats = db.transaction(cats => {
  for (const cat of cats) insert.run(cat);
  return cats.length;
});

const count = insertCats([{ $name: "Keanu" }, { $name: "Salem" }, { $name: "Crookshanks" }]);

console.log(`Inserted ${count} cats`);
```

The driver automatically [begins](https://www.sqlite.org/lang_transaction.html) a transaction when you call `insertCats` and commits it when the wrapped function returns. If an exception is thrown, the driver rolls back the transaction. The exception propagates as usual; the driver does not catch it.

<Note>
  **Nested transactions** — You can call transaction functions from inside other transaction functions. When you do, the inner transaction becomes a [savepoint](https://www.sqlite.org/lang_savepoint.html).

  <Accordion title="View nested transaction example">
    ```ts db.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
    // setup
    import { Database } from "bun:sqlite";
    const db = Database.open(":memory:");
    db.run("CREATE TABLE expenses (id INTEGER PRIMARY KEY AUTOINCREMENT, note TEXT, dollars INTEGER);");
    db.run("CREATE TABLE cats (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT UNIQUE, age INTEGER)");
    const insertExpense = db.prepare("INSERT INTO expenses (note, dollars) VALUES (?, ?)");
    const insert = db.prepare("INSERT INTO cats (name, age) VALUES ($name, $age)");
    const insertCats = db.transaction(cats => {
      for (const cat of cats) insert.run(cat);
    });

    const adopt = db.transaction(cats => {
      insertExpense.run("adoption fees", 20);
      insertCats(cats); // nested transaction
    });

    adopt([
      { $name: "Joey", $age: 2 },
      { $name: "Sally", $age: 4 },
      { $name: "Junior", $age: 1 },
    ]);
    ```
  </Accordion>
</Note>

Transactions also come with `deferred`, `immediate`, and `exclusive` versions.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
insertCats(cats); // uses "BEGIN"
insertCats.deferred(cats); // uses "BEGIN DEFERRED"
insertCats.immediate(cats); // uses "BEGIN IMMEDIATE"
insertCats.exclusive(cats); // uses "BEGIN EXCLUSIVE"
```

### `.loadExtension()`

To load a [SQLite extension](https://www.sqlite.org/loadext.html), call `.loadExtension(name)` on your `Database` instance:

```ts db.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" highlight={4} theme={"theme":{"light":"github-light","dark":"dracula"}}
import { Database } from "bun:sqlite";

const db = new Database();
db.loadExtension("myext");
```

<Note>
  **macOS users** By default, macOS ships with Apple's proprietary build of SQLite, which doesn't support extensions. To use extensions, install a vanilla build of SQLite.

  ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
  brew install sqlite
  which sqlite # get path to binary
  ```

  To point `bun:sqlite` to the new build, call `Database.setCustomSQLite(path)` before creating any `Database` instances. (On other operating systems, this is a no-op.) Pass a path to the SQLite `.dylib` file, *not* the executable. With recent versions of Homebrew this is something like `/opt/homebrew/Cellar/sqlite/<version>/libsqlite3.dylib`.

  ```ts db.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" highlight={3} theme={"theme":{"light":"github-light","dark":"dracula"}}
  import { Database } from "bun:sqlite";

  Database.setCustomSQLite("/path/to/libsqlite.dylib");

  const db = new Database();
  db.loadExtension("myext");
  ```
</Note>

### `.fileControl(cmd: number, value: any)`

To use the advanced `sqlite3_file_control` API, call `.fileControl(cmd, value)` on your `Database` instance. See [WAL sidecar file cleanup](#wal-sidecar-file-cleanup) for a practical example.

```ts db.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" highlight={6} theme={"theme":{"light":"github-light","dark":"dracula"}}
import { Database, constants } from "bun:sqlite";

const db = new Database();
// Ensure WAL mode is NOT persistent
// this prevents WAL files from lingering after the database is closed
db.fileControl(constants.SQLITE_FCNTL_PERSIST_WAL, 0);
```

`value` can be:

* `number`
* `TypedArray`
* `undefined` or `null`

***

## Reference

```ts Type Reference icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" expandable theme={"theme":{"light":"github-light","dark":"dracula"}}
class Database {
  constructor(
    filename: string,
    options?:
      | number
      | {
          readonly?: boolean;
          create?: boolean;
          readwrite?: boolean;
          safeIntegers?: boolean;
          strict?: boolean;
        },
  );

  query<ReturnType, ParamsType>(sql: string): Statement<ReturnType, ParamsType>;
  prepare<ReturnType, ParamsType>(sql: string): Statement<ReturnType, ParamsType>;
  run(sql: string, params?: SQLQueryBindings): { lastInsertRowid: number; changes: number };
  exec = this.run;

  transaction(insideTransaction: (...args: any) => void): CallableFunction & {
    deferred: (...args: any) => void;
    immediate: (...args: any) => void;
    exclusive: (...args: any) => void;
  };

  close(throwOnError?: boolean): void;
}

class Statement<ReturnType, ParamsType> {
  all(...params: ParamsType[]): ReturnType[];
  get(...params: ParamsType[]): ReturnType | null;
  run(...params: ParamsType[]): {
    lastInsertRowid: number;
    changes: number;
  };
  values(...params: ParamsType[]): unknown[][];

  finalize(): void; // destroy statement and clean up resources
  toString(): string; // serialize to SQL

  columnNames: string[]; // the column names of the result set
  columnTypes: string[]; // types based on actual values in first row (call .get()/.all() first)
  declaredTypes: (string | null)[]; // types from CREATE TABLE schema (call .get()/.all() first)
  paramsCount: number; // the number of parameters expected by the statement
  native: any; // the native object representing the statement

  as<T>(Class: new (...args: any[]) => T): Statement<T, ParamsType>;
}

type SQLQueryBindings =
  | string
  | bigint
  | TypedArray
  | number
  | boolean
  | null
  | Record<string, string | bigint | TypedArray | number | boolean | null>;
```

### Datatypes

| JavaScript type | SQLite type            |
| --------------- | ---------------------- |
| `string`        | `TEXT`                 |
| `number`        | `INTEGER` or `DECIMAL` |
| `boolean`       | `INTEGER` (1 or 0)     |
| `Uint8Array`    | `BLOB`                 |
| `Buffer`        | `BLOB`                 |
| `bigint`        | `INTEGER`              |
| `null`          | `NULL`                 |


# Streams
Source: https://bun.com/docs/runtime/streams

Use Bun's streams API to work with binary data without loading it all into memory at once

Streams are an important abstraction for working with binary data without loading it all into memory at once. They are commonly used for reading and writing files, sending and receiving network requests, and processing large amounts of data.

Bun implements the Web APIs [`ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream) and [`WritableStream`](https://developer.mozilla.org/en-US/docs/Web/API/WritableStream).

<Note>
  Bun also implements the `node:stream` module, including
  [`Readable`](https://nodejs.org/api/stream.html#stream_readable_streams),
  [`Writable`](https://nodejs.org/api/stream.html#stream_writable_streams), and
  [`Duplex`](https://nodejs.org/api/stream.html#stream_duplex_and_transform_streams). For complete documentation, refer
  to the [Node.js docs](https://nodejs.org/api/stream.html).
</Note>

To create a `ReadableStream`:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const stream = new ReadableStream({
  start(controller) {
    controller.enqueue("hello");
    controller.enqueue("world");
    controller.close();
  },
});
```

You can read the contents of a `ReadableStream` chunk-by-chunk with `for await` syntax.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
for await (const chunk of stream) {
  console.log(chunk);
}

// hello
// world
```

***

## Direct `ReadableStream`

Bun implements an optimized version of `ReadableStream` that avoids unnecessary queue management.

With a traditional `ReadableStream`, you *enqueue* chunks of data. The stream adds each chunk to a queue, where it sits until the stream is ready to send more data.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const stream = new ReadableStream({
  start(controller) {
    controller.enqueue("hello");
    controller.enqueue("world");
    controller.close();
  },
});
```

With a direct `ReadableStream`, you write chunks of data directly to the stream. No queueing happens. The `controller` API reflects this: you call `.write()` instead of `.enqueue()`.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const stream = new ReadableStream({
  type: "direct", // [!code ++]
  pull(controller) {
    controller.write("hello");
    controller.write("world");
  },
});
```

When using a direct `ReadableStream`, the destination handles all chunk queueing. The consumer of the stream receives exactly what is passed to `controller.write()`, without any encoding or modification.

### Handling backpressure

`controller.write()` returns the number of bytes written, or a **pending `Promise<number>`** when the destination's internal buffer is full (for example, a slow HTTP client). The chunk is accepted either way. The promise resolves once the destination has drained, so `await`ing the result is enough:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const stream = new ReadableStream({
  type: "direct",
  async pull(controller) {
    for (const chunk of chunks) {
      await controller.write(chunk);
    }
    controller.close();
  },
});
```

`await controller.flush(true)` is equivalent, and you can use it after a write returns a `Promise`.

For default (non-`direct`) `ReadableStream`s and async-generator response bodies, Bun applies this backpressure automatically: it pauses the producer while the destination is backed up.

***

## Async generator streams

Bun also supports async generator functions as a source for `Response` and `Request`. Use async generators to create a `ReadableStream` that fetches data from an asynchronous source.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const response = new Response(
  (async function* () {
    yield "hello";
    yield "world";
  })(),
);

await response.text(); // "helloworld"
```

You can also use `[Symbol.asyncIterator]` directly.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const response = new Response({
  [Symbol.asyncIterator]: async function* () {
    yield "hello";
    yield "world";
  },
});

await response.text(); // "helloworld"
```

For more control over the stream, `yield` returns the direct `ReadableStream` controller.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const response = new Response({
  [Symbol.asyncIterator]: async function* () {
    const controller = yield "hello";
    await controller.end();
  },
});

await response.text(); // "hello"
```

***

## `Bun.ArrayBufferSink`

The `Bun.ArrayBufferSink` class is a fast incremental writer for constructing an `ArrayBuffer` of unknown size.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const sink = new Bun.ArrayBufferSink();

sink.write("h");
sink.write("e");
sink.write("l");
sink.write("l");
sink.write("o");

sink.end();
// ArrayBuffer(5) [ 104, 101, 108, 108, 111 ]
```

To instead retrieve the data as a `Uint8Array`, pass the `asUint8Array` option to the `start` method.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const sink = new Bun.ArrayBufferSink();
sink.start({
  asUint8Array: true, // [!code ++]
});

sink.write("h");
sink.write("e");
sink.write("l");
sink.write("l");
sink.write("o");

sink.end();
// Uint8Array(5) [ 104, 101, 108, 108, 111 ]
```

The `.write()` method supports strings, typed arrays, `ArrayBuffer`, and `SharedArrayBuffer`.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
sink.write("h");
sink.write(new Uint8Array([101, 108]));
sink.write(Buffer.from("lo").buffer);

sink.end();
```

Once you call `.end()`, you can't write any more data to the `ArrayBufferSink`. However, when buffering a stream you may want to keep writing data and periodically `.flush()` the contents (say, into a `WritableStream`). To support this, pass `stream: true` to the `start` method.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const sink = new Bun.ArrayBufferSink();
sink.start({
  stream: true, // [!code ++]
});

sink.write("h");
sink.write("e");
sink.write("l");
sink.flush();
// ArrayBuffer(3) [ 104, 101, 108 ]

sink.write("l");
sink.write("o");
sink.flush();
// ArrayBuffer(2) [ 108, 111 ]
```

The `.flush()` method returns the buffered data as an `ArrayBuffer` (or `Uint8Array` if `asUint8Array: true`) and clears the internal buffer.

To manually set the size of the internal buffer in bytes, pass a value for `highWaterMark`:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const sink = new Bun.ArrayBufferSink();
sink.start({
  highWaterMark: 1024 * 1024, // 1 MB  // [!code ++]
});
```

***

## Reference

```ts See Typescript Definitions expandable theme={"theme":{"light":"github-light","dark":"dracula"}}
/**
 * Fast incremental writer that becomes an `ArrayBuffer` on end().
 */
export class ArrayBufferSink {
  constructor();

  start(options?: {
    asUint8Array?: boolean;
    /**
     * Preallocate an internal buffer of this size
     * This can significantly improve performance when the chunk size is small
     */
    highWaterMark?: number;
    /**
     * On {@link ArrayBufferSink.flush}, return the written data as a `Uint8Array`.
     * Writes will restart from the beginning of the buffer.
     */
    stream?: boolean;
  }): void;

  write(chunk: string | ArrayBufferView | ArrayBuffer | SharedArrayBuffer): number;
  /**
   * Flush the internal buffer
   *
   * If {@link ArrayBufferSink.start} was passed a `stream` option, this will return a `ArrayBuffer`
   * If {@link ArrayBufferSink.start} was passed a `stream` option and `asUint8Array`, this will return a `Uint8Array`
   * Otherwise, this will return the number of bytes written since the last flush
   *
   * This API might change later to separate Uint8ArraySink and ArrayBufferSink
   */
  flush(): number | Uint8Array<ArrayBuffer> | ArrayBuffer;
  end(): ArrayBuffer | Uint8Array<ArrayBuffer>;
}
```


# bun create
Source: https://bun.com/docs/runtime/templating/create

Create a new Bun project from a React component, a `create-<template>` npm package, a GitHub repo, or a local template

<Note>
  `bun create` is optional — Bun works without any configuration. This command exists to make getting started faster.
</Note>

***

Template a new Bun project with `bun create`. It creates a project from a React component, a `create-<template>` npm package, a GitHub repo, or a local template.

To create an empty project, use [`bun init`](/docs/runtime/templating/init).

## From a React component

`bun create ./MyComponent.tsx` turns an existing React component into a complete dev environment with hot reload and production builds in one command.

```bash theme={"theme":{"light":"github-light","dark":"dracula"}}
bun create ./MyComponent.jsx # .tsx also supported
```

<Frame>
  <video>
    <source type="video/mp4" />
  </video>
</Frame>

<Note>
  🚀 **Create React App Successor** — `bun create <component>` provides everything developers loved about Create React App, but with modern tooling, faster builds, and backend support.
</Note>

#### How this works

When you run `bun create <component>`, Bun:

1. Uses [Bun's JavaScript bundler](/docs/bundler) to analyze your module graph.
2. Collects all the dependencies needed to run the component.
3. Scans the exports of the entry point for a React component.
4. Generates a `package.json` file with the dependencies and scripts needed to run the component.
5. Installs any missing dependencies using [`bun install --only-missing`](/docs/pm/cli/install).
6. Generates the following files:
   * `${component}.html`
   * `${component}.client.tsx` (entry point for the frontend)
   * `${component}.css`
7. Starts a frontend dev server.

### Using TailwindCSS with Bun

[TailwindCSS](https://tailwindcss.com/) is a utility-first CSS framework for styling web applications.

When you run `bun create <component>`, Bun scans your JSX/TSX file (and any files it imports) for TailwindCSS class names. If Bun detects TailwindCSS class names, it adds the following dependencies to your `package.json`:

```json package.json icon="file-json" theme={"theme":{"light":"github-light","dark":"dracula"}}
{
  "dependencies": {
    "tailwindcss": "^4",
    "bun-plugin-tailwind": "latest"
  }
}
```

Bun also configures `bunfig.toml` to use its TailwindCSS plugin with `Bun.serve()`:

```toml bunfig.toml icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[serve.static]
plugins = ["bun-plugin-tailwind"]
```

Bun also writes a `${component}.css` file with `@import "tailwindcss";` at the top:

```css MyComponent.css icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
@import "tailwindcss";
```

### Using `shadcn/ui` with Bun

[`shadcn/ui`](https://ui.shadcn.com/) is a component library tool for building web applications.

`bun create <component>` scans for any shadcn/ui components imported from `@/components/ui`.

If it finds any, it runs:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
# Assuming bun detected imports to @/components/ui/accordion and @/components/ui/button
bunx shadcn@canary add accordion button # and any other components
```

Since `shadcn/ui` itself uses TailwindCSS, `bun create` also adds the TailwindCSS dependencies to your `package.json` and configures `bunfig.toml` to use Bun's TailwindCSS plugin with `Bun.serve()`, as described earlier.

`bun create` also sets up:

* `tsconfig.json` to alias `"@/*"` to `"src/*"` or `.` (depending on whether there is a `src/` directory)
* `components.json` so that shadcn/ui knows it's a shadcn/ui project
* `styles/globals.css` file that configures Tailwind v4 the way shadcn/ui expects
* `${component}.build.ts` file that builds the component for production with `bun-plugin-tailwind` configured

Use `bun create ./MyComponent.jsx` to run code generated by LLMs like [Claude](https://claude.ai) or ChatGPT locally.

## From `npm`

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun create <template> [<destination>]
```

If you don't have a [local template](#from-a-local-template) with the same name, this command downloads and runs the `create-<template>` package from npm. These two commands are equivalent:

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun create remix
bunx create-remix
```

Refer to the `create-<template>` package's documentation for usage instructions.

## From GitHub

`bun create <user>/<repo>` downloads the contents of the GitHub repo to disk.

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun create <user>/<repo>
bun create github.com/<user>/<repo>
```

Optionally specify a name for the destination folder. If you don't, Bun uses the repo name.

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun create <user>/<repo> mydir
bun create github.com/<user>/<repo> mydir
```

Bun then:

* Downloads the template
* Copies all template files into the destination folder
* Installs dependencies with `bun install`
* Initializes a fresh Git repo. Opt out with the `--no-git` flag.

<Note>By default Bun does *not overwrite* existing files. Use the `--force` flag to overwrite them.</Note>

## From a local template

<Warning>
  Unlike remote templates, running `bun create` with a local template deletes the entire destination folder if it
  already exists.
</Warning>

You can define custom templates on your local file system. Put them in one of the following directories:

* `$HOME/.bun-create/<name>`: global templates
* `<project root>/.bun-create/<name>`: project-specific templates

<Note>Set the `BUN_CREATE_DIR` environment variable to change the global template path.</Note>

To create a local template, create a directory in `$HOME/.bun-create` named after your template.

```bash theme={"theme":{"light":"github-light","dark":"dracula"}}
cd $HOME/.bun-create
mkdir foo
cd foo
```

Then, create a `package.json` file in that directory with the following contents:

```json package.json icon="file-json" theme={"theme":{"light":"github-light","dark":"dracula"}}
{
  "name": "foo"
}
```

Run `bun create foo` elsewhere on your file system to verify that Bun finds your local template.

#### Setup logic

You can specify pre- and post-install setup scripts in the `"bun-create"` section of your local template's `package.json`.

```json package.json icon="file-json" theme={"theme":{"light":"github-light","dark":"dracula"}}
{
  "name": "@bun-examples/simplereact",
  "version": "0.0.1",
  "main": "index.js",
  "dependencies": {
    "react": "^17.0.2",
    "react-dom": "^17.0.2"
  },
  "bun-create": {
    "preinstall": "echo 'Installing...'", // a single command
    "postinstall": ["echo 'Done!'"], // an array of commands
    "start": "bun run echo 'Hello world!'"
  }
}
```

`preinstall` and `postinstall` accept a string or an array of strings. An array of commands runs in order.

| Field         | Description                                                                                 |
| ------------- | ------------------------------------------------------------------------------------------- |
| `postinstall` | runs after installing dependencies                                                          |
| `preinstall`  | runs before installing dependencies                                                         |
| `start`       | the command Bun prints under "To get started, run:" when it finishes; defaults to `bun dev` |

After cloning a template, `bun create` removes the `"bun-create"` section from `package.json` before writing it to the destination folder.

## Reference

### CLI flags

| Flag           | Description                            |
| -------------- | -------------------------------------- |
| `--force`      | Overwrite existing files               |
| `--no-install` | Skip installing `node_modules` & tasks |
| `--no-git`     | Don't initialize a git repository      |
| `--open`       | Start & open in-browser after finish   |

### Environment variables

| Name                                      | Description                                                                                                                             |
| ----------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| `GITHUB_API_DOMAIN`                       | The GitHub domain Bun downloads from. Set this if you use GitHub Enterprise or a proxy                                                  |
| `GITHUB_TOKEN` (or `GITHUB_ACCESS_TOKEN`) | Lets `bun create` access private repositories and avoid rate limits. Bun picks `GITHUB_TOKEN` over `GITHUB_ACCESS_TOKEN` if both exist. |

<Accordion title={<span>How <code>bun create</code> works</span>}>
  When you run `bun create ${template} ${destination}`, here’s what happens:

  IF remote template (`elysia`, `elysia-buchta`, or `stric`; Bun hands any other npm template name to `bunx create-${template}` instead)

  1. GET `registry.npmjs.org/@bun-examples/${template}/latest` and parse it
  2. GET `registry.npmjs.org/@bun-examples/${template}/-/${template}-${latestVersion}.tgz`
  3. Decompress & extract `${template}-${latestVersion}.tgz` into `${destination}`
     * If files would be overwritten, warn and exit unless you pass `--force`

  IF GitHub repo

  1. Download the tarball from GitHub’s API
  2. Decompress & extract into `${destination}`
     * If files would be overwritten, warn and exit unless you pass `--force`

  ELSE IF local template

  1. Open local template folder
  2. Delete destination directory recursively
  3. Copy files recursively using the fastest system calls available (on macOS, `fcopyfile`; on Linux, `copy_file_range`). Do not copy or traverse into the `node_modules` folder if it exists (this alone makes it faster than `cp`)

  THEN, for remote templates, GitHub repos, and local templates alike:

  4. Parse the `package.json` (again!), update `name` to be `${basename(destination)}`, remove the `bun-create` section from the `package.json` and save the updated `package.json` to disk.
  5. Run any tasks defined in `"bun-create": { "preinstall" }`
  6. Run `bun install` unless you pass `--no-install` OR no dependencies are in package.json
  7. Run any tasks defined in `"bun-create": { "postinstall" }`
  8. Run `git init; git add -A .; git commit -am "Initial Commit";`
     * Rename `gitignore` to `.gitignore`. npm strips `.gitignore` files from published packages.
     * If there are dependencies, this step runs in a separate thread concurrently while Bun installs node\_modules
     * We tested using libgit2 if available, and it performed 3x slower in microbenchmarks
</Accordion>


# bun init
Source: https://bun.com/docs/runtime/templating/init

Scaffold an empty Bun project with the interactive `bun init` command

Scaffold a new Bun project with `bun init`.

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun init my-app
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
? Select a project template - Press return to submit.
❯ Blank
  React
  Library

✓ Select a project template: Blank

 + .gitignore
 + CLAUDE.md
 + .cursor/rules/use-bun-instead-of-node-vite-npm-pnpm.mdc -> CLAUDE.md
 + index.ts
 + tsconfig.json (for editor autocomplete)
 + README.md
```

Press `enter` to accept the default answer for each prompt, or pass the `-y` flag to auto-accept the defaults.

***

`bun init` infers settings with sane defaults and is non-destructive when run multiple times.

<Frame>
  ![Demo](https://user-images.githubusercontent.com/709451/183006613-271960a3-ff22-4f7c-83f5-5e18f684c836.gif)
</Frame>

It creates:

* a `package.json` file with a name that defaults to the current directory name
* a `tsconfig.json` or `jsconfig.json` file, depending on whether the entry point is a TypeScript file
* an entry point, which defaults to `index.ts` unless any of `index.{tsx, jsx, js, mts, mjs}` exist or the `package.json` specifies a `module` or `main` field
* a `README.md` file

AI Agent rules (disable with `$BUN_AGENT_RULE_DISABLED=1`):

* a `CLAUDE.md` file when `bun init` detects Claude CLI (disable with `CLAUDE_CODE_AGENT_RULE_DISABLED` env var)
* a `.cursor/rules/*.mdc` file when `bun init` detects Cursor (disable with `CURSOR_AGENT_RULE_DISABLED` env var); the file tells [Cursor AI](https://cursor.sh) to use Bun instead of Node.js and npm

Pass `-y` or `--yes` to accept the defaults without prompting.

At the end, it runs `bun install` to install `@types/bun`.

***

## CLI Usage

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun init <folder?>
```

### Initialization Options

<ParamField type="boolean">
  Accept all default prompts without asking questions. Alias: <code>-y</code>
</ParamField>

<ParamField type="boolean">
  Only initialize type definitions (skip app scaffolding). Alias: <code>-m</code>
</ParamField>

### Project Templates

<ParamField type="string|boolean">
  Scaffold a React project. When used without a value, creates a baseline React app.
  <br /> Accepts values for presets:

  <ul>
    <li>
      <code>tailwind</code> – React app preconfigured with Tailwind CSS
    </li>

    <li>
      <code>shadcn</code> – React app with <code>@shadcn/ui</code> and Tailwind CSS
    </li>
  </ul>

  Examples:

  <pre>
    <code>
      bun init --reactbun init --react=tailwindbun init --react=shadcn
    </code>
  </pre>
</ParamField>

### Output & Files

<ParamField type="info">
  Initializes project files and configuration for the chosen options. Exact files vary by template.
</ParamField>

### Help

<ParamField type="boolean">
  Print this help menu. Alias: <code>-h</code>
</ParamField>

### Examples

* Accept all defaults

  ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
  bun init -y
  ```

* React

  ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
  bun init --react
  ```

* React + Tailwind CSS

  ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
  bun init --react=tailwind
  ```

* React + @shadcn/ui
  ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
  bun init --react=shadcn
  ```


# TOML
Source: https://bun.com/docs/runtime/toml

Use Bun's built-in support for TOML files through both runtime APIs and bundler integration

In Bun, TOML is a first-class citizen alongside JSON, JSON5, and YAML. You can:

* Parse TOML strings with `Bun.TOML.parse`
* `import` & `require` TOML files as modules at runtime (including hot reloading & watch mode support)
* `import` & `require` TOML files in frontend apps with Bun's bundler

***

## Runtime API

### `Bun.TOML.parse()`

Parse a TOML string into a JavaScript object.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
import { TOML } from "bun";
const text = `
name = "my-app"
version = "1.0.0"
debug = true

[database]
host = "localhost"
port = 5432

[features]
tags = ["web", "api"]
`;

const data = TOML.parse(text);
console.log(data);
// {
//   name: "my-app",
//   version: "1.0.0",
//   debug: true,
//   database: { host: "localhost", port: 5432 },
//   features: { tags: ["web", "api"] }
// }
```

#### Supported TOML Features

Bun's TOML parser implements the full [TOML v1.1.0 specification](https://github.com/toml-lang/toml/releases/tag/1.1.0) and passes the complete official [toml-test](https://github.com/toml-lang/toml-test) conformance suite.

* **Strings**: basic (`"..."`) and literal (`'...'`), including multi-line, with all escapes (`\uHHHH`, `\UHHHHHHHH`, and TOML 1.1's `\xHH` and `\e`)
* **Integers**: decimal, hex (`0x`), octal (`0o`), and binary (`0b`). Integers outside ±(2^53 - 1) throw, because a JavaScript number cannot represent them losslessly
* **Floats**: including `inf` and `nan`
* **Booleans**: `true` and `false`
* **Date/times**: returned as [Temporal](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal) objects — offset date-time as `Temporal.Instant`, local date-time as `Temporal.PlainDateTime`, local date as `Temporal.PlainDate`, and local time as `Temporal.PlainTime`
* **Arrays**: including mixed types and nested arrays
* **Tables**: standard (`[table]`) and inline (`{ key = "value" }`), including TOML 1.1 multi-line inline tables
* **Array of tables**: `[[array]]`
* **Dotted keys**: `a.b.c = "value"`
* **Comments**: using `#`

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const data = Bun.TOML.parse(`
# Application config
title = "My App"

[owner]
name = "John Doe"

[database]
enabled = true
ports = [8000, 8001, 8002]
connection_max = 5000

[servers.alpha]
ip = "10.0.0.1"
role = "frontend"

[servers.beta]
ip = "10.0.0.2"
role = "backend"
`);
```

#### Date/times

Each of TOML's four date/time types maps 1:1 onto a Temporal type. Temporal carries nanosecond precision; as the TOML spec permits, fractional seconds beyond nine digits are truncated:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const doc = Bun.TOML.parse(`
created = 1979-05-27T00:32:00-07:00  # offset date-time
meeting = 1979-05-27T07:32:00       # local date-time
birthday = 1979-05-27               # local date
opens = 07:32:00                    # local time
`);

doc.created; // Temporal.Instant (an offset date-time specifies an instant;
//              the written offset normalizes away: 1979-05-27T07:32:00Z)
doc.meeting; // Temporal.PlainDateTime
doc.birthday; // Temporal.PlainDate
doc.opens; // Temporal.PlainTime
```

#### Error Handling

`Bun.TOML.parse()` throws a `SyntaxError` if the TOML is invalid:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
try {
  Bun.TOML.parse("invalid = = =");
} catch (error) {
  console.error("Failed to parse TOML:", error.message);
  // Failed to parse TOML: TOML Parse error: Expected a value but found '='
}
```

### `Bun.TOML.stringify()`

Serialize a JavaScript object to a TOML document. Scalar keys come first,
followed by `[table]` and `[[array-of-tables]]` sections:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
Bun.TOML.stringify({
  name: "app",
  server: { host: "localhost", port: 8080 },
  points: [{ x: 1 }, { x: 2 }],
});
// name = "app"
//
// [server]
// host = "localhost"
// port = 8080
//
// [[points]]
// x = 1
//
// [[points]]
// x = 2
```

The top-level value must be an object — a TOML document is a table.
`Temporal.Instant`, `Temporal.PlainDateTime`, `Temporal.PlainDate`, and
`Temporal.PlainTime` values become the corresponding TOML date/time
literals, so `stringify(parse(doc))` round-trips date/time types.
`Temporal.ZonedDateTime` becomes an offset date-time and `Date` becomes
an offset date-time in UTC. TOML has no syntax for time-zone or calendar
annotations, so those are dropped (the ISO fields are written), and its
years are four digits, so date values outside 0000–9999 and invalid
`Date`s throw. Because TOML cannot represent them, `null` values,
`BigInt`, circular structures, `Temporal.PlainYearMonth`,
`Temporal.PlainMonthDay`, and `Temporal.Duration` also throw; `undefined`,
function, and symbol properties are skipped (inside arrays they throw,
since TOML arrays cannot have holes), and passing one of those as the
top-level value returns `undefined`, as `JSON.stringify` does.

***

## Module Import

### ES Modules

Import TOML files directly as ES modules. Bun parses the TOML and exposes it as both default and named exports:

```toml config.toml theme={"theme":{"light":"github-light","dark":"dracula"}}
[database]
host = "localhost"
port = 5432
name = "myapp"

[redis]
host = "localhost"
port = 6379

[features]
auth = true
rateLimit = true
analytics = false
```

#### Default Import

```ts app.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import config from "./config.toml";

console.log(config.database.host); // "localhost"
console.log(config.redis.port); // 6379
```

#### Named Imports

You can destructure top-level TOML tables as named imports:

```ts app.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { database, redis, features } from "./config.toml";

console.log(database.host); // "localhost"
console.log(redis.port); // 6379
console.log(features.auth); // true
```

Or combine both:

```ts app.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import config, { database, features } from "./config.toml";

// Use the full config object
console.log(config);

// Or use specific parts
if (features.rateLimit) {
  setupRateLimiting(database);
}
```

#### Import Attributes

Use an import attribute to load any file as TOML:

```ts app.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import myConfig from "./my.config" with { type: "toml" };
```

### CommonJS

You can also `require` TOML files in CommonJS:

```ts app.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
const config = require("./config.toml");
console.log(config.database.name); // "myapp"

// Destructuring also works
const { database, redis } = require("./config.toml");
console.log(database.port); // 5432
```

***

## Hot Reloading with TOML

When you run your application with `bun --hot`, Bun detects changes to TOML files and reloads them without restarting:

```toml config.toml theme={"theme":{"light":"github-light","dark":"dracula"}}
[server]
port = 3000
host = "localhost"

[features]
debug = true
verbose = false
```

```ts server.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { server, features } from "./config.toml";

console.log(`Starting server on ${server.host}:${server.port}`);

Bun.serve({
  port: server.port,
  hostname: server.host,
  fetch(req) {
    if (features.verbose) {
      console.log(`${req.method} ${req.url}`);
    }
    return new Response("Hello World");
  },
});
```

Run with hot reloading:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun --hot server.ts
```

***

## Bundler Integration

When you bundle with Bun, the bundler parses imported TOML at build time and includes it as a JavaScript module:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun build app.ts --outdir=dist
```

This means:

* Zero runtime TOML parsing overhead in production
* Smaller bundle sizes
* Tree shaking of unused properties (named imports)

### Dynamic Imports

You can also dynamically import TOML files:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const config = await import("./config.toml");
```


# Transpiler
Source: https://bun.com/docs/runtime/transpiler

Use Bun's transpiler to transpile JavaScript and TypeScript code

Bun exposes its internal transpiler as the `Bun.Transpiler` class. To create an instance:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const transpiler = new Bun.Transpiler({
  loader: "tsx", // "js" | "jsx" | "ts" | "tsx"
});
```

***

## `.transformSync()`

Transpile code synchronously with the `.transformSync()` method. The transpiler does not resolve modules or execute the code. The result is a string of vanilla JavaScript code.

<CodeGroup>
  ```ts transpile.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b"  theme={"theme":{"light":"github-light","dark":"dracula"}}
  const transpiler = new Bun.Transpiler({
    loader: 'tsx',
  });

  const code = `
  import * as whatever from "./whatever.ts"
  export function Home(props: {title: string}){
    return <p>{props.title}</p>;
  }`;

  const result = transpiler.transformSync(code);

  ```

  ```ts output theme={"theme":{"light":"github-light","dark":"dracula"}}
  import * as whatever from "./whatever.ts";
  export function Home(props) {
    return jsxDEV_7x81h0kn("p", {
      children: props.title
    }, undefined, false, undefined, this);
  }
  ```
</CodeGroup>

To override the default loader specified in the `new Bun.Transpiler()` constructor, pass a second argument to `.transformSync()`.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
transpiler.transformSync("<div>hi!</div>", "tsx");
```

<Accordion title="Nitty gritty">
  `.transformSync` runs the transpiler in the same thread as the calling code.

  [Macros](/docs/bundler/macros) run in the same thread as the transpiler, but in a separate event loop from the rest of your application. Macros and regular code share globals, so it is possible (but not recommended) to share state between them. Using AST nodes outside of a macro is undefined behavior.
</Accordion>

***

## `.transform()`

The `transform()` method is an async version of `.transformSync()` that returns a `Promise<string>`.

```js theme={"theme":{"light":"github-light","dark":"dracula"}}
const transpiler = new Bun.Transpiler({ loader: "jsx" });
const result = await transpiler.transform("<div>hi!</div>");
console.log(result);
```

Unless you're transpiling *many* large files, use `Bun.Transpiler.transformSync`. The threadpool overhead often costs more than the transpilation itself.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
await transpiler.transform("<div>hi!</div>", "tsx");
```

<Accordion title="Nitty gritty">
  The `.transform()` method runs the transpiler in Bun's worker threadpool, so running it 100 times spreads the work across `Math.floor($cpu_count * 0.8)` threads without blocking the main JavaScript thread.

  If your code uses a macro, the transpiler may spawn a new copy of Bun's JavaScript runtime environment in that new thread.
</Accordion>

## `.scan()`

The `.scan()` method scans source code and returns a list of its imports and exports, plus metadata about each one. [Type-only](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-8.html#type-only-imports-and-export) imports and exports are ignored.

<CodeGroup>
  ```ts example.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
  const transpiler = new Bun.Transpiler({
    loader: "tsx",
  });

  const code = `
  import React from 'react';
  import type {ReactNode} from 'react';
  const val = require('./cjs.js')
  import('./loader');

  export const name = "hello";
  `;

  const result = transpiler.scan(code);
  ```

  ```json output theme={"theme":{"light":"github-light","dark":"dracula"}}
  {
    "exports": ["name"],
    "imports": [
      {
        "kind": "import-statement",
        "path": "react"
      },
      {
        "kind": "dynamic-import",
        "path": "./loader"
      }
    ]
  }
  ```
</CodeGroup>

Each import in the `imports` array has a `path` and `kind`. Bun categorizes imports into the following kinds:

* `import-statement`: `import React from 'react'`
* `require-call`: `const val = require('./cjs.js')`
* `require-resolve`: `require.resolve('./cjs.js')`
* `dynamic-import`: `import('./loader')`
* `import-rule`: `@import 'foo.css'`
* `url-token`: `url('./foo.png')`

***

## `.scanImports()`

In performance-sensitive code, use the `.scanImports()` method to get a list of imports. It's faster than `.scan()` (especially for large files) but marginally less accurate due to its performance optimizations.

<CodeGroup>
  ```ts example.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
  const transpiler = new Bun.Transpiler({
    loader: "tsx",
  });

  const code = `
  import React from 'react';
  import type {ReactNode} from 'react';
  const val = require('./cjs.js')
  import('./loader');

  export const name = "hello";
  `;

  const result = transpiler.scanImports(code);
  ```

  ```json results icon="file-json" theme={"theme":{"light":"github-light","dark":"dracula"}}
  [
    {
      "kind": "import-statement",
      "path": "react"
    },
    {
      "kind": "require-call",
      "path": "./cjs.js"
    },
    {
      "kind": "dynamic-import",
      "path": "./loader"
    }
  ]
  ```
</CodeGroup>

***

## Reference

```ts See Typescript Definitions expandable theme={"theme":{"light":"github-light","dark":"dracula"}}
type Loader = "jsx" | "js" | "ts" | "tsx";

interface TranspilerOptions {
  // Replace key with value. Value must be a JSON string.
  // { "process.env.NODE_ENV": "\"production\"" }
  define?: Record<string, string>,

  // Default loader for this transpiler
  loader?: Loader,

  // Default platform to target
  // This affects how import and/or require is used
  target?: "browser" | "bun" | "node",

  // Specify a tsconfig.json file as stringified JSON or an object
  // Use this to set a custom JSX factory, fragment, or import source
  // For example, if you want to use Preact instead of React. Or if you want to use Emotion.
  tsconfig?: string | TSConfig,

  // Replace imports with macros
  macro?: MacroMap,

  // Specify a set of exports to eliminate
  // Or rename certain exports
  exports?: {
      eliminate?: string[];
      replace?: Record<string, string>;
  },

  // Whether to remove unused imports from transpiled file
  // Default: false
  trimUnusedImports?: boolean,

  // Whether to enable a set of JSX optimizations
  // jsxOptimizationInline ...,

  // Experimental whitespace minification
  minifyWhitespace?: boolean,

  // Whether to inline constant values
  // Typically improves performance and decreases bundle size
  // Default: false
  inline?: boolean,
}

// Map import paths to macros
interface MacroMap {
  // {
  //   "react-relay": {
  //     "graphql": "bun-macro-relay/bun-macro-relay.tsx"
  //   }
  // }
  [packagePath: string]: {
    [importItemName: string]: string,
  },
}

class Bun.Transpiler {
  constructor(options: TranspilerOptions)

  transform(code: string, loader?: Loader): Promise<string>
  transformSync(code: string, loader?: Loader): string

  scan(code: string): {exports: string[], imports: Import[]}
  scanImports(code: string): Import[]
}

type Import = {
  path: string,
  kind:
  // import foo from 'bar'; in JavaScript
  | "import-statement"
  // require("foo") in JavaScript
  | "require-call"
  // require.resolve("foo") in JavaScript
  | "require-resolve"
  // Dynamic import() in JavaScript
  | "dynamic-import"
  // @import() in CSS
  | "import-rule"
  // url() in CSS
  | "url-token"
  // The import was injected by Bun
  | "internal"
  // Entry point (not common)
  | "entry-point-build"
  | "entry-point-run"
}

const transpiler = new Bun.Transpiler({ loader: "jsx" });
```


# Utils
Source: https://bun.com/docs/runtime/utils

Use Bun's utility functions to work with the runtime

## `Bun.version`

A `string` containing the version of the `bun` CLI that is currently running.

```ts terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
Bun.version;
// => "1.3.3"
```

## `Bun.revision`

The git commit of [Bun](https://github.com/oven-sh/bun) that was compiled to create the current `bun` CLI.

```ts terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
Bun.revision;
// => "f02561530fda1ee9396f51c8bc99b38716e38296"
```

## `Bun.env`

An alias for `process.env`.

## `Bun.main`

An absolute path to the entrypoint of the current program (the file you executed with `bun run`).

```ts script.ts theme={"theme":{"light":"github-light","dark":"dracula"}}
Bun.main;
// /path/to/script.ts
```

Use this to determine whether a script is running directly or another script is importing it.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
if (import.meta.path === Bun.main) {
  // this script is being directly executed
} else {
  // this file is being imported from another script
}
```

This check is analogous to the [`require.main = module` trick](https://stackoverflow.com/questions/6398196/detect-if-called-through-require-or-directly-by-command-line) in Node.js.

## `Bun.sleep()`

`Bun.sleep(ms: number)`

Returns a `Promise` that resolves after the given number of milliseconds.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
console.log("hello");
await Bun.sleep(1000);
console.log("hello one second later!");
```

Alternatively, pass a `Date` object to receive a `Promise` that resolves at that point in time.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const oneSecondInFuture = new Date(Date.now() + 1000);

console.log("hello");
await Bun.sleep(oneSecondInFuture);
console.log("hello one second later!");
```

## `Bun.sleepSync()`

`Bun.sleepSync(ms: number)`

A blocking synchronous version of `Bun.sleep`.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
console.log("hello");
Bun.sleepSync(1000); // blocks thread for one second
console.log("hello one second later!");
```

## `Bun.which()`

`Bun.which(bin: string)`

Returns the path to an executable, similar to typing `which` in your terminal.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const ls = Bun.which("ls");
console.log(ls); // "/usr/bin/ls"
```

By default Bun looks at the current `PATH` environment variable to determine the path. To configure `PATH`:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const ls = Bun.which("ls", {
  PATH: "/usr/local/bin:/usr/bin:/bin",
});
console.log(ls); // "/usr/bin/ls"
```

Pass a `cwd` option to resolve the executable from within a specific directory.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const ls = Bun.which("ls", {
  cwd: "/tmp",
  PATH: "",
});

console.log(ls); // null
```

This is a built-in alternative to the [`which`](https://www.npmjs.com/package/which) npm package.

## `Bun.randomUUIDv7()`

`Bun.randomUUIDv7()` returns a [UUID v7](https://www.ietf.org/archive/id/draft-peabody-dispatch-new-uuid-format-01.html#name-uuidv7-layout-and-bit-order), which is monotonic and suitable for sorting and databases.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
import { randomUUIDv7 } from "bun";

const id = randomUUIDv7();
// => "0192ce11-26d5-7dc3-9305-1426de888c5a"
```

A UUID v7 is a 128-bit value that encodes the current timestamp, a random value, and a counter. The timestamp is encoded using the most significant 48 bits, and the random value and counter are encoded using the remaining bits.

The `timestamp` parameter defaults to the current time in milliseconds. When the clock moves forward, Bun reseeds the counter to a new pseudo-random integer. Bun leaves the high bit of the 12-bit counter clear when reseeding, so at least 2048 values remain before rollover. If the clock has not advanced past the last emitted timestamp, Bun reuses the last emitted timestamp and increments the counter. If that counter rolls over, Bun bumps the emitted timestamp forward instead of wrapping the counter, so the returned UUIDs stay strictly increasing (RFC 9562 §6.2). The counter is atomic and threadsafe, so calls to `Bun.randomUUIDv7()` from many Workers in the same process at the same timestamp don't produce colliding counter values.

When you pass an explicit `timestamp`, Bun encodes that value verbatim and tracks a separate counter for it, so explicit-timestamp calls do not observe or alter the monotonic state used by the default path. Repeated calls with the same explicit timestamp increment that separate counter, and bump the emitted timestamp on rollover, so they stay sortable. A call with a different explicit timestamp reseeds that counter.

The final 8 bytes of the UUID, apart from the two variant bits, are a cryptographically secure random value. `Bun.randomUUIDv7()` uses the same random number generator as `crypto.randomUUID()`. That generator comes from BoringSSL. BoringSSL's randomness in turn comes from the platform-specific system random number generator, which the underlying hardware usually provides.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
namespace Bun {
  function randomUUIDv7(encoding?: "hex" | "base64" | "base64url" = "hex", timestamp?: number = Date.now()): string;
  /**
   * If you pass "buffer", you get a 16-byte buffer instead of a string.
   */
  function randomUUIDv7(encoding: "buffer", timestamp?: number = Date.now()): Buffer;

  // If you only pass a timestamp, you get a hex string
  function randomUUIDv7(timestamp?: number = Date.now()): string;
}
```

Pass `"buffer"` as the encoding to get a 16-byte buffer instead of a string. This can avoid string conversion overhead.

```ts buffer.ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const buffer = Bun.randomUUIDv7("buffer");
```

`base64` and `base64url` encodings are also supported when you want a slightly shorter string.

```ts base64.ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const base64 = Bun.randomUUIDv7("base64");
const base64url = Bun.randomUUIDv7("base64url");
```

## `Bun.peek()`

`Bun.peek(prom: Promise)`

Reads a promise's result without `await` or `.then`, but only if the promise has already fulfilled or rejected.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
import { peek } from "bun";

const promise = Promise.resolve("hi");

// no await!
const result = peek(promise);
console.log(result); // "hi"
```

Use it to avoid extraneous microticks in performance-sensitive code. It's an advanced API; review the following examples before using it in production.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
import { peek } from "bun";
import { expect, test } from "bun:test";

test("peek", () => {
  const promise = Promise.resolve(true);

  // no await necessary!
  expect(peek(promise)).toBe(true);

  // if we peek again, it returns the same value
  const again = peek(promise);
  expect(again).toBe(true);

  // if we peek a non-promise, it returns the value
  const value = peek(42);
  expect(value).toBe(42);

  // if we peek a pending promise, it returns the promise again
  const pending = new Promise(() => {});
  expect(peek(pending)).toBe(pending);

  // If we peek a rejected promise, it:
  // - returns the error
  // - does not mark the promise as handled
  const rejected = Promise.reject(new Error("Successfully tested promise rejection"));
  expect(peek(rejected).message).toBe("Successfully tested promise rejection");
});
```

`peek.status` reads the status of a promise without resolving it.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
import { peek } from "bun";
import { expect, test } from "bun:test";

test("peek.status", () => {
  const promise = Promise.resolve(true);
  expect(peek.status(promise)).toBe("fulfilled");

  const pending = new Promise(() => {});
  expect(peek.status(pending)).toBe("pending");

  const rejected = Promise.reject(new Error("oh nooo"));
  expect(peek.status(rejected)).toBe("rejected");
});
```

## `Bun.openInEditor()`

Opens a file in your default editor. Bun auto-detects your editor from the `$VISUAL` or `$EDITOR` environment variables.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const currentFile = import.meta.url;
Bun.openInEditor(currentFile);
```

You can override this with the `debug.editor` setting in your [`bunfig.toml`](/docs/runtime/bunfig).

```toml bunfig.toml icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[debug] # [!code ++]
editor = "code" # [!code ++]
```

Or specify an editor with the `editor` param. You can also specify a line and column number.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
Bun.openInEditor(import.meta.url, {
  editor: "vscode", // or "subl"
  line: 10,
  column: 5,
});
```

## `Bun.deepEquals()`

Recursively checks if two objects are equivalent. `expect().toEqual()` in `bun:test` uses this internally.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const foo = { a: 1, b: 2, c: { d: 3 } };

// true
Bun.deepEquals(foo, { a: 1, b: 2, c: { d: 3 } });

// false
Bun.deepEquals(foo, { a: 1, b: 2, c: { d: 4 } });
```

Pass a third boolean parameter to enable "strict" mode. `expect().toStrictEqual()` in the test runner uses this.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const a = { entries: [1, 2] };
const b = { entries: [1, 2], extra: undefined };

Bun.deepEquals(a, b); // => true
Bun.deepEquals(a, b, true); // => false
```

In strict mode, Bun considers the following unequal:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
// undefined values
Bun.deepEquals({}, { a: undefined }, true); // false

// undefined in arrays
Bun.deepEquals(["asdf"], ["asdf", undefined], true); // false

// sparse arrays
Bun.deepEquals([, 1], [undefined, 1], true); // false

// object literals vs instances w/ same properties
class Foo {
  a = 1;
}
Bun.deepEquals(new Foo(), { a: 1 }, true); // false
```

## `Bun.escapeHTML()`

`Bun.escapeHTML(value: string | object | number | boolean): string`

Escapes the following characters from an input string:

* `"` becomes `&quot;`
* `&` becomes `&amp;`
* `'` becomes `&#x27;`
* `<` becomes `&lt;`
* `>` becomes `&gt;`

This function is optimized for large input. On an M1X, it processes 480 MB/s -
20 GB/s, depending on how much data is being escaped and whether there is non-ASCII
text. Bun converts non-string types to a string before escaping.

## `Bun.stringWidth()`

<Note>\~6,756x faster `string-width` alternative</Note>

Get the column count of a string as a terminal would display it.
Supports ANSI escape codes, emoji, and wide characters.

Example usage:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
Bun.stringWidth("hello"); // => 5
Bun.stringWidth("\u001b[31mhello\u001b[0m"); // => 5
Bun.stringWidth("\u001b[31mhello\u001b[0m", { countAnsiEscapeCodes: true }); // => 12
```

Use it to align text in a terminal or to check whether a string contains ANSI escape codes.

The API matches the "string-width" npm package, so you can port existing code to Bun and vice versa.

[In this benchmark](https://github.com/oven-sh/bun/blob/5147c0ba7379d85d4d1ed0714b84d6544af917eb/bench/snippets/string-width.mjs#L13), `Bun.stringWidth` is \~6,756x faster than the `string-width` npm package for input larger than about 500 characters. Big thanks to [sindresorhus](https://github.com/sindresorhus) for their work on `string-width`.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
❯ bun string-width.mjs
cpu: 13th Gen Intel(R) Core(TM) i9-13900
runtime: bun 1.0.29 (x64-linux)

benchmark                                          time (avg)             (min … max)       p75       p99      p995
------------------------------------------------------------------------------------- -----------------------------
Bun.stringWidth     500 chars ascii              37.09 ns/iter   (36.77 ns … 41.11 ns)  37.07 ns  38.84 ns  38.99 ns

❯ node string-width.mjs

benchmark                                          time (avg)             (min … max)       p75       p99      p995
------------------------------------------------------------------------------------- -----------------------------
npm/string-width    500 chars ascii             249,710 ns/iter (239,970 ns … 293,180 ns) 250,930 ns  276,700 ns 281,450 ns
```

`Bun.stringWidth` is implemented in native code with SIMD instructions and accounts for Latin1, UTF-16, and UTF-8 encodings. It passes `string-width`'s tests.

<Accordion title="View full benchmark">
  1 nanosecond (ns) is 1 billionth of a second. For converting between units:

  | Unit | 1 Millisecond |
  | ---- | ------------- |
  | ns   | 1,000,000     |
  | µs   | 1,000         |
  | ms   | 1             |

  ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
  ❯ bun string-width.mjs
  cpu: 13th Gen Intel(R) Core(TM) i9-13900
  runtime: bun 1.0.29 (x64-linux)

  benchmark                                          time (avg)             (min … max)       p75       p99      p995
  ------------------------------------------------------------------------------------- -----------------------------
  Bun.stringWidth      5 chars ascii              16.45 ns/iter   (16.27 ns … 19.71 ns)  16.48 ns  16.93 ns  17.21 ns
  Bun.stringWidth     50 chars ascii              19.42 ns/iter   (18.61 ns … 27.85 ns)  19.35 ns   21.7 ns  22.31 ns
  Bun.stringWidth    500 chars ascii              37.09 ns/iter   (36.77 ns … 41.11 ns)  37.07 ns  38.84 ns  38.99 ns
  Bun.stringWidth  5,000 chars ascii              216.9 ns/iter  (215.8 ns … 228.54 ns) 216.23 ns 228.52 ns 228.53 ns
  Bun.stringWidth 25,000 chars ascii               1.01 µs/iter     (1.01 µs … 1.01 µs)   1.01 µs   1.01 µs   1.01 µs
  Bun.stringWidth      7 chars ascii+emoji         54.2 ns/iter   (53.36 ns … 58.19 ns)  54.23 ns  57.55 ns  57.94 ns
  Bun.stringWidth     70 chars ascii+emoji       354.26 ns/iter (350.51 ns … 363.96 ns) 355.93 ns 363.11 ns 363.96 ns
  Bun.stringWidth    700 chars ascii+emoji          3.3 µs/iter      (3.27 µs … 3.4 µs)    3.3 µs    3.4 µs    3.4 µs
  Bun.stringWidth  7,000 chars ascii+emoji        32.69 µs/iter   (32.22 µs … 45.27 µs)   32.7 µs  34.57 µs  34.68 µs
  Bun.stringWidth 35,000 chars ascii+emoji       163.35 µs/iter (161.17 µs … 170.79 µs) 163.82 µs 169.66 µs 169.93 µs
  Bun.stringWidth      8 chars ansi+emoji         66.15 ns/iter   (65.17 ns … 69.97 ns)  66.12 ns   69.8 ns  69.87 ns
  Bun.stringWidth     80 chars ansi+emoji        492.95 ns/iter  (488.05 ns … 499.5 ns)  494.8 ns 498.58 ns  499.5 ns
  Bun.stringWidth    800 chars ansi+emoji          4.73 µs/iter     (4.71 µs … 4.88 µs)   4.72 µs   4.88 µs   4.88 µs
  Bun.stringWidth  8,000 chars ansi+emoji         47.02 µs/iter   (46.37 µs … 67.44 µs)  46.96 µs  49.57 µs  49.63 µs
  Bun.stringWidth 40,000 chars ansi+emoji        234.45 µs/iter (231.78 µs … 240.98 µs) 234.92 µs 236.34 µs 236.62 µs
  Bun.stringWidth     19 chars ansi+emoji+ascii  135.46 ns/iter (133.67 ns … 143.26 ns) 135.32 ns 142.55 ns 142.77 ns
  Bun.stringWidth    190 chars ansi+emoji+ascii    1.17 µs/iter     (1.16 µs … 1.17 µs)   1.17 µs   1.17 µs   1.17 µs
  Bun.stringWidth  1,900 chars ansi+emoji+ascii   11.45 µs/iter   (11.26 µs … 20.41 µs)  11.45 µs  12.08 µs  12.11 µs
  Bun.stringWidth 19,000 chars ansi+emoji+ascii  114.06 µs/iter (112.86 µs … 120.06 µs) 114.25 µs 115.86 µs 116.15 µs
  Bun.stringWidth 95,000 chars ansi+emoji+ascii  572.69 µs/iter (565.52 µs … 607.22 µs) 572.45 µs 604.86 µs 605.21 µs
  ```

  ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
  ❯ node string-width.mjs
  cpu: 13th Gen Intel(R) Core(TM) i9-13900
  runtime: node v21.4.0 (x64-linux)

  benchmark                                           time (avg)             (min … max)       p75       p99      p995
  -------------------------------------------------------------------------------------- -----------------------------
  npm/string-width      5 chars ascii               3.19 µs/iter     (3.13 µs … 3.48 µs)   3.25 µs   3.48 µs   3.48 µs
  npm/string-width     50 chars ascii              20.09 µs/iter  (18.93 µs … 435.06 µs)  19.49 µs  21.89 µs  22.59 µs
  npm/string-width    500 chars ascii             249.71 µs/iter (239.97 µs … 293.18 µs) 250.93 µs  276.7 µs 281.45 µs
  npm/string-width  5,000 chars ascii               6.69 ms/iter     (6.58 ms … 6.76 ms)   6.72 ms   6.76 ms   6.76 ms
  npm/string-width 25,000 chars ascii             139.57 ms/iter (137.17 ms … 143.28 ms) 140.49 ms 143.28 ms 143.28 ms
  npm/string-width      7 chars ascii+emoji          3.7 µs/iter     (3.62 µs … 3.94 µs)   3.73 µs   3.94 µs   3.94 µs
  npm/string-width     70 chars ascii+emoji        23.93 µs/iter   (22.44 µs … 331.2 µs)  23.15 µs  25.98 µs   30.2 µs
  npm/string-width    700 chars ascii+emoji       251.65 µs/iter (237.78 µs … 444.69 µs) 252.92 µs 325.89 µs 354.08 µs
  npm/string-width  7,000 chars ascii+emoji         4.95 ms/iter     (4.82 ms … 5.19 ms)      5 ms   5.04 ms   5.19 ms
  npm/string-width 35,000 chars ascii+emoji        96.93 ms/iter  (94.39 ms … 102.58 ms)  97.68 ms 102.58 ms 102.58 ms
  npm/string-width      8 chars ansi+emoji          3.92 µs/iter     (3.45 µs … 4.57 µs)   4.09 µs   4.57 µs   4.57 µs
  npm/string-width     80 chars ansi+emoji         24.46 µs/iter     (22.87 µs … 4.2 ms)  23.54 µs  25.89 µs  27.41 µs
  npm/string-width    800 chars ansi+emoji        259.62 µs/iter (246.76 µs … 480.12 µs) 258.65 µs 349.84 µs 372.55 µs
  npm/string-width  8,000 chars ansi+emoji          5.46 ms/iter     (5.41 ms … 5.57 ms)   5.48 ms   5.55 ms   5.57 ms
  npm/string-width 40,000 chars ansi+emoji        108.91 ms/iter  (107.55 ms … 109.5 ms) 109.25 ms  109.5 ms  109.5 ms
  npm/string-width     19 chars ansi+emoji+ascii    6.53 µs/iter     (6.35 µs … 6.75 µs)   6.54 µs   6.75 µs   6.75 µs
  npm/string-width    190 chars ansi+emoji+ascii   55.52 µs/iter  (52.59 µs … 352.73 µs)  54.19 µs  80.77 µs 167.21 µs
  npm/string-width  1,900 chars ansi+emoji+ascii  701.71 µs/iter (653.94 µs … 893.78 µs)  715.3 µs 855.37 µs  872.9 µs
  npm/string-width 19,000 chars ansi+emoji+ascii   27.19 ms/iter   (26.89 ms … 27.41 ms)  27.28 ms  27.41 ms  27.41 ms
  npm/string-width 95,000 chars ansi+emoji+ascii     3.68 s/iter        (3.66 s … 3.7 s)    3.69 s     3.7 s     3.7 s
  ```
</Accordion>

TypeScript definition:

```ts expandable theme={"theme":{"light":"github-light","dark":"dracula"}}
namespace Bun {
  export function stringWidth(
    /**
     * The string to measure
     */
    input: string,
    options?: {
      /**
       * If `true`, count ANSI escape codes as part of the string width. If `false`, ANSI escape codes are ignored when calculating the string width.
       *
       * @default false
       */
      countAnsiEscapeCodes?: boolean;
      /**
       * When it's ambiugous and `true`, count emoji as 1 characters wide. If `false`, emoji are counted as 2 character wide.
       *
       * @default true
       */
      ambiguousIsNarrow?: boolean;
    },
  ): number;
}
```

***

## `Bun.fileURLToPath()`

Converts a `file://` URL to an absolute path.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const path = Bun.fileURLToPath(new URL("file:///foo/bar.txt"));
console.log(path); // "/foo/bar.txt"
```

***

## `Bun.pathToFileURL()`

Converts an absolute path to a `file://` URL.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const url = Bun.pathToFileURL("/foo/bar.txt");
console.log(url); // "file:///foo/bar.txt"
```

***

## `Bun.gzipSync()`

Compresses a `Uint8Array` using zlib's GZIP algorithm.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const buf = Buffer.from("hello".repeat(100)); // Buffer extends Uint8Array
const compressed = Bun.gzipSync(buf);

buf; // => Uint8Array(500)
compressed; // => Uint8Array(30)
```

Optionally, pass a parameters object as the second argument:

<Accordion title="zlib compression options">
  ```ts expandable theme={"theme":{"light":"github-light","dark":"dracula"}}
  export type ZlibCompressionOptions = {
    /**
     * The compression level to use. Must be between `-1` and `9`.
     * - A value of `-1` uses the default compression level (Currently `6`)
     * - A value of `0` gives no compression
     * - A value of `1` gives least compression, fastest speed
     * - A value of `9` gives best compression, slowest speed
     */
    level?: -1 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9;
    /**
     * How much memory should be allocated for the internal compression state.
     *
     * A value of `1` uses minimum memory but is slow and reduces compression ratio.
     *
     * A value of `9` uses maximum memory for optimal speed. The default is `8`.
     */
    memLevel?: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9;
    /**
     * The base 2 logarithm of the window size (the size of the history buffer).
     *
     * Larger values of this parameter result in better compression at the expense of memory usage.
     *
     * The following value ranges are supported:
     * - `9..15`: The output will have a zlib header and footer (Deflate)
     * - `-9..-15`: The output will **not** have a zlib header or footer (Raw Deflate)
     * - `25..31` (16+`9..15`): The output will have a gzip header and footer (gzip)
     *
     * The gzip header will have no file name, no extra data, no comment, no modification time (set to zero) and no header CRC.
     */
    windowBits?:
      | -9
      | -10
      | -11
      | -12
      | -13
      | -14
      | -15
      | 9
      | 10
      | 11
      | 12
      | 13
      | 14
      | 15
      | 25
      | 26
      | 27
      | 28
      | 29
      | 30
      | 31;
    /**
     * Tunes the compression algorithm.
     *
     * - `Z_DEFAULT_STRATEGY`: For normal data **(Default)**
     * - `Z_FILTERED`: For data produced by a filter or predictor
     * - `Z_HUFFMAN_ONLY`: Force Huffman encoding only (no string match)
     * - `Z_RLE`: Limit match distances to one (run-length encoding)
     * - `Z_FIXED` prevents the use of dynamic Huffman codes
     *
     * `Z_RLE` is designed to be almost as fast as `Z_HUFFMAN_ONLY`, but give better compression for PNG image data.
     *
     * `Z_FILTERED` forces more Huffman coding and less string matching, it is
     * somewhat intermediate between `Z_DEFAULT_STRATEGY` and `Z_HUFFMAN_ONLY`.
     * Filtered data consists mostly of small values with a somewhat random distribution.
     */
    strategy?: number;
  };
  ```
</Accordion>

***

## `Bun.gunzipSync()`

Decompresses a `Uint8Array` using zlib's GUNZIP algorithm.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const buf = Buffer.from("hello".repeat(100)); // Buffer extends Uint8Array
const compressed = Bun.gzipSync(buf);

const dec = new TextDecoder();
const uncompressed = Bun.gunzipSync(compressed);
dec.decode(uncompressed);
// => "hellohellohello..."
```

***

## `Bun.deflateSync()`

Compresses a `Uint8Array` using zlib's DEFLATE algorithm.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const buf = Buffer.from("hello".repeat(100));
const compressed = Bun.deflateSync(buf);

buf; // => Buffer(500)
compressed; // => Uint8Array(12)
```

The second argument supports the same set of configuration options as [`Bun.gzipSync`](#bun-gzipsync).

***

## `Bun.inflateSync()`

Decompresses a `Uint8Array` using zlib's INFLATE algorithm.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const buf = Buffer.from("hello".repeat(100));
const compressed = Bun.deflateSync(buf);

const dec = new TextDecoder();
const decompressed = Bun.inflateSync(compressed);
dec.decode(decompressed);
// => "hellohellohello..."
```

***

## `Bun.zstdCompress()` / `Bun.zstdCompressSync()`

Compresses a `Uint8Array` using the Zstandard algorithm.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const buf = Buffer.from("hello".repeat(100));

// Synchronous
const compressedSync = Bun.zstdCompressSync(buf);
// Asynchronous
const compressedAsync = await Bun.zstdCompress(buf);

// With compression level (1-22, default: 3)
const compressedLevel = Bun.zstdCompressSync(buf, { level: 6 });
```

## `Bun.zstdDecompress()` / `Bun.zstdDecompressSync()`

Decompresses a `Uint8Array` using the Zstandard algorithm.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const buf = Buffer.from("hello".repeat(100));
const compressed = Bun.zstdCompressSync(buf);

// Synchronous
const decompressedSync = Bun.zstdDecompressSync(compressed);
// Asynchronous
const decompressedAsync = await Bun.zstdDecompress(compressed);

const dec = new TextDecoder();
dec.decode(decompressedSync);
// => "hellohellohello..."
```

***

## `Bun.inspect()`

Serializes an object to a `string` exactly as `console.log` would print it.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const obj = { foo: "bar" };
Bun.inspect(obj);
// => '{\n  foo: "bar",\n}'

const arr = new Uint8Array([1, 2, 3]);
Bun.inspect(arr);
// => "Uint8Array(3) [ 1, 2, 3 ]"
```

### `Bun.inspect.custom`

The symbol Bun uses to implement `Bun.inspect`. Override it to customize how Bun prints your objects. It is identical to `util.inspect.custom` in Node.js.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
class Foo {
  [Bun.inspect.custom]() {
    return "foo";
  }
}

const foo = new Foo();
console.log(foo); // => "foo"
```

### `Bun.inspect.table(tabularData, properties, options)`

Format tabular data into a string. Like [`console.table`](https://developer.mozilla.org/en-US/docs/Web/API/console/table_static), except it returns a string rather than printing to the console.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
console.log(
  Bun.inspect.table([
    { a: 1, b: 2, c: 3 },
    { a: 4, b: 5, c: 6 },
    { a: 7, b: 8, c: 9 },
  ]),
);
//
// ┌───┬───┬───┬───┐
// │   │ a │ b │ c │
// ├───┼───┼───┼───┤
// │ 0 │ 1 │ 2 │ 3 │
// │ 1 │ 4 │ 5 │ 6 │
// │ 2 │ 7 │ 8 │ 9 │
// └───┴───┴───┴───┘
```

Pass an array of property names to display only those properties.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
console.log(
  Bun.inspect.table(
    [
      { a: 1, b: 2, c: 3 },
      { a: 4, b: 5, c: 6 },
    ],
    ["a", "c"],
  ),
);
//
// ┌───┬───┬───┐
// │   │ a │ c │
// ├───┼───┼───┤
// │ 0 │ 1 │ 3 │
// │ 1 │ 4 │ 6 │
// └───┴───┴───┘
```

Pass `{ colors: true }` to enable ANSI colors.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
console.log(
  Bun.inspect.table(
    [
      { a: 1, b: 2, c: 3 },
      { a: 4, b: 5, c: 6 },
    ],
    {
      colors: true,
    },
  ),
);
```

***

## `Bun.nanoseconds()`

Returns the number of nanoseconds since the current `bun` process started, as a `number`. Useful for high-precision timing and benchmarking.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
Bun.nanoseconds();
// => 7288958
```

***

## `Bun.readableStreamTo*()`

Bun implements a set of convenience functions for asynchronously consuming the body of a `ReadableStream` and converting it to various binary formats.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const stream = (await fetch("https://bun.com")).body;
stream; // => ReadableStream

await Bun.readableStreamToArrayBuffer(stream);
// => ArrayBuffer

await Bun.readableStreamToBytes(stream);
// => Uint8Array

await Bun.readableStreamToBlob(stream);
// => Blob

await Bun.readableStreamToJSON(stream);
// => object

await Bun.readableStreamToText(stream);
// => string

// returns all chunks as an array
await Bun.readableStreamToArray(stream);
// => unknown[]

// returns all chunks as a FormData object (encoded as x-www-form-urlencoded)
await Bun.readableStreamToFormData(stream);

// returns all chunks as a FormData object (encoded as multipart/form-data)
await Bun.readableStreamToFormData(stream, multipartFormBoundary);
```

***

## `Bun.resolveSync()`

Resolves a file path or module specifier using Bun's internal [module resolution](/docs/runtime/module-resolution) algorithm. The first argument is the path to resolve, and the second argument is the "root". If nothing matches, it throws an `Error`.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
Bun.resolveSync("./foo.ts", "/path/to/project");
// => "/path/to/project/foo.ts"

Bun.resolveSync("zod", "/path/to/project");
// => "/path/to/project/node_modules/zod/index.ts"
```

To resolve relative to the current working directory, pass `process.cwd()` or `"."` as the root.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
Bun.resolveSync("./foo.ts", process.cwd());
Bun.resolveSync("./foo.ts", "/path/to/project");
```

To resolve relative to the directory containing the current file, pass `import.meta.dir`.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
Bun.resolveSync("./foo.ts", import.meta.dir);
```

***

## `Bun.stripANSI()`

<Note>\~6-57x faster `strip-ansi` alternative</Note>

`Bun.stripANSI(text: string): string`

Strip ANSI escape codes from a string. Use it to remove colors and formatting from terminal output.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const coloredText = "\u001b[31mHello\u001b[0m \u001b[32mWorld\u001b[0m";
const plainText = Bun.stripANSI(coloredText);
console.log(plainText); // => "Hello World"

// Works with various ANSI codes
const formatted = "\u001b[1m\u001b[4mBold and underlined\u001b[0m";
console.log(Bun.stripANSI(formatted)); // => "Bold and underlined"
```

`Bun.stripANSI` is faster than the [`strip-ansi`](https://www.npmjs.com/package/strip-ansi) npm package:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun bench/snippets/strip-ansi.mjs
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
cpu: Apple M3 Max
runtime: bun 1.2.21 (arm64-darwin)

benchmark                               avg (min … max) p75 / p99
------------------------------------------------------- ----------
Bun.stripANSI      11 chars no-ansi        8.13 ns/iter   8.27 ns
                                   (7.45 ns … 33.59 ns)  10.29 ns

Bun.stripANSI      13 chars ansi          51.68 ns/iter  52.51 ns
                                 (46.16 ns … 113.71 ns)  57.71 ns

Bun.stripANSI  16,384 chars long-no-ansi 298.39 ns/iter 305.44 ns
                                (281.50 ns … 331.65 ns) 320.70 ns

Bun.stripANSI 212,992 chars long-ansi    227.65 µs/iter 234.50 µs
                                (216.46 µs … 401.92 µs) 262.25 µs
```

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
node bench/snippets/strip-ansi.mjs
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
cpu: Apple M3 Max
runtime: node 24.6.0 (arm64-darwin)

benchmark                                avg (min … max) p75 / p99
-------------------------------------------------------- ---------
npm/strip-ansi      11 chars no-ansi      466.79 ns/iter 468.67 ns
                                 (454.08 ns … 570.67 ns) 543.67 ns

npm/strip-ansi      13 chars ansi         546.77 ns/iter 550.23 ns
                                 (532.74 ns … 651.08 ns) 590.35 ns

npm/strip-ansi  16,384 chars long-no-ansi   4.85 µs/iter   4.89 µs
                                     (4.71 µs … 5.00 µs)   4.98 µs

npm/strip-ansi 212,992 chars long-ansi      1.36 ms/iter   1.38 ms
                                     (1.27 ms … 1.73 ms)   1.49 ms

```

***

## `Bun.wrapAnsi()`

<Note>Drop-in replacement for `wrap-ansi` npm package</Note>

`Bun.wrapAnsi(input: string, columns: number, options?: WrapAnsiOptions): string`

Wrap text to a specified column width. It preserves ANSI escape codes and hyperlinks and handles Unicode/emoji width correctly. This is a native alternative to the [`wrap-ansi`](https://www.npmjs.com/package/wrap-ansi) npm package.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
// Basic wrapping at 20 columns
Bun.wrapAnsi("The quick brown fox jumps over the lazy dog", 20);
// => "The quick brown fox\njumps over the lazy\ndog"

// Preserves ANSI escape codes: an open style is closed at each row end
// and re-opened on the next row so every row renders correctly on its own
Bun.wrapAnsi("\u001b[31mThe quick brown fox jumps over the lazy dog\u001b[0m", 20);
// => "\u001b[31mThe quick brown fox\u001b[39m\n\u001b[31mjumps over the lazy\u001b[39m\n\u001b[31mdog\u001b[0m"
```

### Options

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
Bun.wrapAnsi("Hello World", 5, {
  hard: true, // Break words that exceed column width (default: false)
  wordWrap: true, // Wrap at word boundaries (default: true)
  trim: true, // Trim leading/trailing whitespace per line (default: true)
  ambiguousIsNarrow: true, // Treat ambiguous-width characters as narrow (default: true)
});
```

| Option              | Default | Description                                                                                                     |
| ------------------- | ------- | --------------------------------------------------------------------------------------------------------------- |
| `hard`              | `false` | If `true`, break words in the middle if they exceed the column width.                                           |
| `wordWrap`          | `true`  | If `true`, wrap at word boundaries. If `false`, break every line at exactly the column width.                   |
| `trim`              | `true`  | If `true`, trim leading and trailing whitespace from each line.                                                 |
| `ambiguousIsNarrow` | `true`  | If `true`, treat ambiguous-width Unicode characters as 1 column wide. If `false`, treat them as 2 columns wide. |

TypeScript definition:

```ts expandable theme={"theme":{"light":"github-light","dark":"dracula"}}
namespace Bun {
  export function wrapAnsi(
    /**
     * The string to wrap
     */
    input: string,
    /**
     * The maximum column width
     */
    columns: number,
    /**
     * Wrapping options
     */
    options?: {
      /**
       * If `true`, break words in the middle if they don't fit on a line.
       * If `false`, only break at word boundaries.
       *
       * @default false
       */
      hard?: boolean;
      /**
       * If `true`, wrap at word boundaries when possible.
       * If `false`, break every line at exactly the column width (characters
       * are split wherever the limit falls, ignoring word boundaries).
       *
       * @default true
       */
      wordWrap?: boolean;
      /**
       * If `true`, trim leading and trailing whitespace from each line.
       * If `false`, preserve whitespace.
       *
       * @default true
       */
      trim?: boolean;
      /**
       * When it's ambiguous and `true`, count ambiguous width characters as 1 character wide.
       * If `false`, count them as 2 characters wide.
       *
       * @default true
       */
      ambiguousIsNarrow?: boolean;
    },
  ): string;
}
```

***

## `serialize` & `deserialize` in `bun:jsc`

To save a JavaScript value into a SharedArrayBuffer & back, use `serialize` and `deserialize` from the `"bun:jsc"` module.

```js theme={"theme":{"light":"github-light","dark":"dracula"}}
import { serialize, deserialize } from "bun:jsc";

const buf = serialize({ foo: "bar" });
const obj = deserialize(buf);
console.log(obj); // => { foo: "bar" }
```

Internally, [`structuredClone`](https://developer.mozilla.org/en-US/docs/Web/API/structuredClone) and [`postMessage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage) serialize and deserialize the same way. `serialize` and `deserialize` expose the underlying [HTML Structured Clone Algorithm](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm) to JavaScript as a SharedArrayBuffer.

***

## `estimateShallowMemoryUsageOf` in `bun:jsc`

The `estimateShallowMemoryUsageOf` function returns a best-effort estimate of the memory usage of an object in bytes, excluding the memory usage of properties or other objects it references. For accurate per-object memory usage, use `Bun.generateHeapSnapshot`.

```js theme={"theme":{"light":"github-light","dark":"dracula"}}
import { estimateShallowMemoryUsageOf } from "bun:jsc";

const obj = { foo: "bar" };
const usage = estimateShallowMemoryUsageOf(obj);
console.log(usage); // => 16

const buffer = Buffer.alloc(1024 * 1024);
estimateShallowMemoryUsageOf(buffer);
// => 1048624

const req = new Request("https://bun.com");
estimateShallowMemoryUsageOf(req);
// => 167

const array = Array(1024).fill({ a: 1 });
// Arrays are usually not stored contiguously in memory, so this will not return a useful value (which isn't a bug).
estimateShallowMemoryUsageOf(array);
// => 16
```


# Watch Mode
Source: https://bun.com/docs/runtime/watch-mode

Automatic reloading in Bun with --watch and --hot modes

Bun supports two kinds of automatic reloading:

* `--watch` mode, which hard restarts Bun's process when imported files change.
* `--hot` mode, which soft reloads the code (without restarting the process) when imported files change.

***

## `--watch` mode

Watch mode works with `bun test` and `bun build`, and when running TypeScript, JSX, and JavaScript files.

To run a file in `--watch` mode:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun --watch index.tsx
```

To run your tests in `--watch` mode:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun --watch test
```

In `--watch` mode, Bun keeps track of all imported files and watches them for changes. When a file changes, Bun restarts the process with the same CLI arguments and environment variables as the initial run. If Bun crashes, `--watch` attempts to restart the process.

<Note>
  **⚡️ Reloads are fast.** The filesystem watchers you're probably used to have several layers of libraries wrapping the native APIs or, worse, rely on polling.

  Instead, Bun uses the operating system's native filesystem watcher APIs, like kqueue or inotify, to detect file changes. Bun also applies several optimizations to scale to larger projects, such as setting a high rlimit for file descriptors, statically allocating file path buffers, and reusing file descriptors when possible.
</Note>

The following examples show Bun live-reloading a file as you edit it, with VSCode configured to save the file [on each keystroke](https://code.visualstudio.com/docs/editor/codebasics#_save-auto-save).

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun run --watch watchy.tsx
```

```tsx title="watchy.tsx" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { serve } from "bun";

console.log("I restarted at:", Date.now());

serve({
  port: 4003,
  fetch(request) {
    return new Response("Sup");
  },
});
```

<Frame>
  ![bun watch gif](https://user-images.githubusercontent.com/709451/228439002-7b9fad11-0db2-4e48-b82d-2b88c8625625.gif)
</Frame>

Running `bun test` in watch mode with `save-on-keypress` enabled:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun --watch test
```

<Frame>
  ![bun test gif](https://user-images.githubusercontent.com/709451/228396976-38a23864-4a1d-4c96-87cc-04e5181bf459.gif)
</Frame>

<Note>
  The **`--no-clear-screen`** flag, like TypeScript's `--preserveWatchOutput`, keeps Bun from clearing the terminal in
  watch mode. Use it when running multiple `bun build --watch` commands at the same time with a tool like
  `concurrently`, where one instance clearing the screen could hide another's errors: `bun build --watch   --no-clear-screen`.
</Note>

<Note>
  Before each restart, `bun run --watch` runs the handlers your script registered for the kill signal. The default is
  `SIGTERM`, matching the signal Node.js sends its watched process. Use **`--watch-kill-signal`** to pick a different
  signal, e.g. `bun --watch --watch-kill-signal SIGINT index.ts`.
</Note>

***

## `--hot` mode

Use `bun --hot` to enable hot reloading when executing code with Bun. Unlike `--watch` mode, Bun doesn't hard-restart the entire process. It detects code changes and updates its internal module cache with the new code.

<Note>
  Bun's `--hot` is not the same as hot reloading in the browser. Many frameworks provide a "hot reloading" experience,
  where you can edit & save your frontend code (say, a React component) and see the changes reflected in the browser
  without refreshing the page. Bun's `--hot` is the server-side equivalent of this experience. To get hot reloading in
  the browser, use a framework like [Vite](https://vite.dev).
</Note>

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun --hot server.ts
```

Starting from the entrypoint (`server.ts` in this example), Bun builds a registry of all imported source files (excluding those in `node_modules`) and watches them for changes. When a file changes, Bun performs a "soft reload". Bun re-evaluates all files, but global state (notably, the `globalThis` object) persists.

```ts title="server.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
// make TypeScript happy
declare global {
  var count: number;
}

globalThis.count ??= 0;
console.log(`Reloaded ${globalThis.count} times`);
globalThis.count++;

// prevent `bun run` from exiting
setInterval(function () {}, 1000000);
```

If you run this file with `bun --hot server.ts`, you'll see the reload count increment every time you save the file.

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun --hot server.ts
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
Reloaded 1 times
Reloaded 2 times
Reloaded 3 times
```

Traditional file watchers like `nodemon` restart the entire process, so HTTP servers and other stateful objects are lost. By contrast, `bun --hot` reflects the updated code without restarting the process.

### HTTP servers

You can update your HTTP request handler without shutting down the server: when you save the file, Bun reloads the server with the updated code without restarting the process. This results in seriously fast refresh speeds.

```ts title="server.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
globalThis.count ??= 0;
globalThis.count++;

Bun.serve({
  fetch(req: Request) {
    return new Response(`Reloaded ${globalThis.count} times`);
  },
  port: 3000,
});
```

<Note>
  Support for Vite's `import.meta.hot` is planned, to enable better lifecycle management for hot reloading and to align with the ecosystem.
</Note>

<Accordion title="Implementation details">
  On hot reload, Bun:

  * Resets the internal `require` cache and ES module registry (`Loader.registry`)
  * Runs the garbage collector synchronously (to minimize memory leaks, at the cost of runtime performance)
  * Re-transpiles all of your code from scratch (including sourcemaps)
  * Re-evaluates the code with JavaScriptCore

  This implementation isn't particularly optimized. It re-transpiles files that haven't changed. It makes no attempt at incremental compilation. It's a starting point.
</Accordion>


# Web APIs
Source: https://bun.com/docs/runtime/web-apis

Web-standard APIs supported by Bun for server-side JavaScript

Some Web APIs, like the [DOM API](https://developer.mozilla.org/en-US/docs/Web/API/HTML_DOM_API#html_dom_api_interfaces) and [History API](https://developer.mozilla.org/en-US/docs/Web/API/History_API), aren't relevant in a server-first runtime like Bun. Many others are broadly useful outside the browser; when possible, Bun implements these Web-standard APIs instead of introducing new ones.

Bun partially or completely supports the following Web APIs.

| Category              | APIs                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| HTTP                  | [`fetch`](https://developer.mozilla.org/en-US/docs/Web/API/fetch), [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response), [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request), [`Headers`](https://developer.mozilla.org/en-US/docs/Web/API/Headers), [`AbortController`](https://developer.mozilla.org/en-US/docs/Web/API/AbortController), [`AbortSignal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal)                                                                                                                                                                                                                              |
| URLs                  | [`URL`](https://developer.mozilla.org/en-US/docs/Web/API/URL), [`URLSearchParams`](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| Web Workers           | [`Worker`](https://developer.mozilla.org/en-US/docs/Web/API/Worker), [`self.postMessage`](https://developer.mozilla.org/en-US/docs/Web/API/DedicatedWorkerGlobalScope/postMessage), [`structuredClone`](https://developer.mozilla.org/en-US/docs/Web/API/structuredClone), [`MessagePort`](https://developer.mozilla.org/en-US/docs/Web/API/MessagePort), [`MessageChannel`](https://developer.mozilla.org/en-US/docs/Web/API/MessageChannel), [`BroadcastChannel`](https://developer.mozilla.org/en-US/docs/Web/API/BroadcastChannel)                                                                                                                                                      |
| Streams               | [`ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream), [`WritableStream`](https://developer.mozilla.org/en-US/docs/Web/API/WritableStream), [`TransformStream`](https://developer.mozilla.org/en-US/docs/Web/API/TransformStream), [`ByteLengthQueuingStrategy`](https://developer.mozilla.org/en-US/docs/Web/API/ByteLengthQueuingStrategy), [`CountQueuingStrategy`](https://developer.mozilla.org/en-US/docs/Web/API/CountQueuingStrategy) and associated classes                                                                                                                                                                                          |
| Blob                  | [`Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Blob)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| WebSockets            | [`WebSocket`](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
| Encoding and decoding | [`Uint8Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array), [`Uint8Array.prototype.toBase64()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array/toBase64), [`Uint8Array.fromBase64()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array/fromBase64), [`TextEncoder`](https://developer.mozilla.org/en-US/docs/Web/API/TextEncoder), [`TextDecoder`](https://developer.mozilla.org/en-US/docs/Web/API/TextDecoder), [`atob`](https://developer.mozilla.org/en-US/docs/Web/API/atob), [`btoa`](https://developer.mozilla.org/en-US/docs/Web/API/btoa) |
| JSON                  | [`JSON`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| Timeouts              | [`setTimeout`](https://developer.mozilla.org/en-US/docs/Web/API/setTimeout), [`clearTimeout`](https://developer.mozilla.org/en-US/docs/Web/API/clearTimeout)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| Intervals             | [`setInterval`](https://developer.mozilla.org/en-US/docs/Web/API/setInterval), [`clearInterval`](https://developer.mozilla.org/en-US/docs/Web/API/clearInterval)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| Crypto                | [`crypto`](https://developer.mozilla.org/en-US/docs/Web/API/Crypto), [`SubtleCrypto`](https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto), [`CryptoKey`](https://developer.mozilla.org/en-US/docs/Web/API/CryptoKey)                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| Debugging             | [`console`](https://developer.mozilla.org/en-US/docs/Web/API/console), [`performance`](https://developer.mozilla.org/en-US/docs/Web/API/Performance)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| Microtasks            | [`queueMicrotask`](https://developer.mozilla.org/en-US/docs/Web/API/queueMicrotask)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
| Errors                | [`reportError`](https://developer.mozilla.org/en-US/docs/Web/API/reportError)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| User interaction      | [`alert`](https://developer.mozilla.org/en-US/docs/Web/API/Window/alert), [`confirm`](https://developer.mozilla.org/en-US/docs/Web/API/Window/confirm), [`prompt`](https://developer.mozilla.org/en-US/docs/Web/API/Window/prompt) (intended for interactive CLIs)                                                                                                                                                                                                                                                                                                                                                                                                                          |
| Realms                | [`ShadowRealm`](https://github.com/tc39/proposal-shadowrealm)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| Events                | [`EventTarget`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget), [`Event`](https://developer.mozilla.org/en-US/docs/Web/API/Event), [`ErrorEvent`](https://developer.mozilla.org/en-US/docs/Web/API/ErrorEvent), [`CloseEvent`](https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent), [`MessageEvent`](https://developer.mozilla.org/en-US/docs/Web/API/MessageEvent)                                                                                                                                                                                                                                                                                                 |


# WebView
Source: https://bun.com/docs/runtime/webview

Control a headless browser from Bun for automation, testing, and scraping — zero dependencies on macOS, Chrome DevTools Protocol everywhere else

`Bun.WebView` is a headless browser built into the runtime. Use it to load pages, run JavaScript inside them, simulate real user input, and capture screenshots — without Puppeteer, Playwright, or a separate browser download.

<Warning>This API is experimental and may change in future releases.</Warning>

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
await using view = new Bun.WebView();

await view.navigate("https://example.com");
await view.click("a[href]"); // waits for the link to be clickable
const title = await view.evaluate("document.title");

await Bun.write("page.png", await view.screenshot());
```

On macOS, `Bun.WebView` uses the system's `WKWebView` — nothing to install. On Linux it drives an installed Chrome, Chromium, Edge, or Brave over the Chrome DevTools Protocol.

Each view runs its page in a separate renderer process. All input methods (`click`, `type`, `press`, `scroll`) dispatch **native** browser events, so the page sees `isTrusted: true` — the same as a real user.

***

## Creating a view

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const view = new Bun.WebView({
  width: 1280, // viewport width in CSS pixels (1-16384, default 800)
  height: 720, // viewport height in CSS pixels (1-16384, default 600)
  url: "https://bun.com", // optional: start navigating immediately
});
```

The constructor is synchronous — it returns immediately and spawns the browser subprocess in the background. The first operation you `await` (such as `navigate()` or `evaluate()`) waits for the browser to be ready.

If you pass `url`, the view begins navigating before the constructor returns. Passing `url` is equivalent to calling `view.navigate(url)` on the next line.

### Automatic cleanup with `using`

`Bun.WebView` implements `Symbol.dispose` and `Symbol.asyncDispose`, so you can use `using` or `await using` to close the view automatically when it goes out of scope:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
{
  await using view = new Bun.WebView();
  await view.navigate("https://example.com");
  // ...
} // view.close() called automatically here
```

### Persistent storage

By default, each view uses **ephemeral** in-memory storage — cookies, `localStorage`, IndexedDB, and cache are discarded when the view closes. To persist state across runs, pass a directory:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const view = new Bun.WebView({
  dataStore: { directory: "./browser-profile" },
});
```

Views that share the same `directory` share cookies and storage. Pass `dataStore: "ephemeral"` (the default) to opt back into in-memory storage explicitly.

<Note>
  With the Chrome backend, `dataStore.directory` maps to `--user-data-dir` and applies to the **entire Chrome process**,
  not per-view. Bun spawns Chrome once per Bun process, so the first view's directory wins for all subsequent views.
</Note>

<Note>
  With the WebKit backend, persistent storage requires macOS 15.2+. On older macOS versions, use `dataStore:
      "ephemeral"` (the default).
</Note>

***

## Backends

`Bun.WebView` supports two rendering engines. The default depends on your platform:

| Backend    | Engine    | Platforms     | Requirements                                                                         |
| ---------- | --------- | ------------- | ------------------------------------------------------------------------------------ |
| `"webkit"` | WKWebView | macOS only    | None — uses the system `WebKit.framework`                                            |
| `"chrome"` | Blink     | macOS / Linux | Chrome, Chromium, Edge, or Brave installed (or Playwright's `chrome-headless-shell`) |

On macOS the default is `"webkit"`; elsewhere it's `"chrome"`. Requesting `backend: "webkit"` on a non-macOS platform throws.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
// Force Chrome on macOS
const view = new Bun.WebView({ backend: "chrome" });
```

### How the WebKit backend works

Bun spawns a lightweight host subprocess (the `bun` binary itself, re-executed in a special mode) that owns the `WKWebView` on its main thread. Your Bun process talks to it over a Unix socket using a compact binary protocol. Bun spawns the host process once, and every `"webkit"` view in your program shares it.

### How the Chrome backend works

Bun either **connects** to an already-running Chrome over a WebSocket, or **spawns** a headless Chrome subprocess and talks to it over a pipe (`--remote-debugging-pipe`). Either way, communication uses the [Chrome DevTools Protocol](https://chromedevtools.github.io/devtools-protocol/).

Bun spawns (or connects to) Chrome once per Bun process. Each `new Bun.WebView({ backend: "chrome" })` creates a new tab with `Target.createTarget` in that single Chrome instance.

#### Finding the Chrome executable

When Bun needs to spawn Chrome, it searches in this order:

1. The `path` you passed in `backend: { type: "chrome", path: "..." }`
2. The `BUN_CHROME_PATH` environment variable
3. `$PATH` (`google-chrome-stable`, `google-chrome`, `chromium-browser`, `chromium`, `brave-browser`, `microsoft-edge`, `chrome`)
4. Standard install locations (`/Applications/Google Chrome.app`, `~/Applications/...`, `/usr/bin/...`, `/snap/bin/...`)
5. Playwright's cache (`~/Library/Caches/ms-playwright` or `~/.cache/ms-playwright`) for `chrome-headless-shell`

If Bun finds none, the constructor throws.

<h4>
  Connecting to an already-running Chrome
</h4>

By default, before spawning, Bun reads the `DevToolsActivePort` file from standard profile directories to check whether a Chrome-family browser is **already running** with remote debugging enabled. If found, Bun connects to that browser over WebSocket instead of spawning a new one — your views open as tabs in your existing browser.

To enable remote debugging in a running Chrome, visit `chrome://inspect/#remote-debugging` and flip the toggle, or launch Chrome with `--remote-debugging-port=9222`. Chrome prompts for permission on each new connection when you use the `chrome://inspect` toggle.

To control this behavior explicitly, use the object form of `backend`:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
// Always spawn a fresh headless Chrome; never auto-connect
new Bun.WebView({
  backend: { type: "chrome", url: false },
});

// Connect to a specific DevTools WebSocket URL
new Bun.WebView({
  backend: {
    type: "chrome",
    url: "ws://127.0.0.1:9222/devtools/browser/abc123...",
  },
});
```

If auto-detect finds a stale `DevToolsActivePort` file (Chrome crashed or restarted), the WebSocket connect fails and Bun transparently falls back to spawning its own Chrome. An explicit `url: "ws://..."` does **not** fall back — a failed connection throws.

<Note>
  Passing `path` or `argv` implies spawn mode and skips auto-detect. `url: "ws://..."` cannot be combined with `path` or
  `argv`.
</Note>

#### Launch flags

When spawning, Bun passes a minimal flag set:

```
--user-data-dir=<temp> --remote-debugging-pipe --headless --no-first-run
--no-default-browser-check --disable-gpu --disable-extensions
--disable-background-networking --disable-background-timer-throttling
--disable-backgrounding-occluded-windows --disable-renderer-backgrounding
--disable-ipc-flooding-protection --no-startup-window
```

Append your own with `argv` — Chrome resolves duplicate switches last-wins, so you can override any default:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
new Bun.WebView({
  backend: {
    type: "chrome",
    argv: ["--headless=new", "--lang=ja-JP", "--window-size=1920,1080"],
  },
});
```

#### Subprocess output

Browser subprocess stdout/stderr are silenced by default. Chrome in particular is noisy on stderr (font-config warnings, GCM registration, updater checks). To see it — useful when Chrome crashes silently — pass `"inherit"`:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
new Bun.WebView({
  backend: { type: "chrome", stderr: "inherit", stdout: "inherit" },
});
```

The `"webkit"` backend accepts the same `stdout`/`stderr` options.

***

## Navigation

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
await view.navigate("https://example.com");
await view.navigate("data:text/html,<h1>hello</h1>");
await view.navigate("file:///path/to/index.html");
```

`navigate()` resolves when the main frame's `load` event fires. After it resolves, `view.url` and `view.title` reflect the new page, and `view.loading` is `false`.

If the navigation fails (DNS failure, connection refused, invalid URL), the promise rejects with an `Error` describing the failure.

Only one navigation may be in flight per view at a time. Calling `navigate()` while another is pending throws `ERR_INVALID_STATE` synchronously.

### History

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
await view.goBack(); // like the browser's back button
await view.goForward(); // like the browser's forward button
await view.reload(); // reload the current page
```

Calling `goBack()` at the beginning of history (or `goForward()` at the end) resolves `undefined` without navigating — it doesn't reject.

### Navigation callbacks

Set `onNavigated` and `onNavigationFailed` to observe every navigation, including ones triggered by the page itself (link clicks, `location.href = ...`, redirects) and by `reload()`/`goBack()`/`goForward()`:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
view.onNavigated = (url, title) => {
  console.log("Loaded", url);
};

view.onNavigationFailed = error => {
  console.error("Navigation failed:", error.message);
};
```

These fire **before** the corresponding `navigate()` promise settles, so by the time `await view.navigate(...)` returns, your callback has already run. Set to `null` to remove.

***

## Evaluating JavaScript

Run an expression in the page's main frame and get its result back as a native JavaScript value:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const title = await view.evaluate("document.title");
const items = await view.evaluate("[...document.querySelectorAll('li')].map(li => li.textContent)");
const user = await view.evaluate("({ name: 'bun', ok: true })");
```

Bun wraps the script as `await (<your script>)`, so:

* It must be an **expression**, not a statement sequence. For multiple statements, wrap in an IIFE: `evaluate("(() => { let x = foo(); return x + 1 })()")`.
* If it evaluates to a `Promise`, `evaluate()` awaits the promise and returns its resolved value.

The result round-trips through `JSON.stringify` in the page and `JSON.parse` in Bun. Arrays and plain objects come back as real structures; `undefined`, functions, and symbols resolve to `undefined`; circular references reject.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
await view.evaluate("42"); // 42
await view.evaluate("[1, 2, 3]"); // [1, 2, 3]
await view.evaluate("undefined"); // undefined
await view.evaluate("() => 1"); // undefined (functions don't serialize)
await view.evaluate("fetch('/api').then(r => r.json())"); // awaited
```

If the script throws (or returns a rejected promise), `evaluate()` rejects with an `Error` whose message comes from the page-side exception.

Only one `evaluate()` may be in flight per view at a time; a second concurrent call throws `ERR_INVALID_STATE`.

***

## Screenshots

Capture the current viewport as an image:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const png = await view.screenshot();
await Bun.write("page.png", png);
```

### Image format

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
await view.screenshot({ format: "png" }); // lossless (default)
await view.screenshot({ format: "jpeg", quality: 90 }); // lossy, quality 0-100 (default 80)
await view.screenshot({ format: "webp", quality: 75 }); // Chrome backend only
```

`quality` is ignored for PNG. `"webp"` is only available with `backend: "chrome"` — the WebKit backend throws.

### Return type

The `encoding` option controls how Bun hands back the image bytes:

| `encoding`           | Returns                          | Notes                                                                                                    |
| -------------------- | -------------------------------- | -------------------------------------------------------------------------------------------------------- |
| `"blob"` *(default)* | `Blob`                           | MIME type set automatically. Zero-copy mmap-backed on WebKit. Works with `Bun.write()`, `new Response()` |
| `"buffer"`           | `Buffer`                         | Node `Buffer`. Zero-copy mmap-backed on WebKit                                                           |
| `"base64"`           | `string`                         | Base64-encoded. Zero-decode on Chrome (CDP returns base64 natively)                                      |
| `"shmem"`            | `{ name: string, size: number }` | POSIX shared-memory segment name. Caller owns `shm_unlink`. Not supported on Windows                     |

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const buf = await view.screenshot({ encoding: "buffer" });
console.log(buf[0] === 0x89); // PNG magic byte

const b64 = await view.screenshot({ encoding: "base64" });
console.log(`<img src="data:image/png;base64,${b64}">`);
```

#### Shared memory for terminal graphics

`encoding: "shmem"` is designed for Kitty's [terminal graphics protocol](https://sw.kovidgoyal.net/kitty/graphics-protocol/) `t=s` transmission mode. Bun writes the image to a POSIX shared-memory segment and returns its name. The terminal reads it directly and unlinks it when done. No copying through the pipe.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const { name, size } = await view.screenshot({ encoding: "shmem" });
process.stdout.write(`\x1b_Gf=100,t=s,a=T,S=${size};${btoa(name)}\x1b\\`);
// Kitty reads the PNG from shared memory and unlinks the segment
```

On WebKit, the shm name looks like `/bun-webview-<pid>-<seq>`; on Chrome, `/bun-chrome-<pid>-<seq>`. If you request `"shmem"` and don't hand the name to something that will `shm_unlink` it, the segment leaks until your process exits.

***

## Input simulation

All input methods dispatch **native** browser events. The page receives `pointerdown`/`mousedown`/`keydown`/`wheel` events with `isTrusted: true`. CSS `:active` and `:hover` states apply. Default actions (form submission, link navigation, text selection) fire exactly as if a user performed them.

### Clicking

Click at viewport coordinates:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
await view.click(150, 200);
await view.click(150, 200, { button: "right" });
await view.click(150, 200, { clickCount: 2 }); // double-click
await view.click(150, 200, { modifiers: ["Shift", "Meta"] });
```

The promise resolves **after** the page has processed the full `mousedown` → `mouseup` → `click` sequence, including any JavaScript handlers. No polling needed — a subsequent `evaluate()` sees the result.

#### Clicking by selector

Pass a CSS selector instead of coordinates and Bun waits for the element to become **actionable**, then clicks its center:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
await view.click("#submit");
await view.click("button.primary", { timeout: 5000 });
```

An element is actionable when it:

* exists in the DOM
* has a non-zero bounding box
* is inside the viewport
* has been **stable** (bounding box unchanged) for two consecutive animation frames
* is the topmost element at its center point (not covered by an overlay)

The check runs page-side at `requestAnimationFrame` rate. If the element never becomes actionable within `timeout` milliseconds (default `30000`), the promise rejects with an error like `timeout waiting for '#submit' to be actionable`.

Bun passes the selector as data instead of interpolating it into a script, so selectors containing quotes or JavaScript syntax are safe.

### Typing text

Insert text into the currently focused element:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
await view.click("input#email"); // focus it first
await view.type("hello@example.com");
```

`type()` uses the browser's `InsertText` editing command (the same path as paste), not per-character keystrokes. It fires `beforeinput`/`input` events with `isTrusted: true`, but **no `keydown`/`keyup` events**. There's no IME processing and no smart-quote substitution — the text lands exactly as given.

### Pressing keys

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
await view.press("Enter");
await view.press("Escape");
await view.press("ArrowDown");
await view.press("a", { modifiers: ["Meta"] }); // Cmd+A / Ctrl+A
```

Named virtual keys: `Enter`, `Tab`, `Space`, `Backspace`, `Delete`, `Escape`, `ArrowLeft`, `ArrowRight`, `ArrowUp`, `ArrowDown`, `Home`, `End`, `PageUp`, `PageDown`.

Any single character (for example, `"a"`) combined with `modifiers` sends a keyboard chord.

On the WebKit backend, most named keys (without modifiers) map to editing commands such as `DeleteBackward`, `MoveLeft`, and `InsertNewline`, and resolve after the page has applied them. `Escape`, `Space`, and any key with modifiers fall back to raw `keydown`/`keyup` events. These keys fire a `keydown` the page can observe, but there's no completion barrier. Follow with an `evaluate()` if you need to observe the effect.

Modifier names: `"Shift"`, `"Control"` (or `"Ctrl"`), `"Alt"` (or `"Option"`), `"Meta"` (or `"Cmd"` / `"Command"`).

### Scrolling

Scroll by a pixel delta — fires a native `wheel` event at the viewport center:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
await view.scroll(0, 500); // scroll down 500px
await view.scroll(-100, 0); // scroll left 100px
```

Positive `dy` scrolls down (content moves up), matching `window.scrollBy`. If a scrollable element sits under the viewport center, it receives the wheel event instead of the document.

Scroll an element into view by selector:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
await view.scrollTo("#footer"); // center it (default)
await view.scrollTo("#hero", { block: "start" }); // align its top to the viewport top
await view.scrollTo(".card", { block: "nearest" }); // minimal scroll
```

`scrollTo()` waits (at `requestAnimationFrame` rate) for the element to exist, then calls `element.scrollIntoView({ block, behavior: "instant" })`. It scrolls every scrollable ancestor, not just the document. The default `timeout` is `30000` ms.

### Resizing

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
await view.resize(1920, 1080);
```

Width and height must each be between `1` and `16384`.

***

## Console capture

Forward `console.*` calls from the page to your Bun process by passing the `console` option to the constructor.

### Mirror to Bun's console

Pass `globalThis.console` (the actual object, by reference). Page-side `console.log("hi")` then prints `hi` to your stdout with Bun's formatter, and `console.error` goes to stderr. This path dispatches directly through Bun's console implementation with no per-call JavaScript overhead.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const view = new Bun.WebView({
  console: globalThis.console,
});
```

### Custom handler

Pass a function to receive each call yourself:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const view = new Bun.WebView({
  console: (type, ...args) => {
    // type is "log" | "warn" | "error" | "info" | "debug" | ...
    if (type === "error") reportError(args);
  },
});
```

Primitive arguments (strings, numbers, booleans, `null`, `undefined`) unwrap to their raw values. Object arguments arrive as a serialized descriptor:

* **Chrome backend**: the raw CDP [`RemoteObject`](https://chromedevtools.github.io/devtools-protocol/tot/Runtime/#type-RemoteObject) — an object with `type`, `className`, `description`, and (when available) a `preview.properties` array.
* **WebKit backend**: the `JSON.stringify` round-trip of the object. Functions, circular references, and other non-serializable values fall back to their `String(...)` coercion.

If you don't pass `console`, Bun drops page-side console output.

<Note>
  Ordering guarantee: a `console.log(...)` inside a script you pass to `evaluate()` reaches your handler **before** that
  `evaluate()` resolves. Both travel over the same IPC connection.
</Note>

***

<h2>
  Raw Chrome DevTools Protocol
</h2>

When using `backend: "chrome"`, you can drop down to raw [CDP](https://chromedevtools.github.io/devtools-protocol/) commands for anything the high-level API doesn't cover.

### Sending commands

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const view = new Bun.WebView({ backend: "chrome" });
await view.navigate("https://example.com"); // required: sets up the CDP session

await view.cdp("Emulation.setUserAgentOverride", {
  userAgent: "MyBot/1.0",
});

const { root } = await view.cdp("DOM.getDocument");
const { nodeId } = await view.cdp("DOM.querySelector", {
  nodeId: root.nodeId,
  selector: "input[name=q]",
});
await view.cdp("DOM.focus", { nodeId });
```

`cdp(method, params?)` returns the `result` object from the CDP response. If Chrome returns an error (unknown method, bad params), the promise rejects with its `error.message`.

Commands are scoped to this view's session (they target this tab). You must `await navigate(...)` at least once before calling `cdp()` — the first navigation establishes the session. Calling `cdp()` before that throws `ERR_INVALID_STATE`.

`params` must be a JSON-serializable object; omit it for commands that take no parameters. One `cdp()` call may be in flight at a time per view.

### Subscribing to events

`Bun.WebView` extends `EventTarget`. With the Chrome backend, the view dispatches CDP events as DOM events whose `type` is the CDP method name and whose `data` is the parsed `params` object:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
await view.navigate("about:blank");
await view.cdp("Network.enable"); // Chrome only emits events for enabled domains

view.addEventListener("Network.responseReceived", event => {
  console.log(event.data.response.status, event.data.response.url);
});

await view.navigate("https://example.com");
```

Bun drops events that have no registered listener before it even parses their JSON `params`, so enabling a chatty domain (like `Network`) is cheap if you only listen for one or two event types.

On the WebKit backend, `cdp()` throws `ERR_METHOD_NOT_IMPLEMENTED` — there is no DevTools Protocol bridge. The `EventTarget` interface still works for your own `dispatchEvent()` calls.

***

## Lifecycle

### Closing a view

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
view.close();
```

Closing is synchronous and idempotent. It destroys the page's renderer process, rejects any pending promises on the view with `Error("WebView closed")`, and makes every subsequent method call throw `ERR_INVALID_STATE`.

`view[Symbol.dispose]` and `view[Symbol.asyncDispose]` both point to `close()`, so `using` / `await using` work.

### Killing all browsers

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
Bun.WebView.closeAll();
```

Force-kills (`SIGKILL`) both the Chrome subprocess and the WebKit host subprocess. Pending promises on every view reject on the next event-loop tick. Subsequent `new Bun.WebView()` calls respawn as needed.

Bun calls this automatically at process exit, so browser subprocesses never outlive your script.

### Event-loop behavior

The browser subprocess does **not** keep Bun's event loop alive on its own. An open `WebView` keeps the process alive only while it has a pending operation (such as an unsettled `navigate()` or `evaluate()`). Once you `close()` the last view — or the last pending operation settles — Bun exits naturally.

### Subprocess death

If the browser subprocess dies unexpectedly (crash, OOM-kill, `SIGKILL`), every pending promise on every view rejects with an error describing how it died (`"Chrome killed by signal 9"`, `"WebView host process died"`). Further operations on those views throw.

***

## Concurrency model

Each view has a small number of independent operation "slots". One operation of each kind may be in flight at a time:

* one `navigate()` (shared with `reload()`/`goBack()`/`goForward()` on the Chrome backend)
* one `evaluate()`
* one `screenshot()`
* one `cdp()` (Chrome only)
* one "simple" operation — `click()`, `type()`, `press()`, `scroll()`, `scrollTo()`, `resize()` (and `reload()`/`goBack()`/`goForward()` on the WebKit backend) share this slot

Starting a second operation while its slot is occupied throws `ERR_INVALID_STATE` synchronously — it does **not** queue. In practice, `await` each call.

Operations on **different views** are fully independent and run in parallel — each view has its own renderer process.

***

## Reference

### `new Bun.WebView(options?)`

| Option      | Type                                            | Default                                   | Description                                                                                    |
| ----------- | ----------------------------------------------- | ----------------------------------------- | ---------------------------------------------------------------------------------------------- |
| `width`     | `number`                                        | `800`                                     | Viewport width in CSS pixels. Range 1-16384.                                                   |
| `height`    | `number`                                        | `600`                                     | Viewport height in CSS pixels. Range 1-16384.                                                  |
| `url`       | `string`                                        | —                                         | Begin navigating to this URL immediately.                                                      |
| `headless`  | `boolean`                                       | `true`                                    | Only `true` is implemented; `false` throws.                                                    |
| `backend`   | `"webkit"` \| `"chrome"` \| [object](#backends) | `"webkit"` on macOS, `"chrome"` elsewhere | Rendering engine.                                                                              |
| `console`   | `typeof console` \| `(type, ...args) => void`   | —                                         | Capture page-side `console.*` calls. See [Console capture](#console-capture).                  |
| `dataStore` | `"ephemeral"` \| `{ directory: string }`        | `"ephemeral"`                             | Storage for cookies / localStorage / IndexedDB. See [Persistent storage](#persistent-storage). |

#### `backend` object form

| Option   | Type                      | Description                                                                                                                                            |
| -------- | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `type`   | `"chrome"` \| `"webkit"`  | **Required.** Which engine to use.                                                                                                                     |
| `path`   | `string`                  | (`chrome` only) Path to the Chrome/Chromium executable. Forces spawn mode.                                                                             |
| `argv`   | `string[]`                | (`chrome` only) Extra launch flags, appended after the defaults. Forces spawn mode.                                                                    |
| `url`    | `string` \| `false`       | (`chrome` only) `ws://` URL of an existing Chrome's DevTools endpoint, or `false` to skip auto-detect and always spawn. See [above](#existing-chrome). |
| `stdout` | `"inherit"` \| `"ignore"` | Route the subprocess's stdout to Bun's. Default `"ignore"`.                                                                                            |
| `stderr` | `"inherit"` \| `"ignore"` | Route the subprocess's stderr to Bun's. Default `"ignore"`.                                                                                            |

### Instance properties

| Property             | Type                                             | Description                                                                                 |
| -------------------- | ------------------------------------------------ | ------------------------------------------------------------------------------------------- |
| `url`                | `string` *(readonly)*                            | The current URL. Updated when a navigation completes. Empty string before first navigation. |
| `title`              | `string` *(readonly)*                            | The page's `<title>`. Updated when a navigation completes.                                  |
| `loading`            | `boolean` *(readonly)*                           | `true` while a navigation is in flight.                                                     |
| `onNavigated`        | `((url: string, title: string) => void) \| null` | Fires after each successful navigation, before the `navigate()` promise resolves.           |
| `onNavigationFailed` | `((error: Error) => void) \| null`               | Fires after each failed navigation, before the `navigate()` promise rejects.                |

### Instance methods

| Method                         | Returns                                             | Description                                                                                                     |
| ------------------------------ | --------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
| `navigate(url)`                | `Promise<void>`                                     | Load a URL. Resolves on the main frame's `load` event.                                                          |
| `evaluate(script)`             | `Promise<unknown>`                                  | Run a JS expression in the page and return its JSON-serialized result.                                          |
| `screenshot(options?)`         | `Promise<Blob \| Buffer \| string \| {name, size}>` | Capture the viewport. See [Screenshots](#screenshots).                                                          |
| `click(x, y, options?)`        | `Promise<void>`                                     | Native click at viewport coordinates.                                                                           |
| `click(selector, options?)`    | `Promise<void>`                                     | Wait for the element to be actionable, then click its center.                                                   |
| `type(text)`                   | `Promise<void>`                                     | Insert text into the focused element with the `InsertText` editing command.                                     |
| `press(key, options?)`         | `Promise<void>`                                     | Press a named virtual key or single-character chord.                                                            |
| `scroll(dx, dy)`               | `Promise<void>`                                     | Fire a native `wheel` event at the viewport center. `dx`/`dy` must be finite.                                   |
| `scrollTo(selector, options?)` | `Promise<void>`                                     | Wait for the element to exist, then `scrollIntoView`.                                                           |
| `resize(width, height)`        | `Promise<void>`                                     | Change the viewport size. Each dimension 1-16384.                                                               |
| `goBack()`                     | `Promise<void>`                                     | Navigate back in session history. No-op at the start.                                                           |
| `goForward()`                  | `Promise<void>`                                     | Navigate forward in session history. No-op at the end.                                                          |
| `reload()`                     | `Promise<void>`                                     | Reload the current page.                                                                                        |
| `cdp(method, params?)`         | `Promise<unknown>`                                  | (Chrome only) Send a raw CDP command scoped to this tab. See [Raw CDP](#cdp).                                   |
| `addEventListener(type, fn)`   | `void`                                              | Inherited from `EventTarget`. With Chrome, `type` may be a CDP event name; `event.data` is the parsed `params`. |
| `close()`                      | `void`                                              | Destroy the page. Rejects pending promises. Idempotent.                                                         |

#### `click()` options

| Option       | Type                                          | Default  | Description                                                          |
| ------------ | --------------------------------------------- | -------- | -------------------------------------------------------------------- |
| `button`     | `"left"` \| `"right"` \| `"middle"`           | `"left"` | Mouse button.                                                        |
| `modifiers`  | `("Shift" \| "Control" \| "Alt" \| "Meta")[]` | `[]`     | Modifier keys held during the click.                                 |
| `clickCount` | `1` \| `2` \| `3`                             | `1`      | Click count for double/triple-click.                                 |
| `timeout`    | `number`                                      | `30000`  | (Selector overload only) Max milliseconds to wait for actionability. |

#### `press()` options

| Option      | Type                                          | Default | Description                             |
| ----------- | --------------------------------------------- | ------- | --------------------------------------- |
| `modifiers` | `("Shift" \| "Control" \| "Alt" \| "Meta")[]` | `[]`    | Modifier keys held during the keypress. |

#### `scrollTo()` options

| Option    | Type                                              | Default    | Description                                        |
| --------- | ------------------------------------------------- | ---------- | -------------------------------------------------- |
| `block`   | `"start"` \| `"center"` \| `"end"` \| `"nearest"` | `"center"` | Vertical alignment after scrolling.                |
| `timeout` | `number`                                          | `30000`    | Max milliseconds to wait for the element to exist. |

#### `screenshot()` options

| Option     | Type                                              | Default  | Description                                               |
| ---------- | ------------------------------------------------- | -------- | --------------------------------------------------------- |
| `format`   | `"png"` \| `"jpeg"` \| `"webp"`                   | `"png"`  | Image format. `"webp"` requires the Chrome backend.       |
| `quality`  | `number`                                          | `80`     | 0-100. JPEG/WebP only; ignored for PNG.                   |
| `encoding` | `"blob"` \| `"buffer"` \| `"base64"` \| `"shmem"` | `"blob"` | Return-type encoding. `"shmem"` not supported on Windows. |

### Static methods

| Method               | Description                                                                                                 |
| -------------------- | ----------------------------------------------------------------------------------------------------------- |
| `WebView.closeAll()` | `SIGKILL` every browser subprocess. Pending promises reject on the next tick. Called automatically at exit. |


# Workers
Source: https://bun.com/docs/runtime/workers

Use Bun's Workers API to create and communicate with a new JavaScript instance running on a separate thread while sharing I/O resources with the main thread

<Warning>
  The `Worker` API is still experimental (particularly for terminating workers). We are actively working on improving
  this.
</Warning>

With [`Worker`](https://developer.mozilla.org/en-US/docs/Web/API/Worker), you start and communicate with a new JavaScript instance running on a separate thread while sharing I/O resources with the main thread.

Bun implements a minimal version of the [Web Workers API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API) with extensions that make it work better for server-side use cases. Like the rest of Bun, `Worker` supports CommonJS, ES modules, TypeScript, JSX, and TSX with no extra build step.

## Creating a `Worker`

Like in browsers, [`Worker`](https://developer.mozilla.org/en-US/docs/Web/API/Worker) is a global. Use it to create a new worker thread.

### From the main thread

```ts index.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
const worker = new Worker("./worker.ts");

worker.postMessage("hello");
worker.onmessage = event => {
  console.log(event.data);
};
```

### Worker thread

```ts worker.ts icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
// prevents TS errors
declare var self: Worker;

self.onmessage = (event: MessageEvent) => {
  console.log(event.data);
  postMessage("world");
};
```

To prevent TypeScript errors when using `self`, add this line to the top of your worker file.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
declare var self: Worker;
```

You can use `import` and `export` syntax in your worker code. Unlike in browsers, you don't need to pass `{type: "module"}` to use ES modules.

If the worker's script fails to resolve, Bun emits an `"error"` event on the `Worker` object.

```js theme={"theme":{"light":"github-light","dark":"dracula"}}
const worker = new Worker("/not-found.js");
worker.addEventListener("error", event => {
  console.log(event.message);
});
```

Bun resolves the specifier passed to `Worker` relative to the project root (like typing `bun ./path/to/file.js`).

### `preload` - load modules before the worker starts

Pass an array of module specifiers to the `preload` option to load them before the worker's own code runs, like the `--preload` CLI argument. Use it for code that must load first, such as OpenTelemetry, Sentry, or DataDog.

```ts index.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
const worker = new Worker("./worker.ts", {
  preload: ["./load-sentry.js"],
});
```

You can also pass a single string to the `preload` option:

```ts index.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
const worker = new Worker("./worker.ts", {
  preload: "./load-sentry.js",
});
```

### `blob:` URLs

You can also pass a `blob:` URL to `Worker` to create a worker from a string or other in-memory source.

```js theme={"theme":{"light":"github-light","dark":"dracula"}}
const blob = new Blob([`self.onmessage = (event: MessageEvent) => postMessage(event.data)`], {
  type: "application/typescript",
});
const url = URL.createObjectURL(blob);
const worker = new Worker(url);
```

Like the rest of Bun, workers created from `blob:` URLs support TypeScript, JSX, and other file types. To tell Bun the source is TypeScript, set the `type` on the `Blob` or pass a `filename` to the `File` constructor.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const file = new File([`self.onmessage = (event: MessageEvent) => postMessage(event.data)`], "worker.ts");
const url = URL.createObjectURL(file);
const worker = new Worker(url);
```

### `"open"`

Bun emits the `"open"` event when a worker is created and ready to receive messages. (This event does not exist in browsers.)

```ts index.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
const worker = new Worker(new URL("worker.ts", import.meta.url).href);

worker.addEventListener("open", () => {
  console.log("worker is ready");
});
```

Bun enqueues messages until the worker is ready, so you don't need to wait for the `"open"` event before sending.

## Messages with `postMessage`

To send messages, use [`worker.postMessage`](https://developer.mozilla.org/en-US/docs/Web/API/Worker/postMessage) and [`self.postMessage`](https://developer.mozilla.org/en-US/docs/Web/API/DedicatedWorkerGlobalScope/postMessage). Bun serializes messages with the [HTML Structured Clone Algorithm](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm).

### Performance optimizations

Bun has fast paths for `postMessage` with common data types:

**String fast path** - When posting a pure string, Bun bypasses the structured clone algorithm entirely, so there is no serialization overhead.

**Simple object fast path** - For plain objects containing only primitive values (strings, numbers, booleans, null, undefined), Bun stores properties directly without full structured cloning.

The simple object fast path activates when the object:

* Is a plain object with no prototype chain modifications
* Contains only enumerable, configurable data properties
* Has no indexed properties or getter/setter methods
* All property values are primitives or strings

With these fast paths, Bun's `postMessage` performs **2-241x faster** because the message length no longer has a meaningful impact on performance.

**Bun (with fast paths):**

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
postMessage({ prop: 11 chars string, ...9 more props }) - 648ns
postMessage({ prop: 14 KB string, ...9 more props })    - 719ns
postMessage({ prop: 3 MB string, ...9 more props })     - 1.26µs
```

**Node.js v24.6.0 (for comparison):**

```js theme={"theme":{"light":"github-light","dark":"dracula"}}
postMessage({ prop: 11 chars string, ...9 more props }) - 1.19µs
postMessage({ prop: 14 KB string, ...9 more props })    - 2.69µs
postMessage({ prop: 3 MB string, ...9 more props })     - 304µs
```

```js theme={"theme":{"light":"github-light","dark":"dracula"}}
// String fast path - optimized
postMessage("Hello, worker!");

// Simple object fast path - optimized
postMessage({
  message: "Hello",
  count: 42,
  enabled: true,
  data: null,
});

// Complex objects still work but use standard structured clone
postMessage({
  nested: { deep: { object: true } },
  date: new Date(),
  buffer: new ArrayBuffer(8),
});
```

```js theme={"theme":{"light":"github-light","dark":"dracula"}}
// On the worker thread, `postMessage` is automatically "routed" to the parent thread.
postMessage({ hello: "world" });

// On the main thread
worker.postMessage({ hello: "world" });
```

To receive messages, use the [`message` event handler](https://developer.mozilla.org/en-US/docs/Web/API/Worker/message_event) on the worker and main thread.

```js theme={"theme":{"light":"github-light","dark":"dracula"}}
// Worker thread:
self.addEventListener("message", event => {
  console.log(event.data);
});
// or use the setter:
// self.onmessage = fn

// if on the main thread
worker.addEventListener("message", event => {
  console.log(event.data);
});
// or use the setter:
// worker.onmessage = fn
```

## Terminating a worker

A `Worker` instance terminates automatically once its event loop has no work left to do. Attaching a `"message"` listener on the global or any `MessagePort`s keeps the event loop alive. To forcefully terminate a `Worker`, call `worker.terminate()`.

```ts index.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
const worker = new Worker(new URL("worker.ts", import.meta.url).href);

// ...some time later
worker.terminate();
```

Calling `worker.terminate()` makes the worker exit as soon as possible.

### `process.exit()`

A worker can terminate itself with `process.exit()`. This does not terminate the main process. Like in Node.js, `process.on('beforeExit', callback)` and `process.on('exit', callback)` are emitted on the worker thread, not on the main thread. Bun passes the exit code to the `"close"` event.

### `"close"`

Bun emits the `"close"` event when a worker has been marked as terminated. The worker itself can take some time to fully exit. The `CloseEvent` contains the exit code passed to `process.exit()`, or 0 if it closed for another reason.

```ts index.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
const worker = new Worker(new URL("worker.ts", import.meta.url).href);

worker.addEventListener("close", event => {
  console.log("worker is being closed");
});
```

This event does not exist in browsers.

## Managing lifetime

By default, an active `Worker` keeps the main (spawning) process alive, so async tasks like `setTimeout` and promises keep the process alive. Attaching `message` listeners also keeps the `Worker` alive.

### `worker.unref()`

To stop a running worker from keeping the process alive, call `worker.unref()`. This decouples the worker's lifetime from the main process's, matching the behavior of Node.js' `worker_threads`.

```ts index.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
const worker = new Worker(new URL("worker.ts", import.meta.url).href);
worker.unref();
```

`worker.unref()` is not available in browsers.

### `worker.ref()`

To keep the process alive until the `Worker` terminates, call `worker.ref()`. Workers are ref'd by default; a ref'd worker still needs something on its event loop (such as a `"message"` listener) to continue running.

```ts index.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
const worker = new Worker(new URL("worker.ts", import.meta.url).href);
worker.unref();
// later...
worker.ref();
```

Alternatively, you can also pass `ref: false` in the `options` object to `Worker`, which is equivalent to calling `worker.unref()`:

```ts index.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
const worker = new Worker(new URL("worker.ts", import.meta.url).href, {
  ref: false,
});
```

`worker.ref()` is not available in browsers.

## Memory usage with `smol`

Bun's `Worker` supports a `smol` mode that reduces memory usage at a cost of performance. To enable it, pass `smol: true` in the `Worker` constructor's `options` object.

```ts index.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
const worker = new Worker("./i-am-smol.ts", {
  smol: true,
});
```

<Accordion title="What does `smol` mode actually do?">
  Setting `smol: true` sets `JSC::HeapSize` to be `Small` instead of the default `Large`.
</Accordion>

## Environment Data

Share data between the main thread and workers using `setEnvironmentData()` and `getEnvironmentData()`.

```ts title="index.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { setEnvironmentData, getEnvironmentData } from "worker_threads";

// In main thread
setEnvironmentData("config", { apiUrl: "https://api.example.com" });

// In worker
const config = getEnvironmentData("config");
console.log(config); // => { apiUrl: "https://api.example.com" }
```

## Worker Events

Listen for worker creation events using `process.on()`:

```ts title="index.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
process.on("worker", worker => {
  console.log("New worker created:", worker.threadId);
});
```

## `Bun.isMainThread`

Check `Bun.isMainThread` to tell whether you're on the main thread.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
if (Bun.isMainThread) {
  console.log("I'm the main thread");
} else {
  console.log("I'm in a worker");
}
```


# XML
Source: https://bun.com/docs/runtime/xml

Use Bun's built-in support for XML through both runtime APIs and bundler integration

<Note>New in Bun v1.4</Note>

In Bun, XML is a first-class citizen alongside JSON, TOML, YAML, and JSON5. You can:

* Parse and stringify XML with `Bun.XML.parse` and `Bun.XML.stringify`
* `import` & `require` XML files as modules at runtime (including hot reloading & watch mode support)
* `import` & `require` XML files in frontend apps with Bun's bundler

***

## Runtime API

### `Bun.XML.parse()`

Parse an XML document into a plain JavaScript object.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
import { XML } from "bun";

const data = XML.parse(`
  <order id="A1" currency="USD">
    <customer>Ada</customer>
    <item sku="tea" qty="2">Green tea</item>
    <item sku="mug" qty="1">Mug</item>
    <paid/>
  </order>
`);

console.log(data);
// {
//   order: {
//     "@id": "A1",
//     "@currency": "USD",
//     customer: "Ada",
//     item: [
//       { "@sku": "tea", "@qty": "2", "#text": "Green tea" },
//       { "@sku": "mug", "@qty": "1", "#text": "Mug" },
//     ],
//     paid: "",
//   },
// }
```

By default the result is a compact object keyed by element name — the `@attr` / `#text` convention most XML-to-object libraries use. It works like this:

* The result has one key, the root element's name.
* An element with no attributes and no child elements becomes its character data: a string, `""` when empty. `<paid/>` and `<paid></paid>` are the same thing in XML.
* Any other element becomes an object. It has a `"@name"` key per attribute, then one key per distinct child element name and a `"#text"` key for the element's own text, in the order each first appears.
* When a child element name occurs more than once in the element, its key holds an array in document order. Otherwise it holds the single value. See [One or many](#one-or-many).
* `compact` chooses a structure; it does not change values. Bun returns text as written: leading, trailing and internal whitespace included, CDATA sections and entity references expanded, line ends normalized to `\n` — the same text the tree shape gives for that element. Because an element has one `"#text"`, Bun concatenates its text runs and leaves out whitespace-only runs that sit between child elements (the document's layout). If your documents are hand-formatted (`<name>\n  value\n</name>`), trim where you read.
* All values are strings. Nothing is coerced to numbers, booleans, or `null`.
* Names are kept as written, namespace prefix included (`"soap:Body"`); `xmlns` declarations are ordinary attributes.
* Comments, processing instructions, the `<?xml …?>` declaration and the `<!DOCTYPE …>` are not represented.

`@` and `#` cannot start an XML name, so attribute and text keys do not collide with child element keys.

The compact shape is for *data*. It does not keep the relative order of differently named siblings, or where text sat relative to child elements:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
XML.parse(`<p>Hello <b>world</b>!</p>`);
// { p: { "#text": "Hello !", b: "world" } }
```

When that matters — documents rather than data — pass `{ compact: false }` to get the root element as a tree that keeps the element's content in document order:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const p = XML.parse(`<p class="lead">Hello <b>world</b>!<!-- draft --></p>`, { compact: false });

console.log(p);
// {
//   name: "p",
//   attributes: { class: "lead" },
//   children: [
//     "Hello ",
//     { name: "b", attributes: {}, children: ["world"] },
//     "!",
//     { comment: " draft " },
//   ],
// }
```

Every element is `{ name, attributes, children }`; both keys are present even when empty. `children` holds the element's content in order: text as strings (as written, whitespace-only runs included, adjacent text merged), child elements, comments as `{ comment }`, and processing instructions as `{ target, data }`. Tell object children apart by which key they have. As in the compact shape, the tree does not represent the declaration, the DOCTYPE, or anything before or after the root element.

#### One or many

In the compact shape a list of one and a list of two have different types (`entry: {…}` vs `entry: [{…}, {…}]`). An element that is usually a string also becomes an object when it carries an attribute (`<title>` vs `<title type="html">`). Read values that you iterate, or that may carry attributes, defensively:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const entries = [feed.entry ?? []].flat(); // an array either way
const title = typeof e.title === "string" ? e.title : (e.title["#text"] ?? "");
```

The tree shape has neither ambiguity: `children` is an array and each element is an object.

#### Input types and encodings

`XML.parse` accepts a string, or bytes as a `Buffer`, `TypedArray`, `DataView`, `ArrayBuffer`, or `Blob`.

A string is already-decoded text, so its `encoding` declaration is checked for syntax but otherwise ignored. Bytes are decoded per the XML rules: a byte-order mark or the `encoding` in `<?xml version="1.0" encoding="..."?>` selects UTF-8 (the default), UTF-16 (either byte order), or ISO-8859-1. Other encodings throw.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
XML.parse(await Bun.file("feed.xml").bytes());
```

#### Error handling

`Bun.XML.parse()` throws a `SyntaxError` when the document is not well-formed (there is no lenient mode), and a `RangeError` for pathologically deep nesting:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
try {
  XML.parse("<a><b></a>");
} catch (error) {
  console.error(error.message); // "XML Parse error: Expected closing tag </b> but found </a>"
}
```

### `Bun.XML.stringify()`

Serialize one element, in either shape, to XML.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
import { XML } from "bun";

XML.stringify({
  order: {
    "@id": "A1",
    customer: "Ada",
    item: [{ "@sku": "tea", "#text": "Green tea" }, { "@sku": "mug" }],
    paid: null,
  },
});
// '<order id="A1"><customer>Ada</customer><item sku="tea">Green tea</item><item sku="mug"/><paid/></order>'

XML.stringify({
  name: "p",
  attributes: { class: "lead" },
  children: ["Hello ", { name: "b", children: ["world"] }, "!", { comment: " draft " }],
});
// '<p class="lead">Hello <b>world</b>!<!-- draft --></p>'
```

Bun writes a value as a tree node when it has a string `name` and a `children` or `attributes` property. Inside `children`, an object with `name` is an element, one with `comment` is a comment, and one with `target` is a processing instruction. Anything else is a compact object with one key naming the root element. Bun writes keys in order, `@`-keys as attributes. Strings, numbers, booleans and bigints become text via `String()`, and a `Date` becomes its ISO string. `null` becomes an empty element. Bun skips `undefined`, functions and symbols, as `JSON.stringify` does. An array is one element per item.

The output is well-formed XML, or `stringify` throws. Bun escapes `&`, `<` and `>`. It writes `"`, tabs and newlines in attribute values, and carriage returns anywhere, as character references so they parse back unchanged. Values XML cannot hold produce an error rather than a broken document: element and attribute names that are not XML names (`"first name"`, `"0"`), characters outside XML's repertoire (U+0000 and other control characters, unpaired surrogates — XML 1.0 has no escape for these), `--` inside a comment, `?>` inside a processing instruction, an array at the root or inside another array, and circular structures.

The result is the element only, with no `<?xml …?>` declaration and no DOCTYPE, so you can concatenate results inside an enclosing element. To write a file, prepend the prolog yourself:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
await Bun.write(
  "Info.plist",
  `<?xml version="1.0" encoding="UTF-8"?>\n` +
    `<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n` +
    XML.stringify(plist, null, "\t"),
);
```

#### Pretty printing

Pass a `space` argument (a number of spaces or an indent string, as with `JSON.stringify`) to indent element-only content. Bun writes an element that contains text on one line, so indentation does not change character data:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
console.log(XML.stringify(data, null, 2));
// <order id="A1" currency="USD">
//   <customer>Ada</customer>
//   <item sku="tea" qty="2">Green tea</item>
//   <item sku="mug" qty="1">Mug</item>
//   <paid/>
// </order>
```

The second parameter is reserved; pass `null` or `undefined`.

For a value that `XML.parse` produced, in either shape, `XML.parse(XML.stringify(value))` gives back an equal value.

***

## Module Import

### ES Modules

You can import XML files directly. Bun decodes the file like bytes passed to `XML.parse` (UTF-8, UTF-16, or ISO-8859-1 per the byte-order mark or declaration). The module's value is the compact object described above:

```xml config.xml theme={"theme":{"light":"github-light","dark":"dracula"}}
<?xml version="1.0" encoding="UTF-8"?>
<config env="production">
  <database host="localhost" port="5432" name="myapp"/>
  <feature name="auth"/>
  <feature name="rateLimit"/>
</config>
```

#### Default Import

```ts app.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import doc from "./config.xml";

console.log(doc.config["@env"]); // "production"
console.log(doc.config.database["@host"]); // "localhost"
console.log(doc.config.feature.map(f => f["@name"])); // ["auth", "rateLimit"]
```

#### Named Import

The root element is also available as a named import:

```ts app.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { config } from "./config.xml";

console.log(config.database["@port"]); // "5432"
```

### CommonJS

```ts app.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
const { config } = require("./config.xml");
console.log(config.database["@name"]); // "myapp"
```

### Import Attributes

Use `with { type: "xml" }` to parse a file with another extension as XML:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
import feed from "./export.rss" with { type: "xml" };
```

***

## Hot Reloading with XML

When you run your application with `bun --hot`, Bun reloads XML files when they change:

```ts server.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { config } from "./config.xml";

Bun.serve({
  port: 3000,
  fetch(req) {
    return new Response(`Running in ${config["@env"]} against ${config.database["@host"]}`);
  },
});
```

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun --hot server.ts
```

***

## Bundler Integration

When you bundle with Bun, the bundler parses imported XML files at build time and inlines them as JavaScript objects:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun build app.ts --outdir=dist
```

Parsing at build time means:

* Zero runtime XML parsing overhead in production
* Smaller bundle sizes
* Tree shaking of unused properties

### Dynamic Imports

You can import XML files dynamically:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const { default: doc } = await import("./config.xml");
```

***

## Conformance

Bun's XML parser is written in Rust and implements [XML 1.0 (Fifth Edition)](https://www.w3.org/TR/2008/REC-xml-20081126/) as a non-validating processor that does not read external entities:

* The whole document, including the internal DTD subset, must be well-formed — anything else throws a `SyntaxError`.
* The parser expands internal entities declared in the document, with expansion limits so "billion laughs" payloads fail instead of exhausting memory. It normalizes attribute values and applies attribute defaults declared in the internal subset.
* The parser does not fetch or read external DTDs or external entities, so there is no XXE surface. In a document with no DTD, a reference to an undeclared entity is an error. When the DOCTYPE points at an external subset (or uses parameter entities) that could have declared the entity, the parser keeps the reference as written (`&nbsp;` stays `&nbsp;`), unless the document says `standalone="yes"`.
* The parser validates nothing against the DTD. It does not resolve namespaces and keeps prefixed names verbatim.

The parser is run against the [W3C XML Conformance Test Suite](https://www.w3.org/XML/Test/). All 1,679 cases that have a required outcome for this class of processor pass: the parser rejects not-well-formed documents and accepts well-formed ones. Where the suite gives a canonical output, the element tree of a well-formed document (processing instructions included) matches it byte for byte. The [translated test suite](https://github.com/oven-sh/bun/blob/main/test/js/bun/xml/xml-test-suite.test.ts) lists every case, including the ones whose outcome legitimately depends on not reading external entities.

***

## Performance

The parser works in two stages, like Bun's JSON parser. A SIMD pass (runtime-dispatched AVX2/AVX-512/NEON/SVE kernels) finds the bytes that can change the parse, so the parser never scans character data, attribute values, comments and CDATA sections a byte at a time. Element and attribute names reuse JavaScriptCore's atom-string cache the same way `JSON.parse` does.

[`bench/xml/xml.mjs`](https://github.com/oven-sh/bun/blob/main/bench/xml/xml.mjs) compares `Bun.XML.parse` with popular npm parsers on the same documents (lower is better; Linux x64, one core):

| Document                            | `Bun.XML.parse` |   txml | fast-xml-parser | @xmldom/xmldom | xml2js |
| ----------------------------------- | --------------: | -----: | --------------: | -------------: | -----: |
| S3 `ListObjectsV2` response, 231 KB |      **1.1 ms** | 4.0 ms |           23 ms |          31 ms |  19 ms |
| Atom feed, 193 KB                   |      **1.1 ms** | 3.7 ms |           19 ms |          23 ms |  16 ms |
| libphonenumber metadata, 960 KB     |      **5.3 ms** | 9.6 ms |           56 ms |          53 ms |      — |
| Chromium `enums.xml`, 1.4 MB        |       **16 ms** |  41 ms |          150 ms |         103 ms |      — |
| freedesktop MIME database, 2.2 MB   |       **27 ms** |  56 ms |          299 ms |         280 ms |      — |


# YAML
Source: https://bun.com/docs/runtime/yaml

Use Bun's built-in support for YAML files through both runtime APIs and bundler integration

In Bun, YAML is a first-class citizen alongside JSON and TOML. You can:

* Parse YAML strings with `Bun.YAML.parse`
* `import` & `require` YAML files as modules at runtime (including hot reloading & watch mode support)
* `import` & `require` YAML files in frontend apps with Bun's bundler

***

## Conformance

Bun's YAML parser, written in Rust, passes the official YAML test suite and covers the vast majority of real-world use cases.

***

## Runtime API

### `Bun.YAML.parse()`

Parse a YAML string into a JavaScript object.

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
import { YAML } from "bun";
const text = `
name: John Doe
age: 30
email: john@example.com
hobbies:
  - reading
  - coding
  - hiking
`;

const data = YAML.parse(text);
console.log(data);
// {
//   name: "John Doe",
//   age: 30,
//   email: "john@example.com",
//   hobbies: ["reading", "coding", "hiking"]
// }
```

#### Multi-document YAML

When parsing YAML with multiple documents (separated by `---`), `Bun.YAML.parse()` returns an array:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const multiDoc = `
---
name: Document 1
---
name: Document 2
---
name: Document 3
`;

const docs = Bun.YAML.parse(multiDoc);
console.log(docs);
// [
//   { name: "Document 1" },
//   { name: "Document 2" },
//   { name: "Document 3" }
// ]
```

#### Supported YAML Features

Bun's YAML parser supports the YAML 1.2 specification, including:

* **Scalars**: strings, numbers, booleans, null values
* **Collections**: sequences (arrays) and mappings (objects)
* **Anchors and Aliases**: reusable nodes with `&` and `*`. Aliased collections share identity. An alias may refer to a collection that contains it, so `Bun.YAML.parse` can return cyclic objects. YAML imported as a module cannot be cyclic.
* **Tags**: type hints like `!!str`, `!!int`, `!!float`, `!!bool`, `!!null`
* **Multi-line strings**: literal (`|`) and folded (`>`) scalars
* **Comments**: using `#`
* **Directives**: `%YAML` and `%TAG`

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
const yaml = `
# Employee record
employee: &emp
  name: Jane Smith
  department: Engineering
  skills:
    - JavaScript
    - TypeScript
    - React

manager: *emp  # Reference to employee

config: !!str 123  # Explicit string type

description: |
  This is a multi-line
  literal string that preserves
  line breaks and spacing.

summary: >
  This is a folded string
  that joins lines with spaces
  unless there are blank lines.
`;

const data = Bun.YAML.parse(yaml);
```

#### Error Handling

`Bun.YAML.parse()` throws a `SyntaxError` if the YAML is invalid:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
try {
  Bun.YAML.parse("invalid: yaml: content:");
} catch (error) {
  console.error("Failed to parse YAML:", error.message);
}
```

***

## Module Import

### ES Modules

Import YAML files directly as ES modules. Bun parses the content and exposes it as both default and named exports:

```yaml config.yaml theme={"theme":{"light":"github-light","dark":"dracula"}}
database:
  host: localhost
  port: 5432
  name: myapp

redis:
  host: localhost
  port: 6379

features:
  auth: true
  rateLimit: true
  analytics: false
```

#### Default Import

```ts app.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import config from "./config.yaml";

console.log(config.database.host); // "localhost"
console.log(config.redis.port); // 6379
```

#### Named Imports

Top-level YAML properties are available as named imports:

```ts app.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { database, redis, features } from "./config.yaml";

console.log(database.host); // "localhost"
console.log(redis.port); // 6379
console.log(features.auth); // true
```

Or combine both:

```ts app.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import config, { database, features } from "./config.yaml";

// Use the full config object
console.log(config);

// Or use specific parts
if (features.rateLimit) {
  setupRateLimiting(database);
}
```

### CommonJS

You can also `require` YAML files in CommonJS:

```ts app.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
const config = require("./config.yaml");
console.log(config.database.name); // "myapp"

// Destructuring also works
const { database, redis } = require("./config.yaml");
console.log(database.port); // 5432
```

***

## Hot Reloading with YAML

When you run your application with `bun --hot`, Bun detects changes to YAML files and reloads them without closing connections.

### Configuration Hot Reloading

```yaml config.yaml theme={"theme":{"light":"github-light","dark":"dracula"}}
server:
  port: 3000
  host: localhost

features:
  debug: true
  verbose: false
```

```ts server.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { server, features } from "./config.yaml";

console.log(`Starting server on ${server.host}:${server.port}`);

if (features.debug) {
  console.log("Debug mode enabled");
}

// Your server code here
Bun.serve({
  port: server.port,
  hostname: server.host,
  fetch(req) {
    if (features.verbose) {
      console.log(`${req.method} ${req.url}`);
    }
    return new Response("Hello World");
  },
});
```

Run with hot reloading:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun --hot server.ts
```

Now when you modify `config.yaml`, the running application picks up the change: you can adjust settings or toggle feature flags during development without restarting.

***

## Configuration Management

### Environment-Based Configuration

One YAML file can hold configuration for several environments, sharing defaults with anchors:

```yaml config.yaml theme={"theme":{"light":"github-light","dark":"dracula"}}
defaults: &defaults
  timeout: 5000
  retries: 3
  cache:
    enabled: true
    ttl: 3600

development:
  <<: *defaults
  api:
    url: http://localhost:4000
    key: dev_key_12345
  logging:
    level: debug
    pretty: true

staging:
  <<: *defaults
  api:
    url: https://staging-api.example.com
    key: ${STAGING_API_KEY}
  logging:
    level: info
    pretty: false

production:
  <<: *defaults
  api:
    url: https://api.example.com
    key: ${PROD_API_KEY}
  cache:
    enabled: true
    ttl: 86400
  logging:
    level: error
    pretty: false
```

```ts app.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import configs from "./config.yaml";

const env = process.env.NODE_ENV || "development";
const config = configs[env];

// Environment variables in YAML values can be interpolated
function interpolateEnvVars(obj: any): any {
  if (typeof obj === "string") {
    return obj.replace(/\${(\w+)}/g, (_, key) => process.env[key] || "");
  }
  if (typeof obj === "object") {
    for (const key in obj) {
      obj[key] = interpolateEnvVars(obj[key]);
    }
  }
  return obj;
}

export default interpolateEnvVars(config);
```

### Feature Flags Configuration

```yaml features.yaml theme={"theme":{"light":"github-light","dark":"dracula"}}
features:
  newDashboard:
    enabled: true
    rolloutPercentage: 50
    allowedUsers:
      - admin@example.com
      - beta@example.com

  experimentalAPI:
    enabled: false
    endpoints:
      - /api/v2/experimental
      - /api/v2/beta

  darkMode:
    enabled: true
    default: auto # auto, light, dark
```

```ts feature-flags.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { features } from "./features.yaml";

export function isFeatureEnabled(featureName: string, userEmail?: string): boolean {
  const feature = features[featureName];

  if (!feature?.enabled) {
    return false;
  }

  // Check rollout percentage
  if (feature.rolloutPercentage < 100) {
    const hash = hashCode(userEmail || "anonymous");
    if (hash % 100 >= feature.rolloutPercentage) {
      return false;
    }
  }

  // Check allowed users
  if (feature.allowedUsers && userEmail) {
    return feature.allowedUsers.includes(userEmail);
  }

  return true;
}

// Use with hot reloading to toggle features in real-time
if (isFeatureEnabled("newDashboard", user.email)) {
  renderNewDashboard();
} else {
  renderLegacyDashboard();
}
```

### Database Configuration

```yaml database.yaml icon="yaml" theme={"theme":{"light":"github-light","dark":"dracula"}}
connections:
  primary:
    type: postgres
    host: ${DB_HOST:-localhost}
    port: ${DB_PORT:-5432}
    database: ${DB_NAME:-myapp}
    username: ${DB_USER:-postgres}
    password: ${DB_PASS}
    pool:
      min: 2
      max: 10
      idleTimeout: 30000

  cache:
    type: redis
    host: ${REDIS_HOST:-localhost}
    port: ${REDIS_PORT:-6379}
    password: ${REDIS_PASS}
    db: 0

  analytics:
    type: clickhouse
    host: ${ANALYTICS_HOST:-localhost}
    port: 8123
    database: analytics

migrations:
  autoRun: ${AUTO_MIGRATE:-false}
  directory: ./migrations

seeds:
  enabled: ${SEED_DB:-false}
  directory: ./seeds
```

```ts db.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { connections, migrations } from "./database.yaml";
import { createConnection } from "./database-driver";

// Parse environment variables with defaults
function parseConfig(config: any) {
  return JSON.parse(
    JSON.stringify(config).replace(
      /\${([^:-]+)(?::([^}]+))?}/g,
      (_, key, defaultValue) => process.env[key] || defaultValue || "",
    ),
  );
}

const dbConfig = parseConfig(connections);

export const db = await createConnection(dbConfig.primary);
export const cache = await createConnection(dbConfig.cache);
export const analytics = await createConnection(dbConfig.analytics);

// Auto-run migrations if configured
if (parseConfig(migrations).autoRun === "true") {
  await runMigrations(db, migrations.directory);
}
```

### Bundler Integration

When you bundle an application that imports YAML files, Bun parses the YAML at build time and includes it as a JavaScript module:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun build app.ts --outdir=dist
```

Parsing at build time means:

* No runtime YAML parsing overhead in production
* Smaller bundle sizes
* Tree shaking of unused configuration (named imports)

### Dynamic Imports

Dynamically import YAML files to load configuration on demand:

```ts Load configuration based on environment theme={"theme":{"light":"github-light","dark":"dracula"}}
const env = process.env.NODE_ENV || "development";
const { default: config } = await import(`./configs/${env}.yaml`);

// Load user-specific settings
async function loadUserSettings(userId: string) {
  try {
    const settings = await import(`./users/${userId}/settings.yaml`);
    return settings.default;
  } catch {
    const { default: defaults } = await import("./users/default-settings.yaml");
    return defaults;
  }
}
```


# Code coverage
Source: https://bun.com/docs/test/code-coverage

Use Bun's built-in code coverage reporting to track test coverage and find untested code

Bun's test runner has built-in code coverage reporting. Use it to see how much of your codebase your tests cover and to find untested code.

## Enabling Coverage

`bun:test` can report which lines of code your tests cover. Pass `--coverage` to print a coverage report to the console:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun test --coverage

-------------|---------|---------|-------------------
File         | % Funcs | % Lines | Uncovered Line #s
-------------|---------|---------|-------------------
All files    |   38.89 |   42.11 |
 index-0.ts  |   33.33 |   36.84 | 10-15,19-24
 index-1.ts  |   33.33 |   36.84 | 10-15,19-24
 index-10.ts |   33.33 |   36.84 | 10-15,19-24
 index-2.ts  |   33.33 |   36.84 | 10-15,19-24
 index-3.ts  |   33.33 |   36.84 | 10-15,19-24
 index-4.ts  |   33.33 |   36.84 | 10-15,19-24
 index-5.ts  |   33.33 |   36.84 | 10-15,19-24
 index-6.ts  |   33.33 |   36.84 | 10-15,19-24
 index-7.ts  |   33.33 |   36.84 | 10-15,19-24
 index-8.ts  |   33.33 |   36.84 | 10-15,19-24
 index-9.ts  |   33.33 |   36.84 | 10-15,19-24
 index.ts    |  100.00 |  100.00 |
-------------|---------|---------|-------------------
```

### Enable by Default

To enable coverage reporting by default, add this to your `bunfig.toml`:

```toml title="bunfig.toml" theme={"theme":{"light":"github-light","dark":"dracula"}}
[test]
# Always enable coverage
coverage = true
```

By default, coverage reports exclude test files and use sourcemaps. Both are configurable in `bunfig.toml`.

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[test]
coverageSkipTestFiles = false  # default true
```

## Coverage Thresholds

Set a coverage threshold in `bunfig.toml`. If your test suite does not meet or exceed it, `bun test` exits with a non-zero exit code.

### Simple Threshold

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[test]
# To require 90% line-level and function-level coverage
coverageThreshold = 0.9
```

### Detailed Thresholds

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[test]
# To set different thresholds for lines and functions
coverageThreshold = { lines = 0.9, functions = 0.9, statements = 0.9 }
```

Setting any of these thresholds causes the test run to fail if coverage is below the threshold.

## Coverage Reporters

By default, Bun prints coverage reports to the console.

To save a report for CI or other tools, pass `--coverage-reporter=lcov` on the command line or set `coverageReporter` in `bunfig.toml`.

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[test]
coverageReporter = ["text", "lcov"]  # default ["text"]
coverageDir = "path/to/somewhere"    # default "coverage"
```

### Available Reporters

| Reporter | Description                                          |
| -------- | ---------------------------------------------------- |
| `text`   | Prints a text summary of the coverage to the console |
| `lcov`   | Save coverage in lcov format                         |

### LCOV Coverage Reporter

The lcov reporter writes an `lcov.info` file to the coverage directory.

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[test]
coverageReporter = "lcov"
```

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
# Or via CLI
bun test --coverage --coverage-reporter=lcov
```

Tools and services that read the LCOV format include:

* **Code editors**: VS Code extensions can show coverage inline
* **CI/CD services**: GitHub Actions, GitLab CI, CircleCI
* **Coverage services**: Codecov, Coveralls
* **IDEs**: WebStorm, IntelliJ IDEA

#### Using LCOV with GitHub Actions

```yaml title=".github/workflows/test.yml" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
name: Test with Coverage
on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: oven-sh/setup-bun@v2
      - run: bun install
      - run: bun test --coverage --coverage-reporter=lcov
      - name: Upload coverage to Codecov
        uses: codecov/codecov-action@v3
        with:
          file: ./coverage/lcov.info
```

## Excluding Files from Coverage

### Skip Test Files

Coverage reports exclude test files by default. To include them:

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[test]
coverageSkipTestFiles = false  # default true
```

When `coverageSkipTestFiles` is `true` (the default), the coverage report excludes files matching test patterns (for example `*.test.ts`, `*.spec.js`).

### Ignore Specific Paths and Patterns

`coveragePathIgnorePatterns` excludes specific files or file patterns from coverage reports:

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[test]
# Single pattern
coveragePathIgnorePatterns = "**/*.spec.ts"

# Multiple patterns
coveragePathIgnorePatterns = [
  "**/*.spec.ts",
  "**/*.test.ts",
  "src/utils/**",
  "*.config.js"
]
```

The option accepts glob patterns and works like Jest's `collectCoverageFrom` ignore patterns. Bun excludes files matching any of the patterns from coverage calculation and reporting in both text and LCOV output.

#### Common Use Cases

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[test]
coveragePathIgnorePatterns = [
  # Exclude utility files
  "src/utils/**",

  # Exclude configuration files
  "*.config.js",
  "webpack.config.ts",
  "vite.config.ts",

  # Exclude specific test patterns
  "**/*.spec.ts",
  "**/*.e2e.ts",

  # Exclude build artifacts
  "dist/**",
  "build/**",

  # Exclude generated files
  "src/generated/**",
  "**/*.generated.ts",

  # Exclude vendor/third-party code
  "vendor/**",
  "third-party/**"
]
```

## Sourcemaps

Bun transpiles all files by default, generating an internal source map that maps lines of your original source code onto Bun's internal representation. To make coverage reports ignore this source map, set `test.coverageIgnoreSourcemaps` to `true`. You rarely want this outside of advanced use cases.

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[test]
coverageIgnoreSourcemaps = true  # default false
```

<Warning>
  When using this option, you probably want to stick a `// @bun` comment at the top of the source file to opt out of the
  transpilation process.
</Warning>

## Coverage Defaults

By default, coverage reports:

* **Exclude** `node_modules` directories
* **Exclude** files loaded with non-JS/TS loaders (for example `.css`, `.txt`) unless you specify a custom JS loader
* **Exclude** test files themselves (include them with `coverageSkipTestFiles = false`)
* Can exclude additional files with `coveragePathIgnorePatterns`

## Advanced Configuration

### Custom Coverage Directory

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[test]
coverageDir = "coverage-reports"  # default "coverage"
```

### Multiple Reporters

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[test]
coverageReporter = ["text", "lcov"]
```

### Coverage with Specific Test Patterns

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
# Run coverage only on specific test files
bun test --coverage src/components/*.test.ts

# Run coverage with name pattern
bun test --coverage --test-name-pattern="API"
```

## CI/CD Integration

### GitHub Actions Example

```yaml title=".github/workflows/coverage.yml" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
name: Coverage Report
on: [push, pull_request]

jobs:
  coverage:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup Bun
        uses: oven-sh/setup-bun@v2

      - name: Install dependencies
        run: bun install

      - name: Run tests with coverage
        run: bun test --coverage --coverage-reporter=lcov

      - name: Upload to Codecov
        uses: codecov/codecov-action@v3
        with:
          file: ./coverage/lcov.info
          fail_ci_if_error: true
```

### GitLab CI Example

```yaml title=".gitlab-ci.yml" theme={"theme":{"light":"github-light","dark":"dracula"}}
test:coverage:
  stage: test
  script:
    - bun install
    - bun test --coverage --coverage-reporter=text --coverage-reporter=lcov
  coverage: '/All files\s*\|\s*[\d.]+\s*\|\s*(\d+\.\d+)/'
  artifacts:
    paths:
      - coverage/lcov.info
```

## Interpreting Coverage Reports

### Text Output Explanation

```
-------------|---------|---------|-------------------
File         | % Funcs | % Lines | Uncovered Line #s
-------------|---------|---------|-------------------
All files    |   85.71 |   90.48 |
 src/        |   85.71 |   90.48 |
  utils.ts   |  100.00 |  100.00 |
  api.ts     |   75.00 |   85.71 | 15-18,25
  main.ts    |   80.00 |   88.89 | 42,50-52
-------------|---------|---------|-------------------
```

* **% Funcs**: Percentage of functions called during tests
* **% Lines**: Percentage of executable lines run during tests
* **Uncovered Line #s**: Line numbers that were never executed

### What to Aim For

* **80%+ overall coverage**: Generally considered good
* **90%+ critical paths**: Important business logic should be well-tested
* **100% utility functions**: Pure functions and utilities can be tested completely
* **Lower coverage for UI components**: Often acceptable as they may require integration tests

## Best Practices

### Focus on Quality, Not Just Quantity

```ts title="test.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
// Good: Test actual functionality
test("calculateTax should handle different tax rates", () => {
  expect(calculateTax(100, 0.08)).toBe(8);
  expect(calculateTax(100, 0.1)).toBe(10);
  expect(calculateTax(0, 0.08)).toBe(0);
});

// Avoid: Just hitting lines for coverage
test("calculateTax exists", () => {
  calculateTax(100, 0.08); // No assertions!
});
```

### Test Edge Cases

```ts title="test.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
test("user input validation", () => {
  // Test normal case
  expect(validateEmail("user@example.com")).toBe(true);

  // Test edge cases that improve coverage meaningfully
  expect(validateEmail("")).toBe(false);
  expect(validateEmail("invalid")).toBe(false);
  expect(validateEmail(null)).toBe(false);
});
```

### Use Coverage to Find Missing Tests

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
# Run coverage to identify untested code
bun test --coverage

# Look at specific files that need attention
bun test --coverage src/critical-module.ts
```

### Combine with Other Quality Metrics

Coverage is only one metric. Also consider:

* **Code review quality**
* **Integration test coverage**
* **Error handling tests**
* **Performance tests**
* **Type safety**

## Troubleshooting

### Coverage Not Showing for Some Files

If files aren't appearing in coverage reports, your tests might not import them. Coverage only tracks files that are loaded.

```ts title="test.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
// Make sure to import the modules you want to test
import { myFunction } from "../src/my-module";

test("my function works", () => {
  expect(myFunction()).toBeDefined();
});
```

### False Coverage Reports

If you see coverage reports that don't match your expectations:

1. Check if source maps are working correctly
2. Verify file patterns in `coveragePathIgnorePatterns`
3. Ensure test files import the code to test

### Performance Issues with Large Codebases

For large projects, coverage collection can slow down tests:

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[test]
# Exclude large directories you don't need coverage for
coveragePathIgnorePatterns = [
  "vendor/**",
  "generated/**"
]
```

Consider running coverage only on CI or specific branches rather than every test run during development.


# Test configuration
Source: https://bun.com/docs/test/configuration

Configure bun test behavior with bunfig.toml and command-line options

Configure `bun test` with `bunfig.toml` and command-line options.

## Configuration File

To configure `bun test` in `bunfig.toml`, add a `[test]` section:

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[test]
# Options go here
```

## Test Discovery

### root

The `root` option sets the directory Bun scans for tests, instead of the project root.

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[test]
root = "src"  # Only scan for tests in the src directory
```

#### Examples

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[test]
# Only run tests in the src directory
root = "src"

# Run tests in a specific test directory
root = "tests"

# Run tests in multiple specific directories (not currently supported - use patterns instead)
# root = ["src", "lib"]  # This syntax is not supported
```

### Preload Scripts

The `preload` option loads scripts before the tests run:

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[test]
preload = ["./test-setup.ts", "./global-mocks.ts"]
```

This is equivalent to using `--preload` on the command line:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun test --preload ./test-setup.ts --preload ./global-mocks.ts
```

#### Common Preload Use Cases

```ts title="test-setup.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
// Global test setup
import { beforeAll, afterAll } from "bun:test";

beforeAll(() => {
  // Set up test database
  setupTestDatabase();
});

afterAll(() => {
  // Clean up
  cleanupTestDatabase();
});
```

```ts title="global-mocks.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
// Global mocks
import { mock } from "bun:test";

// Mock environment variables
process.env.NODE_ENV = "test";
process.env.API_URL = "http://localhost:3001";

// Mock external dependencies
mock.module("./external-api", () => ({
  fetchData: mock(() => Promise.resolve({ data: "test" })),
}));
```

### Path Ignore Patterns

`pathIgnorePatterns` excludes files and directories from test discovery entirely, using glob patterns. Unlike `coveragePathIgnorePatterns`, which only affects coverage reports, `pathIgnorePatterns` prevents Bun from discovering matching paths and running them as tests.

Use it when your project contains submodules, vendored code, or other directories with `*.test.ts` files that you don't want `bun test` to pick up.

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[test]
# Single pattern
pathIgnorePatterns = "vendor/**"

# Multiple patterns
pathIgnorePatterns = [
  "vendor/**",
  "submodules/**",
  "fixtures/**"
]
```

This is equivalent to using `--path-ignore-patterns` on the command line:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun test --path-ignore-patterns 'vendor/**' --path-ignore-patterns 'fixtures/**'
```

Bun prunes directories matching a pattern during scanning and never traverses their contents, so ignoring a large directory tree is cheap.

#### Common Use Cases

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[test]
pathIgnorePatterns = [
  # Git submodules with their own test suites
  "submodules/**",

  # Vendored dependencies
  "vendor/**",
  "third-party/**",

  # Test fixtures that look like tests but aren't
  "fixtures/**",
  "**/test-data/**",

  # Integration / E2E tests you want to run separately
  "**/integration/**",
  "e2e/**"
]
```

<Note>
  Command-line `--path-ignore-patterns` flags override the `bunfig.toml` value entirely. Bun does not merge the two.
</Note>

## Reporters

### JUnit Reporter

Configure the JUnit reporter output file path directly in the config file:

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[test.reporter]
junit = "path/to/junit.xml"  # Output path for JUnit XML report
```

This complements the `--reporter=junit` and `--reporter-outfile` CLI flags:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
# Equivalent command line usage
bun test --reporter=junit --reporter-outfile=./junit.xml
```

#### Multiple Reporters

You can use multiple reporters simultaneously:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
# CLI approach
bun test --reporter=junit --reporter-outfile=./junit.xml

# Config file approach
```

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[test.reporter]
junit = "./reports/junit.xml"

[test]
# Also enable coverage reporting
coverage = true
coverageReporter = ["text", "lcov"]
```

## Memory Usage

### smol Mode

Enable the `--smol` memory-saving mode specifically for the test runner:

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[test]
smol = true  # Reduce memory usage during test runs
```

This is equivalent to using the `--smol` flag on the command line:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun test --smol
```

The `smol` mode reduces memory usage by:

* Using less memory for the JavaScript heap
* Being more aggressive about garbage collection
* Reducing buffer sizes where possible

Use it in memory-constrained environments, such as CI runners, or for large test suites.

## Test execution

### concurrentTestGlob

Run test files matching a glob pattern with concurrent test execution enabled.

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[test]
concurrentTestGlob = "**/concurrent-*.test.ts"  # Run files matching this pattern concurrently
```

Test files matching the pattern behave as if you passed the `--concurrent` flag: every test in those files runs concurrently. Use this to migrate a test suite to concurrent execution gradually, or to run one kind of test (say, integration tests) concurrently while the rest stay sequential.

The `--concurrent` CLI flag overrides this setting, forcing all tests to run concurrently regardless of the glob pattern.

#### randomize

Run tests in random order to identify tests with hidden dependencies:

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[test]
randomize = true
```

#### seed

Specify a seed for reproducible random test order. Requires `randomize = true`:

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[test]
randomize = true
seed = 2444615283
```

#### retry

Default retry count for all tests. Bun retries a failed test up to this many times. Per-test `{ retry: N }` overrides this value. Default `0` (no retries).

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[test]
retry = 3
```

The `--retry` CLI flag overrides this setting.

#### rerunEach

Re-run each test file multiple times to identify flaky tests:

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[test]
rerunEach = 3
```

## Coverage Options

### Basic Coverage Settings

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[test]
# Enable coverage by default
coverage = true

# Set coverage reporter
coverageReporter = ["text", "lcov"]

# Set coverage output directory
coverageDir = "./coverage"
```

### Skip Test Files from Coverage

Exclude files matching test patterns (for example `*.test.ts`) from the coverage report:

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[test]
coverageSkipTestFiles = true  # Exclude test files from coverage reports
```

### Coverage Thresholds

Specify the coverage threshold as a single number or as an object with per-metric thresholds:

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[test]
# Simple threshold - applies to lines, functions, and statements
coverageThreshold = 0.8

# Detailed thresholds
coverageThreshold = { lines = 0.9, functions = 0.8, statements = 0.85 }
```

Setting any of these causes the test run to fail if coverage is below the threshold.

#### Threshold Examples

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[test]
# Require 90% coverage across the board
coverageThreshold = 0.9

# Different requirements for different metrics
coverageThreshold = {
  lines = 0.85,      # 85% line coverage
  functions = 0.90,  # 90% function coverage
  statements = 0.80  # 80% statement coverage
}
```

### Coverage Path Ignore Patterns

Exclude specific files or file patterns from coverage reports using glob patterns:

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[test]
# Single pattern
coveragePathIgnorePatterns = "**/*.spec.ts"

# Multiple patterns
coveragePathIgnorePatterns = [
  "**/*.spec.ts",
  "**/*.test.ts",
  "src/utils/**",
  "*.config.js",
  "generated/**",
  "vendor/**"
]
```

Bun excludes files matching any of these patterns from coverage calculation and reporting. See [Code coverage](/docs/test/code-coverage).

#### Common Ignore Patterns

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[test]
coveragePathIgnorePatterns = [
  # Test files
  "**/*.test.ts",
  "**/*.spec.ts",
  "**/*.e2e.ts",

  # Configuration files
  "*.config.js",
  "*.config.ts",
  "webpack.config.*",
  "vite.config.*",

  # Build output
  "dist/**",
  "build/**",
  ".next/**",

  # Generated code
  "generated/**",
  "**/*.generated.ts",

  # Vendor/third-party
  "vendor/**",
  "third-party/**",

  # Utilities that don't need testing
  "src/utils/constants.ts",
  "src/types/**"
]
```

### Sourcemap Handling

Bun transpiles every file, so coverage results pass through sourcemaps before they're reported. `coverageIgnoreSourcemaps` opts out of this, but the results are confusing: during transpilation, Bun may move code around and rename variables. The option is mostly useful for debugging coverage issues.

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[test]
coverageIgnoreSourcemaps = true  # Don't use sourcemaps for coverage analysis
```

<Warning>
  When using this option, you probably want to stick a `// @bun` comment at the top of the source file to opt out of the
  transpilation process.
</Warning>

## Install Settings Inheritance

`bun test` inherits network and installation configuration (such as `registry`, `cafile`, `prefer`, and `exact`) from the `[install]` section of `bunfig.toml`. This matters if your tests reach a private registry or trigger installs during the run.

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[install]
# These settings are inherited by bun test
registry = "https://npm.company.com/"
exact = true
prefer = "offline"

[test]
# Test-specific configuration
coverage = true
```

## Environment Variables

Set environment variables for tests with `.env` files, which Bun loads from your project root automatically. For test-specific variables, create a `.env.test` file, which `bun test` loads automatically:

```ini title=".env.test" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
NODE_ENV=test
DATABASE_URL=postgresql://localhost:5432/test_db
LOG_LEVEL=error
```

## Complete Configuration Example

An example showing the available test configuration options:

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[install]
# Install settings inherited by tests
registry = "https://registry.npmjs.org/"
exact = true

[test]
# Test discovery
root = "src"
preload = ["./test-setup.ts", "./global-mocks.ts"]
pathIgnorePatterns = ["vendor/**", "submodules/**"]

# Execution settings
smol = true

# Coverage configuration
coverage = true
coverageReporter = ["text", "lcov"]
coverageDir = "./coverage"
coverageThreshold = { lines = 0.85, functions = 0.90, statements = 0.80 }
coverageSkipTestFiles = true
coveragePathIgnorePatterns = [
  "**/*.spec.ts",
  "src/utils/**",
  "*.config.js",
  "generated/**"
]

# Advanced coverage settings
coverageIgnoreSourcemaps = false

# Reporter configuration
[test.reporter]
junit = "./reports/junit.xml"
```

## CLI Override Behavior

Command-line options always override configuration file settings:

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[test]
coverage = false
```

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
# This CLI flag overrides the config file
bun test --coverage
# coverage will be enabled
```


# Dates and times
Source: https://bun.com/docs/test/dates-times

Learn how to manipulate time and dates in your Bun tests using setSystemTime and Jest compatibility functions

`bun:test` lets you change what time it is in your tests.

This works with any of the following:

* `Date.now`
* `new Date()`
* `new Intl.DateTimeFormat().format()`

## setSystemTime

To change the system time, use `setSystemTime`:

```ts title="test.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { setSystemTime, beforeAll, test, expect } from "bun:test";

beforeAll(() => {
  setSystemTime(new Date("2020-01-01T00:00:00.000Z"));
});

test("it is 2020", () => {
  expect(new Date().getFullYear()).toBe(2020);
});
```

`bun:test` also supports Jest's `useFakeTimers` and `useRealTimers`, so existing tests that use them keep working:

```ts title="test.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
test("just like in jest", () => {
  jest.useFakeTimers();
  jest.setSystemTime(new Date("2020-01-01T00:00:00.000Z"));
  expect(new Date().getFullYear()).toBe(2020);
  jest.useRealTimers();
  expect(new Date().getFullYear()).toBeGreaterThan(2020);
});

test("unlike in jest", () => {
  const OriginalDate = Date;
  jest.useFakeTimers();
  if (typeof Bun === "undefined") {
    // In Jest, the Date constructor changes
    // That can cause all sorts of bugs because suddenly Date !== Date before the test.
    expect(Date).not.toBe(OriginalDate);
    expect(Date.now).not.toBe(OriginalDate.now);
  } else {
    // In bun:test, Date constructor does not change when you useFakeTimers
    expect(Date).toBe(OriginalDate);
    expect(Date.now).toBe(OriginalDate.now);
  }
});
```

## Reset the system time

To reset the system time, pass no arguments to `setSystemTime`:

```ts title="test.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { setSystemTime, expect, test } from "bun:test";

test("it was 2020, for a moment.", () => {
  // Set it to something!
  setSystemTime(new Date("2020-01-01T00:00:00.000Z"));
  expect(new Date().getFullYear()).toBe(2020);

  // reset it!
  setSystemTime();

  expect(new Date().getFullYear()).toBeGreaterThan(2020);
});
```

## Get mocked time with jest.now()

When the time is mocked (with `setSystemTime` or `useFakeTimers`), `jest.now()` returns the current mocked timestamp:

```ts title="test.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { test, expect, jest } from "bun:test";

test("get the current mocked time", () => {
  jest.useFakeTimers();
  jest.setSystemTime(new Date("2020-01-01T00:00:00.000Z"));

  expect(Date.now()).toBe(1577836800000); // Jan 1, 2020 timestamp
  expect(jest.now()).toBe(1577836800000); // Same value

  jest.useRealTimers();
});
```

Use it to read the mocked time without creating a new `Date` object.

## Set the time zone

By default, `bun test` runs in UTC (`Etc/UTC`). To change the time zone, either pass the `TZ` environment variable to `bun test`:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
TZ=America/Los_Angeles bun test
```

Or set `process.env.TZ` at runtime:

```ts title="test.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { test, expect } from "bun:test";

test("Welcome to California!", () => {
  process.env.TZ = "America/Los_Angeles";
  expect(new Date("2020-07-01T00:00:00.000Z").getTimezoneOffset()).toBe(420);
  expect(new Intl.DateTimeFormat().resolvedOptions().timeZone).toBe("America/Los_Angeles");
});

test("Welcome to New York!", () => {
  // Unlike in Jest, you can set the timezone multiple times at runtime and it will work.
  process.env.TZ = "America/New_York";
  expect(new Date("2020-07-01T00:00:00.000Z").getTimezoneOffset()).toBe(240);
  expect(new Intl.DateTimeFormat().resolvedOptions().timeZone).toBe("America/New_York");
});
```

<Info>Unlike in Jest, you can change the time zone multiple times at runtime.</Info>


# Finding tests
Source: https://bun.com/docs/test/discovery

Learn how Bun's test runner discovers and filters test files in your project

`bun test` decides which files to run as tests by matching their paths against a set of patterns.

## Default Discovery Logic

By default, `bun test` recursively searches the project directory for files that match these patterns:

* `*.test.{js|jsx|ts|tsx|mjs|cjs|mts|cts}` - Files ending with `.test.js`, `.test.jsx`, `.test.ts`, `.test.tsx`, `.test.mjs`, `.test.cjs`, `.test.mts`, or `.test.cts`
* `*_test.{js|jsx|ts|tsx|mjs|cjs|mts|cts}` - Files ending with `_test.js`, `_test.jsx`, `_test.ts`, `_test.tsx`, `_test.mjs`, `_test.cjs`, `_test.mts`, or `_test.cts`
* `*.spec.{js|jsx|ts|tsx|mjs|cjs|mts|cts}` - Files ending with `.spec.js`, `.spec.jsx`, `.spec.ts`, `.spec.tsx`, `.spec.mjs`, `.spec.cjs`, `.spec.mts`, or `.spec.cts`
* `*_spec.{js|jsx|ts|tsx|mjs|cjs|mts|cts}` - Files ending with `_spec.js`, `_spec.jsx`, `_spec.ts`, `_spec.tsx`, `_spec.mjs`, `_spec.cjs`, `_spec.mts`, or `_spec.cts`

## Exclusions

By default, `bun test` ignores:

* `node_modules` directories
* Hidden directories (those starting with a period `.`)
* Files that don't have JavaScript-like extensions (based on available [loaders](/docs/bundler/loaders))

## Customizing Test Discovery

### Position Arguments as Filters

To filter which test files run, pass additional positional arguments to `bun test`:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun test <filter> <filter> ...
```

Any test file with a path that contains one of the filters runs. Filters are substring matches, not glob patterns.

For example, to run all tests in a `utils` directory:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun test utils
```

This matches files like `src/utils/string.test.ts` and `lib/utils/array_test.js`.

### Specifying Exact File Paths

To run a specific file in the test runner, make sure the path starts with `./` or `/` to distinguish it from a filter name:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun test ./test/specific-file.test.ts
```

### Filter by Test Name

To filter tests by name rather than file path, use the `-t`/`--test-name-pattern` flag with a regex pattern:

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
# run all tests with "addition" in the name
bun test --test-name-pattern addition
```

`bun test` matches the pattern against the test name prefixed with the labels of all its parent `describe` blocks, separated by spaces. For example, a test defined as:

```ts title="math.test.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
describe("Math", () => {
  describe("operations", () => {
    test("should add correctly", () => {
      // ...
    });
  });
});
```

For this test, `bun test` matches the pattern against the string "Math operations should add correctly".

### Changing the Root Directory

By default, Bun looks for test files starting from the current working directory. Change this with the `root` option in `bunfig.toml`:

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[test]
root = "src"  # Only scan for tests in the src directory
```

## Execution Order

Tests run in the following order:

1. Test files run sequentially, or across worker processes with [`--parallel`](/docs/test/parallel)
2. Within each file, tests run sequentially in definition order


# DOM testing
Source: https://bun.com/docs/test/dom

Learn how to test DOM elements and components using Bun with happy-dom and React Testing Library

Bun's test runner plays well with existing component and DOM testing libraries, including React Testing Library and happy-dom.

## happy-dom

For headless tests of your frontend code and components, we recommend happy-dom. It implements a complete set of HTML and DOM APIs in plain JavaScript, so it can simulate a browser environment with high fidelity.

Install the `@happy-dom/global-registrator` package as a dev dependency.

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun add -d @happy-dom/global-registrator
```

Use Bun's preload feature to register the happy-dom globals before your tests run, which makes browser APIs like `document` available in the global scope. Create a file called `happydom.ts` in the root of your project with the following code:

```ts title="happydom.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { GlobalRegistrator } from "@happy-dom/global-registrator";

GlobalRegistrator.register();
```

To preload this file before `bun test`, open or create a `bunfig.toml` file and add the following lines.

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[test]
preload = ["./happydom.ts"]
```

`bun test` now executes `happydom.ts` before your tests, so they can use browser APIs like `document` and `window`.

```ts title="dom.test.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { test, expect } from "bun:test";

test("dom test", () => {
  document.body.innerHTML = `<button>My button</button>`;
  const button = document.querySelector("button");
  expect(button?.innerText).toEqual("My button");
});
```

### TypeScript Support

Depending on your `tsconfig.json` setup, you may see a "Cannot find name 'document'" type error in the earlier code. To load the types for `document` and other browser APIs, add the following triple-slash directive to the top of any test file.

```ts title="dom.test.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
/// <reference lib="dom" />

import { test, expect } from "bun:test";

test("dom test", () => {
  document.body.innerHTML = `<button>My button</button>`;
  const button = document.querySelector("button");
  expect(button?.innerText).toEqual("My button");
});
```

Run the test with `bun test`:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun test
```

```
bun test v1.3.3

dom.test.ts:
✓ dom test [0.82ms]

 1 pass
 0 fail
 1 expect() calls
Ran 1 test across 1 file. [125.00ms]
```

## React Testing Library

Bun works with React Testing Library for testing React components. After setting up happy-dom as described earlier, install and use React Testing Library normally.

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun add -d @testing-library/react @testing-library/jest-dom
```

```ts title="component.test.tsx" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
/// <reference lib="dom" />

import { test, expect } from 'bun:test';
import { render, screen } from '@testing-library/react';
import '@testing-library/jest-dom';

function Button({ children }: { children: React.ReactNode }) {
  return <button>{children}</button>;
}

test('renders button', () => {
  render(<Button>Click me</Button>);
  expect(screen.getByRole('button')).toHaveTextContent('Click me');
});
```

## Advanced DOM Testing

### Custom Elements

Test custom elements and web components with the same setup:

```ts title="custom-element.test.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
/// <reference lib="dom" />

import { test, expect } from "bun:test";

test("custom element", () => {
  // Define a custom element
  class MyElement extends HTMLElement {
    constructor() {
      super();
      this.innerHTML = "<p>Custom element content</p>";
    }
  }

  customElements.define("my-element", MyElement);

  // Use it in tests
  document.body.innerHTML = "<my-element></my-element>";
  const element = document.querySelector("my-element");
  expect(element?.innerHTML).toBe("<p>Custom element content</p>");
});
```

### Event Testing

Test DOM events and user interactions:

```ts title="events.test.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
/// <reference lib="dom" />

import { test, expect } from "bun:test";

test("button click event", () => {
  let clicked = false;

  document.body.innerHTML = '<button id="test-btn">Click me</button>';
  const button = document.getElementById("test-btn");

  button?.addEventListener("click", () => {
    clicked = true;
  });

  button?.click();
  expect(clicked).toBe(true);
});
```

## Configuration Tips

### Global Setup

For more involved setups, create a preload file that also registers global mocks:

```ts title="test-setup.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { GlobalRegistrator } from "@happy-dom/global-registrator";
import "@testing-library/jest-dom";

// Register happy-dom globals
GlobalRegistrator.register();

// Add any global test configuration here
global.ResizeObserver = class ResizeObserver {
  observe() {}
  unobserve() {}
  disconnect() {}
};

// Mock other APIs as needed
Object.defineProperty(window, "matchMedia", {
  writable: true,
  value: jest.fn().mockImplementation(query => ({
    matches: false,
    media: query,
    onchange: null,
    addListener: jest.fn(),
    removeListener: jest.fn(),
    addEventListener: jest.fn(),
    removeEventListener: jest.fn(),
    dispatchEvent: jest.fn(),
  })),
});
```

Then update your `bunfig.toml`:

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[test]
preload = ["./test-setup.ts"]
```

## Troubleshooting

### Common Issues

**TypeScript errors for DOM APIs**: Include the `/// <reference lib="dom" />` directive at the top of your test files.

**Missing globals**: Check that your preload file imports and registers `@happy-dom/global-registrator`.

**React component rendering issues**: Check that `@testing-library/react` is installed and happy-dom is set up.

### Performance Considerations

happy-dom is fast, but for very large test suites you may want to:

* Use `beforeEach` to reset the DOM state between tests
* Avoid creating too many DOM elements in a single test
* Use `cleanup` functions from testing libraries

```ts title="test-setup.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { afterEach } from "bun:test";
import { cleanup } from "@testing-library/react";

afterEach(() => {
  cleanup();
  document.body.innerHTML = "";
});
```


# Test runner
Source: https://bun.com/docs/test/index

Bun's fast, built-in, Jest-compatible test runner with TypeScript support, lifecycle hooks, mocking, and watch mode

Bun ships with a fast, built-in, Jest-compatible test runner. Tests run in the Bun runtime and support the following features.

* TypeScript and JSX
* Lifecycle hooks
* Snapshot testing
* UI & DOM testing
* Watch mode with `--watch`
* Script pre-loading with `--preload`

<Note>
  Bun aims for compatibility with Jest, but not everything is implemented. To track compatibility, see [this tracking
  issue](https://github.com/oven-sh/bun/issues/1825).
</Note>

## Run tests

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun test
```

You write tests in JavaScript or TypeScript with a Jest-like API. See [Writing tests](/docs/test/writing-tests).

```ts math.test.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { expect, test } from "bun:test";

test("2 + 2", () => {
  expect(2 + 2).toBe(4);
});
```

The runner recursively searches the working directory for files that match the following patterns:

* `*.test.{js|jsx|ts|tsx|mjs|cjs|mts|cts}`
* `*_test.{js|jsx|ts|tsx|mjs|cjs|mts|cts}`
* `*.spec.{js|jsx|ts|tsx|mjs|cjs|mts|cts}`
* `*_spec.{js|jsx|ts|tsx|mjs|cjs|mts|cts}`

To filter the set of *test files* to run, pass additional positional arguments to `bun test`. Any test file with a path that matches one of the filters runs. Filters are commonly file or directory names; glob patterns are not yet supported.

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun test <filter> <filter> ...
```

To filter by *test name*, use the `-t`/`--test-name-pattern` flag.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
# run all tests or test suites with "addition" in the name
bun test --test-name-pattern addition
```

To run a specific file in the test runner, make sure the path starts with `./` or `/` to distinguish it from a filter name.

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun test ./test/specific-file.test.ts
```

By default the test runner runs all tests in a single process: it loads all `--preload` scripts (see [Lifecycle](/docs/test/lifecycle)), then runs every file in one shared global. Pass [`--parallel`](/docs/test/parallel) to spread files across CPU cores instead. If a test fails, the test runner exits with a non-zero exit code.

## CI/CD integration

`bun test` supports a variety of CI/CD integrations.

### GitHub Actions

`bun test` automatically detects when it's running inside GitHub Actions and emits GitHub Actions annotations to the console directly.

No configuration is needed, other than installing `bun` in the workflow and running `bun test`.

#### How to install `bun` in a GitHub Actions workflow

To use `bun test` in a GitHub Actions workflow, add the following step:

```yaml title=".github/workflows/test.yml" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
jobs:
  build:
    name: build-app
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4
      - name: Install bun
        uses: oven-sh/setup-bun@v2
      - name: Install dependencies # (assuming your project has dependencies)
        run: bun install # You can use npm/yarn/pnpm instead if you prefer
      - name: Run tests
        run: bun test
```

### JUnit XML reports (GitLab, etc.)

To write a JUnit XML report, pass `--reporter=junit` together with `--reporter-outfile`.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun test --reporter=junit --reporter-outfile=./bun.xml
```

`bun test` still writes to stdout/stderr as usual, and writes the JUnit XML report to the given path at the end of the run.

JUnit XML is a popular format for reporting test results in CI/CD pipelines.

## Timeouts

Use the `--timeout` flag to specify a *per-test* timeout in milliseconds. If a test times out, Bun marks it as failed. The default value is `5000`.

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
# default value is 5000
bun test --timeout 20
```

## Concurrent test execution

To run test **files** across CPU cores, see [`--parallel`](/docs/test/parallel). The flags below control concurrency of tests *within* a file.

By default, Bun runs all tests sequentially within each test file. Concurrent execution runs async tests in parallel, which speeds up test suites with independent tests.

### `--concurrent` flag

Use the `--concurrent` flag to run all tests concurrently within their respective files:

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun test --concurrent
```

When this flag is enabled, all tests run in parallel unless marked with `test.serial`.

### `--max-concurrency` flag

Control the maximum number of tests running simultaneously with the `--max-concurrency` flag:

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
# Limit to 4 concurrent tests
bun test --concurrent --max-concurrency 4

# Default: 20
bun test --concurrent
```

The limit helps prevent resource exhaustion when running many concurrent tests. The default value is 20.

### `test.concurrent`

Mark individual tests to run concurrently, even when the `--concurrent` flag is not used:

```ts title="math.test.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { test, expect } from "bun:test";

// These tests run in parallel with each other
test.concurrent("concurrent test 1", async () => {
  await fetch("/api/endpoint1");
  expect(true).toBe(true);
});

test.concurrent("concurrent test 2", async () => {
  await fetch("/api/endpoint2");
  expect(true).toBe(true);
});

// This test runs sequentially
test("sequential test", () => {
  expect(1 + 1).toBe(2);
});
```

### `test.serial`

Force tests to run sequentially, even when the `--concurrent` flag is enabled:

```ts title="math.test.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { test, expect } from "bun:test";

let sharedState = 0;

// These tests must run in order
test.serial("first serial test", () => {
  sharedState = 1;
  expect(sharedState).toBe(1);
});

test.serial("second serial test", () => {
  // Depends on the previous test
  expect(sharedState).toBe(1);
  sharedState = 2;
});

// This test can run concurrently if --concurrent is enabled
test("independent test", () => {
  expect(true).toBe(true);
});

// Chaining test qualifiers
test.failing.each([1, 2, 3])("chained qualifiers %d", input => {
  expect(input).toBe(0); // This test is expected to fail for each input
});
```

## Retry failed tests

Use the `--retry` flag to automatically retry failed tests up to a given number of times. If a test fails and then passes on a subsequent attempt, Bun reports it as passing.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun test --retry 3
```

Per-test `{ retry: N }` overrides the global `--retry` value:

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
// Uses the global --retry value
test("uses global retry", () => {
  /* ... */
});

// Overrides --retry with its own value
test("custom retry", { retry: 1 }, () => {
  /* ... */
});
```

You can also set this in `bunfig.toml`:

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[test]
retry = 3
```

## Rerun tests

Use the `--rerun-each` flag to run each test multiple times. This surfaces flaky or non-deterministic test failures.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun test --rerun-each 100
```

## Randomize test execution order

Use the `--randomize` flag to run tests in a random order. This helps detect tests that depend on shared state or execution order.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun test --randomize
```

With `--randomize`, Bun displays the seed used for randomization in the test summary:

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun test --randomize
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
# ... test output ...
 --seed=12345
 2 pass
 8 fail
Ran 10 tests across 2 files. [50.00ms]
```

### Reproducible random order with `--seed`

Use the `--seed` flag to specify the randomization seed and reproduce the same test order when debugging order-dependent failures.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
# Reproduce a previous randomized run
bun test --seed 123456
```

The `--seed` flag implies `--randomize`, so you don't need to specify both. The same seed always produces the same test execution order.

## Bail out with `--bail`

Use the `--bail` flag to abort the test run after a given number of test failures. By default, Bun runs all tests and reports all failures, but in CI it can be preferable to stop early and reduce CPU usage.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
# bail after 1 failure
bun test --bail

# bail after 10 failure
bun test --bail=10
```

## Watch mode

Like `bun run`, `bun test` accepts the `--watch` flag to watch for changes and re-run tests.

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun test --watch
```

## Lifecycle hooks

Bun supports the following lifecycle hooks:

| Hook         | Description                 |
| ------------ | --------------------------- |
| `beforeAll`  | Runs once before all tests. |
| `beforeEach` | Runs before each test.      |
| `afterEach`  | Runs after each test.       |
| `afterAll`   | Runs once after all tests.  |

Define hooks inside test files, or in a separate file preloaded with the `--preload` flag.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun test --preload ./setup.ts
```

See [Lifecycle](/docs/test/lifecycle).

## Mocks

Create mock functions with the `mock` function.

```ts title="math.test.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { test, expect, mock } from "bun:test";
const random = mock(() => Math.random());

test("random", () => {
  const val = random();
  expect(val).toBeGreaterThan(0);
  expect(random).toHaveBeenCalled();
  expect(random).toHaveBeenCalledTimes(1);
});
```

Alternatively, use `jest.fn()`; it behaves identically.

```ts title="math.test.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { test, expect, mock } from "bun:test"; // [!code --]
import { test, expect, jest } from "bun:test"; // [!code ++]

const random = mock(() => Math.random()); // [!code --]
const random = jest.fn(() => Math.random()); // [!code ++]
```

See [Mocks](/docs/test/mocks).

## Snapshot testing

`bun test` supports snapshot testing.

```ts title="math.test.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
// example usage of toMatchSnapshot
import { test, expect } from "bun:test";

test("snapshot", () => {
  expect({ a: 1 }).toMatchSnapshot();
});
```

To update snapshots, use the `--update-snapshots` flag.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun test --update-snapshots
```

See [Snapshots](/docs/test/snapshots).

## UI & DOM testing

Bun is compatible with popular UI testing libraries:

* [HappyDOM](https://github.com/capricorn86/happy-dom)
* [DOM Testing Library](https://testing-library.com/docs/dom-testing-library/intro/)
* [React Testing Library](https://testing-library.com/docs/react-testing-library/intro)

See [DOM testing](/docs/test/dom).

## Large codebases

For a suite with thousands of test files, `bun test` has several knobs that stack: worker processes, isolation level, sharding across machines, and duration-aware scheduling. [Parallel & isolated test runs](/docs/test/parallel) covers each in depth. Here is how they fit together, roughly in order of payoff:

**1. Use every core: [`--parallel`](/docs/test/parallel#--parallel).** One worker per core, files handed out one at a time.

**2. Decide how much isolation you need.** `--parallel` gives every file a fresh global, which is the safe default and what Jest/Vitest do. If your files don't leak state into each other (they already pass under plain `bun test`, which shares one global), [`--parallel --no-isolate`](/docs/test/parallel#every-file-is-isolated-unless-you-opt-out) lets each worker evaluate your imports and preloads once instead of once per file. On suites made of many small files, that is the single biggest win. See [how it compares](/docs/test/parallel#how-it-compares).

**3. Split across machines: [`--shard=i/n`](/docs/test/parallel#splitting-a-suite-across-ci-machines-with---shard).** Deterministic, no coordinator. Each CI job runs one slice, and each slice still uses `--parallel` locally.

**4. Balance by time, not count: [`--timings`](/docs/test/parallel#balancing-with---timings).** With recorded durations, Bun cuts shards so each gets about the same total time. The split is longest-processing-time style, but keeps path-neighbours together so a worker's module cache stays warm. Each worker starts its slowest file first, and idle workers steal the slowest remaining file. That way, one long file that happened to start last doesn't hold up the run.

**5. Keep the timings fresh automatically: `--update-timings`.** Each shard writes the durations of the files it ran; the next run reads all of them. In GitHub Actions that looks like:

```yaml title=".github/workflows/test.yml" icon="github" theme={"theme":{"light":"github-light","dark":"dracula"}}
jobs:
  test:
    strategy:
      matrix:
        shard: [1, 2, 3, 4]
    steps:
      - uses: actions/checkout@v4
      - uses: oven-sh/setup-bun@v2
      - run: bun install
      # last successful run's per-shard timings (nothing on the very first run)
      - uses: actions/cache/restore@v4
        with:
          path: .bun-test-timings
          key: bun-test-timings-${{ github.run_id }}
          restore-keys: bun-test-timings-
      - run: |
          bun test --parallel --shard=${{ matrix.shard }}/4 --update-timings \
            --timings=.bun-test-timings/next/${{ matrix.shard }}.json \
            $(ls .bun-test-timings/*.json 2>/dev/null | sed 's/^/--timings=/')
      - uses: actions/upload-artifact@v4
        with:
          name: timings-${{ matrix.shard }}
          path: .bun-test-timings/next/${{ matrix.shard }}.json
  save-timings:
    needs: test
    runs-on: ubuntu-latest
    steps:
      - uses: actions/download-artifact@v4
        with:
          pattern: timings-*
          path: .bun-test-timings
          merge-multiple: true
      - uses: actions/cache/save@v4
        with:
          path: .bun-test-timings
          key: bun-test-timings-${{ github.run_id }}
```

Every shard must read the *same set* of timings files for the shards to add up to the whole suite. That is why a run reads the previous run's files (restored from the cache), and why it writes its own where sibling shards still in flight won't pick them up (`next/` above). Add `--no-isolate` to the `bun test` line if step 2 applies to you.

**6. Within a file: [`test.concurrent`](#concurrent-test-execution)** for I/O-bound tests that spend their time awaiting.

## Performance

Bun's test runner is fast.

<Frame>
  <img alt="Running 266 React SSR tests faster than Jest can print its version number." />
</Frame>

## AI Agent Integration

When you use Bun's test runner with an AI coding assistant, you can enable quieter output that keeps failure details but drops the rest of the noise.

### Environment Variables

Set any of the following environment variables to enable AI-friendly output:

* `CLAUDECODE=1` - For Claude Code
* `REPL_ID=1` - For Replit
* `AGENT=1` - Generic AI agent flag

### Behavior

When Bun detects an AI agent environment:

* Only test failures are displayed in detail
* Passing, skipped, and todo test indicators are hidden
* Summary statistics remain intact

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
# Example: Enable quiet output for Claude Code
CLAUDECODE=1 bun test

# Still shows failures and summary, but hides verbose passing test output
```

***

# CLI Usage

```bash theme={"theme":{"light":"github-light","dark":"dracula"}}
bun test <patterns>
```

### Execution Control

<ParamField type="number">
  Set the per-test timeout in milliseconds (default 5000)
</ParamField>

<ParamField type="number">
  Re-run each test file <code>NUMBER</code> times to help catch certain bugs
</ParamField>

<ParamField type="number">
  Retry failed tests up to <code>NUMBER</code> times. Per-test  overrides this flag
</ParamField>

<ParamField type="boolean">
  Treat all tests as <code>test.concurrent()</code> tests
</ParamField>

<ParamField type="boolean">
  Run tests in random order
</ParamField>

<ParamField type="number">
  Set the random seed for test randomization
</ParamField>

<ParamField type="number">
  Exit the test suite after <code>NUMBER</code> failures. If you do not specify a number, it defaults to 1.
</ParamField>

<ParamField type="number">
  Maximum number of concurrent tests to execute at once (default 20)
</ParamField>

### Test Filtering

<ParamField type="boolean">
  Include tests that are marked with <code>test.todo()</code>
</ParamField>

<ParamField type="string">
  Run only tests with a name that matches the given regex. Alias: <code>-t</code>
</ParamField>

### Reporting

<ParamField type="string">
  Test output reporter format. Available: <code>junit</code> (requires --reporter-outfile), <code>dots</code>. Default:
  console output.
</ParamField>

<ParamField type="string">
  Output file path for the reporter format (required with --reporter=junit)
</ParamField>

<ParamField type="boolean">
  Enable dots reporter. Shorthand for --reporter=dots
</ParamField>

### Coverage

<ParamField type="boolean">
  Generate a coverage profile
</ParamField>

<ParamField type="string">
  Report coverage in <code>text</code> and/or <code>lcov</code>. Defaults to <code>text</code>
</ParamField>

<ParamField type="string">
  Directory for coverage files. Defaults to <code>coverage</code>
</ParamField>

### Snapshots

<ParamField type="boolean">
  Update snapshot files. Alias: <code>-u</code>
</ParamField>

## Examples

Run all test files:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun test
```

Run all test files with "foo" or "bar" in the file name:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun test foo bar
```

Run all test files, only including tests whose name includes "baz":

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun test --test-name-pattern baz
```


# Lifecycle hooks
Source: https://bun.com/docs/test/lifecycle

Learn how to use beforeAll, beforeEach, afterEach, and afterAll lifecycle hooks in Bun tests

The test runner supports the following lifecycle hooks. Use them to load test fixtures, mock data, and configure the test environment.

| Hook             | Description                                                |
| ---------------- | ---------------------------------------------------------- |
| `beforeAll`      | Runs once before all tests.                                |
| `beforeEach`     | Runs before each test.                                     |
| `afterEach`      | Runs after each test.                                      |
| `afterAll`       | Runs once after all tests.                                 |
| `onTestFinished` | Runs after a single test finishes (after all `afterEach`). |

## Per-Test Setup and Teardown

Perform per-test setup and teardown logic with `beforeEach` and `afterEach`.

```ts title="test.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { beforeEach, afterEach, test } from "bun:test";

beforeEach(() => {
  console.log("running test.");
});

afterEach(() => {
  console.log("done with test.");
});

// tests...
test("example test", () => {
  // This test will have beforeEach run before it
  // and afterEach run after it
});
```

## Per-Scope Setup and Teardown

Perform per-scope setup and teardown logic with `beforeAll` and `afterAll`. Where you define the hook determines its scope.

### Scoped to a Describe Block

To scope the hooks to a particular describe block:

```ts title="test.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { describe, beforeAll, afterAll, test } from "bun:test";

describe("test group", () => {
  beforeAll(() => {
    // setup for this describe block
    console.log("Setting up test group");
  });

  afterAll(() => {
    // teardown for this describe block
    console.log("Tearing down test group");
  });

  test("test 1", () => {
    // test implementation
  });

  test("test 2", () => {
    // test implementation
  });
});
```

### Scoped to a Test File

To scope the hooks to an entire test file:

```ts title="test.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { describe, beforeAll, afterAll, test } from "bun:test";

beforeAll(() => {
  // setup for entire file
  console.log("Setting up test file");
});

afterAll(() => {
  // teardown for entire file
  console.log("Tearing down test file");
});

describe("test group", () => {
  test("test 1", () => {
    // test implementation
  });
});
```

### `onTestFinished`

Use `onTestFinished` to run a callback after a single test completes. The callback runs after all `afterEach` hooks.

```ts title="test.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { test, onTestFinished } from "bun:test";

test("cleanup after test", () => {
  onTestFinished(() => {
    // runs after all afterEach hooks
    console.log("test finished");
  });
});
```

Not supported in concurrent tests; use `test.serial` instead.

## Global Setup and Teardown

To scope the hooks to an entire multi-file test run, define the hooks in a separate file.

```ts title="setup.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { beforeAll, afterAll } from "bun:test";

beforeAll(() => {
  // global setup
  console.log("Global test setup");
  // Initialize database connections, start servers, etc.
});

afterAll(() => {
  // global teardown
  console.log("Global test teardown");
  // Close database connections, stop servers, etc.
});
```

Then use `--preload` to run the setup script before any test files.

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun test --preload ./setup.ts
```

To avoid typing `--preload` every time you run tests, add it to your `bunfig.toml`:

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[test]
preload = ["./setup.ts"]
```

## Practical Examples

### Database Setup

```ts title="database-setup.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { beforeAll, afterAll, beforeEach, afterEach } from "bun:test";
import { createConnection, closeConnection, clearDatabase } from "./db";

let connection;

beforeAll(async () => {
  // Connect to test database
  connection = await createConnection({
    host: "localhost",
    database: "test_db",
  });
});

afterAll(async () => {
  // Close database connection
  await closeConnection(connection);
});

beforeEach(async () => {
  // Start with clean database for each test
  await clearDatabase(connection);
});
```

### API Server Setup

```ts title="server-setup.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { beforeAll, afterAll } from "bun:test";
import { startServer, stopServer } from "./server";

let server;

beforeAll(async () => {
  // Start test server
  server = await startServer({
    port: 3001,
    env: "test",
  });
});

afterAll(async () => {
  // Stop test server
  await stopServer(server);
});
```

### Mock Setup

```ts title="mock-setup.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { beforeEach, afterEach } from "bun:test";
import { mock } from "bun:test";

beforeEach(() => {
  // Set up common mocks
  mock.module("./api-client", () => ({
    fetchUser: mock(() => Promise.resolve({ id: 1, name: "Test User" })),
    createUser: mock(() => Promise.resolve({ id: 2 })),
  }));
});

afterEach(() => {
  // Clear all mocks after each test
  mock.clearAllMocks();
});
```

## Async Lifecycle Hooks

All lifecycle hooks support async functions:

```ts title="test.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { beforeAll, afterAll, test, expect } from "bun:test";

beforeAll(async () => {
  // Async setup
  await new Promise(resolve => setTimeout(resolve, 100));
  console.log("Async setup complete");
});

afterAll(async () => {
  // Async teardown
  await new Promise(resolve => setTimeout(resolve, 100));
  console.log("Async teardown complete");
});

test("async test", async () => {
  // Test will wait for beforeAll to complete
  await expect(Promise.resolve("test")).resolves.toBe("test");
});
```

## Nested Hooks

You can nest hooks. They run in the following order:

```ts title="test.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { describe, beforeAll, beforeEach, afterEach, afterAll, test } from "bun:test";

beforeAll(() => console.log("File beforeAll"));
afterAll(() => console.log("File afterAll"));

describe("outer describe", () => {
  beforeAll(() => console.log("Outer beforeAll"));
  beforeEach(() => console.log("Outer beforeEach"));
  afterEach(() => console.log("Outer afterEach"));
  afterAll(() => console.log("Outer afterAll"));

  describe("inner describe", () => {
    beforeAll(() => console.log("Inner beforeAll"));
    beforeEach(() => console.log("Inner beforeEach"));
    afterEach(() => console.log("Inner afterEach"));
    afterAll(() => console.log("Inner afterAll"));

    test("nested test", () => {
      console.log("Test running");
    });
  });
});
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
// Output order:
// File beforeAll
// Outer beforeAll
// Inner beforeAll
// Outer beforeEach
// Inner beforeEach
// Test running
// Inner afterEach
// Outer afterEach
// Inner afterAll
// Outer afterAll
// File afterAll
```

## Error Handling

If a `beforeAll` hook throws, the test runner skips every test in the hook's scope:

```ts title="test.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { beforeAll, test } from "bun:test";

beforeAll(() => {
  // If this throws, all tests in this scope will be skipped
  throw new Error("Setup failed");
});

test("this test will be skipped", () => {
  // This won't run because beforeAll failed
});
```

To log a setup failure and still fail the suite:

```ts title="test.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { beforeAll, test, expect } from "bun:test";

beforeAll(async () => {
  try {
    await setupDatabase();
  } catch (error) {
    console.error("Database setup failed:", error);
    throw error; // Re-throw to fail the test suite
  }
});
```

## Best Practices

### Keep Hooks Simple

```ts title="test.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
// Good: Simple, focused setup
beforeEach(() => {
  clearLocalStorage();
  resetMocks();
});

// Avoid: Complex logic in hooks
beforeEach(async () => {
  // Too much complex logic makes tests hard to debug
  const data = await fetchComplexData();
  await processData(data);
  await setupMultipleServices(data);
});
```

### Use Appropriate Scope

```ts title="test.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
// Good: File-level setup for shared resources
beforeAll(async () => {
  await startTestServer();
});

// Good: Test-level setup for test-specific state
beforeEach(() => {
  user = createTestUser();
});
```

### Clean Up Resources

```ts title="test.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { afterAll, afterEach } from "bun:test";

afterEach(() => {
  // Clean up after each test
  document.body.innerHTML = "";
  localStorage.clear();
});

afterAll(async () => {
  // Clean up expensive resources
  await closeDatabase();
  await stopServer();
});
```


# Mocks
Source: https://bun.com/docs/test/mocks

Learn how to create and use mock functions, spies, and module mocks in Bun tests

Mocking replaces a dependency with a controlled implementation. Bun supports function mocks, spies, and module mocks.

## Basic Function Mocks

Create mocks with the `mock` function.

```ts title="test.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { test, expect, mock } from "bun:test";

const random = mock(() => Math.random());

test("random", () => {
  const val = random();
  expect(val).toBeGreaterThan(0);
  expect(random).toHaveBeenCalled();
  expect(random).toHaveBeenCalledTimes(1);
});
```

### Jest Compatibility

You can also use `jest.fn()`, as in Jest. It behaves identically.

```ts title="test.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { test, expect, jest } from "bun:test";

const random = jest.fn(() => Math.random());

test("random", () => {
  const val = random();
  expect(val).toBeGreaterThan(0);
  expect(random).toHaveBeenCalled();
  expect(random).toHaveBeenCalledTimes(1);
});
```

## Mock Function Properties

`mock()` returns a new function decorated with additional properties.

```ts title="test.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { mock } from "bun:test";

const random = mock((multiplier: number) => multiplier * Math.random());

random(2);
random(10);

random.mock.calls;
// [[ 2 ], [ 10 ]]

random.mock.results;
//  [
//    { type: "return", value: 0.6533907460954099 },
//    { type: "return", value: 0.6452713933037312 }
//  ]
```

### Available Properties and Methods

Mock functions implement the following properties and methods:

| Property/Method                           | Description                                    |
| ----------------------------------------- | ---------------------------------------------- |
| `mockFn.getMockName()`                    | Returns the mock name                          |
| `mockFn.mock.calls`                       | Array of call arguments for each invocation    |
| `mockFn.mock.results`                     | Array of return values for each invocation     |
| `mockFn.mock.instances`                   | Array of instances created with `new`          |
| `mockFn.mock.contexts`                    | Array of `this` contexts for each invocation   |
| `mockFn.mock.lastCall`                    | Arguments of the most recent call              |
| `mockFn.mockClear()`                      | Clears call history                            |
| `mockFn.mockReset()`                      | Clears call history and removes implementation |
| `mockFn.mockRestore()`                    | Restores original implementation               |
| `mockFn.mockImplementation(fn)`           | Sets a new implementation                      |
| `mockFn.mockImplementationOnce(fn)`       | Sets implementation for next call only         |
| `mockFn.mockName(name)`                   | Sets the mock name                             |
| `mockFn.mockReturnThis()`                 | Sets the return value to `this`                |
| `mockFn.mockReturnValue(value)`           | Sets a return value                            |
| `mockFn.mockReturnValueOnce(value)`       | Sets return value for next call only           |
| `mockFn.mockResolvedValue(value)`         | Sets a resolved Promise value                  |
| `mockFn.mockResolvedValueOnce(value)`     | Sets resolved Promise for next call only       |
| `mockFn.mockRejectedValue(value)`         | Sets a rejected Promise value                  |
| `mockFn.mockRejectedValueOnce(value)`     | Sets rejected Promise for next call only       |
| `mockFn.withImplementation(fn, callback)` | Temporarily changes implementation             |

### Practical Examples

#### Basic Mock Usage

```ts title="test.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { test, expect, mock } from "bun:test";

test("mock function behavior", () => {
  const mockFn = mock((x: number) => x * 2);

  // Call the mock
  const result1 = mockFn(5);
  const result2 = mockFn(10);

  // Verify calls
  expect(mockFn).toHaveBeenCalledTimes(2);
  expect(mockFn).toHaveBeenCalledWith(5);
  expect(mockFn).toHaveBeenLastCalledWith(10);

  // Check results
  expect(result1).toBe(10);
  expect(result2).toBe(20);

  // Inspect call history
  expect(mockFn.mock.calls).toEqual([[5], [10]]);
  expect(mockFn.mock.results).toEqual([
    { type: "return", value: 10 },
    { type: "return", value: 20 },
  ]);
});
```

#### Dynamic Mock Implementations

```ts title="test.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { test, expect, mock } from "bun:test";

test("dynamic mock implementations", () => {
  const mockFn = mock();

  // Set different implementations
  mockFn.mockImplementationOnce(() => "first");
  mockFn.mockImplementationOnce(() => "second");
  mockFn.mockImplementation(() => "default");

  expect(mockFn()).toBe("first");
  expect(mockFn()).toBe("second");
  expect(mockFn()).toBe("default");
  expect(mockFn()).toBe("default"); // Uses default implementation
});
```

#### Async Mocks

```ts title="test.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { test, expect, mock } from "bun:test";

test("async mock functions", async () => {
  const asyncMock = mock();

  // Mock resolved values
  asyncMock.mockResolvedValueOnce("first result");
  asyncMock.mockResolvedValue("default result");

  expect(await asyncMock()).toBe("first result");
  expect(await asyncMock()).toBe("default result");

  // Mock rejected values
  const rejectMock = mock();
  rejectMock.mockRejectedValue(new Error("Mock error"));

  await expect(rejectMock()).rejects.toThrow("Mock error");
});
```

## Spies with spyOn()

Use `spyOn()` to track calls to a function without replacing it with a mock. You can pass spies to `.toHaveBeenCalled()` and `.toHaveBeenCalledTimes()`.

```ts title="test.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { test, expect, spyOn } from "bun:test";

const ringo = {
  name: "Ringo",
  sayHi() {
    console.log(`Hello I'm ${this.name}`);
  },
};

const spy = spyOn(ringo, "sayHi");

test("spyon", () => {
  expect(spy).toHaveBeenCalledTimes(0);
  ringo.sayHi();
  expect(spy).toHaveBeenCalledTimes(1);
});
```

### Advanced Spy Usage

```ts title="test.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { test, expect, spyOn, afterEach, jest } from "bun:test";

class UserService {
  async getUser(id: string) {
    // Original implementation
    return { id, name: `User ${id}` };
  }

  async saveUser(user: any) {
    // Original implementation
    return { ...user, saved: true };
  }
}

const userService = new UserService();

afterEach(() => {
  // Restore all spies after each test
  jest.restoreAllMocks();
});

test("spy on service methods", async () => {
  // Spy without changing implementation
  const getUserSpy = spyOn(userService, "getUser");
  const saveUserSpy = spyOn(userService, "saveUser");

  // Use the service normally
  const user = await userService.getUser("123");
  await userService.saveUser(user);

  // Verify calls
  expect(getUserSpy).toHaveBeenCalledWith("123");
  expect(saveUserSpy).toHaveBeenCalledWith(user);
});

test("spy with mock implementation", async () => {
  // Spy and override implementation
  const getUserSpy = spyOn(userService, "getUser").mockResolvedValue({
    id: "123",
    name: "Mocked User",
  });

  const result = await userService.getUser("123");

  expect(result.name).toBe("Mocked User");
  expect(getUserSpy).toHaveBeenCalledWith("123");
});
```

## Module Mocks with mock.module()

Use `mock.module(path: string, callback: () => Object)` to override the behavior of a module.

```ts title="test.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { test, expect, mock } from "bun:test";

mock.module("./module", () => {
  return {
    foo: "bar",
  };
});

test("mock.module", async () => {
  const esm = await import("./module");
  expect(esm.foo).toBe("bar");

  const cjs = require("./module");
  expect(cjs.foo).toBe("bar");
});
```

Like the rest of Bun, module mocks support both `import` and `require`.

### Overriding Already Imported Modules

Calling `mock.module()` overrides the module even if it has already been imported.

```ts title="test.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { test, expect, mock } from "bun:test";

// The module we're going to mock is here:
import { foo } from "./module";

test("mock.module", async () => {
  const cjs = require("./module");
  expect(foo).toBe("bar");
  expect(cjs.foo).toBe("bar");

  // We update it here:
  mock.module("./module", () => {
    return {
      foo: "baz",
    };
  });

  // And the live bindings are updated.
  expect(foo).toBe("baz");

  // The module is also updated for CJS.
  expect(cjs.foo).toBe("baz");
});
```

### Hoisting & Preloading

To make sure a module is mocked before it's imported, use `--preload` to load your mocks before your tests run.

```ts title="my-preload.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { mock } from "bun:test";

mock.module("./module", () => {
  return {
    foo: "bar",
  };
});
```

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun test --preload ./my-preload
```

To avoid typing `--preload` every time you run tests, add it to your `bunfig.toml`:

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[test]
# Load these modules before running tests.
preload = ["./my-preload"]
```

### Module Mock Best Practices

#### When to Use Preload

Mocking a module that's already been imported updates the module cache, so anything that imports it gets the mocked version. Bun has already evaluated the original module, though, so its side effects have already happened.

To prevent the original module from being evaluated at all, use `--preload` to load your mocks before your tests run.

#### Practical Module Mock Examples

```ts title="api-client.test.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { test, expect, mock, beforeEach } from "bun:test";

// Mock the API client module
mock.module("./api-client", () => ({
  fetchUser: mock(async (id: string) => ({ id, name: `User ${id}` })),
  createUser: mock(async (user: any) => ({ ...user, id: "new-id" })),
  updateUser: mock(async (id: string, user: any) => ({ ...user, id })),
}));

test("user service with mocked API", async () => {
  const { fetchUser } = await import("./api-client");
  const { UserService } = await import("./user-service");

  const userService = new UserService();
  const user = await userService.getUser("123");

  expect(fetchUser).toHaveBeenCalledWith("123");
  expect(user.name).toBe("User 123");
});
```

#### Mocking External Dependencies

```ts title="database.test.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { test, expect, mock } from "bun:test";

// Mock external database library
mock.module("pg", () => ({
  Client: mock(function () {
    return {
      connect: mock(async () => {}),
      query: mock(async (sql: string) => ({
        rows: [{ id: 1, name: "Test User" }],
      })),
      end: mock(async () => {}),
    };
  }),
}));

test("database operations", async () => {
  const { Database } = await import("./database");
  const db = new Database();

  const users = await db.getUsers();
  expect(users).toHaveLength(1);
  expect(users[0].name).toBe("Test User");
});
```

## Global Mock Functions

### Clear All Mocks

`mock.clearAllMocks()` resets the `.mock.calls`, `.mock.instances`, `.mock.contexts`, and `.mock.results` properties of every mock. Unlike `mock.restore()`, it does not restore the original implementation:

```ts title="test.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { expect, mock, test } from "bun:test";

const random1 = mock(() => Math.random());
const random2 = mock(() => Math.random());

test("clearing all mocks", () => {
  random1();
  random2();

  expect(random1).toHaveBeenCalledTimes(1);
  expect(random2).toHaveBeenCalledTimes(1);

  mock.clearAllMocks();

  expect(random1).toHaveBeenCalledTimes(0);
  expect(random2).toHaveBeenCalledTimes(0);

  // Note: implementations are preserved
  expect(typeof random1()).toBe("number");
  expect(typeof random2()).toBe("number");
});
```

### Reset All Mocks

`jest.resetAllMocks()` (and its `vi.resetAllMocks()` alias) calls `mockFn.mockReset()` on every mock. On top of what `clearAllMocks()` does, it drops the implementations set by `mockImplementation()`, `mockReturnValue()` and friends. It does not restore the original implementation of a spy:

```ts title="test.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { expect, jest, test } from "bun:test";

const random = jest.fn(() => Math.random());

test("resetting all mocks", () => {
  random();
  expect(random).toHaveBeenCalledTimes(1);

  jest.resetAllMocks();

  expect(random).toHaveBeenCalledTimes(0);
  // unlike clearAllMocks(), the implementation is gone
  expect(random()).toBeUndefined();
});
```

### Restore All Mocks

`mock.restore()` restores every mock at once, instead of calling `mockFn.mockRestore()` on each one. It does not reset modules overridden with `mock.module()`.

```ts title="test.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { expect, mock, spyOn, test } from "bun:test";

import * as fooModule from "./foo.ts";
import * as barModule from "./bar.ts";
import * as bazModule from "./baz.ts";

test("foo, bar, baz", () => {
  const fooSpy = spyOn(fooModule, "foo");
  const barSpy = spyOn(barModule, "bar");
  const bazSpy = spyOn(bazModule, "baz");

  // Original implementations still work
  expect(fooModule.foo()).toBe("foo");
  expect(barModule.bar()).toBe("bar");
  expect(bazModule.baz()).toBe("baz");

  // Mock implementations
  fooSpy.mockImplementation(() => 42);
  barSpy.mockImplementation(() => 43);
  bazSpy.mockImplementation(() => 44);

  expect(fooModule.foo()).toBe(42);
  expect(barModule.bar()).toBe(43);
  expect(bazModule.baz()).toBe(44);

  // Restore all
  mock.restore();

  expect(fooModule.foo()).toBe("foo");
  expect(barModule.bar()).toBe("bar");
  expect(bazModule.baz()).toBe("baz");
});
```

Call `mock.restore()` in an `afterEach` block, or in your test preload script, instead of repeating cleanup in every test.

## Vitest Compatibility

For added compatibility with tests written for Vitest, Bun provides the `vi` object as an alias for parts of the Jest mocking API:

```ts title="test.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { test, expect, vi } from "bun:test";

// Using the 'vi' alias similar to Vitest
test("vitest compatibility", () => {
  const mockFn = vi.fn(() => 42);

  mockFn();
  expect(mockFn).toHaveBeenCalled();

  // The following functions are available on the vi object:
  // vi.fn
  // vi.spyOn
  // vi.mock
  // vi.restoreAllMocks
  // vi.resetAllMocks
  // vi.clearAllMocks
});
```

You can port tests from Vitest without rewriting your mocks.

## Implementation Details

### Cache Interaction

Module mocks interact with both ESM and CommonJS module caches.

### Lazy Evaluation

Bun evaluates the mock factory callback only when the module is imported or required.

### Path Resolution

Bun resolves the module specifier the same way it resolves an `import`, supporting:

* Relative paths (`'./module'`)
* Absolute paths (`'/path/to/module'`)
* Package names (`'lodash'`)

### Import Timing Effects

* **When mocking before first import**: No side effects from the original module occur
* **When mocking after import**: The original module's side effects have already happened

For this reason, use `--preload` for mocks that need to prevent side effects.

### Live Bindings

Mocked ESM modules maintain live bindings, so changing the mock updates all existing imports.

## Advanced Patterns

### Factory Functions

```ts title="test.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { mock } from "bun:test";

function createMockUser(overrides = {}) {
  return {
    id: "mock-id",
    name: "Mock User",
    email: "mock@example.com",
    ...overrides,
  };
}

const mockUserService = {
  getUser: mock(async (id: string) => createMockUser({ id })),
  createUser: mock(async (data: any) => createMockUser(data)),
  updateUser: mock(async (id: string, data: any) => createMockUser({ id, ...data })),
};
```

### Conditional Mocking

```ts title="test.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { test, expect, mock } from "bun:test";

const shouldUseMockApi = process.env.NODE_ENV === "test";

if (shouldUseMockApi) {
  mock.module("./api", () => ({
    fetchData: mock(async () => ({ data: "mocked" })),
  }));
}

test("conditional API usage", async () => {
  const { fetchData } = await import("./api");
  const result = await fetchData();

  if (shouldUseMockApi) {
    expect(result.data).toBe("mocked");
  }
});
```

### Mock Cleanup Patterns

```ts title="test.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { afterEach, beforeEach, mock } from "bun:test";

beforeEach(() => {
  // Set up common mocks
  mock.module("./logger", () => ({
    log: mock(() => {}),
    error: mock(() => {}),
    warn: mock(() => {}),
  }));
});

afterEach(() => {
  // Restore spies and clear call history; neither call resets the mock.module() override
  mock.restore();
  mock.clearAllMocks();
});
```

## Best Practices

### Keep Mocks Simple

```ts title="test.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
// Good: Simple, focused mock
const mockUserApi = {
  getUser: mock(async id => ({ id, name: "Test User" })),
};

// Avoid: Overly complex mock behavior
const complexMock = mock(input => {
  if (input.type === "A") {
    return processTypeA(input);
  } else if (input.type === "B") {
    return processTypeB(input);
  }
  // ... lots of complex logic
});
```

### Use Type-Safe Mocks

```ts theme={"theme":{"light":"github-light","dark":"dracula"}}
interface UserService {
  getUser(id: string): Promise<User>;
  createUser(data: CreateUserData): Promise<User>;
}

const mockUserService: UserService = {
  getUser: mock(async (id: string) => ({ id, name: "Test User" })),
  createUser: mock(async data => ({ id: "new-id", ...data })),
};
```

### Test Mock Behavior

```ts title="test.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
test("service calls API correctly", async () => {
  const mockApi = { fetchUser: mock(async () => ({ id: "1" })) };

  const service = new UserService(mockApi);
  await service.getUser("123");

  // Verify the mock was called correctly
  expect(mockApi.fetchUser).toHaveBeenCalledWith("123");
  expect(mockApi.fetchUser).toHaveBeenCalledTimes(1);
});
```

## Notes

### Auto-mocking

Bun does not support the `__mocks__` directory or auto-mocking. If this is blocking you from switching to Bun, [file an issue](https://github.com/oven-sh/bun/issues).

### ESM vs CommonJS

Module mocks have different implementations for ESM and CommonJS modules. For ES modules, Bun patches JavaScriptCore so it can override export values at runtime and update live bindings recursively.


# Parallel & isolated test runs
Source: https://bun.com/docs/test/parallel

Run test files across CPU cores with --parallel, isolate files from each other with --isolate, run tests within a file concurrently, and split suites across CI machines with --shard and --timings

`bun test` has three independent knobs for running more than one thing at a time:

| Flag                               | Unit of parallelism             | What it does                                                                                                         |
| ---------------------------------- | ------------------------------- | -------------------------------------------------------------------------------------------------------------------- |
| `--parallel[=N]`                   | test **files**, in processes    | Runs files across `N` worker processes (default: number of CPU cores). Implies `--isolate`; `--no-isolate` opts out. |
| `--concurrent` / `test.concurrent` | **tests** within one file       | Lets `async` tests in the same file overlap while one is awaiting.                                                   |
| `--shard=i/n`                      | test files, across **machines** | Runs the `i`-th of `n` deterministic slices of the suite. Combine with `--timings` to balance by duration.           |

They compose: a CI job can run `bun test --shard=2/4 --parallel`, and files in that shard can still contain `test.concurrent` tests.

## `--parallel`

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun test --parallel        # one worker per CPU core
bun test --parallel=4      # exactly 4 workers
```

The main `bun test` process becomes a coordinator. It discovers test files as usual, then starts worker processes and hands each one file at a time. Results stream back as each test finishes, so the output looks the same as a serial run. The coordinator prints each file's results together under its filename, and never interleaves `console.log` output from a test with another file's.

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
bun test v1.4.0 8x PARALLEL

src/router.test.ts:
✓ matches static routes [0.31ms]
✓ matches params [0.12ms]

src/db.test.ts:
✓ migrates up [41.02ms]
✓ migrates down [38.60ms]
...
```

Workers start lazily. The first worker starts immediately; the coordinator spawns the rest only once every running worker has been busy for a few milliseconds (`--parallel-delay=<ms>`, default `5`). A suite of tiny files therefore runs on a single worker with no process-spawn overhead, while the first slow file triggers full fan-out.

### How files are distributed

The coordinator sorts files by path and splits them into one contiguous chunk per worker, so files in the same directory, which usually import the same modules, mostly land in the same process (a chunk boundary can fall inside a directory, and stolen files move). When a worker drains its chunk it steals the back half of the largest remaining chunk from another worker. With [`--timings`](#balancing-with---timings) the coordinator cuts the chunks by recorded duration instead of file count, each worker starts its slowest file first, and an idle worker steals the slowest not-yet-started file from whichever chunk has the most time left.

### Every file is isolated (unless you opt out)

`--parallel` implies [`--isolate`](#--isolate): each file runs in a fresh global object even when two files land on the same worker. Tests that pass with `--parallel` don't depend on state leaked by an earlier file.

`--parallel --no-isolate` turns that off: each worker keeps a single global and module registry for all the files it is handed, exactly like a serial `bun test` does for the whole suite. Each worker evaluates imports (and `--preload` modules) once instead of once per file, which is the fastest way to run a large suite of small files. The price is that a file can observe whatever an earlier file on the same worker left behind. Preload-level `beforeAll`/`afterAll` hooks still wrap every file, since a worker never knows which file is its last.

### Worker environment

Each worker gets `BUN_TEST_WORKER_ID` and `JEST_WORKER_ID` set to its 1-based index, so tests can pick a distinct database, port range, or temp directory per worker:

```ts title="db.test.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
const dbName = `app_test_${process.env.BUN_TEST_WORKER_ID ?? "1"}`;
```

Flags that affect how tests execute (`--timeout`, `--preload`, `--define`, `--coverage`, `--update-snapshots`, `-t`, `--retry`, `--rerun-each`, `--concurrent`, `--randomize`/`--seed`, …) are forwarded to workers. The coordinator handles `--bail` at file granularity: once the failure threshold is reached it starts no new files, but files already running finish.

The coordinator merges coverage, JUnit XML and snapshot writes, so `--parallel --coverage --reporter=junit --reporter-outfile=junit.xml` produces one report.

If a worker crashes (a test calls `process.exit`), the coordinator reports the file that worker was running as failed, and a replacement worker picks up the remaining files. A crash from a fatal signal aborts the whole run, so later passing files can't mask it.

### When `--parallel` helps, and when it doesn't

`--parallel` pays off when the suite is dominated by test execution — I/O waits, real computation, subprocesses, many files. It costs something too: every file re-evaluates its imports in a fresh global (see [`--isolate`](#--isolate)), and each worker is a separate process with its own JIT warm-up. For a suite of very fast files that all import the same large module graph, plain `bun test` (one process, one shared module registry) can be faster. Try both; Bun prints the numbers at the end of every run.

## `--isolate`

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun test --isolate
```

Runs each test file in a fresh JavaScript global object inside the same process. Between files Bun:

* creates a new `globalThis` (so properties a file stuck on `globalThis`, patched built-ins, and module-level state are gone),
* clears the ESM and CommonJS module registries (every file re-evaluates its imports),
* closes servers, sockets, file watchers and subprocesses the file left open, cancels its timers, and restores fake timers,
* re-runs `--preload` scripts in the new global.

Isolating every file is how Jest and Vitest behave by default. It makes "passes alone, fails in the full suite" bugs go away at the cost of re-evaluating imports per file.

To keep that cost low, Bun caches transpiled source and bytecode at the process level and shares them across globals. The second file to import a module skips reading, transpiling and parsing it and goes straight to evaluation. Only the module's top-level code runs again.

Without `--isolate` (the default), all files share one global and one module registry. That is the fastest mode and is fine for suites whose files don't leak state into each other.

## Concurrent tests within a file

`--parallel` spreads *files* across cores. Within one file tests still run one at a time unless you opt in to concurrency, which lets `async` tests overlap while one is waiting on I/O:

```ts title="api.test.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { test, expect } from "bun:test";

test.concurrent("GET /users", async () => {
  const res = await fetch(`${baseUrl}/users`);
  expect(res.status).toBe(200);
});

test.concurrent("GET /posts", async () => {
  const res = await fetch(`${baseUrl}/posts`);
  expect(res.status).toBe(200);
});

// runs after the concurrent group, alone
test.serial("resets the database", async () => {
  await resetDb();
});
```

* `test.concurrent(...)` / `describe.concurrent(...)` mark individual tests or whole groups.
* `--concurrent` treats every test as concurrent; `test.serial` opts back out.
* `--max-concurrency=N` caps how many run at once (default 20).
* [`concurrentTestGlob`](/docs/test/configuration#concurrenttestglob) in `bunfig.toml` turns it on for matching files only.

Concurrent tests share a thread and a global; this is cooperative concurrency for I/O-bound tests, not extra CPU cores. `expect.assertions()` and other per-test global state need care under concurrency — see [Concurrent test execution](/docs/test/index#concurrent-test-execution).

## Splitting a suite across CI machines with `--shard`

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun test --shard=1/3   # machine 1
bun test --shard=2/3   # machine 2
bun test --shard=3/3   # machine 3
```

Every machine sorts the discovered test files by path and takes a deterministic slice, so together the shards cover each file exactly once with no coordination. Without `--timings`, file `i` of the sorted list goes to shard `(i mod n) + 1` — balanced by file count, not by how long files take.

### Balancing with `--timings`

File count is a poor proxy for duration: one shard can end up with all the slow integration tests. Give `bun test` a record of how long each file takes and it cuts shards by total time instead, keeping neighbouring files (which share imports) together:

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
# Record durations (any run can do this; --parallel is fine)
bun test --timings=.bun-test-timings.json --update-timings

# Use them
bun test --shard=2/8 --parallel --timings=.bun-test-timings.json
```

The file is plain JSON, slowest first, so it doubles as a "what's slow" report:

```json title=".bun-test-timings.json" icon="file-json" theme={"theme":{"light":"github-light","dark":"dracula"}}
{
  "version": 1,
  "files": {
    "test/integration/build.test.ts": 41234,
    "test/db/migrate.test.ts": 9876,
    "src/router.test.ts": 112
  }
}
```

* Paths are relative to the project root; values are wall-clock milliseconds for the whole file.
* Without `--shard`, `--update-timings` merges into what it read, so re-running part of the suite locally refreshes those entries and keeps the rest. Entries for files that no longer exist are left alone; delete the file to start over.
* With `--shard`, `--update-timings` writes **only the files that shard ran** — see below.
* Bun assumes files with no entry take the median time when cutting shards, and starts them first under `--parallel`.
* With `--timings`, `--parallel` also uses the durations: the coordinator cuts worker chunks by time and each worker starts its slowest file first.

#### One timings file per shard

You can pass `--timings` more than once. Bun reads the files as one table and skips paths that don't exist yet. `--update-timings` writes to the **first** path. Under `--shard` that output contains only the files the shard ran, so the shards' outputs are disjoint. Read together on the next run, they add up to the whole suite with no merge step. [Large codebases](/docs/test/index#large-codebases) on the main page has the full CI workflow.

## How it compares

2 000 TypeScript test files × 8 small tests each, all importing a small app built on `zod`, `date-fns` and `lodash`, with a shared setup file (custom matcher + `beforeEach`/`afterEach`) loaded via each runner's preload mechanism — the shape of a large application's unit-test suite ([`bench/test/app`](https://github.com/oven-sh/bun/tree/main/bench/test/app), `bun app/setup.ts 2000 20`). 16-core Apple M4 Max:

| Mode                             | Bun                                           | Vitest 4.1                                            | Jest 30 (`@swc/jest`)                                  |
| -------------------------------- | --------------------------------------------- | ----------------------------------------------------- | ------------------------------------------------------ |
| all cores, one global per worker | `bun test --parallel --no-isolate` **0.75 s** | `vitest run --no-isolate` 3.7 s                       | —                                                      |
| all cores, fresh global per file | `bun test --parallel` **6.8 s**               | `vitest run` 133 s                                    | `jest` 19.1 s                                          |
| one thread, one global           | `bun test` **2.4 s**                          | `vitest run --no-isolate --no-file-parallelism` 8.5 s | — (`jest --runInBand`, 80 s, still isolates each file) |

<Note>
  Wall-clock, `hyperfine --warmup 1`, Bun 1.4, Node.js 25.6, each runner's stock config plus its setup-file option
  (`bunfig.toml` `test.preload`, `setupFilesAfterEnv`, `setupFiles`). The generator and configs are in the repository so
  you can rerun it. The ratios move with what your tests do: this suite is deliberately dominated by per-file overhead
  rather than test bodies.
</Note>

Where the time goes: with a fresh global per file, every runner re-evaluates the imports and setup file 2 000 times. Bun shares transpiled source and bytecode across those globals so nothing is re-parsed, but module evaluation and JIT warm-up still repeat per file. That repeated work is why, on this shape, one shared global (`bun test`) beats sixteen isolated workers, and sixteen shared globals (`--parallel --no-isolate`) beat both.


# Test Reporters
Source: https://bun.com/docs/test/reporters



`bun test` supports different output formats through reporters, both built-in and custom.

***

## Built-in Reporters

### Default Console Reporter

By default, `bun test` outputs results to the console in a human-readable format:

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
test/package-json-lint.test.ts:
✓ test/package.json [0.88ms]
✓ test/js/third_party/grpc-js/package.json [0.18ms]
✓ test/js/third_party/svelte/package.json [0.21ms]
✓ test/js/third_party/express/package.json [1.05ms]

 4 pass
 0 fail
 4 expect() calls
Ran 4 tests across 1 file. [1.44ms]
```

When a terminal doesn't support colors, the output avoids non-ASCII characters:

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
test/package-json-lint.test.ts:
(pass) test/package.json [0.48ms]
(pass) test/js/third_party/grpc-js/package.json [0.10ms]
(pass) test/js/third_party/svelte/package.json [0.04ms]
(pass) test/js/third_party/express/package.json [0.04ms]

 4 pass
 0 fail
 4 expect() calls
Ran 4 tests across 1 file. [0.66ms]
```

### Dots Reporter

The dots reporter shows `.` for passing tests and prints full error details for failures, useful for large test suites.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun test --dots
bun test --reporter=dots
```

### JUnit XML Reporter

For CI/CD environments, Bun can generate JUnit XML reports. JUnit XML is a widely-adopted test result format that many CI/CD systems can parse, including GitLab and Jenkins.

#### Using the JUnit Reporter

To generate a JUnit XML report, use the `--reporter=junit` flag along with `--reporter-outfile` to specify the output file:

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun test --reporter=junit --reporter-outfile=./junit.xml
```

Console output is unchanged; Bun writes the JUnit XML report to the specified path at the end of the test run.

#### Configuring via bunfig.toml

You can also configure the JUnit reporter in `bunfig.toml`:

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[test.reporter]
junit = "path/to/junit.xml"  # Output path for JUnit XML report
```

#### Environment Variables in JUnit Reports

The JUnit reporter includes environment information as `<properties>` in the XML output, so you can tell which environment and commit a test run was for.

It includes the following environment variables when available:

| Environment Variable                                                    | Property Name | Description            |
| ----------------------------------------------------------------------- | ------------- | ---------------------- |
| `GITHUB_RUN_ID`, `GITHUB_SERVER_URL`, `GITHUB_REPOSITORY`, `CI_JOB_URL` | `ci`          | CI build information   |
| `GITHUB_SHA`, `CI_COMMIT_SHA`, `GIT_SHA`                                | `commit`      | Git commit identifiers |
| System hostname                                                         | `hostname`    | Machine hostname       |

#### Current Limitations

The JUnit reporter does not include:

* `stdout` and `stderr` output from individual tests
* Precise timestamp fields per test case

### GitHub Actions reporter

`bun test` detects when it's running inside GitHub Actions and emits GitHub Actions annotations to the console directly. No configuration is needed beyond installing Bun and running `bun test`.

For an example GitHub Actions workflow, see [CI/CD integration](/docs/pm/cli/install#ci%2Fcd).

***

## Custom Reporters

You can implement custom test reporters with Bun's testing-specific extensions to the WebKit Inspector Protocol, which report detailed information about test execution in real time.

### Inspector Protocol for Testing

Bun extends the standard WebKit Inspector Protocol with two custom domains:

1. **TestReporter**: Reports test discovery, execution start, and completion events
2. **LifecycleReporter**: Reports errors and exceptions during test execution

### Key Events

Custom reporters can listen for these events:

* `TestReporter.found`: Emitted when a test is discovered
* `TestReporter.start`: Emitted when a test starts running
* `TestReporter.end`: Emitted when a test completes
* `Console.messageAdded`: Emitted when console output occurs during a test
* `LifecycleReporter.error`: Emitted when an error or exception occurs


# Runtime behavior
Source: https://bun.com/docs/test/runtime-behavior

Learn about Bun test's runtime integration, environment variables, timeouts, and error handling

`bun test` is deeply integrated with Bun's runtime. This integration is part of what makes `bun test` fast.

## Environment Variables

### NODE\_ENV

`bun test` sets `$NODE_ENV` to `"test"` unless it's already set in the environment or in `.env` files. Most test runners do the same.

```ts title="test.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { test, expect } from "bun:test";

test("NODE_ENV is set to test", () => {
  expect(process.env.NODE_ENV).toBe("test");
});
```

You can override this by setting `NODE_ENV` explicitly:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
NODE_ENV=development bun test
```

### TZ (Timezone)

`bun test` uses UTC (`Etc/UTC`) as the time zone unless the `TZ` environment variable overrides it. This keeps date and time behavior consistent across machines.

```ts title="test.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { test, expect } from "bun:test";

test("timezone is UTC by default", () => {
  const date = new Date();
  expect(date.getTimezoneOffset()).toBe(0);
});
```

To test with a specific time zone:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
TZ=America/New_York bun test
```

## Test Timeouts

Each test has a default timeout of 5000ms (5 seconds). Tests that exceed it fail.

### Global Timeout

Change the timeout globally with the `--timeout` flag:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun test --timeout 10000  # 10 seconds
```

### Per-Test Timeout

Set a per-test timeout as the third argument to the test function:

```ts title="test.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { test, expect } from "bun:test";

test("fast test", () => {
  expect(1 + 1).toBe(2);
}, 1000); // 1 second timeout

test("slow test", async () => {
  await new Promise(resolve => setTimeout(resolve, 8000));
}, 10000); // 10 second timeout
```

### Infinite Timeout

Use `0` or `Infinity` to disable the timeout:

```ts title="test.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
test("test without timeout", async () => {
  // This test can run indefinitely
  await someVeryLongOperation();
}, 0);
```

## Error Handling

### Unhandled Errors

`bun test` tracks unhandled promise rejections and errors that occur between tests. If any occur, the final exit code is non-zero, even if all tests pass.

This helps catch errors in asynchronous code that might otherwise go unnoticed:

```ts title="test.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { test, expect } from "bun:test";

test("test 1", () => {
  // This test passes
  expect(true).toBe(true);
});

// This error happens outside any test
setTimeout(() => {
  throw new Error("Unhandled error");
}, 0);

test("test 2", () => {
  // This test also passes
  expect(true).toBe(true);
});

// The test run will still fail with a non-zero exit code
// because of the unhandled error
```

### Promise Rejections

The test runner also catches unhandled promise rejections:

```ts title="test.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { test, expect } from "bun:test";

test("passing test", () => {
  expect(1).toBe(1);
});

// This will cause the test run to fail
Promise.reject(new Error("Unhandled rejection"));
```

### Custom Error Handling

You can set up custom error handlers in your test setup:

```ts title="test-setup.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
process.on("uncaughtException", error => {
  console.error("Uncaught Exception:", error);
  process.exit(1);
});

process.on("unhandledRejection", (reason, promise) => {
  console.error("Unhandled Rejection at:", promise, "reason:", reason);
  process.exit(1);
});
```

## CLI Flags Integration

Several Bun CLI flags also work with `bun test`:

### Memory Usage

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
# Reduces memory usage for the test runner VM
bun test --smol
```

### Debugging

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
# Attaches the debugger to the test runner process
bun test --inspect
bun test --inspect-brk
```

### Module Loading

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
# Runs scripts before test files (useful for global setup/mocks)
bun test --preload ./setup.ts

# Sets compile-time constants
bun test --define "process.env.API_URL='http://localhost:3000'"

# Maps file extensions to built-in loaders
bun test --loader .svg:text

# Uses a different tsconfig
bun test --tsconfig-override ./test-tsconfig.json

# Sets package.json conditions for module resolution
bun test --conditions development

# Loads environment variables for tests
bun test --env-file .env.test
```

### Installation-related Flags

```bash theme={"theme":{"light":"github-light","dark":"dracula"}}
# Affect any network requests or auto-installs during test execution
bun test --prefer-offline
bun test --frozen-lockfile
```

## Watch and Hot Reloading

### Watch Mode

With the `--watch` flag, the test runner watches for file changes and re-runs tests.

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun test --watch
```

### Hot Reloading

The `--hot` flag is similar, but more aggressive about preserving state between runs:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun test --hot
```

For most tests, use `--watch`: it gives better isolation between runs.

## Global Variables

The following globals are available in test files without importing:

```ts title="test.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
// All of these are available globally
test("global test function", () => {
  expect(true).toBe(true);
});

describe("global describe", () => {
  beforeAll(() => {
    // global beforeAll
  });

  it("global it function", () => {
    // it is an alias for test
  });
});

// Jest compatibility
jest.fn();

// Vitest compatibility
vi.fn();
```

You can also import them explicitly:

```ts title="test.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { test, it, describe, expect, beforeAll, beforeEach, afterAll, afterEach, jest, vi } from "bun:test";
```

## Process Integration

### Exit Codes

`bun test` uses standard exit codes:

* `0`: All tests passed, no unhandled errors
* `1`: Test failures or unhandled errors occurred

### Signal Handling

The test runner handles common signals:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
# Gracefully stops test execution
kill -SIGTERM <test-process-pid>

# Immediately stops test execution
kill -SIGKILL <test-process-pid>
```

### Environment Detection

Bun automatically detects certain environments and adjusts behavior:

```ts title="test.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
// GitHub Actions detection
if (process.env.GITHUB_ACTIONS) {
  // Bun automatically emits GitHub Actions annotations
}

// CI detection
if (process.env.CI) {
  // Certain behaviors may be adjusted for CI environments
}
```

## Performance Considerations

### Single Process

The test runner runs all tests in a single process by default. This provides:

* **Faster startup** - No need to spawn multiple processes
* **Shared memory** - Efficient resource usage
* **Simpler debugging** - All tests in one process

However, this means:

* Tests share global state (use lifecycle hooks to clean up)
* One test crash can affect others
* No true parallelization of individual tests

### Memory Management

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
# Monitor memory usage
bun test --smol  # Reduces memory footprint

# For large test suites, consider splitting files
bun test src/unit/
bun test src/integration/
```

### Test Isolation

Since tests run in the same process, ensure proper cleanup:

```ts title="test.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { afterEach, jest } from "bun:test";

afterEach(() => {
  // Clean up global state
  global.myGlobalVar = undefined;
  delete process.env.TEST_VAR;

  // Restore mocked functions if needed
  jest.restoreAllMocks();
});
```


# Snapshots
Source: https://bun.com/docs/test/snapshots

Learn how to use snapshot testing in Bun to save and compare output between test runs

Snapshot testing saves the output of a value and compares it against future test runs. Use it for UI components, complex objects, or any output that needs to remain consistent.

## Basic Snapshots

Write snapshot tests with the `.toMatchSnapshot()` matcher:

```ts title="test.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { test, expect } from "bun:test";

test("snap", () => {
  expect("foo").toMatchSnapshot();
});
```

The first time this test runs, Bun serializes the argument to `expect` and writes it to a snapshot file in a `__snapshots__` directory alongside the test file.

### Snapshot Files

After the first run, Bun creates:

```text title="directory structure" icon="file-directory" theme={"theme":{"light":"github-light","dark":"dracula"}}
your-project/
├── snap.test.ts
└── __snapshots__/
    └── snap.test.ts.snap
```

The snapshot file contains:

```ts title="__snapshots__/snap.test.ts.snap" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
// Bun Snapshot v1, https://bun.sh/docs/test/snapshots

exports[`snap 1`] = `"foo"`;
```

On future runs, Bun compares the argument against the snapshot on disk.

## Updating Snapshots

Regenerate snapshots with:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun test --update-snapshots
```

Do this when you've intentionally changed the output. In CI environments, Bun does not write new snapshots unless you pass this flag.

## Inline Snapshots

For smaller values, use `.toMatchInlineSnapshot()`. Bun stores inline snapshots directly in your test file:

```ts title="test.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { test, expect } from "bun:test";

test("inline snapshot", () => {
  // First run: snapshot will be inserted automatically
  expect({ hello: "world" }).toMatchInlineSnapshot();
});
```

After the first run, Bun automatically updates your test file:

```ts title="test.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { test, expect } from "bun:test";

test("inline snapshot", () => {
  expect({ hello: "world" }).toMatchInlineSnapshot(`
{
  "hello": "world",
}
`);
});
```

### Using Inline Snapshots

1. Write your test with `.toMatchInlineSnapshot()`
2. Run the test once
3. Bun automatically updates your test file with the snapshot
4. On subsequent runs, Bun compares the value against the inline snapshot

## Error Snapshots

You can also snapshot error messages with `.toThrowErrorMatchingSnapshot()` and `.toThrowErrorMatchingInlineSnapshot()`:

```ts title="test.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { test, expect } from "bun:test";

test("error snapshot", () => {
  expect(() => {
    throw new Error("Something went wrong");
  }).toThrowErrorMatchingSnapshot();

  expect(() => {
    throw new Error("Another error");
  }).toThrowErrorMatchingInlineSnapshot();
});
```

After running, the inline version becomes:

```ts title="test.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
test("error snapshot", () => {
  expect(() => {
    throw new Error("Something went wrong");
  }).toThrowErrorMatchingSnapshot();

  expect(() => {
    throw new Error("Another error");
  }).toThrowErrorMatchingInlineSnapshot(`"Another error"`);
});
```

## Advanced Snapshot Usage

### Complex Objects

Snapshots work well with complex nested objects:

```ts title="test.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { test, expect } from "bun:test";

test("complex object snapshot", () => {
  const user = {
    id: 1,
    name: "John Doe",
    email: "john@example.com",
    profile: {
      age: 30,
      preferences: {
        theme: "dark",
        notifications: true,
      },
    },
    tags: ["developer", "javascript", "bun"],
  };

  expect(user).toMatchSnapshot();
});
```

### Array Snapshots

Arrays are also well-suited for snapshot testing:

```ts title="test.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { test, expect } from "bun:test";

test("array snapshot", () => {
  const numbers = [1, 2, 3, 4, 5].map(n => n * 2);
  expect(numbers).toMatchSnapshot();
});
```

### Function Output Snapshots

Snapshot the output of functions:

```ts title="test.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { test, expect } from "bun:test";

function generateReport(data: any[]) {
  return {
    total: data.length,
    summary: data.map(item => ({ id: item.id, name: item.name })),
    timestamp: "2024-01-01", // Fixed for testing
  };
}

test("report generation", () => {
  const data = [
    { id: 1, name: "Alice", age: 30 },
    { id: 2, name: "Bob", age: 25 },
  ];

  expect(generateReport(data)).toMatchSnapshot();
});
```

## React Component Snapshots

Snapshots work well for React components:

```tsx title="test.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { test, expect } from "bun:test";
import { render } from "@testing-library/react";

function Button({ children, variant = "primary" }) {
  return <button className={`btn btn-${variant}`}>{children}</button>;
}

test("Button component snapshots", () => {
  const { container: primary } = render(<Button>Click me</Button>);
  const { container: secondary } = render(<Button variant="secondary">Cancel</Button>);

  expect(primary.innerHTML).toMatchSnapshot();
  expect(secondary.innerHTML).toMatchSnapshot();
});
```

## Property Matchers

For values that change between test runs (like timestamps or IDs), use property matchers:

```ts title="test.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { test, expect } from "bun:test";

test("snapshot with dynamic values", () => {
  const user = {
    id: Math.random(), // This changes every run
    name: "John",
    createdAt: new Date().toISOString(), // This also changes
  };

  expect(user).toMatchSnapshot({
    id: expect.any(Number),
    createdAt: expect.any(String),
  });
});
```

The snapshot file stores:

```txt title="snapshot file" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
exports[`snapshot with dynamic values 1`] = `
{
  "createdAt": Any<String>,
  "id": Any<Number>,
  "name": "John",
}
`;
```

## Best Practices

### Keep Snapshots Small

```ts title="test.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
// Good: Focused snapshots
test("user name formatting", () => {
  const formatted = formatUserName("john", "doe");
  expect(formatted).toMatchInlineSnapshot(`"John Doe"`);
});

// Avoid: Huge snapshots that are hard to review
test("entire page render", () => {
  const page = renderEntirePage();
  expect(page).toMatchSnapshot(); // This could be thousands of lines
});
```

### Use Descriptive Test Names

```ts title="test.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
// Good: Clear what the snapshot represents
test("formats currency with USD symbol", () => {
  expect(formatCurrency(99.99)).toMatchInlineSnapshot(`"$99.99"`);
});

// Avoid: Unclear what's being tested
test("format test", () => {
  expect(format(99.99)).toMatchInlineSnapshot(`"$99.99"`);
});
```

### Group Related Snapshots

```ts title="test.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { describe, test, expect } from "bun:test";

describe("Button component", () => {
  test("primary variant", () => {
    expect(render(<Button variant="primary">Click</Button>))
      .toMatchSnapshot();
  });

  test("secondary variant", () => {
    expect(render(<Button variant="secondary">Cancel</Button>))
      .toMatchSnapshot();
  });

  test("disabled state", () => {
    expect(render(<Button disabled>Disabled</Button>))
      .toMatchSnapshot();
  });
});
```

### Handle Dynamic Data

```ts title="test.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
// Good: Normalize dynamic data
test("API response format", () => {
  const response = {
    data: { id: 1, name: "Test" },
    timestamp: Date.now(),
    requestId: generateId(),
  };

  expect({
    ...response,
    timestamp: "TIMESTAMP",
    requestId: "REQUEST_ID",
  }).toMatchSnapshot();
});

// Or use property matchers
test("API response with matchers", () => {
  const response = getApiResponse();

  expect(response).toMatchSnapshot({
    timestamp: expect.any(Number),
    requestId: expect.any(String),
  });
});
```

## Managing Snapshots

### Reviewing Snapshot Changes

When snapshots change, carefully review them:

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
# See what changed
git diff __snapshots__/

# Update if changes are intentional
bun test --update-snapshots

# Commit the updated snapshots
git add __snapshots__/
git commit -m "Update snapshots after UI changes"
```

### Organizing Large Snapshot Files

For large projects, consider organizing tests to keep snapshot files manageable:

```text title="directory structure" icon="file-directory" theme={"theme":{"light":"github-light","dark":"dracula"}}
tests/
├── components/
│   ├── Button.test.tsx
│   └── __snapshots__/
│       └── Button.test.tsx.snap
├── utils/
│   ├── formatters.test.ts
│   └── __snapshots__/
│       └── formatters.test.ts.snap
```

## Troubleshooting

### Snapshot Failures

When snapshots fail, Bun shows a diff:

```diff title="diff" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
  {
-   "name": "John",
+   "name": "Jane",
  }

- Expected  - 1
+ Received  + 1
```

Common causes:

* Intentional changes (update with `--update-snapshots`)
* Unintentional changes (fix the code)
* Dynamic data (use property matchers)
* Environment differences (normalize the data)

### Platform Differences

Be aware of platform-specific differences:

```ts title="test.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
// Paths might differ between Windows/Unix
test("file operations", () => {
  const result = processFile("./test.txt");

  expect({
    ...result,
    path: result.path.replace(/\\/g, "/"), // Normalize paths
  }).toMatchSnapshot();
});
```


# Writing tests
Source: https://bun.com/docs/test/writing-tests

Write tests with Bun's Jest-compatible API, including async tests, timeouts, and test modifiers

Define tests with a Jest-like API imported from the built-in `bun:test` module. Long term, Bun aims for complete Jest compatibility.

## Basic Usage

To define a test:

```ts title="math.test.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { expect, test } from "bun:test";

test("2 + 2", () => {
  expect(2 + 2).toBe(4);
});
```

### Grouping Tests

Group tests into suites with `describe`.

```ts title="math.test.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { expect, test, describe } from "bun:test";

describe("arithmetic", () => {
  test("2 + 2", () => {
    expect(2 + 2).toBe(4);
  });

  test("2 * 2", () => {
    expect(2 * 2).toBe(4);
  });
});
```

### Async Tests

Tests can be async.

```ts title="math.test.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { expect, test } from "bun:test";

test("2 * 2", async () => {
  const result = await Promise.resolve(2 * 2);
  expect(result).toEqual(4);
});
```

Alternatively, use the `done` callback to signal completion. If your test function takes a `done` parameter, you must call it or the test hangs.

```ts title="math.test.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { expect, test } from "bun:test";

test("2 * 2", done => {
  Promise.resolve(2 * 2).then(result => {
    expect(result).toEqual(4);
    done();
  });
});
```

## Timeouts

Optionally specify a per-test timeout in milliseconds by passing a number as the third argument to `test`.

```ts title="math.test.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { expect, test } from "bun:test";

test("wat", async () => {
  const data = await slowOperation();
  expect(data).toBe(42);
}, 500); // test must run in <500ms
```

In `bun:test`, a timeout throws an uncatchable exception to force the test to stop running and fail. Bun also kills any child processes spawned in the test, so they don't linger as zombie processes.

The default timeout for each test is 5000ms (5 seconds) unless you override it with this timeout option or `jest.setTimeout()`.

## Retries and Repeats

### test.retry

Use the `retry` option to automatically retry a flaky test when it fails. The test passes if it succeeds within the specified number of attempts.

```ts title="example.test.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { expect, test } from "bun:test";

test(
  "flaky network request",
  async () => {
    const response = await fetch("https://example.com/api");
    expect(response.ok).toBe(true);
  },
  { retry: 3 }, // Retry up to 3 times if the test fails
);
```

### test.repeats

Use the `repeats` option to run a test multiple times regardless of pass/fail status; the test fails if any iteration fails. Use it to detect flaky tests or for stress testing. `repeats: N` runs the test N+1 times total (1 initial run + N repeats).

```ts title="example.test.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { expect, test } from "bun:test";

test(
  "ensure test is stable",
  () => {
    expect(Math.random()).toBeLessThan(1);
  },
  { repeats: 20 }, // Runs 21 times total (1 initial + 20 repeats)
);
```

<Note>You cannot use both `retry` and `repeats` on the same test.</Note>

### 🧟 Zombie Process Killer

When a test times out, Bun kills any still-running processes that the test spawned with `Bun.spawn`, `Bun.spawnSync`, or `node:child_process`, and logs a message to the console. This prevents zombie processes from lingering after timed-out tests.

## Test Modifiers

### test.skip

Skip individual tests with `test.skip`. Bun does not run these tests.

```ts title="math.test.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { expect, test } from "bun:test";

test.skip("wat", () => {
  // TODO: fix this
  expect(0.1 + 0.2).toEqual(0.3);
});
```

### test.todo

Mark a test as a todo with `test.todo`. Bun does not run these tests.

```ts title="math.test.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { expect, test } from "bun:test";

test.todo("fix this", () => {
  myTestFunction();
});
```

To run todo tests and find any that pass, use `bun test --todo`.

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun test --todo
```

```
math.test.ts:
✗ fix this
  ^ this test is marked as todo but passes. Remove `.todo` if tested behavior now works

 0 pass
 1 fail
```

With this flag, failing todo tests do not cause an error, but Bun marks todo tests that pass as failing so you can remove the todo mark or fix the test.

### test.only

To run a particular test or suite of tests, use `test.only()` or `describe.only()`.

```ts title="example.test.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { test, describe } from "bun:test";

test("test #1", () => {
  // does not run
});

test.only("test #2", () => {
  // runs
});

describe.only("only", () => {
  test("test #3", () => {
    // runs
  });
});
```

The following command runs only tests #2 and #3.

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun test --only
```

### test.if

To run a test conditionally, use `test.if()`. The test runs if the condition is truthy. Use it for tests that should only run on a specific architecture or operating system.

```ts title="example.test.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
test.if(Math.random() > 0.5)("runs half the time", () => {
  // ...
});

const macOS = process.platform === "darwin";
test.if(macOS)("runs on macOS", () => {
  // runs if macOS
});
```

### test.skipIf

To instead skip a test based on some condition, use `test.skipIf()` or `describe.skipIf()`.

```ts title="example.test.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
const macOS = process.platform === "darwin";

test.skipIf(macOS)("runs on non-macOS", () => {
  // runs if *not* macOS
});
```

### test.todoIf

To mark the test as TODO instead, use `test.todoIf()` or `describe.todoIf()`. The choice between `skipIf` and `todoIf` signals intent: "invalid for this target" versus "planned but not implemented yet."

```ts title="example.test.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
const macOS = process.platform === "darwin";

// TODO: we've only implemented this for Linux so far.
test.todoIf(macOS)("runs on posix", () => {
  // runs if *not* macOS
});
```

### test.failing

Use `test.failing()` when you know a test is failing but you want to track it and be notified when it starts passing. This inverts the test result:

* A failing test marked with `.failing()` passes
* A passing test marked with `.failing()` fails, with a message that it now passes and should be fixed

```ts math.test.ts icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
// This will pass because the test is failing as expected
test.failing("math is broken", () => {
  expect(0.1 + 0.2).toBe(0.3); // fails due to floating point precision
});

// This will fail with a message that the test is now passing
test.failing("fixed bug", () => {
  expect(1 + 1).toBe(2); // passes, but we expected it to fail
});
```

Use it to track known bugs you plan to fix later, or for test-driven development.

## Conditional Tests for Describe Blocks

The conditional modifiers `.if()`, `.skipIf()`, and `.todoIf()` also work on `describe` blocks, affecting all tests in the suite:

```ts title="example.test.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
const isMacOS = process.platform === "darwin";

// Only runs the entire suite on macOS
describe.if(isMacOS)("macOS-specific features", () => {
  test("feature A", () => {
    // only runs on macOS
  });

  test("feature B", () => {
    // only runs on macOS
  });
});

// Skips the entire suite on Windows
describe.skipIf(process.platform === "win32")("Unix features", () => {
  test("feature C", () => {
    // skipped on Windows
  });
});

// Marks the entire suite as TODO on Linux
describe.todoIf(process.platform === "linux")("Upcoming Linux support", () => {
  test("feature D", () => {
    // marked as TODO on Linux
  });
});
```

## Parametrized Tests

### `test.each` and `describe.each`

To run the same test with multiple sets of data, use `test.each`. This creates a parametrized test that runs once for each test case provided.

```ts title="math.test.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
const cases = [
  [1, 2, 3],
  [3, 4, 7],
];

test.each(cases)("%p + %p should be %p", (a, b, expected) => {
  expect(a + b).toBe(expected);
});
```

`describe.each` creates a parametrized suite that runs once for each test case:

```ts title="sum.test.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
describe.each([
  [1, 2, 3],
  [3, 4, 7],
])("add(%i, %i)", (a, b, expected) => {
  test(`returns ${expected}`, () => {
    expect(a + b).toBe(expected);
  });

  test(`sum is greater than each value`, () => {
    expect(a + b).toBeGreaterThan(a);
    expect(a + b).toBeGreaterThan(b);
  });
});
```

### Argument Passing

How Bun passes arguments to your test function depends on the structure of your test cases:

* If a table row is an array (like `[1, 2, 3]`), Bun passes each element as an individual argument
* If a row is not an array (like an object), Bun passes it as a single argument

```ts title="example.test.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
// Array items passed as individual arguments
test.each([
  [1, 2, 3],
  [4, 5, 9],
])("add(%i, %i) = %i", (a, b, expected) => {
  expect(a + b).toBe(expected);
});

// Object items passed as a single argument
test.each([
  { a: 1, b: 2, expected: 3 },
  { a: 4, b: 5, expected: 9 },
])("add($a, $b) = $expected", data => {
  expect(data.a + data.b).toBe(data.expected);
});
```

### Format Specifiers

Use these specifiers to format the test title:

| Specifier | Description             |
| --------- | ----------------------- |
| `%p`      | pretty-format           |
| `%s`      | String                  |
| `%d`      | Number                  |
| `%i`      | Integer                 |
| `%f`      | Floating point          |
| `%j`      | JSON                    |
| `%o`      | Object                  |
| `%#`      | Index of the test case  |
| `%%`      | Single percent sign (%) |

#### Examples

```ts title="example.test.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
// Basic specifiers
test.each([
  ["hello", 123],
  ["world", 456],
])("string: %s, number: %i", (str, num) => {
  // "string: hello, number: 123"
  // "string: world, number: 456"
});

// %p for pretty-format output
test.each([
  [{ name: "Alice" }, { a: 1, b: 2 }],
  [{ name: "Bob" }, { x: 5, y: 10 }],
])("user %p with data %p", (user, data) => {
  // "user { name: 'Alice' } with data { a: 1, b: 2 }"
  // "user { name: 'Bob' } with data { x: 5, y: 10 }"
});

// %# for index
test.each(["apple", "banana"])("fruit #%# is %s", fruit => {
  // "fruit #0 is apple"
  // "fruit #1 is banana"
});
```

## Assertion Counting

Bun supports verifying that a specific number of assertions were called during a test:

### expect.hasAssertions()

Use `expect.hasAssertions()` to verify that at least one assertion is called during a test:

```ts title="example.test.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
test("async work calls assertions", async () => {
  expect.hasAssertions(); // Will fail if no assertions are called

  const data = await fetchData();
  expect(data).toBeDefined();
});
```

`expect.hasAssertions()` is especially useful in async tests, to make sure your assertions run.

### expect.assertions(count)

Use `expect.assertions(count)` to verify that a specific number of assertions are called during a test:

```ts title="example.test.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
test("exactly two assertions", () => {
  expect.assertions(2); // Will fail if not exactly 2 assertions are called

  expect(1 + 1).toBe(2);
  expect("hello").toContain("ell");
});
```

`expect.assertions(count)` helps ensure all your assertions run, especially in complex async code with multiple code paths.

## Type Testing

Bun includes `expectTypeOf` for testing TypeScript types, compatible with Vitest.

### expectTypeOf

<Warning>These functions are no-ops at runtime. Run TypeScript separately to verify the type checks.</Warning>

The `expectTypeOf` function provides type-level assertions that TypeScript's type checker verifies. To test your types:

1. Write your type assertions using `expectTypeOf`
2. Run `bunx tsc --noEmit` to check that your types are correct

```ts title="example.test.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { expectTypeOf } from "bun:test";

// Basic type assertions
expectTypeOf<string>().toEqualTypeOf<string>();
expectTypeOf(123).toBeNumber();
expectTypeOf("hello").toBeString();

// Object type matching
expectTypeOf({ a: 1, b: "hello" }).toMatchObjectType<{ a: number }>();

// Function types
function greet(name: string): string {
  return `Hello ${name}`;
}

expectTypeOf(greet).toBeFunction();
expectTypeOf(greet).parameters.toEqualTypeOf<[string]>();
expectTypeOf(greet).returns.toEqualTypeOf<string>();

// Array types
expectTypeOf([1, 2, 3]).items.toBeNumber();

// Promise types
expectTypeOf(Promise.resolve(42)).resolves.toBeNumber();
```

For full documentation on `expectTypeOf` matchers, see the [API Reference](https://bun.com/reference/bun/test/expectTypeOf).

## Matchers

Bun implements the following matchers. Full Jest compatibility is planned; see the [tracking issue](https://github.com/oven-sh/bun/issues/1825).

### Basic Matchers

| Status | Matcher            |
| ------ | ------------------ |
| ✅      | `.not`             |
| ✅      | `.toBe()`          |
| ✅      | `.toEqual()`       |
| ✅      | `.toBeNull()`      |
| ✅      | `.toBeUndefined()` |
| ✅      | `.toBeNaN()`       |
| ✅      | `.toBeDefined()`   |
| ✅      | `.toBeFalsy()`     |
| ✅      | `.toBeTruthy()`    |
| ✅      | `.toStrictEqual()` |

### String and Array Matchers

| Status | Matcher               |
| ------ | --------------------- |
| ✅      | `.toContain()`        |
| ✅      | `.toHaveLength()`     |
| ✅      | `.toMatch()`          |
| ✅      | `.toContainEqual()`   |
| ✅      | `.stringContaining()` |
| ✅      | `.stringMatching()`   |
| ✅      | `.arrayContaining()`  |

### Object Matchers

| Status | Matcher                 |
| ------ | ----------------------- |
| ✅      | `.toHaveProperty()`     |
| ✅      | `.toMatchObject()`      |
| ✅      | `.toContainAllKeys()`   |
| ✅      | `.toContainValue()`     |
| ✅      | `.toContainValues()`    |
| ✅      | `.toContainAllValues()` |
| ✅      | `.toContainAnyValues()` |
| ✅      | `.objectContaining()`   |

### Number Matchers

| Status | Matcher                     |
| ------ | --------------------------- |
| ✅      | `.toBeCloseTo()`            |
| ✅      | `.closeTo()`                |
| ✅      | `.toBeGreaterThan()`        |
| ✅      | `.toBeGreaterThanOrEqual()` |
| ✅      | `.toBeLessThan()`           |
| ✅      | `.toBeLessThanOrEqual()`    |

### Function and Class Matchers

| Status | Matcher             |
| ------ | ------------------- |
| ✅      | `.toThrow()`        |
| ✅      | `.toBeInstanceOf()` |

### Promise Matchers

| Status | Matcher       |
| ------ | ------------- |
| ✅      | `.resolves()` |
| ✅      | `.rejects()`  |

### Mock Function Matchers

| Status | Matcher                       |
| ------ | ----------------------------- |
| ✅      | `.toHaveBeenCalled()`         |
| ✅      | `.toHaveBeenCalledTimes()`    |
| ✅      | `.toHaveBeenCalledWith()`     |
| ✅      | `.toHaveBeenLastCalledWith()` |
| ✅      | `.toHaveBeenNthCalledWith()`  |
| ✅      | `.toHaveReturned()`           |
| ✅      | `.toHaveReturnedTimes()`      |
| ✅      | `.toHaveReturnedWith()`       |
| ✅      | `.toHaveLastReturnedWith()`   |
| ✅      | `.toHaveNthReturnedWith()`    |

### Snapshot Matchers

| Status | Matcher                                 |
| ------ | --------------------------------------- |
| ✅      | `.toMatchSnapshot()`                    |
| ✅      | `.toMatchInlineSnapshot()`              |
| ✅      | `.toThrowErrorMatchingSnapshot()`       |
| ✅      | `.toThrowErrorMatchingInlineSnapshot()` |

### Utility Matchers

| Status | Matcher            |
| ------ | ------------------ |
| ✅      | `.extend`          |
| ✅      | `.anything()`      |
| ✅      | `.any()`           |
| ✅      | `.assertions()`    |
| ✅      | `.hasAssertions()` |

### Not Yet Implemented

| Status | Matcher                    |
| ------ | -------------------------- |
| ❌      | `.addSnapshotSerializer()` |

## Best Practices

### Use Descriptive Test Names

```ts title="example.test.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
// Good
test("should calculate total price including tax for multiple items", () => {
  // test implementation
});

// Avoid
test("price calculation", () => {
  // test implementation
});
```

### Group Related Tests

```ts title="auth.test.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
describe("User authentication", () => {
  describe("with valid credentials", () => {
    test("should return user data", () => {
      // test implementation
    });

    test("should set authentication token", () => {
      // test implementation
    });
  });

  describe("with invalid credentials", () => {
    test("should throw authentication error", () => {
      // test implementation
    });
  });
});
```

### Use Appropriate Matchers

```ts title="auth.test.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
// Good: Use specific matchers
expect(users).toHaveLength(3);
expect(user.email).toContain("@");
expect(response.status).toBeGreaterThanOrEqual(200);

// Avoid: Using toBe for everything
expect(users.length === 3).toBe(true);
expect(user.email.includes("@")).toBe(true);
expect(response.status >= 200).toBe(true);
```

### Test Error Conditions

```ts title="example.test.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
test("should throw error for invalid input", () => {
  expect(() => {
    validateEmail("not-an-email");
  }).toThrow("Invalid email format");
});

test("should handle async errors", async () => {
  await expect(async () => {
    await fetchUser("invalid-id");
  }).rejects.toThrow("User not found");
});
```

### Use Setup and Teardown

```ts title="example.test.ts" icon="https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35b" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { beforeEach, afterEach, test } from "bun:test";

let testUser;

beforeEach(() => {
  testUser = createTestUser();
});

afterEach(() => {
  cleanupTestUser(testUser);
});

test("should update user profile", () => {
  // Use testUser in test
});
```


# TypeScript
Source: https://bun.com/docs/typescript

Using TypeScript with Bun, including type definitions and compiler options

To get TypeScript definitions for Bun's built-in APIs, install `@types/bun`.

```zsh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun add -d @types/bun # dev dependency
```

You can now reference the `Bun` global in your TypeScript files without errors in your editor.

## Suggested `compilerOptions`

Bun supports top-level await, JSX, and imports with `.ts` extensions, which TypeScript doesn't allow by default. Use these `compilerOptions` in a Bun project so TypeScript doesn't warn about those features.

```json tsconfig.json icon="file-json" theme={"theme":{"light":"github-light","dark":"dracula"}}
{
  "compilerOptions": {
    // Environment setup & latest features
    "lib": ["ESNext"],
    "target": "ESNext",
    "module": "Preserve",
    "moduleDetection": "force",
    "jsx": "react-jsx",
    "allowJs": true,
    "types": ["bun"],

    // Bundler mode
    "moduleResolution": "bundler",
    "allowImportingTsExtensions": true,
    "verbatimModuleSyntax": true,
    "noEmit": true,

    // Best practices
    "strict": true,
    "skipLibCheck": true,
    "noFallthroughCasesInSwitch": true,
    "noUncheckedIndexedAccess": true,
    "noImplicitOverride": true,

    // Some stricter flags (disabled by default)
    "noUnusedLocals": false,
    "noUnusedParameters": false,
    "noPropertyAccessFromIndexSignature": false
  }
}
```

Running `bun init` in a new directory generates this `tsconfig.json` for you.

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun init
```

## TypeScript 6 and 7

If you're using TypeScript 6.0 or later, you also need `"types": ["bun"]` in your `compilerOptions`. See [TypeScript 6 and 7](/docs/typescript-6).


# TypeScript 6 and 7
Source: https://bun.com/docs/typescript-6

How to configure Bun's type definitions for TypeScript 6.0 and 7.0, which no longer auto-discover @types packages. Fix 'Cannot find name Bun' and other missing type errors after upgrading TypeScript.

TypeScript 6.0 changed how it discovers type definitions. If you've upgraded TypeScript and your editor no longer recognizes `Bun`, `Request`, or other globals from `@types/bun`, here's how to fix it.

## What changed

Starting in TypeScript 6.0, the `types` field in `compilerOptions` defaults to an empty array instead of including all `@types/*` packages. You now need to explicitly list the type packages you use.

## Add `"types": ["bun"]` to your tsconfig

In your `tsconfig.json`, add `"types": ["bun"]` to `compilerOptions`:

```jsonc tsconfig.json icon="file-json" theme={"theme":{"light":"github-light","dark":"dracula"}}
{
  "compilerOptions": {
    "types": ["bun"], // [!code ++]
  },
}
```

The `types` array tells TypeScript to load type definitions from `@types/bun`. If you use other `@types/*` packages, include them too:

```jsonc tsconfig.json icon="file-json" theme={"theme":{"light":"github-light","dark":"dracula"}}
{
  "compilerOptions": {
    "types": ["bun", "react"],
  },
}
```

You still need `@types/bun` installed. The `types` option tells TypeScript *which* packages to include, but the package itself must exist in `node_modules`:

```sh terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun add -d @types/bun
```

## Full recommended tsconfig.json

Here's the full recommended `tsconfig.json` for a Bun project using TypeScript 6.0 or later:

```jsonc tsconfig.json icon="file-json" theme={"theme":{"light":"github-light","dark":"dracula"}}
{
  "compilerOptions": {
    // Environment setup & latest features
    "lib": ["ESNext"],
    "target": "ESNext",
    "module": "Preserve",
    "moduleDetection": "force",
    "jsx": "react-jsx",
    "allowJs": true,
    "types": ["bun"],

    // Bundler mode
    "moduleResolution": "bundler",
    "allowImportingTsExtensions": true,
    "verbatimModuleSyntax": true,
    "noEmit": true,

    // Best practices
    "strict": true,
    "skipLibCheck": true,
    "noFallthroughCasesInSwitch": true,
    "noUncheckedIndexedAccess": true,
    "noImplicitOverride": true,

    // Some stricter flags (disabled by default)
    "noUnusedLocals": false,
    "noUnusedParameters": false,
    "noPropertyAccessFromIndexSignature": false,
  },
}
```

## Does this apply to TypeScript 7?

Yes. TypeScript 7 carries forward the same default. If you're upgrading directly from TypeScript 5 to 7, the same fix applies — add `"types": ["bun"]` to your `compilerOptions`.


