# NW.js User Guide

NW.js is an application runtime that lets you build native desktop applications for **Windows, macOS, and Linux** using ordinary web technologies — HTML, CSS, and JavaScript — while giving your page direct access to the full **Node.js** API and the entire npm ecosystem. It is built on the same engines that power Google Chrome (**Chromium**) and the Node.js server runtime, fused so that your DOM scripts and your Node modules share a single JavaScript heap.

This guide shows you how to write, run, debug, package, and ship an NW.js application. It is organized around the journey of building a real app: a five-minute Hello World, then the manifest that configures your app, then the `nw.*` desktop APIs, then advanced topics and a complete reference. Written from the NW.js source code at the version below.

> [!NOTE]
> NW.js was originally called **node-webkit**. You will still see that name in older blog posts, the `node-webkit` npm distribution channels, and the `process.versions['node-webkit']` field. They refer to the same project.

---

## 1. Quick Start

### 1.1 What you need

To develop an NW.js app you need exactly two things:

- A copy of the **NW.js binary** for your platform (download below).
- A folder containing a `package.json` manifest and at least one HTML or JavaScript file.

You do **not** need a compiler, a build toolchain, or a bundler to get started. NW.js runs your source files directly.

> [!TIP]
> While developing, always use the **SDK build flavor**. It includes the DevTools and developer tools (`nwjc`, `chromedriver`). Switch to the Normal build only for production, to ship a smaller package. See [Build Flavors](#82-build-flavors).

### 1.2 Get NW.js

Download the latest binaries from the official site **https://nwjs.io**, or grab a specific version directly from the download server `https://dl.nwjs.io`.

| Platform | Package |
|---|---|
| Windows | `nwjs-vX.Y.Z-win-x64.zip` (64-bit) / `nwjs-vX.Y.Z-win-ia32.zip` (32-bit) |
| macOS 10.10+ | `nwjs-vX.Y.Z-osx-arm64.zip` |
| Linux | `nwjs-vX.Y.Z-linux-x64.tar.gz` / `nwjs-vX.Y.Z-linux-arm64.tar.gz` |

Nightly builds from the git tip are available at `https://dl.nwjs.io/live-build/`. Previous versions and the version-mapping file are listed at `https://nwjs.io/versions.json`.

> [!NOTE]
> Each NW.js release pins a specific Node.js and Chromium version. Read the release notes for the version you download so you know which Node and Chromium (and therefore which Web Platform and Node APIs) you are targeting.

### 1.3 Hello World in three steps

**Step 1 — Create `package.json`:**

```json
{
  "name": "helloworld",
  "main": "index.html"
}
```

`package.json` is your app's **manifest**. The two required fields are `main` (the first page or script to load) and `name` (a globally unique app name — see [Section 3](#3-the-manifest-packagejson)).

**Step 2 — Create `index.html`:**

```html
<!DOCTYPE html>
<html>
  <head>
    <title>Hello World!</title>
  </head>
  <body>
    <h1>Hello World!</h1>
    We are using Node.js <script>document.write(process.version)</script>.
  </body>
</html>
```

**Step 3 — Run your app.** Point the `nw` binary at the folder containing `package.json`:

=== "Windows"

    ```bat
    cd \path\to\your\app
    \path\to\nw.exe .
    ```

    You can also drag the folder containing `package.json` onto `nw.exe`.

=== "macOS"

    ```bash
    cd /path/to/your/app
    /path/to/nwjs.app/Contents/MacOS/nwjs .
    ```

=== "Linux"

    ```bash
    cd /path/to/your/app
    /path/to/nw .
    ```

Expected result — a native window opens showing your heading and the embedded Node.js version:

```text
Hello World!
We are using Node.js v26.1.0.
```

That you can print `process.version` from inside an HTML page is the whole point of NW.js: **Node.js is live inside the DOM**.

### 1.4 Your first desktop feature: a native context menu

This example creates a real OS-native right-click menu — something a plain web page cannot do. Replace `index.html` with:

```html
<!DOCTYPE html>
<html>
<head><title>Context Menu</title></head>
<body>
<p>Right-click here to show the native context menu.</p>
<script>
// Create an empty context menu
var menu = new nw.Menu();

// Add some items
menu.append(new nw.MenuItem({
  label: 'Item A',
  click: function () { alert('You clicked "Item A"'); }
}));
menu.append(new nw.MenuItem({ label: 'Item B' }));
menu.append(new nw.MenuItem({ type: 'separator' }));
menu.append(new nw.MenuItem({ label: 'Item C' }));

// Hook the DOM "contextmenu" event and popup the native menu
document.body.addEventListener('contextmenu', function (ev) {
  ev.preventDefault();           // suppress the default web menu
  menu.popup(ev.x, ev.y);        // show the native menu at the cursor
  return false;
}, false);
</script>
</body>
</html>
```

All NW.js APIs live on the global `nw` object and can be used directly from any script in your app. See [Section 4](#4-the-nw-desktop-apis) for the full API set.

> [!TIP]
> The legacy way to load the APIs, `require('nw.gui')`, still works and returns the same `nw` object. New code should just use the global `nw`.

### 1.5 Calling Node.js from the page

Because Node.js is built in, you can `require()` core modules and npm packages straight from your page:

```html
<script>
// Query the OS platform using Node's `os` module
var os = require('os');
document.write('You are running on ', os.platform());
</script>
```

Expected output (on a 64-bit Windows machine, for example):

```text
You are running on win32
```

You can install and use any npm package the same way you would in a Node project — run `npm install` in your app folder, then `require()` the package.

> [!IMPORTANT]
> **Native (compiled) Node modules** built by a plain `npm install` are **not** ABI-compatible with NW.js and must be rebuilt against NW.js headers. See [Section 7.3](#73-using-native-node-modules).

---

## 2. Installation & Project Layout

### 2.1 Anatomy of an NW.js project

A minimal project is just a folder:

```text
my-app/
├── package.json     ← the manifest (required)
├── index.html       ← your "main" entry (HTML or JS)
├── js/
│   └── app.js
├── css/
│   └── style.css
└── node_modules/    ← npm dependencies (optional)
```

NW.js does not impose any directory structure beyond requiring a `package.json` at the app root. Paths inside the manifest (such as `main` and window `icon`) are resolved relative to the folder that contains `package.json`.

### 2.2 HTML entry vs. JavaScript entry

The `main` field can point at either an HTML file or a JavaScript file, and the choice changes how your app starts:

| `main` value | Startup behavior |
|---|---|
| `"index.html"` | NW.js opens a window and loads the HTML page immediately. |
| `"main.js"` | The JS file runs in the **background page** on start; **no window opens automatically**. You open windows yourself with `nw.Window.open()`. |

The JS-entry pattern is useful when you want to do initialization (read config, check a license, choose a window size) before showing anything:

```javascript
// main.js — runs in the background page, no window yet
// ...do initialization...
nw.Window.open('index.html', {}, function (win) {
  // window is ready
});
```

> [!NOTE]
> NW.js apps always have an invisible **background page** that lives for the entire lifetime of the app. This is where long-lived objects (menus, trays, global state) should live. See [Section 5.1](#51-javascript-contexts).

### 2.3 The current working directory

On startup NW.js changes the process current directory to the folder where your package files reside. Use `nw.App.startPath` to recover the directory the app was launched from (see [Section 4.2](#42-app--application-lifecycle-and-environment)).

---

## 3. The Manifest (`package.json`)

The manifest controls everything about how your app launches: its identity, which runtime features are enabled, and how the main window looks. It is a standard JSON file.

### 3.1 Minimal manifest

```json
{
  "main": "index.html",
  "name": "nw-demo"
}
```

### 3.2 Required fields

| Field | Type | Description |
|---|---|---|
| `main` | String | The HTML page to open or JS file to execute on start. A URL, filename, or path relative to `package.json`. |
| `name` | String | A globally **unique, lowercase, alphanumeric** name (`.`, `_`, `-` allowed; no spaces). NW.js stores your app's data under a directory named after this. |

> [!CAUTION]
> `name` must be globally unique because NW.js derives your app's per-user **data directory** from it (see [`App.dataPath`](#42-app--application-lifecycle-and-environment)). Two apps sharing a `name` will share cookies, localStorage, and caches.

### 3.3 Feature-control fields

These top-level fields turn runtime features on or off and shape how the app behaves.

| Field | Type | Description |
|---|---|---|
| `product_string` | String | Renames the per-process **Helper** application on macOS (shown as `<product_string> Helper`). |
| `nodejs` | Boolean | Set to `false` to **disable Node.js** support entirely. |
| `node-main` | String | Path to a Node.js script executed on startup **in the Node context, before the first DOM window loads**. Node CLI args may precede the path in the string. |
| `domain` | String | Sets the host used in the `chrome-extension://` URL for your app, so your app and a same-domain website share cookies. |
| `bg-script` | String | A background script executed in the background page at app start. |
| `user-agent` | String | Overrides the HTTP `User-Agent` for requests made by the app. Supports placeholders (table below). |
| `node-remote` | Array \| String | Enables calling Node from **remote pages**. Value is a list of [Chrome match patterns](https://developer.chrome.com/extensions/match_patterns) (e.g. `"<all_urls>"`, `"http://*.example.com/*"`). |
| `chromium-args` | String | Extra Chromium command-line switches applied every launch. Multiple switches separated by spaces; group flags in single quotes. Prefixed by the value of the `NW_PRE_ARGS` env var. |
| `js-flags` | String | Flags passed to the V8 JavaScript engine (e.g. `"--harmony_proxies"`). |
| `crash_report_url` | String | URL of a breakpad/crashpad crash-report server; minidumps are POSTed there on crash. |
| `inject_js_start` | String | Local JS file injected **after CSS but before any DOM is built or any other script runs**. |
| `inject_js_end` | String | Local JS file injected **after the document loads, before `onload` fires**. |
| `additional_trust_anchors` | Array | PEM-encoded certificates added as extra trusted roots (for self-signed / custom-CA servers). |
| `dom_storage_quota` | Integer | DOM-storage quota in **MB**. Suggestion: set to double the value you actually want. |
| `single-instance` | Boolean | ~~Deprecated since 0.13.0; always `true`.~~ |
| `no-edit-menu` | Boolean | ~~(Mac) Deprecated since 0.13.0.~~ |

**`user-agent` placeholders:**

| Placeholder | Replaced with |
|---|---|
| `%name` | the manifest `name` |
| `%ver` | the manifest `version` |
| `%nwver` | the NW.js version |
| `%webkit_ver` | the Blink engine version |
| `%chromium_ver` | the Chromium version |
| `%osinfo` | OS and CPU info, as in a normal browser UA |

> [!NOTE]
> `node-remote` is the single most security-sensitive manifest field. Granting Node access to a remote origin gives that site full read/write access to the user's machine. Only ever point it at origins you fully control. See [Section 6, Security](#6-security).

### 3.4 The `window` object

The `window` field is an object controlling the main window's appearance and behavior. Most subfields are **inherited** by child windows opened via `window.open()` or `<a target="_blank">` — except `fullscreen`, `kiosk`, `position`, `resizable`, and `show`, which reset to defaults for opened windows. Every subfield can also be overridden per-window through the [`new-win-policy`](#46-window-events) event.

| `window` subfield | Type | Description |
|---|---|---|
| `id` | String | Identifier used to **remember and restore** the window's size and position across launches. |
| `title` | String | Default window title (useful while the app is starting). |
| `width` / `height` | Integer | Initial inner width / height. |
| `min_width` / `min_height` | Integer | Minimum inner size. |
| `max_width` / `max_height` | Integer | Maximum inner size. |
| `position` | String | `null`, `center`, or `mouse` — where the window opens. |
| `resizable` | Boolean | Whether the user can resize the window. |
| `always_on_top` | Boolean | Keep window above all others. |
| `visible_on_all_workspaces` | Boolean | (Mac & Linux) Show on every workspace. |
| `fullscreen` | Boolean | Start fullscreen. |
| `kiosk` | Boolean | Start in **kiosk** mode (fullscreen + locked down). |
| `show_in_taskbar` | Boolean | Show in the taskbar / dock. Default `true`. |
| `frame` | Boolean | Set `false` for a frameless window. |
| `show` | Boolean | Set `false` to start hidden. |
| `transparent` | Boolean | Enable a transparent window. Default `false`. |
| `icon` | String | Path to the window icon. |
| `as_desktop` | Boolean | (Linux/X11) Show as a desktop-background window. |
| `toolbar` | Boolean | ~~Deprecated since 0.13.0; always `false`.~~ |

**Frameless drag regions** — a frameless window has no title bar to drag, so designate draggable areas with CSS:

```css
.titlebar     { -webkit-app-region: drag; }
.titlebar button { -webkit-app-region: no-drag; }
```

> [!WARNING]
> Do not combine `frame: false` with `fullscreen: true`: a frameless fullscreen window prevents the mouse from being captured at the very edges of the screen. Likewise, avoid `resizable: false` + `frame: true` on macOS, where the user can still trigger fullscreen.

### 3.5 The `webkit` object

| `webkit` subfield | Type | Description |
|---|---|---|
| `plugin` | Boolean | Load external browser plugins (e.g. Flash). Default `true`. |
| `double_tap_to_zoom_enabled` | Boolean | (Mac) Two-finger double-tap zoom. Default `false`. |

### 3.6 The `webview` object (partitions)

To let a `<webview>` load local files or trusted resources, declare a partition in the manifest and add `partition="trusted"` to the tag (see [Section 4.10](#410-the-webview-tag)):

```json
{
  "name": "nw-demo",
  "main": "index.html",
  "webview": {
    "partitions": [
      { "name": "trusted", "accessible_resources": ["<all_urls>"] }
    ]
  }
}
```

### 3.7 A realistic manifest

```json
{
  "name": "my-editor",
  "main": "index.html",
  "version": "1.2.0",
  "user-agent": "MyEditor/%ver (NW.js %nwver)",
  "node-remote": [],
  "chromium-args": "--disable-accelerated-video",
  "js-flags": "--expose-gc",
  "dom_storage_quota": 200,
  "window": {
    "id": "main",
    "title": "My Editor",
    "icon": "assets/icon.png",
    "width": 1100,
    "height": 720,
    "min_width": 640,
    "min_height": 480,
    "position": "center",
    "frame": true,
    "show": false
  }
}
```

> [!TIP]
> Setting `"show": false` and then calling `win.show()` once the page has loaded eliminates the white "flash of unstyled window" that users see while your app boots.

---

## 4. The `nw.*` Desktop APIs

All NW.js APIs are exposed on the global `nw` object. This section is the practical reference for everything you can *do* — windows, menus, the system tray, the clipboard, global hotkeys, the screen, and the shell.

> [!NOTE]
> Several API objects (`Window`, `MenuItem`, `Shortcut`, `Screen`) are **EventEmitters**. You listen with `.on('eventname', handler)` and stop with `.removeListener()` / `.removeAllListeners()`, exactly as in Node.

### 4.1 API at a glance

| Object | Purpose | Construct / access |
|---|---|---|
| `nw.App` | App lifecycle, args, data paths, proxy, global hotkeys | static object |
| `nw.Window` | Native window control and events | `nw.Window.get()` / `.open()` |
| `nw.Menu` | Menubars and context menus | `new nw.Menu()` |
| `nw.MenuItem` | An item inside a menu | `new nw.MenuItem({...})` |
| `nw.Tray` | System tray / status-bar icon | `new nw.Tray({...})` |
| `nw.Shell` | Open files/URLs in the desktop's default apps | static object |
| `nw.Clipboard` | Read/write the system clipboard | `nw.Clipboard.get()` |
| `nw.Shortcut` | System-wide global hotkeys | `new nw.Shortcut({...})` |
| `nw.Screen` | Display geometry and screen-capture sources | `nw.Screen.Init()` |

### 4.2 `App` — application lifecycle and environment

**Properties**

| Member | Returns | Description |
|---|---|---|
| `App.argv` | Array | Command-line args **with NW.js-internal flags filtered out**. |
| `App.fullArgv` | Array | All args, including NW.js's own (`--nwapp`, `--remote-debugging-port`, …). |
| `App.filteredArgv` | Array | The regex patterns used to filter `argv` (e.g. `/^--url=/`, `/^--nwapp=/`). |
| `App.startPath` | String | The directory the app was started from. |
| `App.dataPath` | String | The app's per-user data directory (see below). |
| `App.manifest` | Object | The parsed `package.json` as a JS object. |

`App.dataPath` resolves per-platform, where `<name>` is the manifest `name`:

| Platform | Path |
|---|---|
| Windows | `%LOCALAPPDATA%/<name>` |
| Linux | `~/.config/<name>` |
| macOS | `~/Library/Application Support/<name>/Default` |

**Methods**

| Method | Description |
|---|---|
| `App.quit()` | Quit immediately. Does **not** send `close` events — no chance to save. |
| `App.closeAllWindows()` | Send `close` to every window; quit after all shut down cleanly. |
| `App.clearCache()` | Clear the in-memory and on-disk HTTP cache (synchronous). |
| `App.clearAppCache(manifest_url)` | Mark an AppCache group obsolete (synchronous). |
| `App.getProxyForURL(url)` | Return the proxy for a URL in PAC format (e.g. `"PROXY localhost:8080"`). |
| `App.setProxyConfig(config, pac_url)` | Set proxy rules or a PAC URL for all network requests. |
| `App.addOriginAccessWhitelistEntry(src, proto, host, allowSub)` | Allow a cross-origin access path (e.g. let `github.com` redirect into your app). |
| `App.removeOriginAccessWhitelistEntry(...)` | Reverse the above with identical arguments. |
| `App.registerGlobalHotKey(shortcut)` | Register a system-wide hotkey (see [Shortcut](#48-shortcut--global-hotkeys)). |
| `App.unregisterGlobalHotKey(shortcut)` | Unregister a hotkey. |
| `App.crashBrowser()` / `App.crashRenderer()` | Deliberately crash the browser/renderer process (to test crash dumps). |
| `App.enableComponent(component, cb)` | _Experimental._ Enable a component; only `WIDEVINE` is supported. |
| `App.updateComponent(component, cb)` | _Experimental._ Install/update a component. |

**Events**

| Event | Fires when |
|---|---|
| `open(args)` | The user opens a file with your app, or a second instance is launched (its full command line arrives here). |
| `reopen` | (Mac) The user clicks the dock icon of an already-running app. |

Setting a proxy, for example:

```javascript
nw.App.setProxyConfig('http=foopy:80;ftp=foopy2', '');
nw.App.getProxyForURL('http://example.com'); // → "PROXY foopy:80"
```

> [!TIP]
> Because `single-instance` is always on, handle the `open` event to react when a user tries to launch your app a second time (e.g. focus the existing window and open the file they double-clicked).

### 4.3 `Window` — controlling the native window

Get the current window, or open a new one:

```javascript
var win = nw.Window.get();              // current window
nw.Window.open('popup.html', {}, function (newWin) { /* ... */ });
```

| Static method | Description |
|---|---|
| `Window.get([dom_window])` | Return the native `Window` (current, or for a given DOM window — always the topmost, even for an iframe). |
| `Window.getAll(callback)` | Callback receives an array of all `Window` objects (since 0.42.6). |
| `Window.open(url, [options], [callback])` | Open a new window loading `url`. `options` accepts all [window subfields](#34-the-window-object) plus the extras below. |

**Extra `Window.open()` options:** `new_instance` (open in a separate render process), `mixed_context` (merge Node + DOM context; requires `new_instance`), `inject_js_start`, `inject_js_end`, `id`, and `focus`.

> [!NOTE]
> Wait for the new window's `loaded` event before touching its components. The opened window is **not focused by default** — pass `focus: true` if you need it focused.

**Geometry & state — properties**

| Property | Description |
|---|---|
| `win.window` | The corresponding DOM `window` object. |
| `win.x` / `win.y` | Get/set the window's screen position. |
| `win.width` / `win.height` | Get/set size (including frame). |
| `win.title` | Get/set the title. |
| `win.menu` | Get/set the menubar (a `Menu` of type `menubar`; set `null` to remove). |
| `win.zoomLevel` | Get/set zoom: `0` normal, positive zoom-in, negative zoom-out. |
| `win.isAlwaysOnTop` / `win.isFullscreen` / `win.isTransparent` / `win.isKioskMode` | State queries. |
| `win.cookies.*` | Cookie API (`get`, `getAll`, `set`, `remove`, `onChanged`), mirroring the [Chrome cookies API](https://developer.chrome.com/extensions/cookies). |

**Movement, sizing, visibility — methods**

| Method | Description |
|---|---|
| `win.moveTo(x, y)` / `win.moveBy(dx, dy)` | Absolute / relative move. |
| `win.resizeTo(w, h)` / `win.resizeBy(dw, dh)` | Absolute / relative resize (inner size). |
| `win.setInnerWidth(w)` / `win.setInnerHeight(h)` | Set inner dimensions. |
| `win.setPosition(pos)` | Move to `center` (supported on all platforms), `null`, or `mouse`. |
| `win.setMinimumSize(w, h)` / `win.setMaximumSize(w, h)` | Size constraints. |
| `win.setResizable(bool)` | Toggle resizability. |
| `win.setAlwaysOnTop(bool)` | Toggle always-on-top. |
| `win.setVisibleOnAllWorkspaces(bool)` / `win.canSetVisibleOnAllWorkspaces()` | (Mac/Linux) Multi-workspace visibility. |
| `win.setShowInTaskbar(bool)` | Show/hide in taskbar or dock. |
| `win.show([bool])` / `win.hide()` | Show or hide. `show(false)` ≡ `hide()`. |
| `win.focus()` / `win.blur()` | Focus / unfocus. |
| `win.minimize()` / `win.maximize()` / `win.restore()` | Window state. |
| `win.enterFullscreen()` / `win.leaveFullscreen()` / `win.toggleFullscreen()` | Fullscreen control. |
| `win.enterKioskMode()` / `win.leaveKioskMode()` / `win.toggleKioskMode()` | Kiosk control. |
| `win.reload()` / `win.reloadIgnoringCache()` / `win.reloadDev()` | Reload variants (`reloadDev` starts a fresh renderer). |
| `win.close([force])` | Close; if listening to `close`, you must call `win.close(true)` to really close. |
| `win.setShadow(bool)` | (Mac) Toggle native window shadow (for frameless/transparent windows). |
| `win.requestAttention(flag)` | Flash the taskbar entry. Boolean to start/stop, or integer flash count. |

**DevTools (SDK build only)**

| Method | Description |
|---|---|
| `win.showDevTools([iframe], [callback])` | Open DevTools (optionally jailed to one `<iframe>`); callback gets the DevTools `Window`. |
| `win.closeDevTools()` | Close DevTools. |
| `win.isDevToolsOpen()` | Query DevTools status. |

**Capture & printing**

`win.capturePage(callback, [config])` captures the visible area; `config.format` is `"png"` or `"jpeg"` (default `jpeg`) and `config.datatype` is `"raw"`, `"buffer"`, or `"datauri"` (default `datauri`):

```javascript
win.capturePage(function (buffer) {
  // do something with the PNG buffer
}, { format: 'png', datatype: 'buffer' });
```

`win.captureScreenshot(options, [callback])` can capture **the whole page** beyond the visible area; omit the callback to get a Promise. Options: `fullSize`, `format`, `quality`, and a `clip` region (`x`, `y`, `width`, `height`, `scale`).

`win.getPrinters(callback)` enumerates printers; `win.print(options)` prints (or prints to PDF). Key `print` options:

| Option | Description |
|---|---|
| `autoprint` | Print without user interaction (default `true`). |
| `silent` | Hide the print-preview dialog (default `false`). |
| `printer` | Device name from `getPrinters()`. Omit when printing to PDF. |
| `pdf_path` | Output path when printing to PDF. |
| `landscape`, `copies`, `scaleFactor`, `shouldPrintBackgrounds` | Standard print settings. |
| `headerFooterEnabled`, `headerString`, `footerString` | Header/footer controls. |
| `marginsType` | `0` default, `1` none, `2` minimum, `3` custom (`marginsCustom`). |
| `mediaSize` | Paper-size spec object. |

> [!NOTE]
> When passing no options, call `win.print({})` (an empty object), not `win.print()`.

**Executing code & protected binaries**

| Method | Description |
|---|---|
| `win.eval(frame, script)` | Run a JS string in a frame (`null` = current frame). |
| `win.evalNWBin(frame, path)` | Load and run a compiled `.bin` produced by `nwjc` (path, `Buffer`, or `ArrayBuffer`). See [Section 7.4](#74-protecting-your-javascript-source). |
| `win.evalNWBinModule(frame, path, module_path)` | _Experimental._ Load a compiled ES module (compile with `nwjc --nw-module`); import it later with dynamic `import()`. |

### 4.4 `Window` events

`Window` exposes a rich set of native events. Listen with `win.on(...)`.

| Event | Fires when | Args |
|---|---|---|
| `close` | Window asked to close (lets you intercept) | `(quit?)` on Mac |
| `closed` | Window has finished closing | — |
| `loading` | Window starts (re)loading | — |
| `loaded` | Window fully loaded (like `window.onload`, but DOM-independent) | — |
| `document-start(frame)` | Document object available, before DOM/scripts | `frame` |
| `document-end(frame)` | Document unloaded, before `onunload` | `frame` |
| `focus` / `blur` | Gains / loses focus | — |
| `minimize` / `maximize` / `restore` | Window state changes (`restore` replaces the old `unmaximize`/`leave-fullscreen`) | — |
| `move(x, y)` | After the window moves | `x, y` |
| `resize(width, height)` | After the window resizes | `width, height` |
| `enter-fullscreen` | Window enters fullscreen | — |
| `zoom` | Zoom level changes | new level |
| `devtools-opened(url)` / `devtools-closed` | DevTools opened / closed | — |
| `new-win-policy(frame, url, policy)` | A new window/popup is requested | see below |
| `navigation(frame, url, policy)` | The window navigates to a new page | see below |

**Intercepting close to save data** is the canonical pattern:

```javascript
win.on('close', function () {
  this.hide();                  // appear to close instantly
  // ...save data here...
  this.close(true);             // really close (bypasses this handler)
});
```

> [!CAUTION]
> Inside a `close` handler you must call `this.close(true)` to actually close — calling `this.close()` again re-fires the event and creates an infinite loop.

**Controlling where links open** with `new-win-policy`. The `policy` object offers `ignore()`, `forceCurrent()`, `forceDownload()`, `forceNewWindow()`, `forceNewPopup()`, and `setNewWindowManifest(m)`:

```javascript
nw.Window.get().on('new-win-policy', function (frame, url, policy) {
  policy.ignore();                 // don't open a new NW window
  nw.Shell.openExternal(url);      // open in the system browser instead
});
```

`navigation` works the same way with `policy.ignore()` to block a page from navigating away.

### 4.5 `Menu` and `MenuItem`

A `Menu` is either a **context menu** (default) or a **menubar**. A `MenuItem` is one row.

**Context menu:**

```javascript
var menu = new nw.Menu();                                  // type: 'contextmenu'
menu.append(new nw.MenuItem({ label: 'Cut' }));
menu.append(new nw.MenuItem({ type: 'separator' }));
menu.append(new nw.MenuItem({ label: 'Paste' }));
menu.popup(x, y);                                          // show at a point
```

**Menubar** (a two-level menu assigned to `win.menu`):

```javascript
var menubar = new nw.Menu({ type: 'menubar' });
var fileMenu = new nw.Menu();
fileMenu.append(new nw.MenuItem({ label: 'Open' }));
fileMenu.append(new nw.MenuItem({ label: 'Save' }));
menubar.append(new nw.MenuItem({ label: 'File', submenu: fileMenu }));
nw.Window.get().menu = menubar;
```

**`Menu` members**

| Member | Description |
|---|---|
| `new nw.Menu([{type}])` | `type` is `"contextmenu"` (default) or `"menubar"`. |
| `menu.items` | Array of the menu's `MenuItem`s. |
| `menu.append(item)` / `menu.insert(item, i)` | Add an item. |
| `menu.remove(item)` / `menu.removeAt(i)` | Remove an item. |
| `menu.popup(x, y)` | Show a context menu at a point (contextmenu type only). |
| `menu.createMacBuiltin(appName, [{hideEdit, hideWindow}])` | (Mac) Populate the standard App/Edit/Window menus. |

**`MenuItem` options & properties**

| Field | Type | Notes |
|---|---|---|
| `label` | String | Plain text. |
| `icon` | String | Path to icon (relative or absolute). |
| `tooltip` | String | (Mac) Hover text. |
| `type` | String | `normal` (default), `checkbox`, or `separator`. **Set only at creation.** |
| `click` | Function | Callback when activated. |
| `enabled` | Boolean | Default `true`. |
| `checked` | Boolean | For checkboxes; default `false`. |
| `submenu` | Menu | Nested menu. |
| `key` | String | Shortcut key (see below). |
| `modifiers` | String | `cmd`/`command`/`super`, `shift`, `ctrl`, `alt`, joined by `+` (e.g. `"ctrl+alt"`). |
| `iconIsTemplate` | Boolean | (Mac) Treat icon as a template image (default `true`). |

`MenuItem` is an EventEmitter; it emits `click` when activated. The `key`/`modifiers` pair binds a keyboard accelerator, where `cmd` maps to ⌘ on macOS and the Windows key elsewhere.

> [!WARNING]
> Menus and trays created **in a page that can navigate or reload** stop working after navigation, because the JS engine garbage-collects them. Create long-lived menus and trays in the **background page** (via `main` as a JS file or `bg-script`). This is the single most common menu/tray bug.

### 4.6 `Tray` — system tray / status icon

```javascript
var tray = new nw.Tray({ title: 'Tray', icon: 'img/icon.png' });
var menu = new nw.Menu();
menu.append(new nw.MenuItem({ type: 'checkbox', label: 'Enabled' }));
tray.menu = menu;          // the only way to attach a menu
// later:
tray.remove(); tray = null;
```

| Member | Description |
|---|---|
| `new nw.Tray({title, tooltip, icon, alticon, iconsAreTemplates, menu})` | Create a tray icon. |
| `tray.title` | (Mac shows it; ignored on Win/Linux). |
| `tray.tooltip` | Hover text (all platforms). |
| `tray.icon` / `tray.alticon` (Mac) | Icon paths. |
| `tray.iconsAreTemplates` (Mac) | Template-image styling (default `true`). |
| `tray.menu` | Attach a `Menu` — setting this is the **only** way to give a tray a menu. |
| `tray.remove()` | Remove the icon (permanent; set the var to `null` afterward). |
| Event: `click` | Left-click only (right-click shows the menu; double-clicks ignored). |

### 4.7 `Clipboard`

```javascript
var clip = nw.Clipboard.get();
clip.set('I love NW.js', 'text');     // write
var text = clip.get('text');          // read
clip.clear();                         // empty
```

| Method | Description |
|---|---|
| `Clipboard.get()` | Get the clipboard object. |
| `clip.set(data, [type, [raw]])` | Write `data`. `type`: `text` (default), `png`, `jpeg`, `html`, `rtf`. `raw` (png/jpeg only) controls Base64 framing. |
| `clip.set(clipboardData)` / `clip.set(list)` | Write one object, or **multiple types at once** via an array. |
| `clip.get([type, [raw]])` / `clip.get(list)` | Read one or several types. |
| `clip.readAvailableTypes()` | List currently-available types. |
| `clip.clear()` | Empty the clipboard. |

Writing an image and an HTML `<img>` pointing at it, simultaneously:

```javascript
clip.set([
  { type: 'png',  data: base64png, raw: true },
  { type: 'html', data: '<img src="...">' }
]);
```

> [!NOTE]
> The X11 **selection clipboard** (middle-click paste buffer) is not supported.

### 4.8 `Shortcut` — global hotkeys

A `Shortcut` is a **system-wide** hotkey that fires even when your app has no focus.

```javascript
var shortcut = new nw.Shortcut({
  key: 'Ctrl+Shift+A',
  active: function () { console.log(this.key + ' pressed'); },
  failed: function (msg) { console.log('register failed: ' + msg); }
});
nw.App.registerGlobalHotKey(shortcut);
// later:
nw.App.unregisterGlobalHotKey(shortcut);
```

The `key` is zero or more modifiers plus one key, case-insensitive:

- **Modifiers:** `Ctrl`, `Alt`, `Shift`, `Command` (⌘ on Mac, Windows key elsewhere).
- **Keys:** `A`–`Z`, `0`–`9`, `F1`–`F24`, arrows, `Home`/`End`/`PageUp`/`PageDown`/`Insert`/`Delete`, media keys (`MediaPlayPause`, `MediaNextTrack`, …), punctuation (`Comma`, `Period`, `Tab`, `Backquote`, `Minus`, `Equal`, etc.), and [W3C key codes](http://www.w3.org/TR/DOM-Level-3-Events-code/).

`Shortcut` is an EventEmitter emitting `active` and `failed`.

> [!CAUTION]
> Registering a single key with **no modifier** (e.g. `{ key: "A" }`) hijacks that key system-wide until you unregister it. Only do this deliberately (e.g. for media keys).

### 4.9 `Screen` — displays and capture sources

`Screen` is a singleton EventEmitter; call `nw.Screen.Init()` once at startup.

```javascript
nw.Screen.Init();
nw.Screen.on('displayBoundsChanged', function (screen) { /* ... */ });
console.log(nw.Screen.screens);   // array of display descriptors
```

Each entry in `Screen.screens` has: `id`, `bounds` `{x,y,width,height}`, `work_area` `{x,y,width,height}`, `scaleFactor`, `isBuiltIn`, `rotation`, `touchSupport`.

| Member | Description |
|---|---|
| `Screen.Init()` | Initialize the singleton (call once). |
| `Screen.screens` | Array of connected-display descriptors. |
| `Screen.chooseDesktopMedia([sources], cb)` | Show the screen/window picker; `sources` is `["screen","window"]`; callback gets a `streamId` for `getUserMedia`. |
| Events | `displayBoundsChanged`, `displayAdded`, `displayRemoved` (each passes a screen descriptor). |

`Screen.DesktopCaptureMonitor` is a headless alternative for building your own capture-source UI, with `start(includeScreens, includeWindows)`, `stop()`, `registerStream(id)`, the `started` flag, and events `added`, `removed`, `orderchanged`, `namechanged`, `thumbnailchanged`.

### 4.10 The `<webview>` tag

`<webview>` embeds **untrusted guest content** in a **separate process** from your app, so the guest cannot reach your Node-powered page. Interactions are asynchronous.

```html
<webview id="foo" src="https://www.example.com/"
         style="width:640px; height:480px"></webview>
```

`<webview>` follows the [Chrome `<webview>` API](https://developer.chrome.com/docs/extensions/reference/webviewTag/). NW.js adds:

| Addition | Description |
|---|---|
| `webview.showDevTools(show, [container])` | Open DevTools for the guest. |
| `webview.inspectElementAt(x, y)` | Inspect an element (after DevTools is open). |
| `allownw` attribute | Turn **on** Node.js inside the webview (use with caution — webviews normally load untrusted content). |
| `partition="trusted"` | With a manifest `webview.partitions` entry, allows loading local files / `<all_urls>`. |
| `executeScript({mainWorld:true})` | Inject into the page's main world rather than an isolated world. |
| `webview.getCookieStoreId()` | Returns a store id for the [chrome.cookies](https://developer.chrome.com/extensions/cookies) API. |

> [!IMPORTANT]
> `<webview>` is the **safe** way to display remote/untrusted web content. Prefer it over `<iframe>` with `node-remote` whenever the content is not 100% yours.

---

## 5. Building Real Apps

### 5.1 JavaScript contexts

Understanding contexts is the key to avoiding mysterious bugs in NW.js. By default NW.js runs in **Separate Context Mode**, with two distinct JavaScript contexts:

- **Browser context** — where your `<script>` tags, jQuery, RequireJS, etc. run. Has DOM and Web APIs. Node objects (`nw`, `require`, `process`, `Buffer`, `global`) are **copied in** so you can use them.
- **Node context** — where `require()`-loaded modules and the `node-main` script run. Has Node globals (`__dirname`, `process`, `Buffer`) but **no Web APIs** (no `document`, no `alert`). One shared Node context backs the background page.

`require()` path resolution differs by context:

| Caller's context | Relative paths resolved against |
|---|---|
| Node context | the calling module's own directory (normal Node behavior) |
| Browser context | the app root (the `package.json` directory) |

**Mixed Context Mode** (start NW.js with `--mixed-context`, or add it to `chromium-args`) merges the two: a fresh Node context is created alongside each browser context, so Node modules can touch DOM/`nw` APIs and vice versa.

```json
{ "name": "demo", "main": "index.html", "chromium-args": "--mixed-context" }
```

| | Separate Context (default) | Mixed Context |
|---|---|---|
| Variable sharing across windows | Easy (shared Node context) | Hard (use `postMessage`) |
| `instanceof` / type-check pitfalls | More common | Fewer |
| Node + DOM in one module | No | Yes |

> [!WARNING]
> **Cross-context `instanceof` fails.** A value passed from another window/frame has a different prototype chain, so `value instanceof Array` can be `false` for a real array. Use context-safe checks: `Array.isArray(x)`, or `Object.prototype.toString.call(x) === '[object Date]'`. Do **not** turn on Mixed Context if your app relies on sharing variables between windows via the Node context.

### 5.2 Node and DOM extensions

NW.js extends both the DOM and Node beyond what a browser or Node alone provide.

**Extended `<input type="file">`** (Node frames only, for security):

| Feature | Description |
|---|---|
| `fileinput.value` | Native filesystem path of the selected file. |
| `file.path` | Native path on each item of the `files` list. |
| `nwdirectory` attr | Select a **directory** and return its path. |
| `nwdirectorydesc` attr | Title for the directory dialog (default "Select Folder"). |
| `nwsaveas[="name"]` attr | Open a **Save As** dialog (allows choosing a non-existent file). |
| `nwworkingdir` attr | Initial directory for the dialog. |
| `oncancel` event | Fires when the user cancels the dialog. |

```html
<input type="file" nwsaveas="report.txt" nwworkingdir="/home/me/">
```

**Extended `<iframe>` attributes:**

| Attribute | Effect |
|---|---|
| `nwdisable` | Make the frame a **normal** frame (disables `require`, `nw`, Node APIs). |
| `nwfaketop` | Hide the real `window.parent`/`window.top` from the frame. |
| `nwUserAgent` | Override the User-Agent for the frame. |

> [!NOTE]
> `nwdisable` alone is enough to sandbox an iframe from Node. Adding `nwfaketop` also blocks `window.parent`/`window.top` — but it breaks `window.parent.postMessage()`, so only use it when you specifically need to hide the parent.

**Node changes:** `console.log()` output is redirected to the DevTools Console. The `process.versions` object gains `nw`, `chromium`, and `nw-flavor` (`'sdk'` or `'normal'`); `process.mainModule` points at your `main` (or `node-main` if set).

### 5.3 Reading a user-selected file end-to-end

```html
<input type="file" id="picker">
<pre id="out"></pre>
<script>
const fs = nw.require('fs');
document.getElementById('picker').addEventListener('change', function () {
  const path = this.value;                 // native path (NW.js extension)
  fs.readFile(path, 'utf8', (err, txt) => {
    document.getElementById('out').textContent = err ? String(err) : txt;
  });
});
</script>
```

---

## 6. Security

NW.js apps run with the **full privileges of the user**: a Node-enabled page can read and write any file, spawn processes, and open sockets. Treat security as a first-class concern.

### 6.1 Node frames vs. normal frames

There are two kinds of frames. **Node frames** can use Node/NW.js APIs, the extended DOM features, and bypass sandbox / same-origin restrictions. **Normal frames** cannot. A frame is a **node frame** only if **all** of these hold:

- `nodejs` is `true` in the manifest, **and**
- the frame's URL matches a `node-remote` pattern, or uses the `chrome-extension://` protocol, **and**
- neither the frame nor a parent has the `nwdisable` attribute, **and**
- the frame is **not** inside a `<webview>`.

### 6.2 Practical rules

> [!CAUTION]
> - **Never** point `node-remote` at an origin you don't fully control. It grants that website full machine access.
> - Load untrusted or third-party web content in a **`<webview>`** (separate process, no Node), not an `<iframe>`.
> - Sandbox local iframes that don't need Node with the **`nwdisable`** attribute.
> - Be cautious enabling `allownw` on a webview — it defeats the webview's isolation.

### 6.3 Tamper-resistance options

NW.js cannot make a desktop app fully tamper-proof, but it offers layers:

- **`nwjc`** — compile JS to a platform-specific binary so the source isn't shipped ([Section 7.4](#74-protecting-your-javascript-source)).
- **Content verification** — sign your app files so a stock NW.js binary refuses to load modified files ([Section 8.5](#85-content-verification-app-signing)).
- Move sensitive logic into a **native Node module** or NaCl.

> [!NOTE]
> These deter casual tampering but do not stop a determined attacker from running your files under a different NW.js binary. Combine measures and keep real secrets on a server.

---

## 7. Common Tasks (Recipes)

### 7.1 Debugging with DevTools

DevTools is available **only in the SDK build**.

- **Open it:** press <kbd>F12</kbd> (Windows/Linux) or <kbd>⌘</kbd>+<kbd>⌥</kbd>+<kbd>i</kbd> (Mac), or call `win.showDevTools()`.
- **Debug Node modules (Separate Context):** right-click the app and choose **Inspect Background Page**; the debugger stops there when stepping into Node code.
- **Debug Node modules (Mixed Context):** Node and DOM share one DevTools window.
- **Remote debugging:** launch with `--remote-debugging-port=9222`, then open `http://localhost:9222/` in a browser.

**DevTools extensions** (React, Vue, etc.) are supported: add the permission `"chrome-extension://*"` to the extension's `manifest.json` and load it with `--load-extension=path/to/extension`.

```bash
nw --load-extension=path/to/react-devtools path/to/app
```

> [!TIP]
> For Vue: `npm install --save-dev nw-vue-devtools-prebuilt`, then add
> `"chromium-args": "--load-extension='./node_modules/nw-vue-devtools-prebuilt/extension'"` to your manifest. Use the unminified `vue.js`, not `vue.min.js`.

### 7.2 Getting editor autocomplete for `nw`

Install the types and turn on TypeScript checking — no TypeScript required:

```bash
npm install --save-dev nwjs-types
```

Add `// @ts-check` to the top of your JS file; VS Code then autocompletes `nw.`.

### 7.3 Using native Node modules

Native (C/C++) modules must be rebuilt against NW.js headers because NW.js's V8 ABI differs from stock Node.

=== "NW.js 0.111.3+ (node-gyp)"

    Standard `node-gyp` works directly with NW.js's Node-compatible headers:

    ```bash
    npm install --target=0.111.3 --disturl=https://dl.nwjs.io/
    # or, to rebuild an existing module:
    node-gyp rebuild --target=0.111.3 --disturl=https://dl.nwjs.io/
    ```

=== "Before 0.111.3 (nw-gyp)"

    Older NW.js needs `nw-gyp`:

    ```bash
    npm install -g nw-gyp
    export npm_config_target=0.18.5
    export npm_config_arch=x64
    export npm_config_runtime=node-webkit
    export npm_config_build_from_source=true
    export npm_config_node_gyp=$(which nw-gyp)
    npm install
    ```

=== "node-pre-gyp packages"

    ```bash
    node-pre-gyp build --runtime=node-webkit --target=0.111.3 --target_arch=x64
    ```

> [!WARNING]
> On Windows you need a C++ compiler toolchain (Visual Studio with "Desktop development with C++"). After switching NW.js versions, delete `node_modules` and run a full `npm install` again. `node_modules` built for one platform are **not** portable — rebuild on every target platform.

### 7.4 Protecting your JavaScript source

The SDK build ships `nwjc`, which compiles JS to native code so you can ship the binary instead of the source.

```bash
nwjc source.js binary.bin          # compile a script
nwjc --nw-module mod.js mod.bin    # compile an ES module
```

Load it at runtime:

```javascript
nw.Window.get().evalNWBin(null, 'binary.bin');   // null = main frame
```

You can also fetch a compiled binary over the network as an `ArrayBuffer` and run it on the fly via `evalNWBin(null, xhr.response)`, and Web Workers can run binaries via `importNWBin(arrayBuffer)`.

> [!IMPORTANT]
> Compiled binaries are **not** cross-platform or cross-version. Run `nwjc` once **per platform** (and re-run after upgrading NW.js) when packaging. Compiled code runs in the browser context. (Pre-0.22 it ran ~30% slower; fixed in 0.22.0.)

### 7.5 Leaving fullscreen with the Esc key

NW.js does not bind Esc to exit fullscreen by default — wire it up yourself:

```javascript
nw.App.registerGlobalHotKey(new nw.Shortcut({
  key: 'Escape',
  active: function () { nw.Window.get().leaveFullscreen(); }
}));
```

### 7.6 Transparent windows

Set `frame: false` and `transparent: true`, give the body a transparent background, and (on Linux) pass the right flags:

```json
{ "window": { "frame": false, "transparent": true } }
```

```html
<body style="background-color:rgba(0,0,0,0);">
```

| Platform | Requirement |
|---|---|
| Windows | Vista+ with DWM enabled (no classic theme / basic mode / remote desktop). |
| Linux | Run with `--enable-transparent-visuals --disable-gpu`; WM must support compositing. |
| Click-through (Win/Mac) | Run with `--disable-gpu-compositing --force-cpu-draw`; works on frameless, non-resizable windows. |

### 7.7 Auto-updating your app

NW.js has no built-in updater; use a community module:

| Module | Notes |
|---|---|
| [`nw-autoupdater`](https://github.com/dsheiko/nw-autoupdater) | Modern async/await API; reads a remote manifest, downloads the matching platform package, unpacks, swaps, and restarts. |
| [`nwjs-autoupdater`](https://github.com/oaleynik/nwjs-autoupdater) | Tiny Go helper (~2 MB) that unpacks updates; can update itself and needs no elevated privileges. |
| [`node-webkit-updater`](https://github.com/edjafarov/node-webkit-updater) | Low-level building blocks; you write the update flow. |

---

## 8. Packaging & Distribution

To ship, you redistribute the NW.js binaries **together with** your app files.

### 8.1 Prepare your app

- [ ] Gather source code and resources
- [ ] `npm install` your dependencies **on each target platform**
- [ ] Rebuild native Node modules ([Section 7.3](#73-using-native-node-modules))
- [ ] Compile JS with `nwjc` and remove the originals (if protecting source)
- [ ] Provide the window/app `icon`

> [!CAUTION]
> Linux and most macOS filesystems are **case-sensitive** — `Test.js` ≠ `test.js`. Verify the case of every path. On Windows, keep build paths short (e.g. `C:\build\`) to avoid the 260-character path limit during `npm install`.

### 8.2 Build flavors

| Flavor | DevTools / NaCl | Use for |
|---|---|---|
| **SDK** | Yes | Development & testing |
| **Normal** | No | Production (smaller download) |

Check at runtime with `process.versions['nw-flavor']` (`'sdk'` or `'normal'`).

> [!NOTE]
> Ship **all** files from the downloaded package **except** the SDK-only tools `nwjc`, `payload`, and `chromedriver` — those are development tools, not runtime files.

### 8.3 Package your app

=== "Plain files (recommended)"

    **Windows/Linux:** put your app files in the same folder as `nw`(`.exe`), with `package.json` alongside the binary — or in a subfolder named `package.nw`.

    **macOS:** put your files in `nwjs.app/Contents/Resources/app.nw`.

=== "Zip (package.nw)"

    Zip your files, rename to `package.nw` (Win/Linux) or `app.nw` (Mac, placed in `Contents/Resources/`).

    Optionally append the zip to the binary to hide it:

    ```bash
    # Windows
    copy /b nw.exe+package.nw app.exe
    # Linux
    cat nw package.nw > app && chmod +x app
    ```

> [!WARNING]
> A large `package.nw` or one with many files starts **slowly**, because NW.js unzips it to a temp folder on launch. Prefer plain files for big apps.

### 8.4 Platform-specific steps

| Platform | Tasks |
|---|---|
| **Windows** | Replace the `nw.exe` icon (Resource Hacker, nw-builder, node-winresourcer); build an installer (NSIS, Inno Setup, Windows Installer). |
| **Linux** | Create a `.desktop` file; ship via `makeself`/`shar` or distro packages (`apt`, `yum`, `pacman`). |
| **macOS** | Replace `Contents/Resources/nw.icns`; edit `Contents/Info.plist` (bundle id, `CFBundleDisplayName`); rename helpers; **sign the app** or Gatekeeper blocks it. Use `product_string` in the manifest to rename the Helper. |

The high-level [`nw-builder`](https://github.com/nwjs-community/nw-builder) tool automates much of this.

### 8.5 Content verification (app signing)

Prevents a stock NW.js binary from loading **modified** app files. The public key is built into NW.js; you sign your files with a private key into `verified_contents.json`, then launch with enforcement:

```bash
nw --verify-content=enforce_strict .
```

Signing with the demo keypair (tools live in `tools/sign/`):

1. `cd` into the app directory; remove any existing `verified_contents.json` / `computed_hashes.json`.
2. Run `payload` (e.g. `payload.exe`) → generates `payload.json`.
3. `python sign.py > /tmp/verified_contents.json` (write outside the app dir).
4. Move `verified_contents.json` into the app directory.

To use your own keypair you must rebuild NW.js with your public key compiled in. Verification modes are `none`, `bootstrap`, `enforce`, and `enforce_strict`.

> [!NOTE]
> Content verification does not stop someone from loading your files with a *different* NW.js binary. Combine it with `nwjc` and native modules for defense in depth.

### 8.6 macOS App Store

Download the **MAS build** from nwjs.io, then sign with `build_mas.py` (add `--pkg` to produce an uploadable `.pkg`):

```bash
python build_mas.py -C build.cfg -I myapp-dev.app -O MyApp.app
```

The `build.cfg` specifies `ApplicationIdentity`, `InstallerIdentity`, `NWTeamID` (your Apple Team ID, used to set up IPC), and parent/child entitlements. By default the app is sandboxed (`com.apple.security.app-sandbox`).

### 8.7 Verifying downloaded binaries

From 0.32.0 on, each download directory has a `SHASUMS256.txt`:

```bash
curl -O https://dl.nwjs.io/vX.Y.Z/SHASUMS256.txt
grep nwjs-vX.Y.Z.tar.gz SHASUMS256.txt | sha256sum -c -
```

Stable (non-nightly) releases also ship GPG detached signatures.

---

## 9. Migration: 0.12 → 0.13

NW.js 0.13 was a major architecture change (the "nw2" line). If you maintain an older app, the highlights:

- Apps now run **internally as a Chrome App**; the default protocol changed from `file://`/`app://` to **`chrome-extension://`**. All `chrome.*` platform APIs are available.
- All NW APIs (and `require()`) moved into the global **`nw`** object. The `nw.gui` library still works as a compatibility wrapper.
- The Node context lives in the background page's DOM context, shared across windows.

**Deprecated / changed manifest fields:**

| Field | Change |
|---|---|
| `single-instance` | Deprecated; always `true`. Use the `App` `open` event for second-launch handling, or a different `--user-data-dir`. |
| `toolbar` | Deprecated; always `false`. Use <kbd>F12</kbd> and `win.reload()`/`win.reloadDev()`. |
| `no-edit-menu`, `snapshot` | Deprecated (`snapshot` → use `win.evalNWBin()`). |
| `node-remote` | Now an array of Chrome match patterns. |
| `always-on-top`, `visible-on-all-workspaces`, `new-instance`, `inject-js-start/end` | Renamed with underscores: `always_on_top`, `visible_on_all_workspaces`, `new_instance`, `inject_js_start`, `inject_js_end`. |
| `--data-path` (CLI) | Renamed `--user-data-dir`. |

**Window API changes:** windows now have an `id` for geometry persistence; `Window.open()` and `showDevTools()` pass the created window to the callback; `unmaximize`/`leave-fullscreen` events were replaced by `restore`; `setTransparent()` and `capturepagedone` are deprecated; `Window` is no longer an `EventEmitter` subclass (but `on`/`once`/`removeListener`/`removeAllListeners` still work). `Shortcut` no longer maps `Ctrl`→⌘ on Mac — use the cross-platform `Command` modifier and detect the OS yourself.

---

## 10. Troubleshooting

Format: each entry gives the symptom, the cause, and the fix.

### Error: `var crypto = require('crypto')` returns the wrong object

**Cause:** Chromium defines a global `crypto` object that cannot be overwritten, so your variable named `crypto` collides with it.
**Solution:** rename your variable, e.g. `const nodeCrypto = require('crypto');`.
**Prevention:** avoid variable names that collide with Web globals (`crypto`, `global`, `screen`).

### Error: `Failed to load resource ... net::ERR_UNKNOWN_URL_SCHEME` (images broken in AngularJS)

**Cause:** AngularJS prefixes unknown URL schemes with `unsafe:`; NW.js URLs use the `chrome-extension:` scheme, which Angular doesn't whitelist.
**Solution:** whitelist the scheme in your Angular config:

```javascript
myApp.config(['$compileProvider', function ($compileProvider) {
  $compileProvider.imgSrcSanitizationWhitelist(/^\s*((https?|ftp|file|blob|chrome-extension):|data:image\/)/);
  $compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|ftp|mailto|tel|file|chrome-extension):/);
}]);
```

### Error: exception reporting missing in AngularJS 2+

**Cause:** Angular 2 registers exception handlers on the global `global`, which already exists in NW.js.
**Solution:** rename `global` before loading Angular:

```html
<script>window.nw_global = window.global; window.global = undefined;</script>
```

### Issue: `console.log` produces no output in a Linux terminal

**Cause:** GUI apps don't write to stdout by default.
**Solution:** launch with `--enable-logging=stderr`.

### Issue: a custom `--user-data-dir` still creates an `AppData` folder (Windows)

**Cause:** known behavior (issue #6328) — a default data folder is still created even when a custom directory is set.
**Solution:** track the upstream issue; rely on the explicit `--user-data-dir` for your actual data and ignore the empty default folder.

### Issue: Node ESM built-in modules can't be imported in the DOM/Browser context

**Cause:** Node and Chromium use different ES-module resolvers (issue #8333).
**Solution:** enable the chained resolvers with `--enable-features=NWESM,NWChainImportNode` (or `NWChainImportDom`), and load ESM via `node-main` (0.98.2+). See [Section 11.2](#112-command-line-options).

### Issue: `nwjc`-generated file throws `SyntaxError: Unexpected identifier`

**Cause:** loading a compiled binary built for a different NW.js version/platform, or mismatched module flags (issue #8289).
**Solution:** recompile with `nwjc` using the **same** NW.js version and platform; use `--nw-module` + `evalNWBinModule()` for modules.

### Issue: window positioning fails on Wayland (Linux)

**Cause:** Wayland restricts programmatic window placement (issue #8348).
**Solution:** run under X11/XWayland if precise positioning is required; set positions in the window's callback rather than synchronously.

### Issue: native module crashes or won't load

**Cause:** built against stock Node's ABI, not NW.js's.
**Solution:** rebuild with `node-gyp --target=<ver> --disturl=https://dl.nwjs.io/` (0.111.3+) or `nw-gyp`; delete `node_modules` and reinstall after upgrading NW.js. See [Section 7.3](#73-using-native-node-modules).

### Issue: menu or tray stops working after navigation/reload

**Cause:** the menu/tray was created in a page that navigated, so it was garbage-collected.
**Solution:** create menus and trays in the **background page** (`main` as JS, or `bg-script`). See the warning in [Section 4.5](#45-menu-and-menuitem).

### Issue: a real array fails `value instanceof Array`

**Cause:** the value came from another JavaScript context with a different prototype chain.
**Solution:** use `Array.isArray(value)` or `Object.prototype.toString.call(value)`. See [Section 5.1](#51-javascript-contexts).

### Issue: `--single-process` doesn't work

**Cause:** known limitation (issue #8173).
**Solution:** avoid relying on single-process mode; if testing crash handling with it, also set `--disable-crash-handler=true`.

---

## 11. Reference

### 11.1 The `nw` global — quick index

| Browser-context global | Equivalent | Notes |
|---|---|---|
| `nw` | — | All NW.js APIs. |
| `require` | `nw.require` | Also supports `require('nw.gui')`. |
| `process` | `nw.process` | Node process module. |
| `global` | `nw.global` | Node global object. |
| `Buffer` | — | Node Buffer class. |

### 11.2 Command-line options

Pass these when launching `nw`, or bake them into `chromium-args` in the manifest.

| Option | Description |
|---|---|
| `--url=<url>` | Load `<url>` with the default app. |
| `--mixed-context` | Run in [Mixed Context Mode](#51-javascript-contexts). |
| `--nwapp=<path>` | Alternative way to specify the app path (useful with ChromeDriver). |
| `--user-data-dir=<dir>` | Override the data directory (stored data, caches, crash dumps). |
| `--disable-devtools` | Disable user access to DevTools in an SDK build. |
| `--enable-node-worker` | Enable Node.js integration in Web Workers. |
| `--enable-features=NWESM` | Allow ESM in the Node context (combine with `NWChainImportNode`/`NWChainImportDom`). |
| `--disable-raf-throttling` | Keep `requestAnimationFrame` firing while minimized/hidden (useful for games). |
| `--disable-cookie-encryption` | Don't encrypt the on-disk cookie store (testing only). |
| `--disable-crash-handler=true` | Disable the crash handler (with `--single-process`, yields a single process). |
| `--enable-gcm` | Enable the `chrome.gcm` API. |
| `--enable-transparent-visuals` | Transparent-window support (Linux). |
| `--disable-transparency` | Disable transparent windows entirely. |
| `--disable-gpu` | Disable GPU (used with transparency / click-through). |
| `--force-cpu-draw` | Force CPU drawing (transparency click-through). |
| `--remote-debugging-port=<port>` | Expose the DevTools protocol on a port. |
| `--load-extension=<path>` | Load a (DevTools) extension. |
| `--enable-logging=stderr` | Send `console.log` to the terminal. |
| `--verify-content=<mode>` | Content verification: `none`/`bootstrap`/`enforce`/`enforce_strict`. |

**Node-context option** (place in `node-main`, not the command line):

| Option | Description |
|---|---|
| `--nw-node-inspector` | Enable the V8 inspector in Node (replaces Blink's; breaks `console.log` in the DOM). |

**Environment variables**

| Variable | Effect |
|---|---|
| `NW_PRE_ARGS` | Prepended to the value of `chromium-args`. |
| `NW_DESKTOP` | (Linux/Ubuntu) `.desktop` file used by `setProgressBar`/`setBadgeLabel` (default `nw.desktop`). |

You can also use any [Chromium command-line switch](http://peter.sh/experiments/chromium-command-line-switches/).

### 11.3 Manifest field index

See [Section 3](#3-the-manifest-packagejson) for full descriptions. Top-level: `main`, `name`, `version`, `product_string`, `nodejs`, `node-main`, `domain`, `bg-script`, `user-agent`, `node-remote`, `chromium-args`, `js-flags`, `crash_report_url`, `inject_js_start`, `inject_js_end`, `additional_trust_anchors`, `dom_storage_quota`, `window`, `webkit`, `webview`. Deprecated: `single-instance`, `toolbar`, `no-edit-menu`, `snapshot`.

### 11.4 Getting help

- Official docs: **http://docs.nwjs.io/**
- Wiki (community-editable): https://github.com/nwjs/nw.js/wiki
- Mailing list: https://groups.google.com/d/forum/nwjs-general
- Chat: https://gitter.im/nwjs/nw.js
- Issues: https://github.com/nwjs/nw.js/issues

---

_Generated June 2026 from nwjs/nw.js at 036da15. AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it. Unofficial; not affiliated with or reviewed by the project._
