Adding a System Tray Icon to an NW.js Desktop App
Learn how to add a system tray icon to an NW.js desktop app using the nw.Tray API, with a complete example, cross‑platform notes, and cleanup tips.
16 Nov 2025, 19:16 UTC

Problem: Quick access from the background
Many desktop utilities need to stay running while offering a single‑click way to show a window or run a command. A system tray (notification area) icon provides that entry point without cluttering the taskbar.
Thesis
Using the nw.Tray API in NW.js you can create a cross‑platform tray icon, attach a context menu, and respond to left‑ and double‑click events with just a few lines of code.
Setting up the project
Node.js integration is enabled by default in NW.js, so you can require the nw module directly. Bundle an icon file (PNG, ICNS, or ICO) inside your app folder so the runtime can locate it.
Creating the tray icon
// main.js – run in the NW.js process
const nw = require('nw');
const path = require('path');
// Path to the icon relative to the app folder
const iconPath = path.join(__dirname, 'icons', 'tray.png');
// Create the tray object
const tray = new nw.Tray({
icon: iconPath,
tooltip: 'My NW.js App'
});
// Build a simple context menu
const menu = new nw.Menu();
menu.append(new nw.MenuItem({ label: 'Show Window', click: () => { nw.Window.get().show(); } }));
menu.append(new nw.MenuItem({ label: 'Quit', click: () => { nw.App.quit(); } }));
tray.menu = menu;
// Left‑click toggles the main window
tray.on('click', () => {
const win = nw.Window.get();
win.isVisible() ? win.hide() : win.show();
});
// Double‑click shows an alert (example)
tray.on('dblclick', () => {
nw.Window.get().showDevTools();
});
// Clean up when the app exits
nw.App.on('quit', () => {
tray.remove();
});
Worked example
- Create a folder
tray-demo. - Add
package.jsonwith{ "name": "tray-demo", "main": "index.html" }. - Add
index.htmlcontaining a basic UI (e.g.,<h1>Hello NW.js</h1>). - Place an icon at
icons/tray.png. - Save the script above as
main.jsand add a script tag inindex.html:<script src="./main.js"></script>. - Run the app with the NW.js SDK:
nw .(execute from the project directory). No special permissions are required.
Expected checks:
- The icon appears in the system tray/notification area.
- Right‑click shows the menu with “Show Window” and “Quit”.
- Left‑click hides or shows the main window; you can verify by watching the window toggle.
- Double‑click opens the DevTools (or you can replace the handler with any action).
- When you choose “Quit” or close the window, the tray icon disappears; no orphan icon remains.
Trade‑off and limitation
The tray API relies on the underlying desktop environment. On macOS a sandboxed app cannot use the status bar unless you enable the appropriate entitlements; otherwise you must fall back to NSStatusItem via a native module. On Linux, GNOME Shell hides legacy tray icons by default—users need the TopIcons extension or a desktop environment like KDE/XFCE that displays them. Repeatedly creating a new nw.Tray without calling remove() on the previous instance leads to duplicate icons or memory leaks, so always clean up before recreating.
Actionable closing
Add the snippet above to your NW.js project, test with the steps provided, and verify the icon appears and behaves as expected. Remember to call tray.remove() on app exit to keep the system tray clean. If you target macOS sandboxed builds, plan for an alternative status‑item implementation early in development.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.