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

  1. Remove the existing menu item before re-adding (commit 'remove' for that id, then 'add').
  2. Make registration idempotent: check state.externalMenu[id] first and skip or update instead of adding.
  3. 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

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


AI-assisted analysis of beekeeper-studio/beekeeper-studio@4e3e03e322 (2026-08-31). Data as JSON: /api/errors/ec371ab855d61c39. Report an issue: GitHub.