Bun

Bun v1.2.22


Jarred Sumner · September 14, 2025

To install Bun

curl
npm
powershell
scoop
brew
docker
curl
curl -fsSL https://bun.sh/install | bash
npm
npm install -g bun
powershell
powershell -c "irm bun.sh/install.ps1|iex"
scoop
scoop install bun
brew
brew tap oven-sh/bun
brew install bun
docker
docker pull oven/bun
docker run --rm --init --ulimit memlock=-1:-1 oven/bun

To upgrade Bun

bun upgrade

Async Stack Traces

Bun's stack traces now include asynchronous call frames, making it significantly easier to debug code using async/await. Previously, these frames were omitted, leading to incomplete stack traces. Now, you'll see the full asynchronous execution path leading to an error.

async.js
async function foo() {
  return await bar();
}

async function bar() {
  return await baz();
}

async function baz() {
  await 1; // ensure it's a real async function
  throw new Error("oops");
}

try {
  await foo();
} catch (e) {
  console.log(e);
}

This now outputs:

❯ bun async.js
 6 |   return await baz();
 7 | }
 8 |
 9 | async function baz() {
10 |   await 1; // ensure it's a real async function
11 |   throw new Error("oops");
                 ^
error: oops
      at baz (async.js:11:13)
      at async bar (async.js:6:16)
      at async foo (async.js:2:16)

Previously, it would output:

❯ bun-1.2.21 async.js
 6 |   return await baz();
 7 | }
 8 |
 9 | async function baz() {
10 |   await 1; // ensure it's a real async function
11 |   throw new Error("oops");
             ^
error: oops
      at baz (async.js:11:9)

Additionally, a bug that could cause stack traces printed to the console to be missing frames after accessing error.stack has been fixed.

And a bug with the error stack for Error subclases has been fixed.

Huge thanks to @sosukesuzuki for implementing this in JavaScriptCore!

240x faster postMessage and structuredClone for simple objects

A new fast path has been added for postMessage and structuredClone when dealing with "simple" objects. This optimization applies to plain JavaScript objects that contain only primitive values like strings, numbers, booleans, null, and undefined.

Bun.YAML.stringify

Support for interactive TTYs after stdin closes

A common pattern for TUI (Terminal User Interface) applications is to first process data piped from stdin, and then open /dev/tty to start an interactive session. This pattern was previously broken in Bun, causing an ESPIPE error when reading from the TTY stream.

This has been fixed by correctly handling file streams for character devices. Additionally, tty.ReadStream now supports .ref() and .unref() for parity with Node.js, allowing for better control over the event loop in TUI applications. This resolves a long-standing bug affecting many interactive CLI tools.

script.js
import { createReadStream } from "fs";
import { stdin, stdout } from "process";

// 1. Process any data piped from stdin
for await (const chunk of stdin) {
  stdout.write(`Piped data: ${chunk}`);
}

// 2. After stdin closes, open /dev/tty for interactive input
stdout.write(
  "stdin closed. Now accepting interactive input (type 'exit' to quit):\n"
);
const tty = createReadStream("/dev/tty");

for await (const chunk of tty) {
  const line = chunk.toString().trim();
  stdout.write(`You typed: "${line}"\n`);
  if (line === "exit") {
    tty.destroy();
  }
}

To run:

echo "Initial data" | bun script.js

Bun.SQL improvements

MySQL Adapter

In Bun v1.2.21, we introduced MySQL & SQLite adapters for Bun.SQL. This release includes several improvements to the MySQL adapter.

affectedRows and lastInsertRowid

affectedRows and lastInsertRowid properties are now returned by the MySQL driver. affectedRows contains the number of rows changed by the query, and lastInsertRowid contains the ID of the last inserted row. This brings the MySQL driver's API closer to Bun.SQL.

import { sql } from "bun";

// For INSERT queries
const insertResult = await sql`INSERT INTO users (name) VALUES ('John Doe');`;
console.log(insertResult.lastInsertRowid); // e.g., 1
console.log(insertResult.affectedRows); // 1

// For UPDATE queries
const updateResult =
  await sql`UPDATE users SET name = 'Jane Doe' WHERE id = 1;`;
console.log(updateResult.affectedRows); // 1

Better column type handling

Thanks to community feedback, we've made the following improvements to MySQL driver's column type handling:

MySQL TypeJavaScript TypeNotes
INT, TINYINT, MEDIUMINTnumberWithin safe integer range
BIT(1)boolean

Previously:

  • TINYINT columns were incorrectly parsed as booleans instead of numbers.
  • BIT(1) columns were incorrectly parsed as numbers instead of booleans.
  • BIT(N) columns (where N > 1) were incorrectly parsed as Buffers instead of numbers, but only when the binary protocol was used.

TLS Support

Bun.SQL now supports connecting to MySQL databases over a TLS/SSL connection. This is done by passing a tls option to the Bun.SQL constructor, matching the behavior of the PostgreSQL adapter and other Bun APIs.

mysql_native_password authentication

Bun.SQL now correctly handles mysql_native_password authentication and authentication switching, improving compatibility with a wider range of MySQL servers.

PostgreSQL improvements

  • Fixed: A bug in Bun.SQL's Postgres driver where an error in a pipelined query could cause the connection to disconnect.
  • Fixed: In certain cases, Bun.SQL could resolve a PostgreSQL query's Promise before the server reported being ready.
  • Fixed: Bun.SQL now correctly decodes TIME and TIMETZ columns from PostgreSQL when using the binary protocol.

Bundler & minifier

Minifier optimizes new expressions for smaller bundles

Bun's JavaScript minifier now implements several new optimizations for built-in constructors, resulting in smaller bundle sizes.

When constructing built-in objects like Object, Array, or Error, the new keyword is often optional and has no effect on the runtime behavior. Bun now automatically removes the new keyword or replaces the entire expression with a more compact literal form, such as converting new Object() to {}.

This optimization applies to new Object(), new Array(), new Error() (and its subtypes), and new Function().

javascript
-const obj=new Object();
+const obj={};
-const arr=new Array(1, 2, 3);
+const arr=[1,2,3];
-const err=new Error("Something went wrong");
+const err=Error("Something went wrong");

Thanks to @dylan-conway for the contribution

typeof undefined checks are now minified

Bun's minifier now optimizes typeof checks for "undefined". This common optimization, also used by tools like esbuild, reduces bundle size by replacing string comparisons with shorter character comparisons. This change also improves dead code elimination for code guarded by these checks.

// Input
console.log(typeof x === "undefined");
console.log(typeof x !== "undefined");

// Minified output
console.log(typeof x > "u");
console.log(typeof x < "u");

Thanks to @Jarred-Sumner and @dylan-conway for this change

onEnd hook for bundler plugins

Bun.build now supports the onEnd hook in plugins, aligning its plugin API closer to esbuild. This hook is triggered after a build is complete—whether it succeeds or fails—and provides access to the BuildOutput object. This is useful for post-processing, cleanup, or sending notifications.

await Bun.build({
  entrypoints: ["./index.ts"],
  outdir: "./out",
  plugins: [
    {
      name: "onEnd example",
      setup(build) {
        build.onEnd((result) => {
          if (result.success) {
            console.log(
              `✅ Build succeeded with ${result.outputs.length} outputs`,
            );
          } else {
            console.error(`❌ Build failed with ${result.logs.length} errors`);
          }
        });
      },
    },
  ],
});

Thanks to @alii for the contribution

jsxSideEffects option

By default, Bun's bundler treats JSX as "pure", meaning it can be removed if its return value is unused. This process, known as dead code elimination, can cause problems when a component has side effects that need to be preserved.

A new option, jsxSideEffects, has been introduced to prevent this. When set to true, Bun will no longer mark JSX as pure, ensuring that components with side effects are always included in the final bundle.

You can enable this feature in your tsconfig.json or by using a command-line flag.

// tsconfig.json
{
  "compilerOptions": {
    "jsxSideEffects": true
  }
}

This ensures that code with side effects, like the example below, behaves as expected.

// component.jsx
let counter = 0;
function MyComponent() {
  counter++; // This side effect will now be preserved
  return <div>Hello</div>;
}

// index.jsx
<MyComponent />;
console.log(counter); // Reliably logs 1

Unused function and class names are now removed during minification

Bun's bundler now removes unused function and class expression names when minifying (--minify-syntax). This matches esbuild's behavior and results in smaller bundle sizes.

A new --keep-names flag has been added to preserve these names, which can be useful for debugging or for libraries that rely on Function.prototype.name.

// input.js
const myFunc = function myInternalName() {
  // "myInternalName" is not used anywhere
};

const myClass = class MyInternalClass {
  // "MyInternalClass" is not used anywhere
};

// After `bun build --minify ./input.js`:
//
// const myFunc = function() {};
// const myClass = class {};

// To preserve the names, use `bun build --minify --keep-names`
// or `keepNames: true` in your build configuration.

Thanks to @dylan-conway for the contribution

Monitor event loop delay with perf_hooks.monitorEventLoopDelay()

Bun now implements the perf_hooks.monitorEventLoopDelay() API for Node.js compatibility. This function creates and returns an IntervalHistogram object that samples the event loop delay in nanoseconds. You can use it to diagnose performance issues and understand your application's responsiveness.

import { monitorEventLoopDelay } from "perf_hooks";

const histogram = monitorEventLoopDelay({ resolution: 20 });
histogram.enable();

// Introduce a delay
await Bun.sleep(100);

histogram.disable();

console.log("Event Loop Delay (ns):");
console.log("Min:", histogram.min);
console.log("Max:", histogram.max);
console.log("Mean:", histogram.mean);
console.log("50th Percentile:", histogram.percentile(50));
console.log("99th Percentile:", histogram.percentile(99));

// Reset for next measurement
histogram.reset();

http.Server.prototype.closeIdleConnections() is now implemented

The server.closeIdleConnections() method from Node.js's http module is now implemented. This method immediately closes all sockets that are not currently handling a request, which is useful for gracefully shutting down an HTTP server without waiting for idle keep-alive connections to time out.

import { createServer } from "http";

const server = createServer((req, res) => {
  res.end("Hello, World!");
});

server.listen(3000, () => {
  console.log("Server listening on port 3000");

  // On a shutdown signal (e.g. SIGINT)
  process.on("SIGINT", () => {
    console.log("Closing server...");

    // Stop accepting new connections
    server.close((err) => {
      if (err) {
        console.error(err);
        process.exit(1);
      }
      console.log("Server closed.");
    });

    // Forcefully close any idle keep-alive connections
    // This allows the server.close() callback to fire sooner.
    server.closeIdleConnections();
  });
});

bun run now supports --workspaces

Thanks to @dylan-conway for the contribution

RFC 6455 compliant WebSocket client subprotocol negotiation

Bun's WebSocket client now correctly implements subprotocol negotiation as specified in RFC 6455. When you instantiate a new WebSocket() with an array of desired subprotocols, Bun will send them in the Sec-WebSocket-Protocol header.

The server can then select one of these protocols to use for the connection. Bun correctly validates the server's choice and makes the selected protocol available on the ws.protocol property. If the server responds with an invalid protocol, or doesn't select a protocol when one is required, the connection is now correctly rejected.

This fixes a long-standing compatibility issue with popular libraries like ws and obs-websocket-js.

// server (which already worked fine):
Bun.serve({
  port: 3000,
  fetch(req, server) {
    // Client is requesting "chat" and "superchat" protocols.
    // We'll select "chat".
    const success = server.upgrade(req, {
      headers: {
        "Sec-WebSocket-Protocol": "chat",
      },
    });
    return success
      ? undefined
      : new Response("Upgrade failed", { status: 500 });
  },
  websocket: {
    open(ws) {
      console.log(`Server: new connection with protocol "${ws.protocol}"`);
    },
  },
});

// Client code:
const ws = new WebSocket("ws://localhost:3000", ["chat", "superchat"]);

ws.onopen = () => {
  // `ws.protocol` is now correctly set to the protocol
  // selected by the server.
  // Before, this would be an empty string.
  console.log(`Client: connected with protocol "${ws.protocol}"`); // "chat"
  ws.close();
};

Override Host and other headers in new WebSocket()

You can now override special WebSocket headers like Host, Sec-WebSocket-Key, and Sec-WebSocket-Protocol when creating a new client-side WebSocket connection. This is done by passing a headers object to the WebSocket constructor.

This is useful for advanced use-cases like connecting through proxies that require a specific Host header, testing servers with specific keys, or implementing custom subprotocol negotiation. Bun will automatically handle generating required WebSocket headers if they are not provided.

// Bun now supports overriding special headers in the WebSocket client
const ws = new WebSocket("ws://localhost:8080", {
  headers: {
    "Host": "custom-host.example.com",
    "Sec-WebSocket-Key": "dGhlIHNhbXBsZSBub25jZQ==", // Must be a valid base64-encoded 16-byte key
    "Sec-WebSocket-Protocol": "chat, superchat",

    // This already worked:
    "X-Custom-Header": "MyValue",
  },
});

Connect to specific Redis databases with Bun.RedisClient

Bun's built-in Redis client, Bun.RedisClient, now supports specifying a database number directly in the connection URL, aligning with the standard Redis URI scheme. You can append /db-number to your connection string to connect to a specific database without manually sending a SELECT command.

import { RedisClient } from "bun";
// Connect to database #2
const client = new RedisClient("redis://localhost:6379/2");

// Set a key in DB 2
await client.set("foo", "bar");
console.log(await client.get("foo")); // "bar"

// Connect to the default database #0
const defaultClient = new RedisClient("redis://localhost:6379/0");

// The key "foo" will not be found in DB 0
console.log(await defaultClient.get("foo")); // null

Thanks to @HeyItsBATMAN for the contribution

Bun.redis now supports hget()

Bun's built-in Redis client now supports the HGET command. This provides a more ergonomic and performant way to retrieve a single field from a Redis hash, returning the value directly instead of a single-element array like hmget. For single-field lookups, hget() is approximately 2x faster than hmget().

import { redis } from "bun";

// Old way: returns an array
const [value] = await redis.hmget("my-hash", "my-field");

// New way: returns the value directly
const value = await redis.hget("my-hash", "my-field");

Faster number handling in Bun's APIs

Bun's internal APIs that return numbers, such as fs.statSync(), performance.now(), and process.memoryUsage(), now use a more efficient number representation when the value is a whole number.

Previously, these values were always represented as double-precision floating-point numbers. Now, they can be represented as tagged 32-bit integers, which makes subsequent arithmetic operations on these values significantly faster in JavaScriptCore.

// Bun's APIs now return numbers in a more efficient format when possible.
// Operations like this can be faster.
const stats = fs.statSync(file);
const size = stats.size; // This is now a tagged integer if it fits.

Node.js compatibility improvements:

  • Fixed: A RangeError exception in child_process.spawnSync when the stdio option was configured with process.stderr or process.stdout. This unblocks usage of tools like the AWS CDK.
  • Fixed: A bug where socket.write() from node:net would incorrectly throw an exception when passed a Uint8Array. Now it allows passing Uint8Arrays, matching Node.js behavior.
  • Fixed: A bug where crypto.verify() would throw an error when a null or undefined algorithm was passed for an RSA key. It now correctly defaults to "SHA256" to match Node.js behavior.
  • Fixed a bug where the N-API function napi_strict_equals incorrectly used Object.is semantics instead of the === operator. This improves compatibility with native modules that rely on strict equality checks, such as NaN !== NaN.
  • Fixed a crash in the N-API function napi_call_function that could occur when the recv (the this value) argument was a null pointer.
  • Fixed napi_create_array_with_length to correctly handle negative or oversized lengths, matching Node.js's behavior.
  • Fixed: util.promisify(http2.connect) now resolves correctly, improving Node.js compatibility for the http2 module.
  • Fixed: In child_process, the stdin, stdout, stderr, and stdio properties are now enumerable to match Node.js behavior. This fixes compatibility with libraries like tinyspawn and youtube-dl-exec that use Object.assign on the child process object.
  • Fixed: Request body streaming when using node-fetch in Bun now works correctly, preventing large request bodies from being fully buffered in memory.
  • Fixed: A bug where Buffer.from(string, 'utf-16le') produced incorrect output in rare cases. This improves Node.js compatibility.
  • Fixed: an incorrect assertion failure in N-API when napi_reference_unref is called during garbage collection. This improves compatibility with Node.js and fixes crashes in packages like rolldown-vite.
  • Fixed: process.versions.llhttp was missing, which improves Node.js compatibility.
  • Fixed: module._compile is now correctly assigned to require('module').prototype._compile.
  • Fixed: A noisy, un-actionable warning for async_hooks is no longer printed to the console by default, especially when using React or Next.js.
  • Fixed: crypto.randomInt was not calling the callback. Now it does.
  • Fixed: new Buffer.isAscii(string) now correctly checks for ASCII; it was previously using the isUtf8 implementation by mistake. This did not impact Buffer.isAscii(string), only new Buffer.isAscii(string), which you probably shouldn't use anyway.
  • Improved: error message in Buffer.concat() when concatenating buffers larger than 4GB. A RangeError is now thrown with a descriptive message.

Bundler & minifier improvements:

  • Fixed: In Bun.build, the onResolve and onLoad plugin hooks now correctly run for entrypoint files, matching esbuild's behavior. (Thanks to @dylan-conway)
  • Fixed: Runtime plugins using onResolve now correctly resolve dynamic import() calls, which previously could fail with an ENOENT error. (Thanks to @dylan-conway)
  • Fixed: Bun.build now throws an AggregateError by default on build failures. To revert to the old behavior, set { throw: false } and inspect the success property on the returned BuildOutput. (Thanks to @dylan-conway)
  • Fixed: A bug where standalone binaries created with bun --compile would incorrectly include the executable's name as an extra argument in process.argv. This could cause argument parsing libraries like node:util.parseArgs to fail.
  • Fixed: An assertion failure in bun build that could occur when using the --compile and --bytecode flags together.
  • Fixed: Bundler plugins can now intercept entry points with onResolve, fixing a bug that prevented the creation of virtual entry points.
  • Fixed: A bug where build.module() in a Bun plugin would fail to parse TypeScript syntax when using loader: 'ts'.
  • Fixed: Bun.build() was missing the splitting property in its TypeScript types.
  • Fixed: On Windows, single-file executables created with bun build --compile no longer have an incorrect "Original Filename" metadata field set to "bun.exe".
  • Fixed: A regression on Windows where bun build --compile would fail when using embedded resources or a relative path for --outfile.
  • Fixed: a memory leak in Bun.plugin where onLoad filters using regular expressions would not be garbage collected, causing memory usage to grow over multiple builds.
  • Fixed: A bug causing non-deterministic module resolution for packages that ship both CommonJS and ES Module versions. This could lead to inconsistent builds and "dual package hazard" errors.
  • Fixed: an error when parsing linear-gradient() with turn angle units.
  • Fixed: A bug where certain modules using top-level await were missing a async keyword, causing a runtime error.
  • Fixed: A bug where banner option with format: "cjs" and target: "bun" could produce a syntax error if banner contained a shebang.

JavaScript runtime improvements:

  • Bun.YAML.parse now accepts Buffer, ArrayBuffer, TypedArrays, DataView, and Blob as input.
  • Fixed: Bun.Cookie.isExpired() now correctly returns true for cookies with an Expires date set to the Unix epoch (Thu, 01 Jan 1970 00:00:00 GMT).
  • Fixed: an exception in crypto.subtle.importKey when importing RSA private keys with Chinese Remainder Theorem parameters. This resolves a compatibility issue with the jose JWT library.
  • Fixed: a bug that caused fetch() requests with large bodies to fail with an ECONNRESET error when using an HTTP proxy for an HTTPS connection.
  • Fixed: Errors thrown inside HTMLRewriter handlers are now correctly propagated as catchable JavaScript errors, instead of causing a [native code: Exception] message or a crash.
  • Fixed: A reliabiltiy issue in HTMLRewriter
  • Fixed: an assertion failure when using the BUN_INSPECT_CONNECT_TO environment variable in a project that loads environment variables from a .env file.
  • Fixed: a crash when using Bun.secrets on Linux.
  • Fixed: fetch() throwing a Decompression error: ShortRead when receiving an empty response with Content-Encoding: gzip and Transfer-Encoding: chunked. This also occurred with brotli and zstd in some cases.
  • Fixed: A bug where structuredClone() would throw a "TypeError: Unable to deserialize data" when cloning nested objects or arrays containing a Blob or File. Additionally, the name property of File objects is now correctly preserved after cloning.
  • Fixed: A bug causing error stack traces to be truncated, which made debugging more difficult.
  • Fixed: A bug causing fetch() to hang when the server responds with a 101 Switching Protocols status, which is used to upgrade a connection to a WebSocket. fetch() in Bun continues to not support WebSocket connections, it now errors instead of hanging.
  • Fixed: new WebSocket() now emits an error event before the close event when a connection handshake fails. Previously, only a close event was emitted, which was inconsistent with browser behavior.
  • process.versions now displays semantic versions for zlib and libdeflate instead of commit hashes.
  • Fixed: A regression causing bun --watch to crash when a file is deleted.
  • Fixed: A bug where bun --watch would not correctly handle changes in swap files.

Bun.SQL bugfixes:

  • Fixed: A regression from Bun v1.2.21 impacting DATABASE_URL options precedence in Bun.SQL connection string parsing.
  • Fixed: A potential crash in Bun.SQL when closing a MySQL or PostgreSQL database connection.

Shell improvements:

  • Fixed: A shell crash when using environment variable assignments in a pipeline, such as VAR=val | command.
  • Fixed: A regression from Bun v1.2.21 where certain commands would trigger an assertion failure on Windows.

bun install improvements:

  • Fixed an internal data structure in bun patch to use one-based line indexing by default.
  • Fixed: a panic when installing a global package with --trust if it contained dependencies that were already trusted.
  • Fixed: A crash that could occur during bun install when applying a malformed patch file with out-of-bounds line numbers.
  • Fixed: A bug where bun audit could hang indefinitely due to a cycle in the dependency graph has been resolved.
  • Fixed: A bug when extracting tarballs with unusual package names could produce an invalid temporary filename, particularly on Windows.

TypeScript types:

  • Fixed: Added missing types for AbortSignal.aborted and RegExp.escape to bun-types.
  • Fixed: Added missing TypeScript types for Bun.YAML.parse.

Thanks to 14 contributors!