Menu (context menu & menu actions)
Renderer-triggered native context menus, plus the event stream every native menu (app/context/dock/tray) uses to report a click back to the renderer. Source: src/main/menu.ts, channel prefix menu:*. For the application/tray/dock menu templates themselves (what items appear), see UI configuration — this page is about the renderer-facing trigger/event API, not menu authoring.
Methods
| Method | Returns | Notes |
|---|---|---|
showContextMenu(options?: ShowContextMenuOptions) |
Promise<boolean> |
Resolves false if ui.contextMenu.enabled isn't true, no window owns the call, or the app's contextMenu factory returns an empty template. |
onMenuAction(callback: (event: MenuActionEvent) => void) |
() => void (unsubscribe) |
Fires when the user clicks any native app/context/dock/tray/Jump-List menu item. Not part of .safe (event subscription, never throws). |
ShowContextMenuOptions
{ x?: number; y?: number; target?: Partial<ContextMenuTarget>; data?: unknown } — x/y default to the current cursor position. target/data are forwarded to the app's own context-menu handler untouched.
MenuActionEvent
{ source: 'app' | 'context' | 'dock' | 'tray' | 'jumpList'; action: string; data?: unknown }
The two-step context-menu handoff
Electron's own webContents.on('context-menu', ...) fires with mouse coordinates but no idea what DOM element was right-clicked — that's browser-side information the main process can't see on its own. So the preload attaches a capture-phase contextmenu listener that walks the click's composed path, finds the most semantically meaningful ancestor (an element with an id, data-* attributes, or a class name matching /context|menu/i), and ships { x, y, target } to main synchronously, via ipcRenderer.sendSync('menu:contextTargetSync', ...). Because that call is synchronous, main already has the target stored by the time Electron's own context-menu event fires on the same tick — the stored value has a 1-second shelf life so a genuinely stale click can't leak into a later, unrelated right-click. This runs unconditionally, even if the app never enables ui.contextMenu — cheap to send, ignored when nothing's listening.
target on the resulting ContextMenuContext (passed to your contextMenu factory) is a normalized ContextMenuTarget: { id?, tagName?, className?, classList?, dataset?, text?, href?, src?, value? } — every string field capped at 500 characters, classList/dataset capped at 32 entries, so a pathological click target can't balloon the IPC payload.
Example
const shown = await window.Electron.showContextMenu({ data: { rowId: 42 } });
const unsubscribe = window.Electron.onMenuAction((event) => {
if (event.source === 'context' && event.action === 'delete') deleteRow(event.data);
});
.safe
showContextMenu is fully mirrored at window.Electron.safe.showContextMenu(). onMenuAction has no .safe counterpart — it's a pure event subscription.