npx skills add ...
npx skills add dmythro/agent-skills --skill bun-cli
Bun CLI reference for package management, script running, type checking, testing, bundling, and compilation. Covers bun install/add/remove/update, bun run, bun check and --check, bun test, bun build, bunx, bun patch, bun audit fix, bun dedupe, bun prune, bun why, bun pm, bun repl, bunfig.toml, bun.lock, workspace catalogs, isolated installs and the global virtual store, offline installs, self-contained workspaces, zero-config frontend dev, parallel/sequential execution, test sharding and parallelism, code splitting, bytecode, profile-guided bytecode layout and cross-compiled executables, compile-to-browser, and replacing npm/npx/yarn/pnpm and tsc --noEmit with bun equivalents. Use for package management, lockfile issues, type checking, test runner config, bundler setup, or frontend dev server Not for Bun runtime APIs (Bun.file(), Bun.$(), Bun.sql()) -- use bun-api skill
npx skills add dmythro/agent-skills --skill bun-cli
Bun is an all-in-one JavaScript/TypeScript runtime, package manager, bundler, test runner, and (v1.4.3+) type checker. Bun runs TypeScript natively — bun file.ts directly, no compile step, no tsc, no ts-node — and bun check replaces tsc --noEmit. Always use bun instead of node, npm, npx, yarn, or pnpm in Bun projects.
Verified against Bun v1.4.3 (2026-10-10). Features are tagged with the version that
introduced them (v1.4+, v1.4.1+, v1.4.2+, v1.4.3+). Where a release changed existing
behavior, both behaviors are stated so this skill stays correct on older projects -- run
bun --version before relying on a version-tagged item.
bun <command> --help is authoritative for flags and always matches the installed binary --
prefer it over any table here when the two disagree.
For everything beyond flags, Bun ships its full docs inside bun-types, version-matched to
the runtime, in any project that has bun-types or @types/bun installed:
Depend on bun-types, not @types/bun. bun-types ships with every Bun release and
carries both the docs and the types. @types/bun lags every release, often by a day or
more, yet bun init adds it as latest -- so bun check rejects the newest APIs
(TS2339, TS2353). Adding bun-types beside @types/bun fixes only the docs path:
type checking keeps @types/bun's nested, pinned copy. Switch both:
Compare bun --version with node_modules/bun-types/package.json before trusting either.
Open by explicit path. With the global virtual store enabled, node_modules/bun-types
is a symlink, so find node_modules -name '*.mdx' and rg <pattern> node_modules return
nothing. find node_modules/bun-types/docs -name '*.mdx' works.
Never edit under node_modules/. Under the global store every project on the machine
shares the same inode. Use bun patch.
Not installed? The same path works online: docs/pm/lockfile.mdx is
https://bun.com/docs/pm/lockfile.
| Task | Doc path (under node_modules/bun-types/docs/) |
|---|---|
| Install behavior, lockfile, linkers | pm/cli/install.mdx, pm/lockfile.mdx, pm/isolated-installs.mdx, pm/global-store.mdx |
| Workspaces, catalogs, filters, overrides | pm/workspaces.mdx, pm/catalogs.mdx, pm/filter.mdx, pm/overrides.mdx |
| Registries, auth, lifecycle scripts | pm/scopes-registries.mdx, pm/npmrc.mdx, pm/lifecycle.mdx |
Type checking (bun check, --check) | runtime/check.mdx |
| Test runner | test/index.mdx, test/discovery.mdx, test/configuration.mdx, test/code-coverage.mdx, test/reporters.mdx, test/mocks.mdx |
| Bundler, executables, loaders | bundler/index.mdx, bundler/executables.mdx, bundler/loaders.mdx, bundler/plugins.mdx |
| Config | runtime/bunfig.mdx |
| Migrating from npm/yarn/pnpm | guides/install/from-npm-install-to-bun-install.mdx |
A project uses Bun if any of these are present:
bun.lock or bun.lockb in the project rootbunfig.toml in the project rootbun field in package.json (e.g., "bun": { "install": { ... } })"packageManager": "bun@..."[run] bun = true in bunfig.toml (forces Bun runtime for all scripts)In a Bun project, ALWAYS use bun for everything. Never fall back to node, npm, npx, yarn, or pnpm. This avoids compatibility issues, unnecessary retries, and cryptic errors from Node.js/npm not understanding Bun-specific features (workspace protocol, lockfile format, trustedDependencies, etc.).
bun file.ts (not node file.ts)bun run dev (not npm run dev)bunx tool (not npx tool)bun add pkg (not npm install pkg)bun test (not npx jest or node --test)bun check (not tsc --noEmit; v1.4.3+)| Command | Purpose |
|---|---|
bun --version | Runtime version |
bun info <pkg> | Package metadata, available versions |
bun info <pkg> versions | List all published versions |
bun pm ls | List installed packages (alias: bun list) |
bun pm ls --all | List all (including transitive) |
bun pm ls --trusted | List only packages allowed to run lifecycle scripts (v1.4+) |
bun pm hash | Print lockfile hash |
bun pm cache | Show cache directory |
bun pm bin | Print the bin directory (-g for global) |
bun pm licenses | List dependencies grouped by license (v1.4+; --json, --prod) |
bun pm diff <pkg> | Diff two versions of a package (v1.4+) |
bun pm untrusted | List packages whose lifecycle scripts were blocked |
bun why <pkg> | Explain why a package is installed |
bun outdated | Check for outdated dependencies |
bun audit | Security vulnerability audit |
bun dedupe --check | Exit 1 if the lockfile has removable duplicates (v1.4+) |
bun prune --dry-run | Show what would be removed from node_modules (v1.4+) |
bun test | Run test suite |
bun --check | Type check the whole project and exit; never writes files or runs a script (v1.4.3+). bun check does the same unless package.json defines a check script, which it runs instead |
bun run lint | Run linter (project-specific) |
bun run check-types | Type checking (project-specific) |
Reference: See
references/allowlist.mdfor copy-pasteBash(command:*)patterns for Claude Code / OpenCode settings.
| npm/npx/node | Bun equivalent |
|---|---|
npm install | bun install |
npm install pkg | bun add pkg |
npm install -D pkg | bun add -d pkg |
npm install -g pkg | bun add -g pkg |
npm uninstall pkg | bun remove pkg |
npm update | bun update |
npm run script | bun run script |
npx command | bunx command |
node file.js | bun file.js |
node --watch file.js | bun --watch file.js |
npm test | bun test |
tsc --noEmit | bun check (v1.4.3+) |
npm pack | bun pm pack |
npm publish | bun publish |
npm info pkg | bun info pkg |
npm outdated | bun outdated |
npm audit | bun audit |
npm link | bun link |
npm run prefix needed: bun run dev works, but so does bun dev (direct script execution)--bun flag: Forces Bun runtime instead of Node.js for scripts that use node in their shebang. In bunfig.toml, set [run] bun = true to make this the defaultbun.lock (text-based, v1.2+) or bun.lockb (binary, legacy). Text lockfile is default for new projects--filter flag: bun --filter 'pkg-name' add deptrustedDependencies in package.json to allowlist packages that need postinstall etc.--offline suits CI jobs that restore ~/.bun/install/cache and air-gapped machines -- a
missing package fails by name (no cached manifest for 'left-pad'). --prefer-offline skips
the staleness check but still fetches uncached packages. Both have bunfig.toml forms:
[install] offline = true and [install] prefer = "offline".
Changed in 1.4.3 -- installs cache only what they place. Without a lockfile, bun install
and bun add no longer download tarballs that never land in node_modules: groups dropped by
--production or --omit, other platforms' optional packages, bundled dependencies. A cache
warmed by bun install --production then fails a full --offline install with
"left-pad" is not in the cache. Warm an offline cache with an install that places at least
the same packages, on the same platform.
Changed in 1.4: bun add, bun remove, and bun update accept --filter, and
bun install <pkg> --filter x now edits workspace x rather than the root. In a workspace
whose default catalog already lists the package, a plain bun add <pkg> writes catalog:.
--filter 'web...' selects web and everything it depends on; '...web' selects everything
that depends on web.
Changed in 1.4: bun update re-resolves transitive dependencies, not just the ones named
in package.json, and bun update <name> errors with exit 1 when nothing depends on that
name (it used to add the package).
bun pm diff leads with a summary -- files changed, new install scripts, and new imports of
child_process, fs, net, or vm -- then the diff, with minified files expanded and
formatting-only changes skipped. Useful for reviewing a dependency bump.
bun prune --production fits the Docker build stage:
Reference: See
references/package-management.mdfor complete flag details.
Two settings decide how node_modules is laid out. Both matter more than they look.
hoisted | isolated | |
|---|---|---|
| Layout | npm-style flat node_modules | symlinks into node_modules/.bun/ (pnpm-style) |
| Phantom dependencies | resolve silently | fail, as they should |
| Default when | configVersion = 0 (any project), or configVersion = 1 without workspaces | configVersion = 1 and the project uses workspaces |
The default depends on the lockfile's configVersion, not on the Bun version: isolated only
when configVersion = 1 and the project uses workspaces. Lockfiles created before v1.3.2
are treated as configVersion = 0 and keep hoisted, so upgrading Bun does not change an
existing project's layout. Override with --linker or [install] linker.
Package files live once in ~/.bun/install/cache/links/ and each project symlinks into them:
warm installs are roughly 7x faster, and node_modules drops from hundreds of MB to a few MB
of links. It is off by default and needs both settings -- with linker = "hoisted" the
globalStore flag is silently ignored.
Consequences worth knowing before enabling it:
node_modules is mostly symlinks. find node_modules -name '*.mdx' and
rg <pattern> node_modules return nothing, and du -sh node_modules reports ~0B
because it measures the symlinks rather than their targets (the real tree is a few MB of
links; du -sh -L shows the target sizes, counted once per link). Address
files by explicit path, or pass find -L (which double-counts through the link layers).
Tools that scan node_modules without following symlinks behave differently -- the same
caveat as any pnpm-style layout.node_modules/ changes it
for every project on the machine. Use bun patch.node_modules/.bun/node_modules/ layer is no longer on the resolution path from
inside a package. Fix by declaring the dependency, or set globalStore = false.trustedDependencies, and anything
depending on a workspace:, file:, or link: dependency. Ineligibility propagates up
the graph, so a monorepo keeps a good share of the tree local regardless.Reference:
node_modules/bun-types/docs/pm/global-store.mdxandpm/isolated-installs.mdx.
Electron packagers and serverless bundlers repackage one workspace's node_modules and expect
every dependency physically under it. List such workspaces in the root package.json, or set
Yarn's "installConfig": { "hoistingLimits": "workspaces" } in the workspace's own file:
Nothing that workspace depends on, directly or transitively, is hoisted above
apps/desktop/node_modules, and those packages are real copies rather than cache hardlinks.
The lockfile does not record it, and it has no effect under the isolated linker.
Replaces npm-run-all and concurrently. Output is prefixed per script (or package:script
under --filter), and pre*/post* hooks stay grouped with their main script.
The flag may also precede run (bun --parallel run build lint); both forms work.
Run HTML files directly as a dev server -- no Vite, Webpack, or any config needed:
Bun automatically transpiles TypeScript, JSX, TSX, and CSS linked from the HTML. Resolves node_modules imports in <script> tags. Enables HMR and React Fast Refresh.
Reference: See
references/running-and-execution.mdfor complete details.
bun check is a built-in port of TypeScript 7: it reads tsconfig.json, reports what tsc
reports, uses every CPU core, needs no typescript package, and never writes files. Exit 1 on
any error, 0 when clean, and 1 when there is nothing to check.
types at bun-types. With no Bun types, or a stale @types/bun, Bun,
bun:test, and every API newer than the installed types fail to check. See "Check the
Version" above.check script wins. When package.json defines check, bun check runs that
script -- from the shell and from other scripts alike, so bun init's
"typecheck": "bun check" then runs the lint script. Only inside the check script and the
scripts it starts is bun check the type checker. bun --check always type checks: use it in
scripts. On an older Bun, "check": "bun check" calls itself forever.CLAUDECODE=1, AGENT=1, or REPL_ID=1 set, errors print as
<error file= line= column= code=> blocks with the source lines. --no-pretty gives one
file(line,col): error TSxxxx: ... line per error, the default when piped; the summary goes to
stderr.typescript is installed: strict is on, types is
empty unless listed, and a removed option (baseUrl, moduleResolution: "node",
target: "es5") stops the check before any file is checked.tsc for .d.ts output -- bun check emits nothing.Reference: See
references/type-checking.mdfor what--checkcovers, output formats, monorepos, CI, and the TypeScript 5-to-7 migration table.
Bun includes a built-in test runner compatible with Jest-like syntax.
--filter is not a test-name filter. In bun test, --filter is another file path
filter, identical to a positional argument; -F is rejected. (Workspace selection is a
package-manager feature: bun run --filter '*' test.) Passing a test name usually matches
no files, and bun test then exits 1 ("did not match any test files") -- so the mistake
fails the run rather than passing it silently, but it still tests nothing. Use -t /
--test-name-pattern / --grep for test names. --pass-with-no-tests turns that exit 1
into 0, which is what would hide it.
--coverage-reporter implies --coverage as of v1.4.3. Before, --coverage-reporter=lcov
alone ran the tests and wrote no report.
--parallel implies --isolate; --no-isolate opts out so each worker reuses one
global and module registry.--isolate fixes "passes alone, fails in the suite" by resetting globalThis and the module
registries between files, and closing servers, sockets, watchers, and subprocesses the file
left open. Transpiled source and bytecode stay cached across globals, so the cost is low.JEST_WORKER_ID / BUN_TEST_WORKER_ID, so Jest
setups that key a database or port off JEST_WORKER_ID work unchanged.--bail stops every worker.--timings (v1.4+) makes shards equal in time rather than file count, and the file is
written slowest-first so it doubles as a slow-test report.mock(), spyOn(), mock.module(), or Bun.plugin() from keeping
the previous module graph alive under --isolate, so memory stays flat across a large suite.By default, Bun finds files matching: *.test.{ts,tsx,js,jsx}, *_test.{ts,tsx,js,jsx}, *.spec.{ts,tsx,js,jsx}, *_spec.{ts,tsx,js,jsx}, and files in __tests__/ directories. Exclude paths with --path-ignore-patterns <glob> or test.pathIgnorePatterns in bunfig.toml.
Reference: See
references/testing.mdfor test API, mocking, lifecycle hooks, and coverage config.
Changed in 1.4.1 -- splitting output. Code shared between an entry and its lazy routes stays
in the entry's chunk (fewer, smaller files), browser builds get <link rel="modulepreload">
for every chunk an entry or import() loads (--no-module-preload to disable), --target bun
turns require() of an ES module into its own chunk (--no-split-require to inline), and
export * as / const { x } = await import() tree-shake unused exports -- zod and effect
bundles shrink 50-80%. Snapshot tests of build output will change.
Changed in 1.4.3 -- chunk names. Large --splitting builds no longer fail with
Multiple files share the same output path: two different chunks whose 8-character [hash]
collided now both get extra characters until they differ. [hash9] through [hash13] set a
wider minimum ([hash13] is the full 64-bit hash; 1.4.2 printed the token literally and failed).
Shared code no longer folds into an entry chunk whose name has no [hash], so an entry loaded
with a query string (index.js?v=1) cannot run twice -- a 1.4.1 regression. Each split
import() in metafile.inputs names the file it loads in entryPoint.
Available compilation targets: bun-linux-x64, bun-linux-arm64, bun-darwin-x64, bun-darwin-arm64, bun-windows-x64.
Browser target (v1.3.10+) -- compile to a self-contained HTML file:
--asset (v1.4+) embeds a file or directory into a --compile executable keeping original
filenames, so path.join(import.meta.dir, ...) resolves the same as on disk. node:fs treats
/$bunfs/ as a real tree, so readdirSync (including recursive/withFileTypes) works
inside the binary -- static file servers that enumerate a directory at startup run unmodified.
Changed in v1.3.4: --compile binaries no longer auto-load tsconfig.json or
package.json from the runtime working directory (opt back in with
--compile-autoload-tsconfig / --compile-autoload-package-json); .env and bunfig.toml
still auto-load.
--bytecode gained ES module support back in v1.3.9 (--format=esm, requires
--compile), enabling top-level await,
import.meta, dynamic imports, and code splitting -- it previously forced CommonJS.
As of v1.4.1 bytecode is about 3x the source size instead of 9x, --bytecode-depth=N
limits ahead-of-time compilation to N nesting levels (deeper functions compile on first call),
and cross-compiling with --bytecode works for every target, bun-windows-x64 from macOS or
Linux included, with byte-identical output.
Profile-guided bytecode layout (v1.4.3+). --bytecode-order puts the bytecode a recorded
run used at the front of a --compile --bytecode executable, so it reads and decodes less of
itself (one large CLI: cold start 1.01 s to 0.53 s). It requires --compile --bytecode.
Functions are matched by a hash of their syntax, so last week's profile still applies --
commit or cache it. --bytecode-order=a.order,b.order takes several, most common first, and
bytecodeOrderStats() from bun:jsc reports how much of a run the profile covered.
--compile-jit-policy <n> starts the executable with JIT tier-up thresholds scaled by n, so
run-once startup code stays in the interpreter; call Bun.unsafe.setJITPolicy(1) once the app
is interactive. --no-optimize-bytecode skips the build-time bytecode optimizer.
Reference: See
references/bundling-and-compilation.mdfor complete options.
bun init also writes a CLAUDE.md of Bun's own agent rules into the project root
(copied from node_modules/bun-types/CLAUDE.md) — "use bun not node/npm", the API
substitution list, and the HTML-imports frontend pattern. Recommend running it in new Bun
projects: it gives every agent working in the repo the same baseline, and points at the
shipped docs.
Changed in 1.4.3: bun init adds a "typecheck": "bun check" script (make it
bun --check if the project has a check script), and the copied rules say bun check instead
of tsc --noEmit. It still adds "@types/bun": "latest" with
"types": ["bun"], which resolves to the previous release for a day or more after each Bun
release -- swap it for bun-types (see "Check the Version").
Changed in 1.4: bun init writes typescript: ^7 (it wrote ^5, or nothing in the React
templates), and with a non-TTY stdin (CI, a piped spawn) it behaves as bun init -y instead
of opening the template picker. bun update -i with a non-TTY stdin now exits 1 -- use
bun update or bun outdated.
Key sections:
Reference: See
references/configuration.mdfor complete bunfig.toml reference.
Bun auto-loads .env, .env.production, .env.local, .env.production.local by default based on NODE_ENV.
Bun has many capabilities built in that eliminate the need for external packages or tooling:
Bun runs .ts, .tsx files directly — no tsc, ts-node, or tsx needed. The transpiler is built into the runtime. Use bun file.ts to run any TypeScript file immediately.
bun check (v1.4.3+) replaces tsc --noEmit, and --check gates bun <file>, bun test, and bun build on a clean type check — no typescript package needed. Keep tsc only to emit .d.ts files.
Bun supports catalog: protocol in package.json for centralized dependency version management across monorepo workspaces — no need for tools like syncpack or manypkg:
bun test is a full Jest-compatible test runner with snapshot testing, mocking, coverage — no need for jest, vitest, or mocha.
bun build replaces esbuild, webpack, rollup for many use cases. Supports code splitting, tree shaking, minification, and standalone executable compilation.
import { Database } from 'bun:sqlite' — zero-dependency SQLite3 with prepared statements and transactions. No need for better-sqlite3 or sql.js.
Bun.$ tagged template — cross-platform shell execution with automatic escaping. Replaces execa, shelljs, zx.
Bun.file() and Bun.write() — fast file operations without importing fs. Auto-detects MIME types.
new Bun.Glob(pattern) — fast glob matching and file scanning. Replaces glob, fast-glob, minimatch.
Bun.password.hash() and .verify() with bcrypt and argon2id. Replaces bcrypt, argon2 packages.
Bun.gzipSync(), Bun.deflateSync(), Bun.zstdCompressSync() — no need for zlib wrapper packages.
Bun.semver.satisfies(), .order() — replaces semver package.
For Bun's built-in runtime helpers (Bun.s3, Bun.redis, Bun.Archive, JSONC, JSON5, JSONL, markdown, cron), see the bun-api skill.
bun ./index.html — serve HTML with auto-bundling of JS/TS/CSS, HMR, and React Fast Refresh. Replaces Vite/Webpack dev server for simple projects.
TC39 standard ES decorators supported natively (v1.3.10+) — no experimentalDecorators tsconfig needed.
bun not npm/node/npx in Bun projectsbun.lock (text, v1.2+) is the default for new projects. Legacy bun.lockb is binary. Don't mix with package-lock.json. v1.4+ writes lockfileVersion: 2 (and 3 when nested or version-scoped overrides are used). Bun 1.3 cannot read version 3 -- check before committing one to a repo whose CI pins an older BuntrustedDependencies in package.json. v1.3.5+ the default trusted list applies only to packages from the npm registry, so a file:/link:/git:/github: dependency named esbuild gets no trust from the real esbuild's entry -- list it yourself. v1.4+ entries match the exact package name rather than a truncated hash--bun flag: Some tools (e.g., Next.js) use Node.js by default even when run with bun run. Use --bun or [run] bun = true in bunfig.toml to force Bun runtime"workspace:*" in package.json to reference workspace packagesbun add -g, located in ~/.bun/bin/bun check or --check (v1.4.3+)[install] auto = true in bunfig.toml)bun run vs bun: bun run script runs a package.json script; bun file.ts runs a file directly. bun script tries script first, then falls back to file--filter is not a name filter for tests. In bun test it filters file paths, same as a positional argument (-F is rejected; workspace selection is bun run --filter '*' test). bun test -t <regex> (or --grep) filters test names. Getting this wrong matches nothing and exits 1 -- unless --pass-with-no-tests is set, which turns it into a green run that tested nothingtext and lcov only -- --coverage-reporter=json is rejected. As of v1.4.3 the flag implies --coveragebunfig.toml is strict TOML as of v1.4. Unquoted values, missing newlines between pairs, and integers past Number.MAX_SAFE_INTEGER now fail at startup with a SyntaxError. linker = isolated must be linker = "isolated"node no longer loads .env (v1.4+). Under bun --bun, bunx --bun, or a node symlink, a script calling node sees those variables as undefined. Pass --env-file, matching Node's behaviorconfigVersion, not the Bun version. See "Install Layout" above-march=haswell build is gone; the -baseline download URLs and npm packages still exist and contain the same binary, and the CPU lacks AVX support warning is removedbun feedback was removed in 1.4import * as ns / export * as ns from bun build currently accept ns.x = 1 silently; --no-deprecated-namespace-object-setters (or deprecatedNamespaceObjectSetters: false) opts into the future default now (v1.4.1+)bun run --filter, --workspaces, --parallel, and --sequential ignored an auto-discovered bunfig.toml before 1.4.1 unless --config was passed; bun run also reused a stale transpile cache for files of 4 KiB or more after [define] or --drop changedbun build could rename a nested var onto a let in the same block, so bundles importing Elysia failed to load with SyntaxError: Cannot declare a var variable that shadows a let/const/class variable, and a let could take a parameter's or catch binding's name and compute wrong values. Fixed in 1.4.2 -- upgrade rather than patch the output, and do not pin 1.4.1 for bundlingcheck script shadows bun check (v1.4.3+). When package.json defines check, bun check runs it -- from other scripts too, except those the check script itself starts -- so bun init's "typecheck": "bun check" then runs the lint script. bun --check type checks regardless: use it in scripts. On an older Bun, "check": "bun check" calls itself forever@types/bun lags every Bun release, often by a day or more, so bun check rejects the newest APIs (TS2339, TS2353). Depend on bun-types@<runtime-version> with "types": ["bun-types"] instead; installing bun-types beside @types/bun does not change what gets type checked--production or --omit install without a lockfile downloads no tarballs for the dropped groups, so it cannot warm a cache for a later full --offline install. Warm it with an install that places the same packages, on the same platform--disallow-code-generation-from-strings is enforced as of 1.4.3 -- Bun accepted and ignored it before. eval() and new Function() now throw EvalError process-wide, Workers included; =strict also blocks node:vm, data:/blob: imports, and plugins that return source text, and refuses --inspect
references/migration-1.4.md-- what changed from 1.3 through 1.4.3 for the CLI, package manager, bundler, and test runnerreferences/package-management.md-- install/add/update/audit/pm flags, workspaces, linkersreferences/running-and-execution.md-- runtime flags, bunx, profiling, watch/hot, code-generation lockdownreferences/type-checking.md--bun check,--check, output formats, TypeScript 7 defaults, monorepos, CIreferences/testing.md-- test runner API, mocking, coverage, CI shapingreferences/bundling-and-compilation.md--bun build, targets, standalone executablesreferences/configuration.md-- fullbunfig.tomlreferencereferences/allowlist.md-- copy-pasteBash(command:*)permission patterns