nwjs/nw.js · error · TypeError

'menu' must be a valid Menu

Error message

'menu' must be a valid Menu

What it means

Thrown by the Tray constructor when option.hasOwnProperty('menu') is true but option.menu.constructor.name !== 'Menu'. The tray menu must be a real nw.MenuItem collection built via nw.Menu({ type: 'menubar' }) so the constructor can read .id and hand it to the native layer. The check uses constructor.name rather than instanceof, so cross-realm Menu objects whose constructor name matches 'Menu' will pass, but plain objects/arrays will not.

Source

Thrown at src/resources/api_nw_tray.js:64

  if (option.hasOwnProperty('iconsAreTemplates'))
    option.iconsAreTemplates = Boolean(option.iconsAreTemplates);
  else
    option.iconsAreTemplates = true;

  if (option.hasOwnProperty('tooltip'))
    option.tooltip = String(option.tooltip);

  if (option.hasOwnProperty('click')) {
    if (typeof option.click != 'function') {
      throw new TypeError("'click' must be a valid Function");
    } else {
      this.click = option.click;
    }
   }

  if (option.hasOwnProperty('menu')) {
    if (option.menu.constructor.name != 'Menu')
      throw new TypeError("'menu' must be a valid Menu");

    // Transfer only object id
    privates(this).menu = option.menu;
    option.menu = option.menu.id;
  }
  
  var id = nw.Obj.allocateId();
  this.id = id;
  privates(this).option = option;

  // All properties must be set after initialization.
  if (!option.hasOwnProperty('icon'))
    option.shadowIcon = '';
  if (!option.hasOwnProperty('alticon'))
    option.shadowAlticon = '';
  if (!option.hasOwnProperty('tooltip'))
    option.tooltip = '';
  

View on GitHub (pinned to e15da848e9)

Solutions

  1. Construct the menu first: const menu = new nw.Menu({ type: 'menubar' }); menu.append(new nw.MenuItem({ label: 'Item' })); then new nw.Tray({ title: 'x', menu: menu }).
  2. Ensure bundlers/minifiers preserve function .name (configure keep_fnames) if you hit the cross-bundle case.

Example fix

// before
new nw.Tray({ title: 'x', menu: [{ label: 'Quit' }] });
// after
const menu = new nw.Menu({ type: 'menubar' });
menu.append(new nw.MenuItem({ label: 'Quit' }));
new nw.Tray({ title: 'x', menu: menu });
Defensive patterns

Strategy: type-guard

Validate before calling

if ('menu' in option && !(option.menu instanceof nw.Menu)) {
  throw new TypeError('option.menu must be an nw.Menu instance');
}
// note: the source checks constructor.name === 'Menu'; instanceof is stricter and safer

Type guard

function isTrayMenuValid(o) { return !o.hasOwnProperty('menu') || (o.menu && o.menu.constructor && o.menu.constructor.name === 'Menu'); }

Prevention

When it happens

Trigger: new nw.Tray({ title: 'x', menu: [{ label: 'a' }] }) (array of plain objects); passing a menu config object instead of a constructed nw.Menu; passing a DOM element or other object whose constructor.name is not 'Menu'.

Common situations: Confusing the menu config with the constructed Menu; building items inline instead of via nw.Menu; minified/transformed code where constructor.name is mangled (rare but possible with aggressive bundlers).

Related errors


AI-assisted analysis of nwjs/nw.js@e15da848e9 (2026-08-13). Data as JSON: /api/errors/b4dd3665c02fd009. Report an issue: GitHub.