nwjs/nw.js · error · TypeError

'submenu' must be a valid Menu

Error message

'submenu' must be a valid Menu

What it means

When the `submenu` property is present on a normal/checkbox MenuItem option, the constructor verifies its constructor name equals 'Menu'. Anything else (a plain array, a string, a MenuItem, a Menu from another realm) throws a TypeError. On success only the Menu's id is forwarded to the native side and the real Menu is stashed in a hidden value.

Source

Thrown at src/api/menuitem/menuitem.js:62

    if (option.hasOwnProperty('icon')) {
      option.shadowIcon = String(option.icon);
      option.icon = nw.getAbsolutePath(option.icon);
    }

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

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

    if (option.hasOwnProperty('enabled'))
      option.enabled = Boolean(option.enabled);

    if (option.hasOwnProperty('submenu')) {
      if (v8_util.getConstructorName(option.submenu) != 'Menu')
        throw new TypeError("'submenu' must be a valid Menu");

      // Transfer only object id
      v8_util.setHiddenValue(this, 'submenu', option.submenu);
      option.submenu = option.submenu.id;
    }

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

View on GitHub (pinned to e15da848e9)

Solutions

  1. Build the submenu as a nw.Menu and pass that instance: `new nw.MenuItem({ label:'x', submenu: subMenu })`.
  2. Construct child items into the submenu Menu first, then attach the submenu.
  3. Keep the Menu in the same JS context as the MenuItem.

Example fix

// before
new nw.MenuItem({ label: 'Edit', submenu: [{label:'Cut'}] }); // throws

// after
var sub = new nw.Menu();
sub.append(new nw.MenuItem({ label: 'Cut', click: onCut }));
new nw.MenuItem({ label: 'Edit', submenu: sub });
Defensive patterns

Strategy: type-guard

Validate before calling

function withSubmenu(opt, submenu) {
  if (submenu && !(submenu instanceof nw.Menu))
    throw new TypeError('submenu must be a nw.Menu');
  opt.submenu = submenu;
  return new nw.MenuItem(opt);
}

Type guard

function isMenu(v) {
  return v instanceof nw.Menu ||
    (v && typeof v === 'object' && v.constructor && v.constructor.name === 'Menu');
}

Try / catch

try { return new nw.MenuItem(opt); }
catch (e) {
  if (e instanceof TypeError && /submenu.*valid Menu/.test(e.message)) {
    var m = new nw.Menu();
    opt.submenu.forEach(function (i) { m.append(new nw.MenuItem(i)); });
    opt.submenu = m;
    return new nw.MenuItem(opt);
  } throw e;
}

Prevention

When it happens

Trigger: Calling new nw.MenuItem({ label:'x', submenu: [] }), { label:'x', submenu: 'file' }, or passing a MenuItem as the submenu. Also a Menu built in a different context.

Common situations: Developers pass an array of items expecting auto-construction (common in other UI libs). Confusing submenu (a Menu) with the items list. Cross-frame menu sharing.

Related errors


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