To install Bun
curl -fsSL https://bun.sh/install | bash
npm install -g bun
powershell -c "irm bun.sh/install.ps1|iex"
scoop install bun
brew tap oven-sh/bun
brew install bun
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 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.
In the next version of Bun & Safari
— Bun (@bunjavascript) September 1, 2025
Error subclasses show where the error was thrown as the 1st frame instead of the subclass' constructor, thanks to @__sosukesuzuki pic.twitter.com/NC8Ex9DC3K
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
.
In the next version of Bun
— Jarred Sumner (@jarredsumner) August 31, 2025
postMessage(object) gets up to 200x faster for objects with strings or primitive values. pic.twitter.com/Z4Nrj4HtJL
Bun.YAML.stringify
In the next version of Bun
— Bun (@bunjavascript) August 27, 2025
Bun.YAML.stringify converts JavaScript objects into YAML pic.twitter.com/ioaSb07EPX
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.
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 Type | JavaScript Type | Notes |
---|---|---|
INT, TINYINT, MEDIUMINT | number | Within 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 asBuffer
s 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
andTIMETZ
columns from PostgreSQL when using the binary protocol.
Bundler & minifier
In the next version of Bun
— Bun (@bunjavascript) September 4, 2025
Bun's JavaScript transpiler & minifier gets slightly smarter pic.twitter.com/W84u1C9Q7a
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()
.
-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
In the next version of Bun
— Bun (@bunjavascript) September 7, 2025
The --workspaces flag in bun run runs the package.json script in each workspace package pic.twitter.com/sXJFwhnx79
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 inchild_process.spawnSync
when thestdio
option was configured withprocess.stderr
orprocess.stdout
. This unblocks usage of tools like the AWS CDK. - Fixed: A bug where
socket.write()
fromnode:net
would incorrectly throw an exception when passed aUint8Array
. Now it allows passingUint8Array
s, matching Node.js behavior. - Fixed: A bug where
crypto.verify()
would throw an error when anull
orundefined
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 usedObject.is
semantics instead of the===
operator. This improves compatibility with native modules that rely on strict equality checks, such asNaN !== NaN
. - Fixed a crash in the N-API function
napi_call_function
that could occur when therecv
(thethis
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 thehttp2
module. - Fixed: In
child_process
, thestdin
,stdout
,stderr
, andstdio
properties are now enumerable to match Node.js behavior. This fixes compatibility with libraries liketinyspawn
andyoutube-dl-exec
that useObject.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 likerolldown-vite
. - Fixed:
process.versions.llhttp
was missing, which improves Node.js compatibility. - Fixed:
module._compile
is now correctly assigned torequire('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 theisUtf8
implementation by mistake. This did not impactBuffer.isAscii(string)
, onlynew Buffer.isAscii(string)
, which you probably shouldn't use anyway. - Improved: error message in
Buffer.concat()
when concatenating buffers larger than 4GB. ARangeError
is now thrown with a descriptive message.
Bundler & minifier improvements:
- Fixed: In
Bun.build
, theonResolve
andonLoad
plugin hooks now correctly run for entrypoint files, matching esbuild's behavior. (Thanks to @dylan-conway) - Fixed: Runtime plugins using
onResolve
now correctly resolve dynamicimport()
calls, which previously could fail with anENOENT
error. (Thanks to @dylan-conway) - Fixed:
Bun.build
now throws anAggregateError
by default on build failures. To revert to the old behavior, set{ throw: false }
and inspect thesuccess
property on the returnedBuildOutput
. (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 inprocess.argv
. This could cause argument parsing libraries likenode: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 usingloader: 'ts'
. - Fixed:
Bun.build()
was missing thesplitting
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
whereonLoad
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()
withturn
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 withformat: "cjs"
andtarget: "bun"
could produce a syntax error ifbanner
contained a shebang.
JavaScript runtime improvements:
Bun.YAML.parse
now acceptsBuffer
,ArrayBuffer
,TypedArray
s,DataView
, andBlob
as input.- Fixed:
Bun.Cookie.isExpired()
now correctly returnstrue
for cookies with anExpires
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 thejose
JWT library. - Fixed: a bug that caused
fetch()
requests with large bodies to fail with anECONNRESET
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 aDecompression error: ShortRead
when receiving an empty response withContent-Encoding: gzip
andTransfer-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 aBlob
orFile
. Additionally, thename
property ofFile
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 a101 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 anerror
event before theclose
event when a connection handshake fails. Previously, only aclose
event was emitted, which was inconsistent with browser behavior. process.versions
now displays semantic versions forzlib
andlibdeflate
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 inBun.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
andRegExp.escape
tobun-types
. - Fixed: Added missing TypeScript types for
Bun.YAML.parse
.