Bun Ships a Zero-Dependency WebView That Screenshots in 3 Lines
Just 3 lines:
await using view = new Bun.WebView();
await view.navigate("https://example.com");
await Bun.write("out.png", await view.screenshot());
bun run demo.ts
Done.
Pain Point: Installing Puppeteer Is a Hassle
Want to screenshot a webpage.
npm i puppeteer.
300MB Chromium download.
On Linux, you also need to install a bunch of system packages like libatk and libnss3.
Half an hour of tinkering, and you still haven't got the screenshot.
Bun.WebView is here to fix that.
Built into Bun v1.3.12, nothing to install.
Where the Simplicity Comes From
macOS: Uses the system WKWebView, zero configuration.
Linux/Windows: Automatically finds local Chrome / Edge / Chromium.
Events are native.
view.click(), view.type() dispatch OS-level events.
isTrusted is always true.
Websites can't tell you're a bot.
Install Bun
Requires v1.3.12 or higher:
curl -fsSL https://bun.sh/install | bash
bun --version
After that, bun run demo.ts just works.
3 More Lines: Get Page Data
const view = new Bun.WebView();
await view.navigate("https://example.com");
const title = await view.evaluate("document.title");
console.log(title);
evaluate runs JS inside the page.
Directly returns JSON-serializable values.
Promises are automatically awaited.
// Run a fetch
const data = await view.evaluate(`
fetch("/api/list").then(r => r.json())
`);
Simulating Real User Actions
await view.click("input[name='q']");
await view.type("Bun WebView is awesome");
await view.press("Enter");
click automatically waits.
Waits for the element to mount, become visible, stable, and unobscured.
type sends real keyboard events.
It doesn't just change input.value.
Vue and React listeners still fire.
// Double-click + Shift
await view.click("a.link", {
button: "left",
count: 2,
modifiers: ["Shift"],
timeout: 5000,
});
Scrolling and resizing are also simple:
await view.scroll(0, 600); // Scroll down 600 pixels
await view.resize(1920, 1080); // Change window size
A Few Ways to Screenshot
Full-page screenshot:
await Bun.write("full.png",
await view.screenshot({ format: "png", fullPage: true })
);
Specific element:
const header = await view.screenshot({
format: "jpeg",
quality: 85,
selector: "header.banner",
});
await Bun.write("header.jpg", header);
Supports png, jpeg, webp.
WebP is usually 30%+ smaller than PNG.
Returns a Buffer.
Plug it in anywhere.
Manually Specifying the Backend
If you don't want to rely on auto-detection:
const view = new Bun.WebView({
backend: {
type: "chrome",
path: "/usr/bin/google-chrome",
},
});
Connecting to an existing Chrome instance is even easier for debugging:
chrome --remote-debugging-port=9222
const view = new Bun.WebView({
backend: {
type: "chrome",
url: "ws://127.0.0.1:9222/devtools/browser/xxx",
},
});
Launch Chrome once and leave it running in the background.
No need to launch a new one every time.
Real-World Use: Scraping a Batch of Article Titles
// crawl.ts
const urls = [
"https://bun.sh/blog",
"https://bun.sh/docs",
"https://bun.sh/docs/cli/run",
];
await using view = new Bun.WebView();
for (const url of urls) {
await view.navigate(url);
const title = await view.evaluate(
`document.querySelector("h1")?.textContent || document.title`
);
console.log(`${url} -> ${title}`);
}
Each view shares a single browser subprocess.
Opening multiple views is like opening multiple tabs.
Much faster to start than Puppeteer.
Want to Persist Login State?
One line with dataStore is enough:
const view = new Bun.WebView({
dataStore: { directory: "./browser-profile" },
});
Cookies and localStorage are saved to disk.
Log in once, and it's ready for next time.
Capturing Page Logs
Want to see frontend errors?
const view = new Bun.WebView({
console: (type, ...args) => {
console.log(`[Page ${type}]`, ...args);
},
});
console.log, console.error — all captured.
Even more convenient than opening DevTools.
Raw CDP Commands Are Also Supported
// Send a command
const result = await view.cdp("Page.getLayoutMetrics");
console.log(result);
// Subscribe to events
const off = view.cdpSubscribe("Network.requestWillBeSent", (params) => {
console.log("Request URL:", params.request.url);
});
await view.navigate("https://example.com");
off(); // Unsubscribe
Network interception, performance monitoring, cookie manipulation — anything CDP can do, you can do here.
Caveats
Bun.WebView is still experimental.
The API might change in the future.
Make sure Bun ≥ 1.3.12 before using it.
On Linux, you need to install Chrome:
sudo apt install google-chrome-stable
macOS has WebKit built-in, zero configuration.
Another gotcha:
Each run leaves behind a temporary directory.
On Linux, it's at /tmp/bun-chrome-xxx.
Use await using to auto-clean.
Otherwise, your disk might fill up.
Summary
Bun.WebView does one thing:
Makes screenshots and browser automation simple.
• 3 lines of code for a screenshot
• Zero configuration on macOS
• Native events, unblockable by anti-bot measures
• An API similar to Playwright
It's not that you can't install Puppeteer or Playwright.
But if you can install less and configure less, why wouldn't you?
Using Bun.WebView is all about saving yourself the hassle.
Found this useful? Hit like and share.
Tell me in the comments: what have you built with Bun.WebView?
Next up: The Bun.S3 family bucket.
See you next time. 👋