Inspect and extract embedded modules from Bun compiled binaries (bun build --compile).
When you compile a JavaScript/TypeScript project with bun build --compile, Bun bundles your source code, bytecode, WASM files, native addons, and other assets into a single standalone executable. unbun lets you look inside these binaries, list all embedded modules, preview their contents, and extract them to disk.
cc2js is a CLI tool based on unbun, it can convert any Bun-compiled Claude Code into a pure-Node build that runs on plain Node v18 or later.
# Global install (recommended for CLI usage)
npm i -g unbunjs # or: bun i -g unbunjs
# Local install (for library usage)
npm i unbunjs # or: bun i unbunjsOr run directly with npx:
npx unbunjs ./my-compiled-app # or: bunx unbunjs ./my-compiled-appunbun list ./myappBun Compiled Binary: ./myapp
Payload offset: 4932608 (4.70 MB)
Payload size: 1871424 (1.78 MB)
Flags: disable_default_env_files
Modules: 4
# Entry Loader Format Enc Side Source Bytecode SrcMap Name
----------------------------------------------------------------------------------------------------------------------------------
0 >>> js esm latin1 server 456.30 KB 3.21 MB - /$bunfs/root/src/app.js
1 json none latin1 server 1.80 KB - - /$bunfs/root/config.json
2 wasm none binary client 851.20 KB - - /$bunfs/root/math.wasm
3 napi none binary client 624.00 KB - - /$bunfs/root/crypto.node
The >>> marker indicates the entry point module.
JSON output:
unbun list ./myapp --json{
"file": "./myapp",
"payload_start": 4932608,
"payload_size": 1871424,
"flags": {
"disable_default_env_files": true,
"disable_autoload_bunfig": false,
"disable_autoload_tsconfig": false,
"disable_autoload_package_json": false
},
"modules": [
{
"index": 0,
"name": "/$bunfs/root/src/app.js",
"contents_length": 467251,
"bytecode_length": 3366912,
"loader": "js",
"module_format": "esm",
"encoding": "latin1",
"side": "server",
"is_entry_point": true
}
]
}You can also just pass the binary path directly (no subcommand):
unbun ./myappExtract all modules to a directory:
unbun extract ./myapp ./output 456.30 KB output/src/app.js
3.21 MB output/src/app.js.bytecode
1.80 KB output/config.json
851.20 KB output/math.wasm
624.00 KB output/crypto.node
Extract specific modules using a filter:
unbun extract ./myapp ./output -m app.js
unbun extract ./myapp ./output -m 0 # by index
unbun extract ./myapp ./output -m .wasm # all WASM filesThe filter matches against both the module index and module name (substring match).
If no output directory is specified, it defaults to ./extracted.
Preview the first N lines of a module's source code:
unbun preview ./myapp app.js # first 50 lines (default)
unbun preview ./myapp app.js 100 # first 100 lines
unbun preview ./myapp 0 # by module indexModule: /$bunfs/root/src/app.js (456.30 KB, 4820 lines)
────────────────────────────────────────────────────────────────────────────────
// @bun
import{serve}from"bun";import{readFileSync}from"node:fs";
var router=new Map;router.set("/",()=>new Response("OK"));...
Inspect raw bytes of a module:
unbun hexdump ./myapp math.wasm # first 512 bytes (default)
unbun hexdump ./myapp math.wasm 256 # first 256 bytesModule: /$bunfs/root/math.wasm
Size: 851.20 KB
First 512 bytes:
004b4a00 00 61 73 6d 01 00 00 00 01 0c 03 60 01 7f 01 7f .asm.......`....
004b4a10 60 02 7f 7f 01 7f 60 00 00 03 04 03 00 01 02 05 `.....`.........
| Command | Alias | Description |
|---|---|---|
unbun list <binary> |
ls |
List all embedded modules |
unbun list <binary> --json |
List as JSON | |
unbun extract <binary> [dir] |
x |
Extract all modules |
unbun extract <binary> [dir] -m <filter> |
Extract matching modules | |
unbun preview <binary> <filter> [lines] |
cat |
Preview source text |
unbun hexdump <binary> <filter> [bytes] |
hex |
Hex dump of contents |
unbun <binary> |
Shorthand for list |
|
unbun --help |
-h |
Show help |
unbun --version |
-v |
Show version |
Use unbun programmatically in your own tools:
import {
parse,
parseBuffer,
isBunBinary,
getModuleSource,
getModuleContents,
getModuleBytecode,
getModuleSourcemap,
findModule,
findModules,
} from "unbunjs";
// Quick check
if (isBunBinary("./myapp")) {
console.log("This is a Bun compiled binary!");
}
// Parse the binary
const binary = parse("./myapp");
console.log(`Modules: ${binary.modules.length}`);
console.log(`Payload size: ${binary.offsets.byte_count} bytes`);
// List all modules
for (const mod of binary.modules) {
console.log(`${mod.name} (${mod.loader}, ${mod.contents_length} bytes)`);
}
// Find a specific module
const entry = findModule(binary, "app.js");
if (entry) {
// Get source as string
const source = getModuleSource(binary, entry);
console.log(source.slice(0, 200));
// Get raw contents as Buffer
const buf = getModuleContents(binary, entry);
// Get bytecode (empty Buffer if none)
const bytecode = getModuleBytecode(binary, entry);
// Get sourcemap (empty Buffer if none)
const sourcemap = getModuleSourcemap(binary, entry);
}
// Find all matching modules
const wasmModules = findModules(binary, ".wasm");
// Parse from a Buffer instead of file path
import { readFileSync } from "fs";
const data = readFileSync("./myapp");
const binary2 = parseBuffer(data);Parse a Bun compiled binary from a file path. Reads the entire file into memory.
Parse a Bun compiled binary from a Buffer. Useful when you already have the data in memory.
Quick check whether a file is a Bun compiled binary, scanning the tail of the file for the trailer instead of loading the whole thing. The default window (32 MiB) covers Linux, Windows, and macOS binaries. On macOS the trailer can sit over a megabyte from the end, behind the code signature. Pass a larger searchBytes for unusually large signed macOS binaries.
Get the source contents of a module as a string, decoded with the encoding Bun tagged it with (latin1, or utf16le for text holding non-Latin-1 characters) — the same text Bun serves when the program reads the module back. Reach for getModuleContents() on binary modules.
Get the raw source contents of a module as a Buffer.
Get the JSC bytecode of a module. Returns an empty Buffer if no bytecode is embedded.
Get the serialized sourcemap of a module. Returns an empty Buffer if no sourcemap is embedded.
Find the first module whose name contains filter, or whose index matches filter.
Find all modules matching the filter.
interface ParsedBunBinary {
offsets: Offsets;
modules: BunModule[];
payload: Buffer;
payload_start: number;
compile_exec_argv: string;
flags: CompileFlags;
}
interface BunModule {
index: number;
name: string; // e.g. "/$bunfs/root/src/index.js"
contents_offset: number;
contents_length: number;
sourcemap_length: number;
bytecode_length: number;
module_info_length: number;
bytecode_origin_path: string;
encoding: string; // "binary" | "latin1" | "utf16le"
loader: string; // "js" | "ts" | "jsx" | "tsx" | "css" | "json" | "wasm" | "napi" | ...
module_format: string; // "none" | "esm" | "cjs"
side: string; // "server" | "client"
is_entry_point: boolean;
}
interface CompileFlags {
disable_default_env_files: boolean;
disable_autoload_bunfig: boolean;
disable_autoload_tsconfig: boolean;
disable_autoload_package_json: boolean;
}Bun compiled binaries use a well-defined format from Bun's open-source StandaloneModuleGraph.zig. The embedded blob is identical across platforms:
┌─────────────────────────┐
│ Platform Binary │ ← ELF / Mach-O / PE (Bun runtime)
├─────────────────────────┤ ← payload_start
│ Payload Data │ ← module names, source, bytecode, WASM,
│ (byte_count bytes) │ native addons, sourcemaps + the module table
├─────────────────────────┤
│ Offsets │ ← { byte_count, modules_ptr, ... }
├─────────────────────────┤
│ Trailer (16 bytes) │ ← "\n---- Bun! ----\n"
└─────────────────────────┘
It's appended to the file on Linux (ELF) and embedded in a section on macOS (Mach-O) / Windows (PE); unbun finds it via the last trailer. Each module entry holds StringPointers (u32 offset + length) into the payload for its name, contents, sourcemap, and bytecode.
Several layouts have shipped as Bun evolved. unbun auto-detects all of them by structure (not version), validating every module's pointers before accepting one:
| Layout | Offsets | Module entry | Roughly | Notable fields |
|---|---|---|---|---|
| Minimal | 24 B | 32 B | Bun ~1.0 | name / contents / sourcemap + loader byte |
| Compact | 24 B | 36 B | Bun ~1.1 | + bytecode, encoding/loader/module_format |
| Midsize | 32 B | 36 B | Bun ~1.2 | + Offsets compile_exec_argv_ptr / flags |
| Extended | 32 B | 52 B | Bun ~1.3+ | + module_info / bytecode_origin_path / side |
unbun supports Bun compiled binaries from all platforms and all Bun versions:
- Linux (x64, arm64, x64-musl, arm64-musl): ELF
- macOS (x64, arm64): Mach-O
- Windows (x64, arm64): PE
- Bun versions: 1.0 through the latest canary: every module graph layout (see How It Works) is auto-detected
bun install # install dependencies
bun run checkall # type-check + lint + format-check + tests (no writes)
bun run fixall # auto-fix lint + format
bun test # run the test suite
bun run build # compile TypeScript to dist/Linting and formatting are handled by Biome. The test suite runs on bun test and builds its own synthetic fixtures, so it needs no external binaries. It also parses a set of tiny committed fixtures in test/fixtures/real/ (truncated tails of real bun build --compile output from Bun 1.0 to 1.3, a few hundred bytes each) as ground truth for every layout. Regenerate them with scripts/e2e/make-fixtures.mjs.
bun run check:claude-code downloads the latest Claude Code binaries for every platform and checks that unbun parses and extracts each one. Everything is cached under .cache/ (gitignored, ~1.8 GB). Manual only and not part of bun test, checkall, or CI.
Releases are automated with vbt and GitHub Actions. Bumping the version rewrites package.json and the CLI's version string, then commits, tags, and pushes:
bun run release:patch # 1.0.0 -> 1.0.1
bun run release:minor # 1.0.0 -> 1.1.0
bun run release:major # 1.0.0 -> 2.0.0Pushing the resulting v* tag triggers the Publish workflow, which re-runs the checks and publishes to npm with provenance.
- cc2js: Convert any Bun-compiled Claude Code into a pure-Node build that runs on plain Node v18 or later
- Bun Standalone Executables docs: Official Bun documentation
MIT