| title | WebviewJS: Cross-Platform Native Webview for Node.js |
|---|---|
| sidebarTitle | Introduction |
| description | WebviewJS is a Rust-powered native webview library for Node.js, Deno, and Bun. Build desktop apps with real OS windows, menus, tray icons, and IPC. |
WebviewJS (@webviewjs/webview) is a native binding to tao and wry — the same battle-tested Rust GUI primitives that power Tauri — exposed directly to Node.js, Deno, and Bun through a zero-overhead NAPI-RS bridge. You get real OS windows, a system webview, menus, tray icons, IPC, custom protocols, and desktop notifications without shipping a bundled browser or adopting a full application framework.
WebviewJS gives you direct access to OS-level GUI primitives from JavaScript:
- Desktop windows — create, resize, move, and style native OS windows with full decoration control
- Embedded webviews — render any URL or inline HTML inside a native window using the OS's own WebKit/WebView2 engine
- Promise-based readiness —
app.whenReady()resolves after the first nativeResumedevent, giving you a clean async entry point for window creation - Non-blocking event pump —
app.run()drives the GUI viasetIntervalso all of Node's async I/O, timers, and network continue to work normally alongside the GUI - Bidirectional IPC — send typed messages from the page to Node via
window.ipc.postMessage()and call back withwebview.evaluateScript() - Exposed function namespaces — publish async Node.js functions to the page with
webview.expose(), callable asawait window.myNS.myFn() - Custom protocols — serve local files or handle
app://requests with a Fetch-compatible handler, including full Hono routing without an HTTP server - Native menus — build cross-platform menu bars with keyboard accelerators, roles, and per-window menus
- System tray icons — create tray icons with menus, tooltips, and pointer event listeners
- Desktop notifications — show native OS notifications with a browser-familiar API; permission is always
"granted"for native apps - Shared browser contexts — isolate cookies, caches, and storage across webviews using
WebContextprofiles - DevTools access — open or close the browser DevTools panel programmatically
- CLI build tool — the
webviewCLI compiles your app to a self-contained single-file executable targeting Node.js, Deno, or Bun
| Runtime | Minimum version | Notes |
|---|---|---|
| Node.js | 24.0.0 | Prebuilt .node binaries via NAPI-RS; no compilation required |
| Deno | Latest stable | Import from npm using npm:@webviewjs/webview |
| Bun | Latest stable | Drop-in compatible; no extra configuration needed |
Prebuilt native binaries are published for every supported platform — npm install downloads the correct binary automatically. No Rust toolchain is required for normal use.
| Target triple | OS | Arch | Status |
|---|---|---|---|
x86_64-pc-windows-msvc |
Windows | x64 | ✅ Supported |
i686-pc-windows-msvc |
Windows | x86 | ✅ Supported |
aarch64-pc-windows-msvc |
Windows | arm64 | ✅ Supported |
x86_64-apple-darwin |
macOS | x64 | ✅ Supported |
aarch64-apple-darwin |
macOS | arm64 (Apple Silicon) | ✅ Supported |
x86_64-unknown-linux-gnu |
Linux | x64 | ✅ Supported |
aarch64-unknown-linux-gnu |
Linux | arm64 | ✅ Supported |
armv7-unknown-linux-gnueabihf |
Linux | armv7 | ✅ Supported |
i686-unknown-linux-gnu |
Linux | x86 | |
aarch64-linux-android |
Android | arm64 | |
armv7-linux-androideabi |
Android | armv7 | |
x86_64-unknown-freebsd |
FreeBSD | x64 |
WebviewJS is a thin NAPI-RS binding. When your JavaScript calls app.createBrowserWindow(), the call crosses the JS/Rust boundary synchronously and creates a native window handle owned by the Rust runtime. The webview is embedded inside that window using wry, which delegates to the OS's own rendering engine — WebView2 on Windows, WebKit on macOS, and WebKitGTK on Linux.
The event loop bridges Node and the native GUI through a non-blocking pump_events() call that drains the OS message queue without blocking Node's event loop. app.run() sets up a setInterval at ~16 ms (configurable) that calls pumpEvents() on each tick. Because this runs inside a normal Node timer, all of Node's async I/O continues working exactly as usual alongside the GUI.
IPC crosses the JS/Rust boundary in the opposite direction: the page calls window.ipc.postMessage(), which routes through the native webview's script-message bridge into your Node handler.