beekeeper-studio/beekeeper-studio · error
Menu item ${menuItem.id} already exists
Error message
Menu item ${menuItem.id} already exists What it means
MenuBarModule's 'add' Vuex mutation registers an ExternalMenuItem in state.externalMenu keyed by id. Menu item ids must be unique, so registering an id that already exists throws `Menu item ${id} already exists` rather than silently overwriting the existing entry.
Source
Thrown at apps/studio/src/store/modules/MenuBarModule.ts:80
}
(parent.submenu as Electron.MenuItemConstructorOptions[]).push({
id: externalItem.id,
label: externalItem.label,
click: () => {
actionHandler.handleAction(externalItem.action);
},
accelerator: externalItem.accelerator,
});
}
return menus;
},
},
mutations: {
add(state, menuItem: ExternalMenuItem) {
if (state.externalMenu[menuItem.id]) {
throw new Error(`Menu item ${menuItem.id} already exists`);
}
state.externalMenu = {
...state.externalMenu,
[menuItem.id]: menuItem,
};
},
remove(state, id: string) {
if (!state.externalMenu[id]) {
throw new Error(`Menu item ${id} does not exist`);
}
state.externalMenu = _.omit(state.externalMenu, id);
},
},
};
View on GitHub (pinned to 4e3e03e322)
Solutions
- Remove the existing menu item before re-adding (commit 'remove' for that id, then 'add').
- Make registration idempotent: check state.externalMenu[id] first and skip or update instead of adding.
- Use a unique id per registration instance (e.g. include a plugin instance/version suffix).
Example fix
// before
store.commit('menuBar/add', item) // throws on hot-reload
// after
if (!store.state.menuBar.externalMenu[item.id]) {
store.commit('menuBar/add', item)
} Defensive patterns
Strategy: validation
Validate before calling
if (store.state.menuBar.externalMenu[item.id]) {
store.commit('menuBar/remove', item.id) // or skip registration
}
store.commit('menuBar/add', item) Type guard
const isNotRegistered = (menu: Record<string, ExternalMenuItem>, id: string) => !Object.prototype.hasOwnProperty.call(menu, id)
Try / catch
try {
store.commit('menuBar/add', item)
} catch (e) {
if (e.message.includes('already exists')) {
store.commit('menuBar/remove', item.id)
store.commit('menuBar/add', item) // idempotent re-register on hot-reload
} else throw e
} Prevention
- Make menu registration idempotent — check existing ids before adding.
- Remove menu items on plugin teardown so reloads can re-add cleanly.
- Namespace plugin menu ids (plugin-name:action) to avoid collisions.
- During HMR, hook reload cleanup to unregister previously added items.
When it happens
Trigger: Dispatching/committing the 'add' mutation twice with the same menuItem.id — e.g. a plugin re-registering its menu on hot-reload or reconnect without first removing the old entry.
Common situations: Plugin reload during development (webpack/vite HMR re-running registration code); a plugin enabling/disabling cycle that re-adds without cleanup; two plugins shipping the same id.
Related errors
- Menu item ${id} does not exist
- Menu item ${options.item.slug} already exists
- Plugin ${newConfig.pluginId} does not exist
- Plugin ${config.pluginId} does not exist
- ${e.responseBody}
AI-assisted analysis of beekeeper-studio/beekeeper-studio@4e3e03e322 (2026-08-31).
Data as JSON: /api/errors/ec371ab855d61c39.
Report an issue: GitHub.