Bun v1.3.12 Bun version 1.3.12 introduces native headless browser automation via `Bun.WebView`, supporting both WebKit and Chrome backends with OS-level event dispatching. The update also adds the ability to render Markdown files directly in the terminal, an in-process `Bun.cron()` scheduler for recurring tasks, and improved async stack traces for native APIs. 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 Bun.WebView — Headless Browser Automation Bun.WebView — Headless Browser AutomationBun now ships with native headless browser automation built into the runtime. Two backends, one API: WebKit macOS default — uses the system WKWebView. Zero external dependencies. Chrome cross-platform — Chrome/Chromium via DevTools Protocol. Auto-detects installed browsers or accepts a custom path. In the next version of Bun — Jarred Sumner @jarredsumner Bun.WebView programmatically controls a headless web browser in Bun pic.twitter.com/Yp8UiNoeoy March 18, 2026 All input is dispatched as OS-level events — sites can't distinguish view.click from a real mouse click isTrusted: true . Selector-based methods auto-wait for actionability, Playwright-style: the element must be attached, visible, stable, and unobscured before the action fires. await using view = new Bun.WebView { width: 800, height: 600 } ; await view.navigate "https://bun.sh" ; await view.click "a href='/docs' " ; // waits for actionability, native click await view.scroll 0, 400 ; // native wheel event, isTrusted: true await view.scrollTo " install" ; // scrolls every ancestor, waits for visible const title = await view.evaluate "document.title" ; const png = await view.screenshot { format: "jpeg", quality: 90 } ; await Bun.write "page.jpg", png ; All methods work across both backends: | Method | Description | |---|---| navigate url | Navigate to a URL | evaluate expr | Evaluate JavaScript in the page | screenshot {format, quality, encoding} | Capture a PNG/JPEG/WebP screenshot | click x, y / click selector | Click at coordinates or a CSS selector | type text | Type text into the focused element | press key, {modifiers} | Press a key with optional modifiers | scroll dx, dy / scrollTo selector | Scroll by delta or to an element | goBack / goForward / reload | Navigation controls | resize w, h | Resize the viewport | cdp method, params | Raw Chrome DevTools Protocol call | view.url / view.title / view.loading | Page state properties | Bun.WebView extends EventTarget — on the Chrome backend, CDP events are dispatched as MessageEvent s with the params on event.data . Constructor options include backend "webkit" , "chrome" , or { type: "chrome", path, argv } , console to capture page logs, and dataStore for persistent profiles. One browser subprocess is shared per Bun process; additional new Bun.WebView calls open tabs in the same instance. Render Markdown in the Terminal with render-markdown-in-the-terminal-with-bun-file-md bun ./file.md bun ./file.md You can now render Markdown files directly in your terminal. When you run bun ./file.md , Bun reads the file, renders it as beautifully formatted ANSI output, and prints it to stdout with no JavaScript VM startup overhead. In the next version of Bun — Bun @bunjavascript bun ./hello.md & Bun.markdown.ansi string pretty-prints markdown to terminal-friendly ansi text pic.twitter.com/yp7jYKZL8k April 4, 2026 You can also use the new Bun.markdown.ansi API programmatically: js // Render markdown to an ANSI-colored string const out = Bun.markdown.ansi " Hello\n\n bold and italic \n" ; process.stdout.write out ; // Plain text mode — no escape codes const plain = Bun.markdown.ansi " Hello", { colors: false } ; // Enable clickable hyperlinks const linked = Bun.markdown.ansi " docs https://bun.sh ", { hyperlinks: true, } ; // Custom line width for wrapping const wrapped = Bun.markdown.ansi longText, { columns: 60 } ; // Inline images via Kitty Graphics Protocol Kitty, WezTerm, Ghostty const withImg = Bun.markdown.ansi " alt ./logo.png ", { kittyGraphics: true, } ; Async stack traces for native errors async-stack-traces-for-native-errors In the next version of Bun — Jarred Sumner @jarredsumner Async stacktraces are supported on native APIs like node:fs, Bun.write, node:http, node:dns & more. This makes debugging easier pic.twitter.com/PHospWtxtg March 30, 2026 In-process in-process-bun-cron-scheduler Bun.cron scheduler Bun.cron scheduler Bun.cron now supports an in-process callback overload that runs a function on a cron schedule. This is ideal for long-running servers and containers where you want scheduled work that shares state with the rest of your application. In the next version of Bun — alistair @alistaiir Bun.cron accepts a callback for recurring in-process tasks pic.twitter.com/HQ3s3rxGEO April 1, 2026 This complements the existing OS-level Bun.cron path, schedule, title which registers persistent crontab/launchd/Task Scheduler entries. The in-process variant is lighter, works identically across platforms, and lets your handler access database pools, caches, and module-level state directly. Key behaviors: No overlap — the next fire is scheduled only after the handler and any returned Promise settles. Slow async work won't pile up concurrent runs. Scheduled in UTC — 0 9 means 9:00 UTC, regardless of the system time zone. The OS-level Bun.cron path, schedule, title variant uses system local time, since that's how crontab/launchd/Task Scheduler work. Error handling matches — synchronous throws emit setTimeout uncaughtException , rejected promises emit unhandledRejection . Without a listener the process exits with code 1; with one, the job reschedules itself.— all in-process cron jobs are cleared before the module graph re-evaluates, so editing the schedule, handler, or removing the call entirely all take effect on save without leaking timers. --hot safe— Disposable using job = Bun.cron ... auto-stops at scope exit.— ref / unref .ref default keeps the process alive; .unref lets it exit naturally. js // Error handling example process.on "unhandledRejection", err = console.error "cron failed:", err ; Bun.cron " ", async = { await mightThrow ; // logged, then retried next minute } ; Thanks to @alii for the contribution UDP Socket: ICMP Error Handling and Truncation Detection udp-socket-icmp-error-handling-and-truncation-detection Two improvements to Bun.udpSocket that bring it closer to libuv/Node.js behavior: ICMP errors no longer silently close the socket. On Linux, sending a UDP packet to an unreachable port previously caused the socket to silently close, breaking all other sends on the same socket. Now, ICMP errors port unreachable, host unreachable, TTL exceeded, etc. are surfaced through the error handler, and the socket stays open: js const sock = await Bun.udpSocket { socket: { error err { console.log err.code ; // 'ECONNREFUSED' }, }, } ; sock.send "ping", 1, "127.0.0.1" ; // dead port — error handler fires, socket stays open Truncated datagrams are now detectable. When a received datagram is larger than the receive buffer, the kernel silently truncates it. The data callback now receives a fifth flags argument so you can tell truncated payloads from complete ones: js const sock = await Bun.udpSocket { socket: { data socket, data, port, address, flags { if flags.truncated { console.log "Datagram was truncated " ; } }, }, } ; Unix Domain Socket Lifecycle Now Matches Node.js unix-domain-socket-lifecycle-now-matches-node-js Bun's unix domain socket behavior was inverted from Node.js/libuv in two important ways: | Node.js / libuv | Bun before | | |---|---|---| | Existing file at bind time | EADDRINUSE | Silently unlinks and binds anyway | Socket file after close | Removed | Left on disk | This meant Bun could silently steal a live socket from another process on listen , and leaked .sock files on stop / close . Now Bun matches Node.js semantics: binding to an existing socket file correctly returns EADDRINUSE , and closing a listener automatically cleans up the socket file. This applies to Bun.listen , Bun.serve , and net.Server with unix sockets. js import { existsSync } from "node:fs"; const listener = Bun.listen { unix: "/tmp/my.sock", socket: { data {}, open {} }, } ; existsSync "/tmp/my.sock" ; // true listener.stop ; existsSync "/tmp/my.sock" ; // false — automatically cleaned up // Binding to an existing socket now correctly throws EADDRINUSE // instead of silently taking over the socket import { listen } from "bun"; const a = listen { unix: "/tmp/my.sock", socket: { data {}, open {} } } ; try { // Previously: silently unlinked and stole the socket // Now: throws EADDRINUSE, matching Node.js behavior const b = listen { unix: "/tmp/my.sock", socket: { data {}, open {} } } ; } catch e { console.log e.code ; // "EADDRINUSE" } finally { a.stop ; } Upgraded JavaScriptCore Engine upgraded-javascriptcore-engine Bun's underlying JavaScript engine WebKit's JavaScriptCore has been upgraded with over 1,650 upstream commits, bringing significant performance improvements, new language features, and bug fixes. Explicit Resource Management explicit-resource-management-using-and-await-using using and await using using and await using The using and await using declarations from the TC39 Explicit Resource Management proposal https://github.com/tc39/proposal-explicit-resource-management are now supported natively in JavaScriptCore: function readFile path { using file = openFile path ; // file Symbol.dispose called automatically at end of block return file.read ; } async function fetchData url { await using connection = await connect url ; // connection Symbol.asyncDispose awaited at end of block return connection.getData ; } JIT Compiler Improvements jit-compiler-improvements Quick tier-up — Functions that are proven stable now tier up to optimized DFG/FTL compilation faster, improving steady-state performance.— Array.isArray intrinsic Array.isArray is now a JIT intrinsic, making it significantly faster in hot paths. Faster — Uses an optimized single-character search fast path. String includes Improved BigInt performance — Smaller memory footprint and faster arithmetic operations. Better register allocation — Rewritten greedy register allocator coalescing for improved generated code. Faster promise resolution — Micro-optimized promise reaction triggering and microtask queue draining, with a new PerformPromiseThen DFG/FTL optimization node. WebAssembly Improvements webassembly-improvements - SIMD shuffle optimizations and additional ARM64/x64 SIMD instruction support. - Memory64 bounds checking fixes. - Improved BBQ and OMG compiler codegen conditional selects, better write barriers, tail-call fixes . JavaScript Spec Conformance Fixes javascript-spec-conformance-fixes TypedArray sort when the comparator accesses .buffer Array includes undefined, fromIndex hole handling Array flat with depth 0 and derived array bailout Array.prototype.concat now checks all indexed accessors Array.prototype.copyWithin return value Set methods properly throw on non-object iterator next result RegExp @@matchAll lastIndex clamping String replace surrogate-advancement fix for non-unicode regexps- Named vs numbered backreference handling in RegExp - Private fields no longer have attributes set when sealing/freezing objects Memory Allocator libpas Improvements memory-allocator-libpas-improvements - Retag-on-scavenge for improved memory safety - Page-based zeroing threshold reduced from 64MB to 1MB for faster large allocations - Faster bitfit heap utilization Thanks to @sosukesuzuki for the contribution Improved standalone executables on Linux improved-standalone-executables-on-linux Standalone executables created with bun build --compile on Linux now use a proper ELF section .bun to embed the module graph, matching the existing approach on macOS and Windows. Previously, the embedded data was read from /proc/self/exe at startup, which failed when the binary had execute-only permissions chmod 111 . With this change, the kernel maps the data via PT LOAD during execve — meaning zero file I/O at startup and no read permission required on the binary. bun build --compile app.ts --outfile myapp chmod 111 myapp ./myapp works now on Linux Thanks to @dylan-conway for the contribution URLPattern is up to 2.3x faster URLPattern is up to 2.3x faster URLPattern.test and URLPattern.exec are now significantly faster. The internal regex matching now calls the compiled regex engine directly instead of allocating temporary JavaScript objects for each URL component, eliminating up to 24 GC allocations per call. | Benchmark | Before | After | Speedup | |---|---|---|---| test match - named groups | 1.05 µs | 487 ns | 2.16x | test no-match | 579 ns | 337 ns | 1.72x | test match - simple | 971 ns | 426 ns | 2.28x | test match - string pattern | 946 ns | 434 ns | 2.18x | exec match - named groups | 1.97 µs | 1.38 µs | 1.43x | exec no-match | 583 ns | 336 ns | 1.73x | exec match - simple | 1.89 µs | 1.30 µs | 1.45x | js const pattern = new URLPattern { pathname: "/api/users/:id/posts/:postId" } ; // 2.16x faster pattern.test "https://example.com/api/users/42/posts/123" ; // 1.43x faster pattern.exec "https://example.com/api/users/42/posts/123" ; As a side effect, URLPattern internals no longer pollute RegExp.lastMatch / RegExp.$N — previously, calling pattern.test url would leak internal regex state into these legacy static properties. Thanks to @sosukesuzuki for the contribution Faster faster-bun-stripansi-and-bun-stringwidth Bun.stripANSI and Bun.stringWidth Bun.stripANSI and Bun.stringWidth SIMD optimizations across Bun.stripANSI , Bun.stringWidth , and the shared ANSI parsing helpers used by Bun.sliceAnsi , Bun.wrapAnsi , and Node's readline.getStringWidth . Key improvements: 4×-unrolled SIMD prologue for escape character scanning — processes 64 bytes at a time instead of 16, reducing the cost of the NEON→GPR transfer that gates the loop branch. SIMD terminator scans inside ANSI escape parsing — CSI and OSC payloads like hyperlink URLs are now skipped in bulk instead of byte-by-byte. Lazy flat buffer allocation in stripANSI — replaces StringBuilder with a raw Vector