# Elements vs Bun Bun is an open-source JavaScript runtime. A runtime is the program that runs your server code, and Bun is built to replace Node.js, the most widely used JavaScript runtime; its docs call it a "drop-in replacement for Node.js." It runs on JavaScriptCore, the JavaScript engine from Apple's Safari. Bun describes itself as "an all-in-one toolkit": alongside the runtime, the `bun` program includes a [package manager](https://bun.com/docs/pm/cli/install) (`bun install`), a Jest-compatible test runner (`bun test`) and a bundler (`bun build`), each run as its own command. The runtime executes TypeScript and JSX files directly, and it has built-in APIs for an HTTP server, SQL databases, Redis, S3 and shell scripting ([Bun documentation](https://bun.com/docs)). Bun is free and MIT licensed. It was built by Oven, a venture-backed startup, and Anthropic acquired it in December 2025; at the time, Bun made "$0 in revenue" ([Bun joins Anthropic](https://bun.com/blog/bun-joins-anthropic)). Elements is not a runtime, and it is not trying to become one: Elements makes tooling. It is an integrated app environment for building web apps, built for people and their agents. At its center is a project server that runs while you work, and around it the same system includes a package installer, a test runner, a bundled Postgres database, and a deploy command for any Ubuntu server you reach over SSH. It ships with a default framework that just works: routing, server rendering, typed server functions, sessions, realtime data, background jobs and email. Elements is its own program, and the apps it builds run on Node.js ([start](/learn/man/start)). Bun is a new runtime plus a set of commands you run one at a time, with no project server: `bun install`, `bun test` and `bun build` each start fresh, and nothing runs while you work to hold the build state or answer an agent. An app still has to be assembled on top of it: a framework, a database, migrations, sessions, a type check and a way to deploy. Every seam between those parts is a place the app breaks, for a person or an agent, and with Bun the runtime under all of them is a seam too. As of October 1, 2026, Bun passes 80.5% of the Node.js test suite, by Bun's own tracker ([Bun Node.js test suite tracker](https://bun.com/node-test-suite)), and its issue tracker holds 183 open crash reports. With Elements, your app runs on Node.js, the runtime npm packages are written and tested against, and the project server keeps installing, type checking, tests, migrations and program analysis current in one build. The framework stays close to the metal, HTML, CSS, TypeScript, SQL and function calls, and its vocabulary is the bare minimum: `app.route()`, `session.login()`, `sql()`, `@rpc` and tests. ## At a Glance | | Elements | Bun | |---|---|---| | What it is | An integrated app environment built around a project server that runs while you work, with a default framework, Postgres, tests and deploy | A runtime plus separate commands, with no project server | | Business model | Sells the tooling directly, per machine, with nothing metered ([pricing](/pricing)) | Owned by Anthropic, which bought it as "the infrastructure powering Claude Code" ([Bun joins Anthropic](https://bun.com/blog/bun-joins-anthropic)) | | Runtime | Your app runs on Node.js | Bun's own, built to replace Node.js, with 183 open issues labeled crash | | Node.js compatibility | Apps run on Node, so compatible by definition | As of October 1, 2026, passes 80.5% of the Node.js v26.3.0 test suite, by Bun's own tracker ([Bun Node.js test suite tracker](https://bun.com/node-test-suite)) | | Build errors | [One build](/learn/man/build) reports type checking, tests, migrations and program analysis | None in the runtime: "Bun does not perform typechecking", and `bun test` is its own command | | Tests | Part of the build, each test in a Postgres transaction that rolls back | A separate command or watch process, with no database isolation built in | | Several agents at once | One shared build state that every agent and editor reads | Each agent runs its own commands | | Package install | A fully functional installer that is part of the project server, so installs feed straight into the build | Only an installer, disconnected from the build | | Local packages | Found on a package search path, watched, and deployed with the app | `bun link` symlinks, or a workspace monorepo | | Database | Bundled Postgres, with migrations part of the build | A SQL client; you run the database and write the migration tooling | | Background work | Durable jobs in Postgres with retries, and cron that runs each tick on exactly one machine | Cron callbacks in one process, or OS cron jobs registered by Bun | | Deploy | `elements deploy` over SSH, which sets up TLS; later deploys often take under a second | A build step and a Dockerfile; you bring the reverse proxy, certificates and process manager | ## A Runtime That Still Crashes A runtime is the foundation every request in your app runs on, so a crash in the runtime is a crash in your app, whatever your code does. As of October 1, 2026, Bun's GitHub repository has 3,736 open issues, 1,959 of them labeled bug and 183 labeled crash ([open Bun issues](https://github.com/oven-sh/bun/issues?q=is%3Aissue%20state%3Aopen), [open crash issues](https://github.com/oven-sh/bun/issues?q=is%3Aissue%20state%3Aopen%20label%3Acrash)). Node.js has 602 open issues ([open Node.js issues](https://github.com/nodejs/node/issues?q=is%3Aissue%20state%3Aopen)). Of Bun's open crash reports, 18 were filed in September 2026 alone. Segfaults, where the runtime itself dies, are a steady part of that record: 35 open issues carry the word in their title, and six of them were filed in the last month ([open segfault issues](https://github.com/oven-sh/bun/issues?q=is%3Aissue%20state%3Aopen%20segfault%20in%3Atitle)). They come from ordinary work: - "[Linux x64 1.4.2 segfault in generateNativeModule on import "node:events"](https://github.com/oven-sh/bun/issues/44276)", filed September 30: a crash on importing one of Node's core modules. - "[Segfault in JSC GC marking (JSScope::visitChildren) in standalone executable (Claude Code), Bun v1.4.3 Linux x64](https://github.com/oven-sh/bun/issues/44009)", filed September 25, in the garbage collector. - "[bun test --isolate: intermittent segfault in Bun__runDeferredWork at exit after all tests pass (1.4.2, Linux x64, JIT off)](https://github.com/oven-sh/bun/issues/43056)", filed September 17, in the test runner. - "[Segfault after ~24h idle on Windows 11 with sleep/wake cycles](https://github.com/oven-sh/bun/issues/28175)", open since March, in a process that only sat idle. Each one is open today. Elements does not ask you to bet your production app on that. Your app runs on Node, and Elements builds it, checks it and deploys it. Because Elements is tooling, the runtime underneath is a choice it can make again, and Elements will consider targeting Bun alongside Node.js once Bun is stable. ## Node Compatibility Is a Gap You Find Yourself npm packages are written and tested against Node.js, and agents have learned from years of code written for it. Bun reimplements Node's APIs so the same code can run on it, and any reimplementation can differ from the original. Bun tracks how far it differs. As of October 1, 2026, Bun passes 80.5% of the Node.js v26.3.0 test suite, 3,708 of 4,608 tests, by Bun's own tracker, so roughly one Node test in five fails on Bun. Some modules score far below that: `inspector`, which debuggers and profilers connect through, at 20%; `async_hooks`, which tracing libraries such as OpenTelemetry build on, at 50%; `worker` at 74%; `domain` at 8% ([Bun Node.js test suite tracker](https://bun.com/node-test-suite)). Bun's compatibility page lists `async_hooks`, `child_process`, `cluster`, `crypto`, `tls`, `worker_threads` and more as partial, and says `createHook`, `executionAsyncId` and `triggerAsyncId` "are stubs" and "async ids are always `0`," and that `node:v8` is missing `startCpuProfile` and `startHeapProfile` ([Bun Node.js compatibility](https://bun.com/docs/runtime/nodejs-compat)). The gaps show up in the open issues with the most reactions: - "[Support V8 C++ APIs for "nan" addons and other packages to work](https://github.com/oven-sh/bun/issues/4290)", open since August 2023 with 274 reactions. Its checklist of packages that do not yet work includes `better-sqlite3`, and Datadog's `dd-trace` and `@datadog/native-metrics` and Sentry's `@sentry/profiling-node`, the monitoring a production app reaches for when something goes wrong. - "[Support Vitest](https://github.com/oven-sh/bun/issues/4145)", open since August 2023 with 278 reactions. - "[support `node:inspector`](https://github.com/oven-sh/bun/issues/2445)", open since March 2023 with 183 reactions, which the Vitest issue lists as a blocker for coverage reporting. Bun's compatibility page says that "if a package works in Node.js but doesn't work in Bun, we consider it a bug in Bun" ([Bun Node.js compatibility](https://bun.com/docs/runtime/nodejs-compat)). Bun may fix that bug, but you are the one who finds it. An Elements app runs on Node itself. Server code is type checked against Node's own types, and route handlers receive Node's request and response objects ([typescript](/learn/man/typescript), [router](/learn/man/router)). Your packages, your monitoring and the code your agent writes all run on the runtime they were tested on. For more on how Elements and Node fit together, see [Elements vs Node.js](/vs/node). ## Who Pays for Bun Bun began as a venture-backed startup. Oven's plan for revenue was "some version of 'we'll eventually build a cloud hosting product'" ([Bun joins Anthropic](https://bun.com/blog/bun-joins-anthropic)). Then Anthropic bought it. Anthropic's announcement ran under the headline "[Anthropic acquires Bun as Claude Code reaches $1B milestone](https://www.anthropic.com/news/anthropic-acquires-bun-as-claude-code-reaches-usd1b-milestone)" and told Claude Code users the deal "means faster performance, improved stability, and new capabilities." Bun's own post is plain about the incentive: "Claude Code ships as a Bun executable to millions of users. If Bun breaks, Claude Code breaks." It also says "We've been prioritizing issues from the Claude Code team for several months now," and that Bun's job is now "to make Bun the best place to build, run, and test AI-driven software" ([Bun joins Anthropic](https://bun.com/blog/bun-joins-anthropic)). Bun's owner sells something else, and it is a wonderful product: Claude Code. But by Bun's own account its issue queue has followed Claude Code's needs ([Bun joins Anthropic](https://bun.com/blog/bun-joins-anthropic)), so an app server on Bun runs on a runtime whose priorities are set by another product. Elements sells its tooling directly, per machine, with nothing metered ([pricing](/pricing)). It charges for that tooling and not for the framework: the app framework and runtime packages such as `@elements/app` are MIT licensed, and everything you build is yours ([licenses](/learn/man/licenses)). ## For an Agent, the Value Is in Correction More app code is now written by AI coding agents such as Claude Code and Codex. For an agent, the value is not in a toolkit's APIs or a package ecosystem. It is in correction: finding out what it got wrong the moment it gets it wrong. A library or a built-in API still earns its place when it does something an agent cannot write quickly from standards, but that list is short. An agent does not need an API of its own for what a few lines of TypeScript do, and it would rather write a page in HTML and CSS than learn a UI library. Bun puts its engineering into APIs. Alongside the runtime, bundler, test runner and package manager come built-in APIs for HTTP, SQL, Redis, S3 and shell scripts ([Bun documentation](https://bun.com/docs)), along with `Bun.Image`, `Bun.WebView`, `Bun.markdown` and `Bun.cron()` ([Bun 1.4](https://bun.com/blog/bun-v1.4.0)). Each is a Bun API rather than a web or Node.js standard, so an agent has to learn it, and the code that uses it runs only on Bun. Correction is left to separate commands: Bun runs TypeScript without checking it, so an agent finds a type error only when it remembers to run `tsc`, and a failing test only when it runs `bun test`. On a line from an agent writing machine code, which nobody does, to a package that does subtraction, the right amount of framework sits at the plumbing every app needs. Elements ships that plumbing: routing, typed server calls with `@rpc`, sessions, `sql()` and the database driver. Past that plumbing comes a point of diminishing returns, where each further abstraction costs an agent more to learn and get right than it saves. So Elements stops there, keeps abstractions minimal and puts its engineering into correction. A page is HTML and CSS, a server call is a TypeScript function marked `@rpc`, a query is SQL in `sql()`, a route is one `app.route()` line, and signing in is `session.login()`. Most server code needs no `async` or `await`, because the compiler adds them ([async](/learn/man/async)). Because the framework and its build were designed together, one coherent system checks every page, function, `sql()` call, test and migration an agent writes. `elements build -json` reports each mistake in microseconds, because the project server already holds the build state. ## One Server Instead of Separate Commands A developer or an agent runs the loop between an edit and knowing it works many times a day. How fast that loop runs depends on how much each step has to redo. Bun calls itself an all-in-one toolkit, but it is one binary with separate commands, and each watch mode keeps one process of its own running. `bun --hot` reloads changed code inside a running process without restarting it, and `bun --watch` restarts a process when its files change, for an app or for `bun test` ([Bun watch mode](https://bun.com/docs/runtime/watch-mode)). Installing, type checking with `tsc` and bundling each run on their own. None of them shares what the others know, no single command reports every error together, and an agent has to run each one and stitch the answers together itself. Elements has a project server that runs while you work. It holds the build graph and build state in memory, and on every save the build state is updated almost instantly. The same save hot reloads the running app and the test workers without restarting either ([build](/learn/man/build)). An agent asks `elements build -json` and gets one structured answer covering type errors, test failures and migration errors. Your terminal, your editor and every agent working in parallel talk to the same server and see the same build state ([build](/learn/man/build)). The same work, side by side: ```bash # Bun: separate tools, each with its own process bun install bunx tsc --noEmit bun --watch test bun --hot index.ts # Elements: one server that is always current elements start # builds and hot reloads in milliseconds as you work elements build -json # one answer, from memory elements deploy # the same build, sent to your server ``` ## Installs Feed Straight Into the Build Both Bun and Elements install npm packages into a `node_modules` folder from a cache shared by every project on the machine. Bun keeps its cache in `~/.bun/install/cache` and places packages with clonefile on macOS and hardlinks on Linux ([bun install](https://bun.com/docs/pm/cli/install)). `bun install` is only an installer, disconnected from the build: it writes `node_modules` and exits, and nothing checks your code against what changed until you run the type check and the tests yourself. The Elements package installer is extremely fast, like Bun's package installer. But Elements does more: it is a fully functional installer, and it is also part of the project server, so installs feed straight into the build. Add a dependency, or edit the dependency map and save, and the project server installs what changed and checks your code against the new packages in the same build that compiles your files and runs your tests. Every build error the new packages cause, from type checking and tests to program analysis, comes back in that one build ([packages](/learn/man/packages)). The installer keeps one store under `~/elements/packages`, resolves from package manifests rather than tarballs, and downloads each package version once per machine. It computes the new `node_modules` tree in memory and compares it with the tree already on disk, so it writes only the packages that were added, changed or removed, and an install that changes nothing touches nothing. Each package is cloned into place, copy-on-write where the filesystem supports it, as APFS on macOS does ([packages](/learn/man/packages)). Because install is part of the build, the build also knows where every import lands. The tree is a real `node_modules`, laid out the way Node resolves modules, with packages from the npm registry. Every import is resolved at build time and rewritten to the exact file it resolved to, so the file your app loads at runtime is the file the build checked. A module that cannot be found fails the build, not the running app ([build](/learn/man/build)). ## Local Packages Without a Monorepo Many teams share code between projects as packages they have not published, such as a component library or a set of utilities. In Bun that takes one of two workarounds. `bun link` registers a directory and symlinks it into another project's `node_modules` ([bun link](https://bun.com/docs/pm/cli/link)), or you move the projects into one repository as workspaces ([Bun workspaces](https://bun.com/docs/pm/workspaces)). Elements has a package search path instead. Give a dependency the version `local` and the project server finds it in the directories listed in `ELEMENTS_PACKAGE_PATH`. Local packages are watched: save a file in the package and the app builds against it. When the package is published, switch the version back to a registry release and the local copy is removed on the next install. A deploy ships local packages to the server and installs them there, so an app built on an unpublished package deploys like any other ([packages](/learn/man/packages)). There is no link step and no reason to merge repositories. ## Every Build Error Stops the Build Bun runs code; checking it is left to you. It runs TypeScript by stripping the types, and its docs say "Bun does not perform typechecking" ([Bun file types](https://bun.com/docs/runtime/file-types)). Tests are their own command, `bun test`, or their own watch process ([Bun test runner](https://bun.com/docs/test)), and migrations belong to whichever library you add. A mistake reaches your running app unless you remember to run each check yourself. In Elements, one build reports every error: type checking, tests, migrations and program analysis. The project server does that work in milliseconds as you save, and `elements build -json` returns the result in microseconds ([build](/learn/man/build)). Server code is checked against Node's types and browser code against the browser's, and templates and `config.jsoc` are part of the same build, so a typo in either fails it ([typescript](/learn/man/typescript)). Only the tests that depend on the changed code rerun, on workers that hot reload rather than restart. Each test runs inside a Postgres transaction that rolls back when it ends, so there is no cleanup code to write. A build with any error, a failing test included, never reaches the running app, and the same tests run again on the server you deploy to, before the new version goes live ([tests](/learn/man/tests)). ## A Runtime Is Not an App A web app needs more than a runtime: a database, migrations, server functions, sessions, background jobs, email and a way to deploy. Bun gives you some of the parts: a SQL client, cron callbacks, and an HTTP server you configure with your own TLS certificate and key ([Bun SQL](https://bun.com/docs/runtime/sql), [Bun cron](https://bun.com/docs/runtime/cron), [Bun TLS](https://bun.com/docs/runtime/http/tls)). Its production guide ends at a build command and a Dockerfile ([Bun fullstack deployment](https://bun.com/docs/bundler/fullstack)). Choosing the rest and connecting it is the work Bun leaves to you. Elements ships all of them as one coherent system: - **Postgres, bundled and set up for you**, with migrations applied as part of the build, and rolled back and reapplied when you edit them ([database](/learn/man/database), [migrations](/learn/man/migrations)). - **`@rpc` functions**: server functions the browser calls directly, with types checked end to end ([rpc](/learn/man/rpc)). - **Sessions**, stored in Postgres, that work the same over HTTP requests and rpc calls over WebSockets ([session](/learn/man/session)). - **Elements HTML**: standard HTML with a small extension for reactive expressions. Pages render on the server and come alive in the browser ([html](/learn/man/html)). - **LiveTable and channels** for realtime. A LiveTable is a set of database rows that stays in sync with every open browser, and a channel pushes messages from the server to the browser, both over Postgres `NOTIFY`/`LISTEN` ([livetable](/learn/man/livetable), [channel](/learn/man/channel)). - **Durable jobs** stored in your database and enqueued inside your transaction, with retries and timeouts, plus **cron** that runs each tick on exactly one machine ([jobs](/learn/man/jobs)). - **Email** written in the same HTML language as pages ([email](/learn/man/email)). - **Deploys over SSH** to any Ubuntu server. A deploy provisions the machine, sets up Postgres and a load balancer with Let's Encrypt certificates, runs migrations once on the first machine, and goes live with an atomic swap that keeps the previous release serving until the new one is ready. The project server on your machine talks directly to the one on the server, so only changed files transfer, and the server updates its build almost instantly. Deploys after the first often finish in under a second, and a failed one leaves the running app untouched ([deploy](/learn/man/deploy), [load balancer](/learn/man/deploy/load-balancer)). The Postgres on the server suits a prototype or a single-server app; for several servers, or for managed backups and scaling, point `DB_HOST` at a hosted Postgres such as Amazon RDS. ## Moving from Bun An agent can port a Bun project easily. Most of Bun is a runtime built to be compatible with Node.js, so the code converts to run on Node.js. You keep a fast package installer in Elements ([packages](/learn/man/packages)), and you gain a more stable runtime and a working, complete app framework ([start](/learn/man/start)). ## Questions ### What is the difference between Bun and Elements? Bun is a JavaScript runtime, the program that runs your server code, built to replace Node.js. It also includes a package manager, a test runner and a bundler, each run as its own command. Elements is not a runtime. Your app runs on Node.js, and Elements is the tooling around it: a project server that installs packages, type checks your code and runs your tests as you work, a default app framework, a bundled Postgres database, and a command that deploys the app to your own servers. ### Why use a build step when Bun runs TypeScript directly? Running TypeScript with no build step means no type check, no test gate and no check that every import resolves. The Elements build does all three, and a single-file change often builds in a few milliseconds, so the check costs nothing you notice. ### Does Elements run on Bun? No. Elements is its own program, and the apps it builds run on Node.js, the runtime npm packages are written and tested against. Elements is tooling, not a runtime, and it will consider targeting Bun alongside Node once Bun is stable. ### Is Bun a drop-in replacement for Node.js? Not yet, by its own record. As of October 1, 2026, Bun passes 80.5% of the Node.js v26.3.0 test suite, 3,708 of 4,608 tests, by Bun's own tracker, with the inspector tests at 20% and async_hooks at 50% ([Bun Node.js test suite tracker](https://bun.com/node-test-suite)). With Elements the question does not come up: your app runs on Node. ### Who makes Bun? Anthropic, which acquired Bun in December 2025 as the infrastructure Claude Code ships on. Before that it was Oven, a venture-backed startup, and Bun made $0 in revenue ([Bun joins Anthropic](https://bun.com/blog/bun-joins-anthropic)). Elements is sold directly, per machine, so its revenue comes from the tooling itself ([pricing](/pricing)). ### Does Bun type check TypeScript? No. Bun strips TypeScript types and leaves checking to tsc, which you run separately. Elements type checks server and browser code as part of the build, and a type error never reaches the running app. ### Can I move a Bun project to Elements? Yes, easily. Most of Bun is a runtime built to be compatible with Node.js, so an agent converts the code to run on Node.js. You keep a fast package installer in Elements, and you gain a more stable runtime and a working, complete app framework.