Jesse A. Green / notebook

NW.js Admin & Architecture 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.

This guide is the deep reference for the people who build, ship, secure, and extend applications on NW.js — release engineers, architects, security reviewers, and contributors to NW.js itself. Where the User Guide explains how to use the nw.* APIs, this guide explains how NW.js works internally: how it fuses Chromium and Node.js, how the JavaScript APIs cross the process boundary into native C++, how an app is loaded as an internal Chrome extension, how the runtime is built from source, and how to operate it in production (signing, distribution, hardening, crash reporting).

Written from the NW.js source tree at the commit recorded in the footer.


1. Executive Summary

What NW.js is. NW.js is an application runtime — not a library you import, but a modified build of the Chromium browser whose renderer also embeds a full Node.js. It lets a single JavaScript heap host both the DOM/Web Platform (from Blink/V8) and the Node.js core API and npm ecosystem. Developers write desktop apps in HTML/CSS/JS; NW.js supplies the native window, menu, tray, clipboard, global-hotkey, and screen capabilities, plus filesystem and OS access via Node.

Where the code lives. This repository (nwjs/nw.js) is one piece of a much larger system. The buildable product is assembled from several repos that are checked out into Chromium's source tree:

Path in the Chromium superproject Repository Role
src/content/nw nwjs/nw.js (this repo) NW.js's own C++/JS source, the landing page, and the issue tracker.
src/third_party/node-nw nwjs/node A modified Node.js.
src/v8 nwjs/v8 A modified V8 (shared by Blink and Node).
src nwjs/chromium.src The patched Chromium that hosts it all.

