How I Fix The JavaScript Tray Icon Error in Our “Windows PC” App

When I started building Tasky, a tiny task-timer that lives in the Windows system tray fix with javascript, I hit a wall almost right away. My tray icon refused to show, and Electron kept shouting:

Uncaught Exception: TypeError: Insufficient number of arguments

Below is the full story of what went wrong, how I solved it, and what I added next so the client gets a smoother product.

Error Code

Here’s the first version of index.js I wrote:

path = require('path');
const { app, BrowserWindow, Tray, Menu, nativeImage } = require('electron');

let mainWindow;
let tray; // I forgot this at first

app.on('ready', () => {
mainWindow = new BrowserWindow({
width: 300,
height: 500,
frame: false,
resizable: false,
webPreferences: { nodeIntegration: true }
});
mainWindow.loadURL(`file://${__dirname}/src/index.html`);

const iconName = process.platform === 'win32'
? 'windows-icon.png' // ← here’s mistake #1
: 'iconTemplate.png';

const iconPath = path.join(__dirname, 'src', 'assets', iconName);
tray = new Tray(iconPath); // mistake #2 shows up later
});

The code looked fine, yet Windows threw the “insufficient arguments” error and my tray stayed empty.

Define the Error

  • Windows only accepts .ico files in the system tray.
    I gave it a PNG, so Electron silently treated the path as invalid and passed “nothing” to the Tray constructor. Electron then complains that it got zero arguments.
  • Electron garbage-collects unreferenced objects.
    If I didn’t keep tray in a global variable, the icon would vanish the moment the event loop tidied up.

Fix the Basics

I replaced the PNG with a proper ICO and kept the Tray object alive. This is the repaired snippet:

iconName = process.platform === 'win32' ? 'windows-icon.ico'
: 'iconTemplate.png';
const iconPath = path.join(__dirname, 'src', 'assets', iconName);
const icon = nativeImage.createFromPath(iconPath);

tray = new Tray(icon); // now Electron gets the image it wants
tray.setToolTip('Tasky – running in tray');

That alone removed the exception and showed the little stopwatch icon right where it belonged.

Correct Code

A working icon is fine, but I wanted a bit more comfort for the user. I split the file into two helper functions and added a few tray actions.

// main.js
const path = require('path');
const { app, BrowserWindow, Tray, Menu, nativeImage } = require('electron');

let mainWindow;
let tray;

function createWindow() {
mainWindow = new BrowserWindow({
width: 300,
height: 500,
frame: false,
resizable: false,
show: false
});

mainWindow.loadURL(`file://${__dirname}/src/index.html`);

// hide instead of quit
mainWindow.on('close', (e) => {
if (!app.isQuiting) {
e.preventDefault();
mainWindow.hide();
}
});
}

function createTray() {
const iconName = process.platform === 'win32' ? 'windows-icon.ico' : 'iconTemplate.png';
const iconPath = path.join(__dirname, 'src', 'assets', iconName);
const icon = nativeImage.createFromPath(iconPath);

tray = new Tray(icon);

const menu = Menu.buildFromTemplate([
{ label: 'Show / Hide', click: () => {
if (mainWindow.isVisible()) mainWindow.hide();
else mainWindow.show();
}},
{ label: 'Reload', click: () => mainWindow.reload() },
{ type: 'separator' },
{ label: 'Quit', click: () => {
app.isQuiting = true;
app.quit();
}}
]);

tray.setToolTip('Tasky – running in tray');
tray.setContextMenu(menu);
tray.on('click', () =>
mainWindow.isVisible() ? mainWindow.hide() : mainWindow.show()
);
}

app.whenReady().then(() => {
createWindow();
createTray();
});

app.on('window-all-closed', () => {
if (process.platform !== 'darwin') app.quit();
});

app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) createWindow();
});

Explain it

FeatureWhy it helps the client
Hide-to-tray instead of closingKeeps Tasky running without cluttering the taskbar
Single-click toggleQuick way to open and hide the window
Context menuProvides explicit Quit and Reload actions
Global tray variablePrevents the icon from disappearing

Quick Ideas for Future Practice

  1. Balloon notifications when a timer finishes (Tray.displayBalloon() on Windows).
  2. Persistent window size using electron-store.
  3. Auto-launch at login through app.setLoginItemSettings().
  4. Global keyboard shortcut to pop the window with globalShortcut.register().

Each one teaches a new Electron API and lets us extend Tasky bit by bit.

Final Thought

I went from a puzzling “insufficient arguments” error to a neat little timer that tucks itself away until you need it. The fix boiled down to two simple points use the right icon format and hold on to the Tray object but walking through the why made the lesson stick. Next sprint, I’ll add those balloon alerts so Tasky politely tells you when time’s up without yanking you out of your flow.

Related blog posts