{"record":{"id":"7c0bd1d977f32e76","repo":"nwjs/nw.js","slug":"submenu-must-be-a-valid-menu","errorCode":null,"errorMessage":"'submenu' must be a valid Menu","messagePattern":"'submenu' must be a valid Menu","errorType":"validation","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"src/api/menuitem/menuitem.js","lineNumber":62,"sourceCode":"    if (option.hasOwnProperty('icon')) {\n      option.shadowIcon = String(option.icon);\n      option.icon = nw.getAbsolutePath(option.icon);\n    }\n\n    if (option.hasOwnProperty('iconIsTemplate'))\n      option.iconIsTemplate = Boolean(option.iconIsTemplate);\n    else\n      option.iconIsTemplate = true;\n\n    if (option.hasOwnProperty('tooltip'))\n      option.tooltip = String(option.tooltip);\n\n    if (option.hasOwnProperty('enabled'))\n      option.enabled = Boolean(option.enabled);\n\n    if (option.hasOwnProperty('submenu')) {\n      if (v8_util.getConstructorName(option.submenu) != 'Menu')\n        throw new TypeError(\"'submenu' must be a valid Menu\");\n\n      // Transfer only object id\n      v8_util.setHiddenValue(this, 'submenu', option.submenu);\n      option.submenu = option.submenu.id;\n    }\n\n    if (option.hasOwnProperty('click')) {\n      if (typeof option.click != 'function')\n        throw new TypeError(\"'click' must be a valid Function\");\n      else\n        this.click = option.click;\n    }\n  } else if (option.type == 'separator') {\n    option = {\n      type: 'separator'\n    };\n  }\n","sourceCodeStart":44,"sourceCodeEnd":80,"githubUrl":"https://github.com/nwjs/nw.js/blob/e15da848e9e08e6e467532dae78995c6ad2f55ee/src/api/menuitem/menuitem.js#L44-L80","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Build the submenu as a nw.Menu and pass that instance: `new nw.MenuItem({ label:'x', submenu: subMenu })`.","Construct child items into the submenu Menu first, then attach the submenu.","Keep the Menu in the same JS context as the MenuItem."],"exampleFix":"// before\nnew nw.MenuItem({ label: 'Edit', submenu: [{label:'Cut'}] }); // throws\n\n// after\nvar sub = new nw.Menu();\nsub.append(new nw.MenuItem({ label: 'Cut', click: onCut }));\nnew nw.MenuItem({ label: 'Edit', submenu: sub });","handlingStrategy":"type-guard","validationCode":"function withSubmenu(opt, submenu) {\n  if (submenu && !(submenu instanceof nw.Menu))\n    throw new TypeError('submenu must be a nw.Menu');\n  opt.submenu = submenu;\n  return new nw.MenuItem(opt);\n}","typeGuard":"function isMenu(v) {\n  return v instanceof nw.Menu ||\n    (v && typeof v === 'object' && v.constructor && v.constructor.name === 'Menu');\n}","tryCatchPattern":"try { return new nw.MenuItem(opt); }\ncatch (e) {\n  if (e instanceof TypeError && /submenu.*valid Menu/.test(e.message)) {\n    var m = new nw.Menu();\n    opt.submenu.forEach(function (i) { m.append(new nw.MenuItem(i)); });\n    opt.submenu = m;\n    return new nw.MenuItem(opt);\n  } throw e;\n}","preventionTips":["Always build the submenu as a nw.Menu instance.","Do not pass arrays of plain option objects as submenu.","Construct the submenu in the same JS context as the parent item."],"tags":["nwjs","menuitem","menu","submenu","type-validation"],"backgroundTag":null,"analyzedSha":"e15da848e9e08e6e467532dae78995c6ad2f55ee","analyzedAt":"2026-08-13T04:15:35.452Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}