Packaging an NW.js Desktop App: Manifest, Node-in-Renderer Access, and Distribution
Build a folder-based NW.js package from scratch: configure package.json, enable Node require() in the renderer, verify the runtime in DevTools, and distribute an app.nw archive without breaking native module ABI compatibility.
13 Dec 2025, 07:30 UTC

Desired outcome
You want a folder-based NW.js package that runs when you pass the folder to the nw binary. The package contains a package.json manifest, an HTML entry point, and any local Node dependencies. The main window should load your HTML, expose require() for Node modules inside the renderer, and give you access to the nw global for native shell integration. For distribution, you need an archive that preserves the Node ABI (Application Binary Interface) of the NW.js runtime rather than the system Node.js installation.
Prerequisites
Install the NW.js runtime for your target operating system. Choose the SDK build if you need DevTools during development; the normal build strips remote debugging. Keep all Node dependencies inside the package root or alongside it so that require() resolves from the app directory. Remember that NW.js bundles its own Node.js runtime, so modules compiled against a different Node version may fail to load.
Create the package manifest
NW.js reads a package.json file at the package root. Unlike a Node.js module manifest, this file describes the desktop window and entry point.
{
"name": "my-desktop-app",
"main": "index.html",
"window": {
"width": 800,
"height": 600,
"toolbar": false,
"frame": true
}
}
The name field identifies the package. The main field is a path relative to the package root; if it is missing or does not resolve, the window will be blank. The window block controls the native chrome. Common keys include width, height, toolbar, and frame. Because NW.js version lines differ—legacy 0.x versus modern LTS releases—some defaults and manifest fields have changed. Confirm the exact behavior against the version you ship.
Wire Node into the renderer
Place index.html and your application scripts next to package.json. In NW.js, the renderer is the Chromium process that displays your HTML. Unlike a standard browser, this renderer has direct access to Node.js APIs through the global require function.
<script>
const fs = require('fs');
const os = require('os');
console.log(typeof nw.Window);
</script>
The nw global is the NW.js shell object. It exposes nw.Window for window management and nw.Menu for native menus. Use require('fs') for file access and require('os') for platform checks. Keep Node access limited to trusted local pages; enabling it for remote content is a security-boundary decision controlled by the node-remote manifest field, which you should never set to allow untrusted URLs.
Package for distribution
To distribute the app, zip the contents of the package folder—not the parent folder—into a file named app.nw. Alternatively, use a builder tool that wraps the NW.js runtime with your package. If your app uses native modules (compiled .node binaries), they must match the Node ABI of the NW.js runtime. A module built for your system Node.js installation commonly causes MODULE_NOT_FOUND or an ABI-mismatch error when loaded inside NW.js.
You can inspect the bundled Node version by running the nw binary with --version and, inside the app, evaluating process.versions in the DevTools console. Rebuild native dependencies against that exact runtime before archiving.
Expected checks
After launching the package, open the DevTools console and verify the runtime integration:
typeof require === 'function'— Node is available in the renderer.typeof nw === 'object'— the NW.js shell global exists.process.versions— reports the NW.js, Chromium, and Node versions. The exact key for the NW.js version may vary by release; look for anode-webkitornwkey.require('os').platform()— returns the host platform string.
Confirm that the window renders with the chrome you configured. To test the security boundary, load a remote page in a second window and assert that require is undefined unless node-remote explicitly lists that origin.
Recovery options
A blank window usually means package.json is invalid JSON or main does not resolve to an existing file relative to the package root. Validate the JSON and check the path before relaunching.
If require is undefined in a page loaded from a remote origin, that is expected sandbox behavior. Node access is restricted to local package content unless node-remote allows the origin. Do not relax this for untrusted URLs.
If a native module fails to load, verify that it was compiled for the NW.js Node ABI rather than system Node. Use the SDK build to surface startup errors that the normal build may hide. If you rely on DevTools during development, use the SDK build, but do not ship it casually because it enables remote debugging.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.