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.

A single Application instance can own as many BrowserWindow objects as you need. Each window has its own webview, title bar, and OS chrome, but they all share the same JavaScript event loop driven by app.run(). Because JavaScript is single-threaded, you do not need locks or inter-process messaging to share state between windows — any object reachable from your Node module is available to every window’s event handlers simultaneously.

Opening Multiple Windows

Create each window by calling app.createBrowserWindow() and attach a webview with win.createWebview(). Both windows run as soon as app.run() is called.
import { Application } from '@webviewjs/webview';

const app = new Application();

const win1 = app.createBrowserWindow({ title: 'Dashboard', width: 1280, height: 800 });
win1.createWebview({ url: 'app://localhost/dashboard' });

const win2 = app.createBrowserWindow({ title: 'Settings', width: 600, height: 500 });
win2.createWebview({ url: 'app://localhost/settings' });

app.run();
Every window shares the same tao event loop. There is no per-window run() call — a single app.run() drives all windows.

Keeping Strong References

JavaScript’s garbage collector may reclaim window objects that are no longer referenced. Keep a Map or array of all live window/webview pairs so they remain reachable.
import { Application } from '@webviewjs/webview';

const app = new Application();
const windows = new Map(); // id → { win, webview }

function openWindow(id, url) {
  // Reuse an existing window instead of creating a duplicate
  if (windows.has(id)) {
    windows.get(id).win.show();
    windows.get(id).win.focus();
    return windows.get(id);
  }

  const win = app.createBrowserWindow({ title: id, width: 900, height: 600 });
  const webview = win.createWebview({ url });
  windows.set(id, { win, webview });
  return { win, webview };
}

openWindow('main',     'app://localhost/');
openWindow('editor',   'app://localhost/editor');
openWindow('preview',  'app://localhost/preview');

app.run();

Child Windows

app.createChildBrowserWindow() creates a window that is logically and visually attached to a parent. Child windows are ideal for dialogs, palettes, floating tool panels, and inspector drawers. They use the same BrowserWindowOptions as regular windows.
import { Application } from '@webviewjs/webview';

const app = new Application();

const mainWin = app.createBrowserWindow({
  title: 'Main Window',
  width: 1200,
  height: 800,
});
mainWin.createWebview({ url: 'app://localhost/' });

// Open a child window for "Find and Replace"
function openFindReplace() {
  const child = app.createChildBrowserWindow({
    title: 'Find & Replace',
    width: 480,
    height: 280,
  });
  child.createWebview({ url: 'app://localhost/find-replace' });
  return child;
}

app.on('custom-menu-click', ({ customMenuEvent }) => {
  if (customMenuEvent.id === 'find') {
    openFindReplace();
  }
});

app.run();
Use createChildBrowserWindow for transient, dependent UI. Use createBrowserWindow for independent top-level windows that can outlive their siblings.

Window IDs and Event Correlation

Every BrowserWindow has a numeric id(). The custom-menu-click event payload includes windowId so you can identify which window triggered the action.
const mainWin = app.createBrowserWindow({ title: 'Main' });
const settingsWin = app.createBrowserWindow({ title: 'Settings' });

console.log('Main window ID:', mainWin.id());
console.log('Settings window ID:', settingsWin.id());

app.on('custom-menu-click', ({ customMenuEvent }) => {
  const { id: menuId, windowId } = customMenuEvent;

  // Look up which window triggered this menu item
  const entry = [...windows.entries()].find(([, v]) => v.win.id() === windowId);
  if (entry) {
    const [name, { win, webview }] = entry;
    console.log(`Menu "${menuId}" triggered from "${name}" (window ${windowId})`);
  }
});

Disposing Individual Windows

Call win.dispose() to destroy a window and release its native resources. After disposal, all method calls on that instance throw. Use win.isDisposed() to check. The using declaration (via Symbol.dispose) works if your runtime supports it.
function closeWindow(id) {
  const entry = windows.get(id);
  if (!entry) return;

  entry.webview.dispose();
  entry.win.dispose();
  windows.delete(id);
}
Disposing a window while it is visible will close it immediately without any OS close-confirmation dialog. Always save user state before calling dispose().

Showing and Hiding Windows

When the user clicks the OS close button, WebviewJS hides the window rather than destroying it. This lets you reuse the window later without recreating it.
// Toggle a window's visibility
function toggleWindow(win) {
  if (win.isVisible()) {
    win.hide();
  } else {
    win.show();
    win.focus();
  }
}

// Show all windows
for (const { win } of windows.values()) {
  win.show();
}

Window Lifecycle Events

1

Listen for individual window close

window-close-requested fires when any one window is hidden by the user. The event payload does not currently identify which window was hidden — use win.isVisible() on tracked windows to find out.
app.on('window-close-requested', () => {
  // One window was hidden. Update any window-list UI here.
  const visible = [...windows.values()].filter(({ win }) => win.isVisible());
  console.log(`${visible.length} window(s) still visible`);
});
2

Detect when the last window is closed

application-close-requested fires when all tracked windows have been hidden. This is the normal exit point.
app.on('application-close-requested', () => {
  // All windows are hidden — safe to exit
  app.exit();
});
3

Prevent accidental exit

If you have unsaved work, show a confirmation dialog before exiting instead of calling app.exit() immediately.
app.on('application-close-requested', () => {
  if (hasUnsavedChanges()) {
    const paths = mainWin.openFileDialog({ title: 'Save before quitting?' });
    if (paths.length) {
      saveFile(paths[0]).then(() => app.exit());
    }
    // If the user cancels the dialog, the app stays open
  } else {
    app.exit();
  }
});

Coordinating Windows with evaluateScript

Because all windows live in the same Node process, you can push data from one webview to another directly:
const { webview: mainWebview }     = windows.get('main');
const { webview: previewWebview }  = windows.get('preview');

// When the editor changes, update the preview
mainWebview.onIpcMessage((message) => {
  const { action, content } = JSON.parse(message.body.toString('utf8'));

  if (action === 'content-changed') {
    const escaped = JSON.stringify(content);
    previewWebview.evaluateScript(`
      document.getElementById('preview').innerHTML = ${escaped};
    `);
  }
});
In some scenarios — for example a Node.js script that launches two completely separate windows with no shared state — you can create two Application instances and await Promise.all([app1.run(), app2.run()]). Each application drives its own event loop independently.
import { Application } from '@webviewjs/webview';

const app1 = new Application();
app1.createBrowserWindow().createWebview({ url: 'https://nodejs.org' });

const app2 = new Application();
app2.createBrowserWindow().createWebview({ url: 'https://wikipedia.org' });

await Promise.all([app1.run(), app2.run()]);
This pattern is unusual — prefer a single Application with multiple windows when the windows need to share data or respond to the same menu events.

Build docs developers (and LLMs) love