Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/webviewjs/webview/llms.txt

Use this file to discover all available pages before exploring further.

WebviewJS follows a straightforward three-object model: an Application owns the native event loop and all top-level resources; a BrowserWindow represents one OS window; and a Webview is the embedded browser engine rendered inside that window. Every meaningful desktop app you build with WebviewJS will start with these three primitives, wire up a handful of event listeners, and then call app.run() or app.whenReady() to hand control to the native GUI layer.

Creating Your First Window

1

Import Application and create an instance

Application is the root object. Create exactly one instance per process — it registers itself with the OS event system on construction.
import { Application } from '@webviewjs/webview';

const app = new Application();
2

Create a BrowserWindow

app.createBrowserWindow() allocates a native OS window. Pass options to set the initial title, dimensions, and other decorations.
const win = app.createBrowserWindow({
  title: 'My App',
  width: 1024,
  height: 768,
});
Keep a strong reference to the returned BrowserWindow handle (e.g. assign it to a module-level variable). If the reference is garbage-collected while the window is still open, method calls on the handle will throw a disposed error.
3

Create a Webview inside the window

win.createWebview() embeds a browser engine into the window. You can point it at a remote URL or supply inline HTML directly.
const webview = win.createWebview({
  url: 'https://example.com',
});
4

Start the event loop

Call app.run() to begin pumping native OS events. This is non-blocking — Node’s own timer, I/O, and Promise machinery keeps running alongside the GUI.
app.run();
Alternatively, use app.whenReady() which also starts the pump and resolves once the native application is fully initialised:
app.whenReady().then(() => {
  console.log('Native application is ready');
});

Minimal Complete Example

import { Application } from '@webviewjs/webview';

const app = new Application();

const win = app.createBrowserWindow({
  title: 'My App',
  width: 1024,
  height: 768,
});

const webview = win.createWebview({ url: 'https://example.com' });

app.run();

Loading Local HTML Content

For apps that bundle their own UI, pass html instead of url. The string is set directly as the document source — no HTTP server required.
const webview = win.createWebview({
  html: `<!DOCTYPE html>
<html>
  <head><title>Simple Example</title></head>
  <body>
    <h1>Hello, WebviewJS!</h1>
    <p>This is a simple example of a webview window.</p>
  </body>
</html>`,
});
For larger applications consider using a Custom Protocol to serve files from disk, or a bundler output directory — this keeps your Node entry file clean and lets the webview load assets normally via <script> and <link> tags.

Handling Application Events

Application is a typed EventEmitter. Subscribe to lifecycle events before calling app.run() so no events are missed.
// Fired when any window's close button is clicked
app.on('window-close-requested', () => {
  console.log('A window was closed');
});

// Fired when the last window closes — the right place to call app.exit()
app.on('application-close-requested', () => {
  console.log('All windows closed — exiting');
  app.exit();
});

// Fired when a custom menu item (one with an id you assigned) is clicked
app.on('custom-menu-click', ({ customMenuEvent }) => {
  console.log('Menu item clicked:', customMenuEvent.id);

  if (customMenuEvent.id === 'quit') {
    app.exit();
  }
});
If you do not listen to application-close-requested and call app.exit() (or otherwise stop the process), the Node process may hang after the last window closes because the event-pump timer keeps running. Always handle this event in production apps.

Retaining Native Handles

Every object returned by a create* method — BrowserWindow, Webview, WebContext, TrayIcon — is a JavaScript wrapper around a native resource. The Application owns those native resources, but the JavaScript wrappers can be garbage-collected independently. Keep strong references in module-level or application-state variables for any handle whose methods or event listeners you still need:
// ✅ Correct — held at module scope
let mainWindow = null;
let mainWebview = null;

