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.

Traditional GUI frameworks block the calling thread in a run() call that never returns until the application exits. WebviewJS takes a fundamentally different approach: it uses Tao’s non-blocking pump_events() integration so that every tick of the GUI message queue is just one call inside an ordinary Node.js setInterval. The result is that file I/O, network requests, child processes, Promises, and all other Node APIs continue operating normally in the same thread — right alongside an open native window.

How the Pump Works

Internally, app.run() is a thin JavaScript wrapper that sets up a setInterval which calls app.pumpEvents() on every tick:
// Conceptually equivalent to what app.run({ interval: 16 }) does
const timer = setInterval(() => {
  const alive = app.pumpEvents();
  if (!alive) {
    clearInterval(timer);
  }
}, 16); // ~60 FPS
Each call to pumpEvents() drains the OS native message queue without waiting for new messages. It returns true while the application is still alive and false when the native layer signals that it should shut down (e.g. all windows closed and no tray icon active). The interval is then cleared automatically. Because this runs as a normal Node timer, the JS event loop never stalls. You can await a network fetch, read a file, or query a database between frames without any special threading or async bridging.
On macOS the native GUI must run on the main thread — the thread that called new Application(). Never create an Application or BrowserWindow from a worker thread or worker_threads module.

app.whenReady() — Promise-Based Startup

app.whenReady() is the recommended way to start most apps. It starts the event pump and returns a Promise that resolves when Tao emits its first native Resumed event, signalling that the application is fully initialised and ready to create windows.
import { Application } from '@webviewjs/webview';

const app = new Application();

app.whenReady().then(() => {
  const win = app.createBrowserWindow({ title: 'Ready', width: 1024, height: 768 });
  win.createWebview({ url: 'https://example.com' });
});
Or with await at the top level of an ES module:
import { Application } from '@webviewjs/webview';

const app = new Application();
await app.whenReady();

const win = app.createBrowserWindow({ title: 'Ready', width: 1024, height: 768 });
win.createWebview({ url: 'https://example.com' });

whenReady Options

whenReady() accepts the same timer configuration options as run():
await app.whenReady({ interval: 33, ref: false });
OptionDefaultDescription
interval16Milliseconds between event pumps (~60 FPS at 16 ms)
reftrueIf false, the timer is unref()’d and won’t prevent process exit
autoRuntrueSet to false to skip auto-starting the pump (see Manual Mode below)
interval and ref are not accepted when autoRun: false. Passing them together with autoRun: false is a type error.

app.run(options?) — Explicit Pump Start

Call app.run() directly when you want to start the pump separately from the readiness Promise, or when you don’t use whenReady() at all.
// Default: 16 ms interval, timer is ref'd (keeps process alive)
app.run();

// 30 FPS — more CPU-friendly for simple or mostly-idle UIs
app.run({ interval: 33 });

// Unref'd timer: the process can exit naturally when all async work is done,
// even while a window is still open. Useful for CLI tools and scripts.
app.run({ ref: false });
OptionDefaultDescription
interval16Milliseconds between pumpEvents() calls
reftrueIf false, the interval is unref()’d via Node’s timer API
For simple utility apps that open a window and also perform async work (e.g. a CLI tool that shows a progress window), use ref: false. The process will exit as soon as all other async tasks complete, without needing an explicit app.exit() call.

Manual Mode: autoRun: false

For advanced use cases where you need to control the exact moment the pump starts — for example, to show a window only after some async initialisation — disable auto-run in whenReady() and call app.run() yourself:
import { Application } from '@webviewjs/webview';

const app = new Application();

// Start listening for the ready event but do NOT start the pump yet
const ready = app.whenReady({ autoRun: false });

// Do some initialisation work before the GUI starts
const config = await loadConfig();

// NOW start pumping (can also pass interval/ref here)
app.run({ interval: 16, ref: true });

// Wait for the application to be fully ready
await ready;

const win = app.createBrowserWindow({ title: config.title, width: 1200, height: 800 });
win.createWebview({ url: config.startUrl });

app.pumpEvents() — Low-Level Manual Pump

pumpEvents() is the primitive that run() calls internally. You can call it directly for maximum control over when the native message queue is drained.
// Manually pump once and check if the app is still alive
const alive = app.pumpEvents();
if (!alive) {
  console.log('Application has exited');
}
A common pattern is a tight manual loop for CPU-intensive frames, combined with handing control back to the interval when done:
// Stop the interval pump
app.stop();

// Run a tight render loop for a fixed number of frames
let frames = 120;
while (frames-- > 0) {
  app.pumpEvents();
}

// Resume the normal interval pump
app.run({ interval: 16 });
pumpEvents() is a synchronous, non-blocking call. It returns immediately after draining the current message queue — it does not wait for new messages to arrive.

app.stop() — Pause the Pump

app.stop() clears the setInterval timer but leaves all windows open and the application alive. The native state is preserved; you just stop ticking the message queue.
app.stop();
// Windows remain visible, but no native events are processed until
// you call app.run() or app.pumpEvents() again
This is distinct from app.exit(), which calls stop(), disposes all windows and native resources, and marks the application as exited so subsequent method calls throw.
app.exit(); // stop() + dispose all windows + mark as exited

Ref/Unref Semantics

Node.js timers have a ref()/unref() concept: a ref’d timer prevents the process from exiting normally, while an unref’d timer does not. WebviewJS exposes this as the ref option.
The pump timer keeps the process alive for as long as the GUI is running. The process only exits when app.exit() is called (or the last window closes and application-close-requested triggers it).
app.run({ ref: true }); // process stays alive until app.exit()
Use this for full desktop applications where the GUI is the primary purpose of the process.

runSync() vs Non-Blocking run()

WebviewJS also exposes a runSync() method that blocks the JavaScript thread in a synchronous loop until the application exits. This mirrors how traditional GUI frameworks behave.
// Blocks until all windows are closed — no other JS runs during this time
app.runSync();
console.log('App exited'); // only reaches here after exit
In most cases you should prefer the non-blocking app.run():
app.run()app.runSync()
Blocks JS threadNoYes
Node I/O / timers✅ Continue running❌ Suspended
async/await✅ Works normally❌ Does not progress
Use caseAll standard applicationsCompatibility shim, legacy scripts
Avoid runSync() in applications that make network requests, read files, use child processes, or have any asynchronous logic. All of those operations will be suspended for the duration of the blocking call.
// Start non-blocking pump (setInterval under the hood)
app.run({ interval?: number, ref?: boolean }): void

// Promise that resolves on native ready; auto-starts pump by default
app.whenReady({ interval?, ref?, autoRun? }): Promise<void>

// Drain the OS message queue once; returns false when app should exit
app.pumpEvents(): boolean

// Clear the setInterval timer (leaves windows open)
app.stop(): void

// stop() + dispose all windows + mark app as exited
app.exit(): void

// Block the JS thread until the app exits (avoid in async apps)
app.runSync(): void

Build docs developers (and LLMs) love