This is why a clone of this repository, by itself, does not build into a runnable binary: the code here is compiled inside a Chromium checkout. (It also explains why most of this repository's file count is the regression-test suite under test/, not product code — see Section 11.)

Version under analysis. src/nw_version.h defines NW_MAJOR_VERSION 0, NW_MINOR_VERSION 112, NW_PATCH_VERSION 0 → 0.112.0, a release build (NW_VERSION_IS_RELEASE 1). The README corroborates v0.112.0 (May 24, 2026), based on Node.js v26.1.0 and Chromium 149.

The defining architectural fact. Since 0.13, an NW.js application runs internally as a Chrome App / extension. App pages execute in a privileged_extension context under the chrome-extension:// protocol; the nw.* APIs are registered as extension APIs; and the bridge from JavaScript to native behavior is the Chromium ExtensionFunction mechanism. Understanding this single design choice explains nearly everything else in the codebase.


2. System Architecture

2.1 The two-engine model

NW.js merges two runtimes that were never designed to coexist:

            ┌─────────────────────────────────────────────┐
            │                  NW.js process                │
            │                                               │
   Browser  │   ┌───────────────┐      ┌────────────────┐  │
   process  │   │ Chromium       │      │ Node.js        │  │
            │   │ browser logic  │      │ (third_party/  │  │
            │   │ (UI, IPC hub)  │      │  node-nw)      │  │
            │   └───────┬────────┘      └───────┬────────┘  │
            └───────────┼───────────────────────┼──────────┘
                        │ Mojo / legacy IPC      │ shared V8 heap
            ┌───────────┼───────────────────────┼──────────┐
   Renderer │   ┌───────▼────────┐      ┌───────▼────────┐  │
   process  │   │ Blink (DOM)    │◄────►│ Node event loop│  │
   (per     │   │ V8 context(s)  │ same │ in DOM context │  │
   window)  │   └────────────────┘ heap └────────────────┘  │
            └───────────────────────────────────────────────┘

The key engineering achievement: Node and Blink run in the same thread and share one V8 heap, so a DOM object and a Node object can reference each other directly — no serialization, no marshalling. This is why require('fs') works inside <script> and why process.version prints in an HTML page.

2.2 Process model

NW.js inherits Chromium's multi-process architecture:

  • Browser process — owns the UI, the window list, the extension/app system, and is the IPC hub. NW.js's native window/menu/tray/clipboard operations execute here.
  • Renderer process(es) — one (or more) per window; runs Blink + V8 + the embedded Node event loop. Your app's JavaScript runs here.
  • Helper / child processes — GPU, utility, etc. (On macOS these are the per-role Helper apps; product_string renames them.)

The nw.* APIs that manipulate native UI therefore cannot run directly in the renderer — they must call across to the browser process. That bridge is the subject of the next section.

2.3 The JS → native bridge

The nw.* objects you call in your page are thin JavaScript shims (in src/api/*/*.js). They don't do the native work themselves; they forward to C++ in the browser process through three mechanisms:

1. Static methods via ExtensionFunction. Each native operation is a C++ class declared with the DECLARE_EXTENSION_FUNCTION macro and a string name. For example, in src/api/nw_app_api.h:

class NwAppQuitFunction : public ExtensionFunction {
  ResponseAction Run() override;
  DECLARE_EXTENSION_FUNCTION("nw.App.quit", UNKNOWN)
};

The JS shim (src/api/app/app.js) calls nw.callStaticMethod('App', 'Quit', []), which is routed by Chromium's extension-function dispatcher to the matching C++ class in the browser process. Synchronous variants subclass NWSyncExtensionFunction and implement RunNWSync(...).

The complete set of registered native functions (one per file, grouped by API object):

Header Registered functions (DECLARE_EXTENSION_FUNCTION)
nw_app_api.h nw.App.quit, .closeAllWindows, .enableComponent, .updateComponent, .getArgvSync, .clearCache, .clearAppCache, .setProxyConfig, .getDataPath, .crashBrowser
nw_window_api.h ~30 nw.currentWindowInternal.* functions: close, showDevToolsInternal, showDevTools2Internal, closeDevTools, capturePageInternal, clearMenu, setMenu, setShadowInternal, setBadgeLabelInternal, requestAttentionInternal, setProgressBarInternal, reloadIgnoringCache, getZoom/setZoom, enter/leave/toggleKioskModeInternal, isKioskInternal, setShowInTaskbarInternal, setTitleInternal/getTitleInternal, getWinParamInternal, getCurrent, getPrinters, setPrintSettingsInternal
nw_clipboard_api.h nw.Clipboard.getListSync, .setListSync, .clearSync, .readAvailableTypes
nw_menu_api.h nw.Menu.getNSStringWithFixup, .getNSStringFWithFixup (Mac string fixups)
nw_screen_api.h nw.Screen.getScreens, .initEventListeners, .startMonitor, .stopMonitor, .isMonitorStarted, .registerStream
nw_shell_api.h nw.Shell.openItem, .openExternal, .showItemInFolder
nw_shortcut_api.h nw.Shortcut.registerAccelerator, .unregisterAccelerator
nw_object_api.h nw.Obj.allocateId, .create, .destroy, .callObjectMethod, .callObjectMethodSync, .callObjectMethodAsync

2. Stateful objects via the ObjectManager. APIs like Window, Menu, MenuItem, and Tray have a JS object on the renderer side and a paired C++ object on the browser side. The pairing is managed by nw::ObjectManager (src/api/object_manager.h), a Chromium KeyedService (one per BrowserContext):

class ObjectManager : public KeyedService {
  static Base* GetApiObject(int id);          // C++ object by id
  static int  AllocateId();
  void OnAllocateObject(int id, const std::string& type, ...);
  void OnCallObjectMethod(... int object_id, type, method, args);
  void OnCallObjectMethodSync(...);
  void SendEvent(Base* object, const std::string& event, const ListValue& args);
};

The renderer calls nw.Obj.create (allocating a C++ object of a given type with options), then nw.Obj.callObjectMethod[Sync/Async] to invoke methods on it by id. Native events flow the other way: the C++ object calls ObjectManager::SendEvent, which delivers an event to the JS object — this is how win.on('minimize', ...) and tray clicks work. ObjectManagerFactory (a BrowserContextKeyedServiceFactory) wires the service into each browser context.

3. Renderer-side custom bindings. Some operations are implemented as native handlers inside the renderer, in NWCustomBindings (src/nw_custom_bindings.h), an ObjectBackedNativeHandler:

class NWCustomBindings : public ObjectBackedNativeHandler {
  void CrashRenderer(...);
  void EvalScript(...);        // win.eval
  void EvalNWBin(...);         // win.evalNWBin (compiled binaries)
  void GetAbsolutePath(...);
  void AddOriginAccessWhitelistEntry(...);
  void RemoveOriginAccessWhitelistEntry(...);
  void GetProxyForURL(...);    // App.getProxyForURL
  void SetDevToolsJail(...);
  void GetWidgetToken(...);
  void CallInWindow(...);
};

These run in-process because they touch the renderer's own V8/Blink state directly (script evaluation, origin-access lists, proxy lookup).

2.4 API surface registration

src/api/_api_features.json declares each nw.* namespace as a stable extension API restricted to the privileged_extension context:

{
  "nw.App":    { "channel": "stable", "contexts": ["privileged_extension"] },
  "nw.Window": { "channel": "stable", "contexts": ["privileged_extension"] },
  "nw.Clipboard": { ... }, "nw.Menu": { ... }, "nw.MenuItem": { ... },
  "nw.Shell": { ... }, "nw.Tray": { ... }, "nw.Screen": { ... },
  "nw.Shortcut": { ... }, "nw.Obj": { ... },
  "nw.currentWindowInternal": {
     "internal": true, "matches": ["<all_urls>"],
     "contexts": ["privileged_extension", "web_page"]
  }
}

The nw.Obj and nw.currentWindowInternal namespaces are the internal plumbing behind the public Window/Menu/Tray/etc. objects — they are not meant to be called directly by app code.

2.5 The package and content hooks

Two C++ components anchor the runtime behavior of an app:

nw::Package (src/nw_package.h) models the loaded package.json. It is constructed from the command line or a path and exposes:

  • GetStartupURL(), GetName(), GetUseNode(), GetUseExtension()
  • root() / window() — the parsed manifest dictionary and its window subobject
  • ReadChromiumArgs() and ReadJsFlags() — parse chromium-args and js-flags
  • self-extraction support: ExtractPackage(zip, ...) unzips a package.nw/app.nw into a ScopedTempDir (self_extract() flags this case). This is what makes the "append the zip to the binary" packaging trick work.

nw_content.cc (declared in src/nw_content.h) is the set of hook functions patched into Chromium's lifecycle. The most important for app behavior:

Hook What it powers
LoadNWAppAsExtensionHook(manifest, error) Loads the app's package.json as an internal extension manifest.
ContextCreationHook(frame, context) Injects the nw bindings when a V8 context is created.
DocumentElementHook(...) / DocumentHook2(...) Runs inject_js_start; fires document-start.
DocumentFinishHook(...) Runs inject_js_end; fires document-end.
willHandleNavigationPolicy(...) Implements the new-win-policy and navigation events.
CalcNewWinParams(...) Computes options (incl. injected JS) for newly-opened windows.
OverrideWebkitPrefsHook(...) Applies manifest-driven WebKit preferences.
GetUserAgentFromManifest / SetUserAgentOverride Implements the user-agent manifest field and its %-placeholders.
MergeManifest() Merges the per-new-window manifest set via setNewWindowManifest.
ExecuteAppCommandHook, ProcessSingletonNotificationCallbackHook Single-instance handling → the App open event.
ShowDevtools, InspectElement DevTools control (SDK builds).
Mac-only: ApplicationShouldHandleReopenHook (reopen event), OSXOpenURLsHook, NWChangeAppMenu, NWSetNSWindowShowInTaskbar, NWSetNSAppKioskOptions macOS integration.

2.6 Custom protocol

src/nw_protocol_handler.h defines NwProtocolHandler, a net::URLRequestJobFactory::ProtocolHandler that serves NW's internal scheme — part of how app resources are resolved under chrome-extension://.

2.7 Export macro

src/nw_export.h defines NW_EXPORT for symbol visibility across the component-build boundary (__declspec(dllexport/dllimport) on Windows, visibility("default") elsewhere) — relevant when NW.js is built as a shared library in a component build.


3. Requirements

3.1 To run an NW.js app (end users)

Platform Minimum
Windows Win10+ for current builds (legacy builds for XP). 32- or 64-bit binaries. Transparency needs DWM (Vista+).
macOS 10.10+. Apps must be signed (Gatekeeper). arm64 builds shipped.
Linux x64 or arm64; a desktop environment with a window manager. Transparency needs a compositing WM.

No separate Node.js or Chromium install is required — both are embedded in the redistributed binaries.

3.2 To build NW.js from source (see Section 9)

  • Chromium's depot_tools and platform build prerequisites.
  • 20 GB+ of download for gclient sync, plus tens of GB of build output.
  • A strong machine: ≥8 CPU cores, SSD, ≥8 GB RAM (more is better); a full build takes hours.
  • Windows: set DEPOT_TOOLS_WIN_TOOLCHAIN=0; Visual Studio toolchain.

3.3 To rebuild native Node modules (app developers)


4. Configuration Mastery

Configuration is split across three surfaces. All three are documented exhaustively in the User Guide; this section frames them for operators and notes the security-sensitive items.

4.1 The three configuration surfaces

Surface Where Scope
Manifest (package.json) App root Per-app, baked into the package
Command-line switches nw <flags> or chromium-args in the manifest Per-launch (CLI) or per-app (manifest)
Environment variables Process environment Per-launch

4.2 Security-sensitive manifest fields

Field Risk Operator guidance
node-remote Critical. Grants Node (full machine access) to remote origins. Keep empty unless absolutely required; never point at third-party origins.
nodejs: false Hardening lever. Set false for apps that display untrusted content and don't need Node.
chromium-args Can disable security features. Audit every flag; treat --disable-* security flags as red flags.
additional_trust_anchors Adds trusted CAs. Only embed certificates you control.
crash_report_url Sends crash data (incl. command-line switches) off-machine. Point only at a server you operate; review what minidumps contain.
js-flags Exposes V8 internals (e.g. --expose-gc). Avoid shipping debug flags to production.

4.3 Environment variables

Variable Effect
NW_PRE_ARGS Value is prepended to chromium-args — a way to inject switches without editing the manifest.
NW_DESKTOP (Linux/Ubuntu) .desktop file used by taskbar progress/badge APIs (default nw.desktop).
DEPOT_TOOLS_WIN_TOOLCHAIN (Build, Windows) set to 0 to use a local toolchain.
GYP_DEFINES, GYP_GENERATORS, npm_config_* Build/native-module configuration (see Sections 8–9).

4.4 The data directory

Per-user app data lives at App.dataPath — %LOCALAPPDATA%/<name> (Windows), ~/.config/<name> (Linux), ~/Library/Application Support/<name>/Default (macOS). It holds cookies, localStorage, caches, and crash dumps (under Crash Reports). Override with --user-data-dir.

Caution

Because the data directory is derived from the manifest name, two apps with the same name share all stored data. Choose a globally unique name.


5. Security Architecture

5.1 Threat model

An NW.js app runs with the user's full privileges. The trust boundary is between your app's Node-enabled pages and any content you don't control. There is no built-in sandbox protecting the OS from a Node frame — Node frames deliberately bypass the renderer sandbox and same-origin policy.

5.2 Frame trust levels

Frame type Node/NW APIs Extended DOM Sandbox/SOP When
Node frame Yes Yes Bypassed nodejs:true + (chrome-extension:// or node-remote match) + no nwdisable + not in a <webview>
Normal frame No No Enforced everything else (incl. nwdisable frames)
<webview> guest No (unless allownw) No Separate process embedded untrusted content

5.3 Isolation primitives

  • <webview> — runs guest content in a separate process with no Node; the correct container for remote/untrusted pages.
  • nwdisable — turns an <iframe> into a normal frame (no require/nw).
  • nwfaketop — additionally hides window.parent/window.top (breaks postMessage to parent).
  • Separate Context Mode — keeps Node objects in a context distinct from page scripts (the default).

5.4 Code-integrity primitives

Mechanism Protects against Limitation
nwjc compilation Casual source reading Not cross-platform/version; reversible by a determined attacker
Content verification (--verify-content) File tampering under the official binary Bypassed by loading files under a different NW.js binary
Native module / NaCl Source exposure of critical logic Adds build complexity
macOS code signing / MAS sandbox Gatekeeper rejection; OS-level sandbox macOS only

5.5 Content verification internals

NWContentVerifierDelegate (src/nw_content_verifier_delegate.h) extends Chromium's extension ContentVerifierDelegate with four modes:

enum Mode { NONE, BOOTSTRAP, ENFORCE, ENFORCE_STRICT };
  • NONE — don't fetch or enforce hashes.
  • BOOTSTRAP — fetch missing hashes, don't enforce.
  • ENFORCE — enforce present hashes; fetch missing ones.
  • ENFORCE_STRICT — treat missing hashes as a verification failure.

The public key is compiled into the binary (GetPublicKey()); GetDefaultMode() sets the default. To use your own key you must rebuild NW.js and change the default mode to ENFORCE_STRICT (see Section 9.6).

5.6 Hardening checklist

  • [ ] node-remote empty (or tightly scoped to origins you own)
  • [ ] All remote/untrusted content rendered in <webview>, not <iframe>+node-remote
  • [ ] nodejs:false for display-only apps
  • [ ] No security-weakening chromium-args in production
  • [ ] Source protected with nwjc; critical logic in native modules
  • [ ] Content verification enabled for tamper detection
  • [ ] macOS app signed (Developer ID or MAS); Windows installer signed
  • [ ] Crash reports sent only to a server you operate

6. Operations

6.1 Distribution model

There is no server to operate: NW.js apps are client-side binaries shipped to users. "Operations" therefore means the build/sign/package/update pipeline.

build app → npm install (per platform) → rebuild native modules →
nwjc (optional) → combine with NW.js binary → sign → package installer → distribute → autoupdate

6.2 Choosing a build flavor for release

Flavor Ship to Why
SDK Internal QA / dev DevTools, nwjc, chromedriver
Normal End users Smaller; no DevTools

Detect at runtime: process.versions['nw-flavor']. Exclude nwjc, payload, and chromedriver from production packages.

6.3 Packaging layout (canonical)

Platform App files location
Windows / Linux Alongside nw(.exe) with package.json, or a package.nw (folder or zip) or zip appended to the binary
macOS nwjs.app/Contents/Resources/app.nw

Warning

Large or many-file package.nw zips slow startup (unzipped to temp on launch). Prefer plain files for big apps.

6.4 Crash reporting

Set crash_report_url in the manifest. On crash, NW.js POSTs a multipart/form-data request (the same format as Chromium's crashpad) containing at least prod (manifest name), ver (manifest version), upload_file_minidump (the minidump), and switch-N fields (the crashing process's command-line switches). Any breakpad/crashpad-compatible server can receive it. Crash dumps are also stored locally under <dataPath>/Crash Reports. --disable-crash-handler=true (with --single-process) disables crash dumping.

6.5 Auto-update strategy

NW.js ships no built-in updater. Standard options: nw-autoupdater (modern async API), nwjs-autoupdater (tiny Go helper, self-updating, no elevation), node-webkit-updater (low-level). The general flow: read a remote manifest, compare versions, download the platform package, unpack to temp, relaunch from temp, copy over the install dir, restart from the original location.

6.6 Testing apps with ChromeDriver

The SDK build includes chromedriver. Point it at your app with --nwapp=<path>; the --chromedriver-port / CHROMEDRIVER_PORT settings (used by NW.js's own test harness in test/sanity/nw_util.py) configure the driver port. This is the basis of NW.js's automated test/sanity and test/browser suites.


7. Performance & Tuning

Lever Effect
--disable-raf-throttling Keep requestAnimationFrame firing while hidden/minimized — important for games and background animation.
js-flags (e.g. --expose-gc) Tune V8; expose manual GC for memory-sensitive apps.
Mixed vs. Separate Context Mixed avoids cross-context type pitfalls but loses easy variable sharing; choose per app.
Plain files vs. zip packaging Plain files start faster (no unzip-to-temp).
dom_storage_quota Raise the DOM-storage cap (value in MB; set to ~2× the target).
nwjc (post-0.22) No runtime penalty after the 0.22 fix; pre-0.22 ran ~30% slower.

Note

Because Node and Blink share a thread, blocking Node work blocks the UI. Offload CPU-heavy work to Web Workers (--enable-node-worker gives them Node access) or child processes.


8. Internals Deep Dive

8.1 src/ module map

Directory Responsibility
src/api/ JS API shims + their C++ ExtensionFunction implementations + ObjectManager. The public nw.* surface.
src/api/<obj>/ Per-API JS shim + native binding (app, menu, menuitem, tray, clipboard, shell, screen, shortcut, base, event).
src/browser/ Browser-process C++ (incl. printing/, pepper/).
src/renderer/ Renderer-process C++ (incl. common/, printing/, prerenderer/).
src/common/ Shared browser/renderer code.
src/net/ Networking integration.
src/media/ Media integration.
src/geolocation/ Geolocation integration.
src/mac/ macOS-specific code and .lproj resources.
src/resources/ Built-in resources (error pages, locale, default app).
Top-level src/*.h/.cc The runtime spine: nw_base, nw_content, nw_package, nw_protocol_handler, nw_custom_bindings, nw_content_verifier_delegate, nw_version, plus Mac/Windows resource files.

8.2 Lifecycle of an API call (worked example: win.minimize())

  1. App JS calls win.minimize() on a Window shim (renderer).
  2. The shim invokes nw.Obj.callObjectMethod(id, 'Window', 'Minimize', []) (or a currentWindowInternal.* ExtensionFunction for window-scoped ops).
  3. Chromium routes the call (Mojo/legacy IPC) to the browser process.
  4. ObjectManager::OnCallObjectMethod looks up the C++ Window object by id and invokes its native Minimize.
  5. The native window minimizes; the OS emits a minimize notification.
  6. The C++ object calls ObjectManager::SendEvent(this, "minimize", []).
  7. The event is delivered to the renderer JS object, firing win.on('minimize', ...).

8.3 Native-module ABI

NW.js's V8 is a modified nwjs/v8, so the ABI differs from stock Node for non-LTS releases — hence nw-gyp historically. Since 0.111.3, NW.js publishes Node.js-compatible header tarballs (https://dl.nwjs.io/), so stock node-gyp works with --target=<ver> --disturl=https://dl.nwjs.io/. node-pre-gyp packages use --runtime=node-webkit. Modules are version- and arch-specific; rebuild after every NW.js upgrade.

8.4 The "internal Chrome App" consequence

Because the app is loaded as an extension:

  • The app origin is chrome-extension://<generated-id> (since 0.13; replaced file:///app://).
  • All chrome.* extension/app platform APIs are available to app pages.
  • App pages run in the privileged_extension V8 context.
  • domain in the manifest sets the host portion of that URL so an app can share cookies with a same-domain website.

8.5 Versioning macros

src/nw_version.h exposes NW_VERSION_STRING, NW_VERSION ("v0.112.0"), and the compile-time predicate NW_VERSION_AT_LEAST(major, minor, patch). The runtime equivalent for app code is process.versions['nw'].


9. Building NW.js From Source

Important

Building NW.js means building a patched Chromium. This is a large, multi-hour, multi-tens-of-GB undertaking. Most teams should download official binaries; build from source only to enable proprietary codecs, change the content-verification key, or contribute to NW.js itself.

9.1 Prerequisites

Install Chromium's depot_tools and follow the upstream platform build instructions (Windows/macOS/Linux). On Windows, set DEPOT_TOOLS_WIN_TOOLCHAIN=0.

9.2 Fetch the code

mkdir -p $HOME/nwjs && cd $HOME/nwjs
gclient config --name=src https://github.com/nwjs/chromium.src.git@origin/nw17

Trim test/reference downloads by editing custom_deps in the generated .gclient, then:

gclient sync --with_branch_heads     # downloads 20G+

Manually check out the NW component repos onto the right branches:

Path Repo
src/content/nw https://github.com/nwjs/nw.js
src/third_party/node-nw https://github.com/nwjs/node
src/v8 https://github.com/nwjs/v8

On a first Linux build, run gclient sync --with_branch_heads --nohooks then ./build/install-build-deps.sh.

9.3 Generate build files

Chromium uses GN + Ninja; Node still uses GYP + Ninja.

cd src
gn gen out/nw          # do not rename 'out' or 'nw'

Official-style GN args:

is_debug=false
is_component_ffmpeg=true
target_cpu="x64"

Use is_component_build=true for faster debug iteration. For Node, set GYP_DEFINES (e.g. target_arch=x64 building_nw=1 clang=1 nwjs_sdk=1 disable_nacl=0 buildtype=Official), GYP_GENERATORS=ninja, then run gyp_main.py against third_party/node-nw/node.gyp.

9.4 Build

cd src
ninja -C out/nw nwjs      # the NW.js binary
ninja -C out/Release node # Node
ninja -C out/nw copy_node # copy Node into the nwjs output

A full build takes hours. See the upstream "faster builds" docs for caching/jumbo tips.

9.5 Build flavors from source

Flavor GN/GYP setting
Standard (Normal) nwjs_sdk=false
SDK enable_nacl=true (SDK adds DevTools + NaCl)

32/64-bit selection is via target_arch/GYP_DEFINES per platform (Windows defaults to 32-bit; Linux/Mac default to 64-bit).

9.6 Content verification with your own key

  1. openssl genrsa -out private_key.pem 2048
  2. python convertkey.py → C source for the public key
  3. Paste it into content/nw/src/nw_content_verifier_delegate.cc, replacing the default key
  4. Change the default mode to ContentVerifierDelegate::ENFORCE_STRICT
  5. Rebuild NW.js

The demo keypair and signing tools (sign.py, payload, convertkey.py) live in tools/sign/.

9.7 Proprietary codecs

Prebuilt binaries exclude H.264/MP3/MP4 for licensing reasons. To enable them, build from source per the repo's Enable Proprietary Codecs document.


10. Contributing

10.1 Where development happens

The README is explicit: NW.js development spans multiple repositories in the nwjs org. This repository is for issue tracking, the landing page, and part of the source (content/nw). Core patches often land in nwjs/chromium.src, nwjs/node, or nwjs/v8.

10.2 Test suites

The test/ tree (the bulk of this repository by file count) drives NW.js's regression testing:

Suite Purpose
test/sanity/ Hundreds of focused regression apps, most named issueNNNN-*, each reproducing a specific historical bug. Driven via ChromeDriver.
test/browser/ Browser-level behavior tests.
test/full/ Full end-to-end scenarios (shortcuts, window behavior).
test/auto/ Automated process/exit-code tests.
test/testpy/, test/jsdriver/, test/sanity/testcfg.py, nw_util.py The Python/JS test harness (test configuration, ChromeDriver setup, helpers).

Each test/sanity/issueNNNN-* directory is a complete NW.js app (its own package.json + assets) plus a test.py that launches it under ChromeDriver and asserts behavior — a living catalog of fixed bugs and a model for writing reproductions.

10.3 Writing a regression test

Add a directory under test/sanity/ with a package.json, the minimal app that reproduces the issue, and a test.py using the helpers in nw_util.py (e.g. wait_for_element_id, switch_to_app, wait_for_execute_script). The harness reads CHROMEDRIVER/CHROMEDRIVER_PORT from the environment.

10.4 Documentation

Project docs live in docs/ (For Users, For Developers, References) and are published to http://docs.nwjs.io/. See docs/For Developers/Writing Documents for NW.js.md and Contributors of Documents.md for the docs workflow.


11. Repository Structure

nw.js/
├── src/                         NW.js C++/JS source (built as content/nw)
│   ├── api/                     nw.* JS shims + C++ ExtensionFunctions + ObjectManager
│   │   ├── app/ window/ menu/ menuitem/ tray/ clipboard/ shell/ screen/ shortcut/
│   │   ├── base/ event/         base object + event plumbing
│   │   └── _api_features.json   API namespace registration
│   ├── browser/  renderer/  common/  net/  media/  geolocation/  mac/
│   ├── resources/               error pages, locale, default app
│   ├── nw_version.h             version macros (0.112.0)
│   ├── nw_package.h/.cc         manifest model + zip self-extract
│   ├── nw_content.h/.cc         Chromium lifecycle hooks
│   ├── nw_custom_bindings.h/.cc renderer-side native handlers
│   ├── nw_content_verifier_delegate.h/.cc   content verification
│   └── nw_protocol_handler.h/.cc            internal protocol
├── docs/                        canonical product docs (→ docs.nwjs.io)
│   ├── For Users/  For Developers/  References/
├── test/                        regression suite (sanity/browser/full/auto/testpy)
├── tools/sign/                  content-verification signing tools + demo key
├── patch/                       patches applied to the wider tree
└── README.md

Note

The file count here is dominated by test/sanity/issueNNNN-* fixtures (each its own mini-app). The actual product code is concentrated in src/ and the canonical documentation in docs/.


12. Troubleshooting (Operations)

Community-reported issues most relevant to operators and integrators (from the project tracker):

Symptom Issue Operator action
Custom --user-data-dir still creates an AppData folder (Windows) #6328 Use the explicit dir for real data; the stray default folder is benign.
Node ESM built-ins not importable in DOM context #8333 Launch with --enable-features=NWESM,NWChainImportNode; load ESM via node-main (0.98.2+).
nwjc-generated file → SyntaxError: Unexpected identifier #8289 Recompile per platform/version; use --nw-module for modules.
Window positioning fails on Wayland #8348 Use X11/XWayland; set positions in callbacks.
--single-process not working #8173 Avoid; pair with --disable-crash-handler=true if testing.
Processes leak / stay open after killing main process #7852 Ensure App.quit()/closeAllWindows() on shutdown; investigate detached child processes.
DevTools segfault / GPU exit on tooltip (Linux/X11) #7844, #8184 Try a different build/flavor; disable GPU as a workaround.
App fails to launch on older macOS / Intel #8141, #1934 Match the NW.js build to the OS; use legacy builds for old macOS.

For app-level errors (the crypto/global name collisions, AngularJS scheme whitelisting, native-module ABI, context instanceof pitfalls, menu/tray after navigation), see the User Guide → Troubleshooting.


13. Quick Reference

13.1 Native function namespaces

nw.App, nw.Window (+ internal nw.currentWindowInternal), nw.Menu, nw.MenuItem, nw.Tray, nw.Clipboard, nw.Shell, nw.Screen, nw.Shortcut, nw.Obj (internal object manager).

13.2 Key source files

File Role
src/nw_version.h Version (0.112.0)
src/nw_package.h Manifest model + self-extract
src/nw_content.h Chromium lifecycle hooks
src/api/object_manager.h JS↔C++ object bridge
src/api/_api_features.json API namespace registration
src/nw_content_verifier_delegate.h Content verification modes
src/nw_custom_bindings.h Renderer-side native handlers

13.3 Build commands

gclient config --name=src https://github.com/nwjs/chromium.src.git@origin/nw17
gclient sync --with_branch_heads
cd src && gn gen out/nw
ninja -C out/nw nwjs && ninja -C out/Release node && ninja -C out/nw copy_node

13.4 Native module rebuild (0.111.3+)

npm install --target=0.112.0 --disturl=https://dl.nwjs.io/

13.5 Sign / verify

# content verification
nw --verify-content=enforce_strict .
# macOS App Store
python build_mas.py -C build.cfg -I dev.app -O Release.app
# verify a download
grep nwjs-vX.Y.Z.tar.gz SHASUMS256.txt | sha256sum -c -

13.6 External resources

  • Docs: http://docs.nwjs.io/ · Downloads: https://nwjs.io / https://dl.nwjs.io · Wiki: https://github.com/nwjs/nw.js/wiki · Issues: https://github.com/nwjs/nw.js/issues · nw-gyp: https://github.com/nwjs/nw-gyp