{"record":{"id":"011b186ade9bd257","repo":"nwjs/nw.js","slug":"menu-must-be-a-valid-menu","errorCode":null,"errorMessage":"'menu' must be a valid Menu","messagePattern":"'menu' must be a valid Menu","errorType":"validation","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"src/api/tray/tray.js","lineNumber":63,"sourceCode":"  if (option.hasOwnProperty('iconsAreTemplates'))\n    option.iconsAreTemplates = Boolean(option.iconsAreTemplates);\n  else\n    option.iconsAreTemplates = true;\n\n  if (option.hasOwnProperty('tooltip'))\n    option.tooltip = String(option.tooltip);\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   }\n\n  if (option.hasOwnProperty('menu')) {\n    if (v8_util.getConstructorName(option.menu) != 'Menu')\n      throw new TypeError(\"'menu' must be a valid Menu\");\n\n    // Transfer only object id\n    v8_util.setHiddenValue(this, 'menu', option.menu);\n    option.menu = option.menu.id;\n  }\n\n  v8_util.setHiddenValue(this, 'option', option);\n  nw.allocateObject(this, option);\n\n  // All properties must be set after initialization.\n  if (!option.hasOwnProperty('icon'))\n    option.shadowIcon = '';\n  if (!option.hasOwnProperty('alticon'))\n    option.shadowAlticon = '';\n  if (!option.hasOwnProperty('tooltip'))\n    option.tooltip = '';\n}\nrequire('util').inherits(Tray, exports.Base);","sourceCodeStart":45,"sourceCodeEnd":81,"githubUrl":"https://github.com/nwjs/nw.js/blob/e15da848e9e08e6e467532dae78995c6ad2f55ee/src/api/tray/tray.js#L45-L81","documentation":"Thrown by the nw.Tray constructor when 'menu' is present but the value's constructor name is not 'Menu'. nw validates the menu via v8_util.getConstructorName, so a plain object or any non-nw.Menu instance is rejected even if it looks menu-like.","triggerScenarios":"Calling `new nw.Tray({ menu: { items: [] } })` or passing a JSON-deserialized object as the menu.","commonSituations":"Passing a submenu built with a different framework; forgetting to wrap items in `new nw.Menu({ ... })`.","solutions":["Build the menu with `new nw.Menu({ type: 'contextmenu', items: [...] })` before passing it.","Ensure the same Menu class (nw.Menu) is used, not a custom subclass with a different constructor name."],"exampleFix":"// before\nnew nw.Tray({ menu: { items: [{ label: 'x' }] } });\n// after\nconst menu = new nw.Menu();\nmenu.append(new nw.MenuItem({ label: 'x' }));\nnew nw.Tray({ menu });","handlingStrategy":"type-guard","validationCode":"if (option.hasOwnProperty('menu') && !(option.menu instanceof nw.Menu)) {\n  throw new Error('option.menu must be a nw.Menu instance');\n}","typeGuard":"function isMenu(v) { return v && v.constructor && v.constructor.name === 'Menu'; }","tryCatchPattern":"try { new nw.Tray(opt); } catch (e) { if (/menu/.test(e.message)) { opt.menu = new nw.Menu(); new nw.Tray(opt); } else throw e; }","preventionTips":["Always construct menus with new nw.Menu().","Do not pass deserialized JSON objects as menus."],"tags":["tray","constructor","type-check","menu","nwjs"],"backgroundTag":null,"analyzedSha":"e15da848e9e08e6e467532dae78995c6ad2f55ee","analyzedAt":"2026-08-13T04:15:35.452Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}