Chrome Extension Context Menu Tutorial
How to add right-click menu entries to a Chrome extension and handle clicks from the service worker.

Context menus are one of the highest-value, lowest-effort features you can add. Highlight text, right-click, and your extension does something useful — no popup required.
Create menus at install time
Add "contextMenus" to permissions, then create items inside chrome.runtime.onInstalled. Creating them at the top level of a service worker causes duplicate-id errors when the worker restarts.
chrome.runtime.onInstalled.addListener(() => { chrome.contextMenus.create({ id: "save-selection", title: "Save \"%s\" to my list", contexts: ["selection"] }); });
Useful contexts
- selection — highlighted text; %s in the title is replaced by the selection.
- link — right-click on an anchor; info.linkUrl holds the target.
- image — info.srcUrl gives the image address.
- page — anywhere on the page, good for a generic action.
Handling the click
chrome.contextMenus.onClicked.addListener((info, tab) => ...) runs in the service worker. Use info.menuItemId to branch, and chrome.scripting.executeScript or a message to the tab if you need page access.
Nested and parent menu items
Pass a parentId to chrome.contextMenus.create() to build submenus: create a parent with id "my-tools", then create children with parentId: "my-tools" to group several related actions under one top-level entry instead of cluttering the right-click menu with five separate items.
Checkbox and radio menu items
- type: "checkbox" toggles a checked boolean each click and is read back from info.checked in onClicked.
- type: "radio" groups items sharing the same parentId so only one is checked at a time — useful for a 3-option mode switcher.
- Persist the chosen state to chrome.storage so the checked state survives a service worker restart, since Chrome does not remember it for you across restarts in all cases.
Updating and removing items
chrome.contextMenus.update("save-selection", { title: "Saved!" }) changes an existing item without recreating it, and chrome.contextMenus.remove(id) or removeAll() cleans up. Always guard update()/remove() calls in a try/catch or check chrome.runtime.lastError, since calling them on an id that no longer exists throws.
Reaching into the page from a menu click
chrome.contextMenus.onClicked cannot touch the DOM directly. Use chrome.scripting.executeScript({ target: { tabId: tab.id }, func: yourFunction, args: [info] }) to run code in the page, which requires the "scripting" permission and either activeTab or a matching host_permissions entry.
Menu items scoped to specific extension surfaces
The "action" context targets your own toolbar icon's right-click menu, separate from page contexts — useful for adding an "Open settings" or "Report a bug" entry directly on your extension's icon.
Frequently asked questions
Why do I get 'Cannot create item with duplicate id'?
The service worker restarted and re-ran your create call. Wrap creation in chrome.runtime.onInstalled, or call chrome.contextMenus.removeAll() first.
Can context menu titles be dynamic per page?
Yes — call chrome.contextMenus.update() from a tab-activation or navigation listener to change the title based on the current page before the user right-clicks.
Do context menus work in incognito?
Only if the user has explicitly enabled 'Allow in Incognito' for your extension on chrome://extensions; otherwise the service worker and its menu items never load in that window.
What's the contexts value for the extension's own popup?
There is no popup context for contextMenus — right-clicking inside your popup shows Chrome's default browser context menu, not one you can customize with the contextMenus API.