app.whenReady().then(() => {
  mainWindow = app.createBrowserWindow({ title: 'Main', width: 1200, height: 800 });
  mainWebview = mainWindow.createWebview({ url: 'https://nodejs.org' });
});
// ❌ Risky — local variables may be GC'd; event listeners on webview become unreachable
app.whenReady().then(() => {
  const win = app.createBrowserWindow();
  const webview = win.createWebview({ url: 'https://example.com' });
  webview.onIpcMessage(() => { /* ... */ }); // listener may be lost
});
Once app.exit() is called, all wrapped resources are disposed. Calling methods on a disposed handle throws a disposed error; use handle.isDisposed() to guard if needed.

IPC: Page ↔ Node Communication

WebviewJS exposes a lightweight IPC channel so the web page and your Node code can exchange messages bidirectionally.

Basic postMessage / onIpcMessage

The web page sends messages via window.ipc.postMessage(). Node receives them with webview.onIpcMessage().
const webview = win.createWebview({
  html: `<!DOCTYPE html>
<html>
  <body>
    <button onclick="window.ipc.postMessage('ping')">Ping</button>
    <script>
      window.onIpcMessage = (data) => {
        document.body.style.background = data === 'pong' ? 'lime' : 'red';
      };
    </script>
  </body>
</html>`,
  preload: `window.onIpcMessage = function(data) {};`, // define before page scripts run
});

webview.onIpcMessage((msg) => {
  const text = msg.body.toString('utf-8');
  console.log('IPC received:', text); // "ping"

  // Send a message back by evaluating a script in the page
  webview.evaluateScript(`window.onIpcMessage("pong")`);
});

Exposed Namespaces with webview.expose()

For structured, typed request/response style calls from the page into Node, use webview.expose(). Each exposed function returns a Promise in the browser.
webview.expose('native', {
  // Plain values are exposed as-is
  version: '1.0.0',

  // Async functions are awaitable from the page
  getGreeting: async (name) => `Hello, ${name}!`,

  readConfig: async () => {
    const { readFile } = await import('node:fs/promises');
    return JSON.parse(await readFile('./config.json', 'utf-8'));
  },
});
In the web page:
console.log(window.native.version);                   // "1.0.0"
const greeting = await window.native.getGreeting('Ada'); // "Hello, Ada!"
const config   = await window.native.readConfig();       // parsed config object
All arguments and return values must be JSON-serializable. Passing non-serializable values (functions, undefined, circular references) throws a SerializationError.
You can also use a custom IPC alias name so the page uses window.bindings (while window.ipc still works):
const webview = win.createWebview({
  html: '...',
  ipcName: 'bindings',
});
webview.onIpcMessage((msg) => console.log(msg.body.toString()));
// Page can use either: window.bindings.postMessage('hello')
//                 or:  window.ipc.postMessage('hello')

Auto-Cleanup with Symbol.dispose

WebviewJS supports the TC39 Explicit Resource Management proposal. Wrap an Application (or any handle) in a using declaration and its dispose() method is called automatically when the block exits — even on exception.
{
  using app = new Application();

  const win  = app.createBrowserWindow({ title: 'Disposable', width: 800, height: 600 });
  const webview = win.createWebview({ html: '<h1>Auto-cleaned up</h1>' });

  app.run();
} // app.exit() called automatically here
Individual BrowserWindow, Webview, WebContext, and TrayIcon objects also support dispose() and [Symbol.dispose]() for fine-grained early cleanup without tearing down the entire application:
// Close a single window early and release its resources
win[Symbol.dispose]();
Symbol.dispose and the using declaration are part of the TC39 Explicit Resource Management proposal. They are fully supported under the Node.js >= 24 baseline that WebviewJS requires.

Next Steps

Event Loop

Understand how app.run(), app.whenReady(), and app.pumpEvents() keep the GUI alive without blocking Node.

IPC Messaging

Deep dive into the full IPC API including custom protocols and navigation guards.

Application API

Full reference for every method and event on the Application class.

BrowserWindow API

Window sizing, theming, decorations, menus, and all window events.

Build docs developers (and LLMs) love