Jesse A. Green / notebook

NW.js User Guide

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.

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.

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:

{
  "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).

Step 2 — Create index.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

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

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

macOS

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

Linux

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

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

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:

<!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 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:

<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):

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.


2. Installation & Project Layout

2.1 Anatomy of an NW.js project

A minimal project is just a folder:

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:

// 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.

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).


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

{
  "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). 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 (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.

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 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:

.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):

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

3.7 A realistic manifest

{
  "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).
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:

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:

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 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.

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):

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.
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:

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):

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:

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):

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 MenuItems.
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

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

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:

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.

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.

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.

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.

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

<webview> follows the Chrome <webview> API. 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 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.

{ "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.
<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

<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).
  • Content verification — sign your app files so a stock NW.js binary refuses to load modified files (Section 8.5).
  • 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 F12 (Windows/Linux) or ⌘+⌥+i (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.

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:

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:

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:

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

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.

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

Load it at runtime:

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:

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:

{ "window": { "frame": false, "transparent": true } }
<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 Modern async/await API; reads a remote manifest, downloads the matching platform package, unpacks, swaps, and restarts.
nwjs-autoupdater Tiny Go helper (~2 MB) that unpacks updates; can update itself and needs no elevated privileges.
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)
  • [ ] 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:

# 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 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:

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):

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:

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 F12 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:

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:

<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.

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.

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.

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.

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.
--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.

11.3 Manifest field index

See Section 3 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