{"record":{"id":"b4dd3665c02fd009","repo":"nwjs/nw.js","slug":"menu-must-be-a-valid-menu-b4dd36","errorCode":null,"errorMessage":"'menu' must be a valid Menu","messagePattern":"'menu' must be a valid Menu","errorType":"exception","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"src/resources/api_nw_tray.js","lineNumber":64,"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 (option.menu.constructor.name != 'Menu')\n      throw new TypeError(\"'menu' must be a valid Menu\");\n\n    // Transfer only object id\n    privates(this).menu = option.menu;\n    option.menu = option.menu.id;\n  }\n  \n  var id = nw.Obj.allocateId();\n  this.id = id;\n  privates(this).option = 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  ","sourceCodeStart":46,"sourceCodeEnd":82,"githubUrl":"https://github.com/nwjs/nw.js/blob/e15da848e9e08e6e467532dae78995c6ad2f55ee/src/resources/api_nw_tray.js#L46-L82","documentation":"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.","triggerScenarios":"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'.","commonSituations":"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).","solutions":["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 }).","Ensure bundlers/minifiers preserve function .name (configure keep_fnames) if you hit the cross-bundle case."],"exampleFix":"// before\nnew nw.Tray({ title: 'x', menu: [{ label: 'Quit' }] });\n// after\nconst menu = new nw.Menu({ type: 'menubar' });\nmenu.append(new nw.MenuItem({ label: 'Quit' }));\nnew nw.Tray({ title: 'x', menu: menu });","handlingStrategy":"type-guard","validationCode":"if ('menu' in option && !(option.menu instanceof nw.Menu)) {\n  throw new TypeError('option.menu must be an nw.Menu instance');\n}\n// note: the source checks constructor.name === 'Menu'; instanceof is stricter and safer","typeGuard":"function isTrayMenuValid(o) { return !o.hasOwnProperty('menu') || (o.menu && o.menu.constructor && o.menu.constructor.name === 'Menu'); }","tryCatchPattern":null,"preventionTips":["Construct the menu with new nw.Menu({ type: 'menubar' }) before passing it.","Do not pass arrays of plain objects as the menu.","Configure bundlers to keep function .name if running in a bundled environment."],"tags":["nwjs","tray","validation","menu","constructor-name"],"backgroundTag":null,"analyzedSha":"e15da848e9e08e6e467532dae78995c6ad2f55ee","analyzedAt":"2026-08-13T04:15:35.452Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}