| title | WebviewJS Quickstart: Build Your First Desktop Window |
|---|---|
| sidebarTitle | Quickstart |
| description | Create your first native desktop window with WebviewJS. Load a URL or inline HTML, handle window events, and use IPC in under 30 lines. |
In this guide you'll install WebviewJS, open a native OS window, load content into it, react to window events, and send a message from the page back to Node.js. By the end you'll have a working desktop app and an understanding of the core building blocks — Application, BrowserWindow, and Webview.
```bash
npm install @webviewjs/webview
```
Make sure you have Node.js 24 or later and that your platform dependencies are satisfied. See the [Installation guide](/installation) for per-platform setup instructions.
```js app.mjs
import { Application } from '@webviewjs/webview';
const app = new Application();
// Hold strong references so the GC doesn't collect them
let mainWindow = null;
let mainWebview = null;
app.whenReady().then(() => {
mainWindow = app.createBrowserWindow({
title: 'My First WebviewJS App',
width: 1024,
height: 768,
});
mainWebview = mainWindow.createWebview({
url: 'https://example.com',
});
});
```
Run it:
```bash
node app.mjs
```
A native window opens and loads `https://example.com`. Close the window to exit.
<Note>
Always store `BrowserWindow` and `Webview` instances in variables that live as long as you need them. If you let these references be garbage-collected, the native handles become unreachable and their event listeners stop firing.
</Note>
```js
mainWebview = mainWindow.createWebview({
html: `<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Hello</title></head>
<body>
<h1>Hello from WebviewJS!</h1>
<p>Running on Node.js ${process.version}</p>
</body>
</html>`,
});
```
Use the `html` option for self-contained pages, prototypes, or any scenario where you want to serve content without a protocol handler or HTTP server.
```js
app.whenReady().then(() => {
mainWindow = app.createBrowserWindow({ title: 'My App', width: 1024, height: 768 });
mainWebview = mainWindow.createWebview({ url: 'https://example.com' });
// Fires when all windows have been closed
app.on('application-close-requested', () => {
console.log('All windows closed — exiting.');
app.exit();
});
// Fires when a single window's close button is clicked
app.on('window-close-requested', () => {
console.log('A window close was requested.');
});
});
```
Without the `application-close-requested` handler calling `app.exit()`, the process stays alive after the last window closes because the event pump timer keeps Node running.
```js
app.whenReady().then(() => {
mainWindow = app.createBrowserWindow({ title: 'IPC Demo', width: 800, height: 600 });
mainWebview = mainWindow.createWebview({
html: `<!DOCTYPE html>
<html>
<body>
<h1 id="status">Waiting…</h1>
<button onclick="window.ipc.postMessage('ping')">Send ping</button>
</body>
</html>`,
});
mainWebview.onIpcMessage((msg) => {
const text = msg.body.toString('utf-8');
console.log('Received from page:', text); // "ping"
// Send a reply back into the page
mainWebview.evaluateScript(
`document.getElementById('status').textContent = 'Node replied: pong'`
);
});
});
```
Click the button in the window — "ping" appears in your terminal and the page updates to show "Node replied: pong".
For a cleaner request/response pattern, use webview.expose() to publish an entire namespace of Node.js functions directly to the page. Every exposed function automatically becomes a Promise in the browser context:
import { Application } from '@webviewjs/webview';
import { readFile } from 'node:fs/promises';
const app = new Application();
let mainWindow = null;
let mainWebview = null;
app.whenReady().then(() => {
mainWindow = app.createBrowserWindow({ title: 'Expose Demo', width: 800, height: 600 });
mainWebview = mainWindow.createWebview({
html: `<!DOCTYPE html>
<html>
<body>
<pre id="out">Loading config…</pre>
<script>
window.native.readConfig().then(cfg => {
document.getElementById('out').textContent = JSON.stringify(cfg, null, 2);
});
</script>
</body>
</html>`,
});
// Expose a namespace called "native" to the page
mainWebview.expose('native', {
version: '1.0.0',
readConfig: async () => JSON.parse(await readFile('./config.json', 'utf-8')),
});
});Values, arguments, and return types must be JSON-serializable. Violations throw a SerializationError.