Packaging NW.js desktop apps across Windows, macOS, and Linux
Use nwbuild to create platform installers from one project, then verify Node integration settings before shipping.
03 Jul 2026, 23:58 UTC

The problem: your web app needs to become a desktop installer
You have an NW.js application that reads local files, spawns helper processes, or otherwise relies on Node.js APIs. It runs fine during development, but now you need to hand users a Windows executable, a macOS app bundle, or a Linux package. The question is not whether NW.js can bundle Chromium and Node.js together—it can—but how to produce installers for each platform from a single source tree without breaking local asset paths or accidentally exposing Node APIs to untrusted content.
The practical takeaway: use the nwbuild command to generate platform-specific output from one project directory, explicitly set node-integration per window in package.json, and verify the packaged executable on the target operating system before signing and shipping.
How NW.js packaging works
NW.js bundles Chromium and the Node.js runtime into one executable. The nwbuild tool takes your application source—HTML, JavaScript, assets, and package.json—and wraps it with the appropriate NW.js binaries for Windows, macOS, or Linux. Because the source tree layout is preserved, relative paths to images, configuration files, and local modules usually keep working after packaging.
Node integration is enabled by default in renderer processes. That means a renderer window can call require('fs') or require('child_process') unless you disable it. This is convenient for desktop tools, but it is also a security risk if your app loads remote HTML or executes user-supplied JavaScript.
Configure the manifest before building
Edit package.json in your project root. The name, version, and window fields carry into the packaged output. Here is a minimal example that disables Node integration for the main window:
{
"name": "my-desktop-tool",
"version": "1.0.0",
"main": "index.html",
"window": {
"title": "My Desktop Tool",
"width": 1024,
"height": 768,
"node-integration": false
}
}
If a specific window must read the local filesystem, enable integration only there and avoid loading remote URLs in that window. Treat the node-integration flag as a security boundary, not a convenience toggle.
Worked example: build for three platforms
Run the following from your project directory. You do not need administrative permissions to create the output, but you do need the NW.js SDK for each target platform available to the builder.
# Run in the project root on macOS, Linux, or Windows
nwbuild -p win64,osx64,linux64 -o ./dist .
Placeholders:
-p win64,osx64,linux64: target platforms. Replace with the subset you actually ship.-o ./dist: output directory. Change this to your build artifact folder..: the source directory containingpackage.json.
Expected checks after the command finishes:
- List
./distand confirm platform-specific folders or installers appear. - Open
package.jsonand confirmnameandversionmatch what you expect in the installer metadata. - Launch the executable on the matching operating system and open a local asset using the same relative path used in source. If the asset loads, the source-tree layout survived packaging.
Risk: do not assume the build is safe to ship just because it completes. Unsigned Windows executables can trigger SmartScreen warnings, and unsigned macOS bundles can be blocked by Gatekeeper. Code signing is a separate step outside nwbuild.
Trade-offs and limitations
| Factor | Impact |
|---|---|
| Installer size | Bundled Chromium adds roughly 30 MB per platform. This matters for bandwidth-constrained distribution or automatic updaters. |
| Node integration | Enabled by default in renderers. Disable it for windows that load remote content, or isolate privileged code in a separate window. |
| Code signing | Required on macOS and Windows to avoid OS-level warnings. Adds certificate management and CI pipeline complexity. |
| Cross-platform from one machine | Supported by nwbuild flags, but you should still test each artifact on its native OS. |
Verify before you ship
After packaging, run this checklist on a clean machine or virtual machine for each platform:
- The executable launches and opens the expected window.
- Local assets load using relative paths identical to the source tree.
- Windows with
node-integration: falsecannot callrequire('fs')from the renderer console. - The installer metadata shows the correct application name and version.
If any check fails, revisit package.json and the nwbuild flags before signing. Because this guidance depends on your NW.js version and target platforms, treat the size estimate and signing requirements as items to confirm against the current NW.js documentation for your release.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.