Bun 1.4 is the first release to move the core codebase from Zig to Rust — one of the biggest JavaScript runtime rewrites in recent years. The team sums it up directly in the announcement: "And it rewrites Bun from Zig to Rust." In this piece we look, source by source, at the scope of the Bun 1.4 Rust migration, the Node.js compatibility numbers, new APIs like Bun.WebView and Bun.cron(), the measured performance gains, and which job you should pick Bun for.
💡 Pro Tip: Before you go to production, pin a fixed version with bun --version; the 1.4.x line is still getting frequent patches, so never run the canary build in prod.Table of Contents
- Bun's own phrasing: "Zig to Rust" — scope and limits
- Release timeline
- Node.js compatibility, in numbers
- Bun.WebView: does it replace Playwright?
- Bun.Image / Bun.markdown / Bun.Terminal
- Bun.cron(): a scheduled-job layer
- Idle CPU, RAM and startup-time gains
- Which job needs Bun, which needs Node
- FAQ
- What changed in Bun 1.4?
- Is Bun really written in Rust?
- Is Bun 1.4 compatible with Node.js 26?
- Are Bun's cron and markdown APIs production-ready?
- Does Bun.WebView fully replace Puppeteer and Playwright?
- How should I migrate an existing Node.js project to Bun 1.4?
- Conclusion
- Sources
Bun's own phrasing: "Zig to Rust" — scope and limits
Bun's official announcement (bun.com/blog/bun-v1.4) sums up the migration in a single sentence; the technical background of the port is covered in a separate post, bun.com/blog/bun-in-rust. Claude Code v2.1.181 (June 17, 2026) and later already use this Rust port. According to the same post, the migration covers 535,496 lines of Zig code excluding comments — that exact figure is Bun's own measurement.
One caveat worth noting: a generalization like "the entire runtime is now Rust" goes beyond the source's own statement.
Bun's own wording is "migration from Zig to Rust" — a specific scope. At FFI boundaries and in some low-level layers, unsafe Rust blocks and C++ bindings are still needed; by Bun's own measurement, about 4% of the Rust code sits inside an unsafe block (~27,000 lines out of ~780,000), and 78% of those blocks are single-line — a pointer from C++, or one call into a C library. The default binary is unambiguous, though: Bun v1.4.0 is the first Bun release written in Rust. Read this as complete but still-maturing, and track it via npm's dist-tags.latest.
The process-side numbers come straight from Bun's own writeup: the migration took 11 days, the bug-fixing-compiler-errors phase was split across 64 Claude instances, and the pre-merge cost, at API pricing, came to roughly $165,000. The same post says the rewrite introduced 19 known regressions, and that every one of them has been fixed.
Release timeline
The "when, in which release" question for the rewrite can be verified one-to-one via the GitHub Releases API and the npm registry:
Version | Date | Note |
|---|---|---|
v1.3.14 | May 13, 2026 | Last major Zig-based release before the rewrite |
v1.4.0 | August 20, 2026 (GitHub: 2026-08-20T14:07:21Z) | First official Rust-based release |
v1.4.2 | September 5, 2026 (GitHub: 2026-09-05T05:55:48Z) | Current stable (as of September 23, 2026) |
The 99-day gap between v1.3.14 and v1.4.0 — computed directly from the two releases' GitHub Releases dates — is indirect evidence that the rewrite's scope was not a small job. To confirm which version is currently considered "stable" in your own environment, you can query the npm registry directly:
1# npm dist-tags — separate the latest and canary tags2curl -s https://registry.npmjs.org/-/package/bun/dist-tagsNode.js compatibility, in numbers
Bun 1.4 is run against Node.js v26.3.0's own test suite and now passes +1,517 new tests — the biggest jump in a single release since Bun 1.0. Over the release cycle (1.3 → 1.4), more than 2,900 issues were closed in total. Don't confuse that with the 128 bugs the Rust rewrite itself fixed: the 2,900+ figure is the release announcement's overall issue-closure count, while 128 is the number, given in the "bun-in-rust" post, of bugs that were reproducible one-to-one on v1.3.14.
Pass rates by module:
Node.js module | Passing / Total tests |
|---|---|
node:quic | 235 / 237 |
node:http (rewritten) | 403 / 415 |
node:fs | 349 / 358 |
node:tls | 191 / 224 |
node:sqlite | 18 / 18 (100%) |
Some modules pass 97-100% of Node's own test set. But Bun itself is clear: "Bun is not 100% compatible with Node.js yet." That limit is the basis for the "which job needs Bun, which needs Node" advice at the end of this piece — there's still risk, especially for codebases that depend on native addons or need rarely-used API surfaces.
The node:tls row stands out: at 191/224, roughly 85%, it's the module with the lowest pass rate. In a security-critical layer like TLS/certificate validation, that gap matters — if you run mTLS or a custom certificate chain, test this module specifically before moving to Bun. node:quic and node:fs sit above 97%, so the risk is lower for everyday web-service scenarios (filesystem operations, HTTP/3).
Bun.WebView: does it replace Playwright?
Introduced in v1.3.12 and expanded in v1.4.0, Bun.WebView is described in the official announcement as "headless browser automation built into Bun, without Puppeteer or Playwright." It works with real user input (clicks, scroll); on macOS it uses the system WebKit, and on all three platforms it can drive an installed Chrome/Chromium/Edge over CDP (Chrome DevTools Protocol). There's also a .cdp() escape hatch for advanced scenarios.
So does this replace Playwright in your test infrastructure? The source is clear: the same post notes, separately, that Playwright also runs on Bun. So Bun.WebView isn't a "Playwright is retired" mandate — it's a built-in, dependency-free alternative. Drop the puppeteer/playwright dependency entirely for simple smoke tests or scraping; for comprehensive E2E matrices needing multiple browser engines (Firefox/WebKit), Playwright is still more mature. Check the official Bun docs for the full API surface (method signatures, option objects).
Bun.Image / Bun.markdown / Bun.Terminal
Three built-in APIs added back-to-back during the 1.4 cycle directly shrink your dependency list:
API | Introduced in | What it does | Note |
|---|---|---|---|
Bun.Image | v1.3.14 | sharp-like image processing, no native addon required | 1.38× faster than sharp on 1080p PNG→400×400 JPEG, 1.19× on JPEG→WebP |
Bun.markdown | v1.3.8 | GFM (GitHub Flavored Markdown) rendering | HTML output is not auto-sanitized |
Bun.Terminal | v1.3.5 | Native PTY (pseudo-terminal), no node-pty | Works on macOS, Linux, Windows |
The sanitization note on Bun.markdown matters: if you render Bun.markdown output directly to the page from user-supplied Markdown, you need a separate sanitization layer (for example something like DOMPurify) against XSS — that's a security layer the tool itself doesn't provide.
What these three APIs share is that they eliminate an entire class of CI problem: packages with native addons like sharp are notorious for build failures across OS/architecture combinations (especially Alpine-based Docker images) — glibc/musl mismatches, missing prebuilds, and other classic breakage. Because Bun.Image provides this functionality without a native addon build, that failure class disappears at the source. The same logic applies to node-pty (Bun.Terminal) and node-cron (Bun.cron, below): all three traditionally required a platform-specific build step.
Bun.cron(): a scheduled-job layer
Bun.cron() registers scheduled jobs at the operating-system level — meaning it integrates with crontab on Linux, launchd on macOS, and Task Scheduler on Windows in the background. The signature design is a deliberate choice: it works the same way as Cloudflare Workers' scheduled(controller) signature for Cron Triggers, so a team coming from Workers has almost no learning curve. According to the source these are two distinct modes: when you pass a file, as in the example below, the job is registered with the operating system; when you pass a function instead, the job runs on the event loop with no system cron involved. The source also states that jobs never overlap — that holds for both modes: a second run of the same job isn't triggered before the previous one finishes.
1// Bun.cron() — same scheduled(controller) signature as Cloudflare Workers Cron Triggers2export default {3 async scheduled(controller: { cron: string }) {4 if (controller.cron === "0 3 * * *") {5 await runNightlyBackup();6 }7 },8};This removes a separate npm dependency like node-cron — one less package in your dependency graph means one less risk surface in the supply chain.
Registering at the OS level points to an important difference from the traditional node-cron approach: node-cron is a scheduler that runs inside the process itself — if the process crashes or restarts, the job that was supposed to fire at that moment can be lost. Bun.cron(), by contrast, hands registration off to the operating system's own scheduler when you pass a file, rather than keeping it inside the process; in the function form the job stays in-process on the event loop, as noted above.
Idle CPU, RAM and startup-time gains
The most concrete promise of the Rust rewrite is performance. The table below collects the figures from Bun's official announcement — all first-party evidence from Bun itself; there's no guarantee you'll see the same result on your own workload, so plan against your own measurement, not this table.
Metric | Before → After | Source note |
|---|---|---|
Idle CPU ("hello world" micro-benchmark) | Dropped 5× | Bun's own measurement |
Claude Code prod CPU (p99) | 24% → 10% | Bun's own measurement, a real production example |
Claude Code prod CPU (p50) | 5.8% → 2.5% (2×) | Bun's own measurement |
HTTP server memory usage | Dropped 13%-48% (varies by framework) | Bun's own measurement |
Startup time (Windows) | 2.5× faster | Bun's own measurement |
Startup time (Linux) | 2× faster, memory less than half | Bun's own measurement |
Binary size | Shrunk by up to 17% | Bun's own measurement |
The Claude Code example matters because it's a real production workload — but it's still single-sourced (Bun itself). To confirm these numbers on your own workload, measure the same scenario with both old and new versions on your own environment; this piece doesn't hand you a ready-made benchmark, it shows which command to run: in the same project, run time bun run start first on the old version, then on 1.4, and compare on your own machine.
Don't conflate the two figures: the "5× idle CPU reduction" is a micro-benchmark ("hello world" level), while the 24%→10% and 5.8%→2.5% Claude Code figures are for a real, complex production app. They represent different measurement conditions — your own service will likely land somewhere between the two, so don't plan production around a "5× speedup" expectation; measure in staging with your own traffic.
Which job needs Bun, which needs Node
On the tooling side, 15 npm dependencies were absorbed into Bun over the 1.4 cycle: sharp, puppeteer, marked, node-cron, node-pty, concurrently, npm-run-all, serve-static, json5, fast-xml-parser, tar, string-width, slice-ansi, cli-truncate, wrap-ansi. In practice this means the bun run --parallel command now stands in for npm-run-all/concurrently.
1# instead of concurrently or npm-run-all2bun run --parallel build:web build:api build:workerbun:ffi now runs JSC-native instead of through TinyCC, and is 3× faster according to official measurements. Native stream implementations pass 100% of the WPT (Web Platform Tests) suite; for download scenarios, Bun's own measurement shows ~7.5× the throughput versus Node — that last figure is also Bun's first-party measurement, so don't carry it over as absolute truth without confirming it on your own traffic.
The practical upshot is a smaller devDependencies list in package.json and shorter node_modules install time — this translates directly into build time, especially for CI pipelines that install from scratch every build. But capturing the gain requires moving code that calls these packages' APIs over to Bun's built-in equivalent — not an automatic drop-in, a deliberate import change.
Read this table together with the Node.js compatibility limits (the section above):
Scenario | Recommendation |
|---|---|
New/mid-size project, you want built-in tooling | Bun |
Prod environment where CPU/RAM is critical, your framework supports Bun | Bun (but confirm with your own measurement) |
Large/legacy codebase that depends on native addons | Node |
Critical infrastructure that needs 100% Node API surface | Node |
Multi-browser (Firefox/WebKit) E2E test matrix with Playwright | Node + Playwright, Bun.WebView can complement it |
GOLDEN TIP
The most valuable insight in this article
This tip holds the article's most important takeaway.
Easter Egg
You found a hidden gem!
There's a hidden detail in this section. Want to uncover it?
Reader Reward
For readers who made it to the end of this article, we've put together a short checklist to help you plan your Bun 1.4 migration safely. If you found the right keyword in the Hidden Feature section, you can use the list below directly as a migration checklist.
FAQ
What changed in Bun 1.4?
Bun 1.4 arrived with the Zig codebase moved to Rust; it started passing +1,517 new tests from the Node.js v26.3.0 test suite, and more than 2,900 issues were closed over the release cycle. New built-in APIs were added, including Bun.Image, Bun.WebView, Bun.markdown, Bun.cron() and Bun.Terminal. Idle CPU dropped 5×; memory usage dropped by up to 35% in the announcement's overall summary, and by 13%-48% in apps running HTTP servers — these figures are Bun's own measurement.
Is Bun really written in Rust?
Yes — in the source's own words: "And it rewrites Bun from Zig to Rust." An 11-day migration converted 535,496 lines of Zig code to Rust. About 4% of the resulting Rust codebase is still inside unsafe blocks (~27,000 lines out of ~780,000), and C++ bindings remain at FFI boundaries; so a generalization like "every line is safe Rust" would be a claim that goes beyond the source's own statement.
Is Bun 1.4 compatible with Node.js 26?
Bun 1.4 is validated against Node.js v26.3.0's test suite; some modules like node:sqlite hit 100%, while modules like node:http/node:fs sit above 95%. But Node.js 26 is still in "Current" status as of September 23, 2026; its move to LTS is on a separate timeline. So saying "LTS-compatible" is premature — "compatible with Node 26's Current test suite" is the more accurate phrasing.
Are Bun's cron and markdown APIs production-ready?
These APIs were newly introduced in the 1.4 cycle, so they don't yet have a long production track record. The cautious path: test on a pinned version in staging first, then promote to prod.
Does Bun.WebView fully replace Puppeteer and Playwright?
No. Bun.WebView offers a dependency-free, built-in option for headless browser automation, but the same source notes that Playwright continues to run on Bun as well. It can be used to cut dependencies for simple smoke-test scenarios; for comprehensive multi-browser E2E matrices, Playwright is still more mature.
How should I migrate an existing Node.js project to Bun 1.4?
Work through it in order: first scan your codebase for use of modules with a low pass rate, like node:tls; then evaluate dropping packages now built into Bun — sharp/puppeteer/node-cron/node-pty/concurrently — from your dependency list. Next, run both your existing Node target and the new Bun target in parallel in CI, paying particular attention to code paths that use node:tls and native addons. Wait a week on a pinned stable version in staging, and if nothing breaks, shift production traffic over gradually. None of these steps requires a "replace everything at once" approach — Bun and Node can be tested side by side on the same package.json for a while.
Conclusion
Bun 1.4 pushes Node.js compatibility forward with concrete numbers alongside its "Zig to Rust" migration, and shrinks the npm dependency graph with built-in APIs like Bun.Image/Bun.WebView/Bun.markdown/Bun.cron()/Bun.Terminal. But the work isn't done: by the Bun team's own account, they're continuing to reduce unsafe usage and move the code closer to idiomatic Rust, the CPU/RAM figures are first-party measurements, and some APIs are new and unproven. The practical path: test on a pinned version in staging, keep a parallel Bun+Node matrix in CI, and roll out to production gradually.
If you've already been through a similar transition on the Node.js runtime side, Next.js 16.3 drops runtime='edge': the return to Node shows how runtime decisions can be reversed. For the ORM-side counterpart of a move to a Rust engine, read Migrating Prisma 6 to 7: the Rust engine is gone, what changed? — you'll see the same "fast, but comes with breakage risk" pattern in a different layer. For a parallel story on the compiler side, TypeScript 7 ships: 10x faster, but ESLint breaks is useful. If you're also revisiting your build tooling, Migrating from Webpack to Vite gives a step-by-step migration plan; and if you want to stay current in the Next.js ecosystem, Next.js 16.3 Instant Navigations and Cache Components guide also covers that release's other major changes.
Sources
- Official Bun v1.4 announcement — the Zig-to-Rust migration, Node compatibility figures, new APIs
- Bun in Rust — technical background — technical details of the rewrite
- npm bun dist-tags — current latest/canary version tags
- InfoQ: Bun's AI-Assisted Rewrite from Zig to Rust — independent news analysis of the rewrite
- Appwrite: Announcing Bun 1.4 — independent integration announcement, API list confirmation
- Node.js v26.0.0 release blog — Node 26's "Current" status
Tags
iOS Development News
Weekly Swift tips, SwiftUI tricks and iOS best practices. No spam, only valuable content.
We respect your privacy. You can unsubscribe at any time.

