Context Menu Contexts and Target Filters
Show a context menu item only where it applies — contexts, documentUrlPatterns and targetUrlPatterns in chrome.contextMenus, and why filtering at registration beats hiding at click time.
Table of Contents
A context menu item that appears everywhere and does nothing on most right-clicks trains users to ignore your extension. chrome.contextMenus offers three filters that decide where an item appears — the kind of thing clicked, the page it is on, and the URL of a link or image — and they are evaluated by the browser before the menu opens, with no worker round trip. Getting them right is most of the difference between a menu item that feels native and one that feels like clutter. This guide is part of context menus and right-click actions.
The three filters
Step-by-step
1. Pick the narrowest context
1chrome.runtime.onInstalled.addListener(() => {
2 chrome.contextMenus.create({
3 id: "define-selection",
4 title: "Define “%s”",
5 contexts: ["selection"],
6 });
7});
Execution context: the service worker, inside onInstalled — menu items persist across restarts, so creating them on every startup produces “duplicate id” errors. %s is replaced by the browser with the selected text, truncated, which makes the item self-describing without any code.
The available contexts are all, page, frame, selection, link, editable, image, video, audio, launcher, browser_action/action and page_action. all is almost never right; it is the context that produces clutter.
2. Restrict by page with documentUrlPatterns
1chrome.contextMenus.create({
2 id: "save-to-reader",
3 title: "Save to Reader",
4 contexts: ["page", "link"],
5 documentUrlPatterns: ["https://*/*", "http://*/*"], // not on chrome://, file:// or PDFs
6});
Execution context: the service worker. Match patterns use the same syntax as content_scripts.matches. Excluding non-web pages here means the item never appears where the action cannot work — the restricted pages listed in handling restricted URLs and tab permissions.
3. Restrict by target with targetUrlPatterns
For link, image, video and audio contexts, the filter can look at the element’s URL rather than the page’s.
1chrome.contextMenus.create({
2 id: "open-issue",
3 title: "Open issue in tracker",
4 contexts: ["link"],
5 targetUrlPatterns: ["https://github.com/*/issues/*", "https://gitlab.com/*/-/issues/*"],
6});
7
8chrome.contextMenus.create({
9 id: "save-image",
10 title: "Save image to library",
11 contexts: ["image"],
12 targetUrlPatterns: ["https://*/*.jpg", "https://*/*.png", "https://*/*.webp"],
13});
Execution context: the service worker. The issue item appears only when right-clicking a link to an issue, on any page — something a page-level filter cannot express. Match patterns cannot match query strings, so a URL like …/image?format=png needs a broader pattern and a check in the click handler.
4. Combine filters rather than checking in the handler
1// Worse: shows everywhere, bails at click time
2chrome.contextMenus.onClicked.addListener((info) => {
3 if (info.menuItemId !== "open-issue") return;
4 if (!/\/issues\/\d+/.test(info.linkUrl)) return; // the user already saw a useless item
5 openInTracker(info.linkUrl);
6});
Execution context: the service worker. A handler-side check still has a job — validating the URL precisely — but it cannot hide the item. Anything expressible as a match pattern belongs in the filter, so the user never sees an item that will do nothing.
5. Change filters at runtime when the user’s configuration changes
1async function syncMenuToSettings({ enabledOrigins }) {
2 await chrome.contextMenus.update("save-to-reader", {
3 documentUrlPatterns: enabledOrigins.length ? enabledOrigins.map((o) => `${o}/*`) : ["https://example.invalid/*"],
4 });
5}
Execution context: the service worker, called from a storage.onChanged listener. update changes an existing item in place, which is cheaper and less error-prone than removing and recreating it. An empty pattern list is rejected, hence the placeholder that matches nothing real.
Visibility, enablement and the cost of each
Beyond the filters, an item has visible and enabled properties. They are the right tool for state the filters cannot express — “only when signed in”, “only when the feature is turned on” — and they cost more, because changing them requires a worker to be running and to call update.
1chrome.storage.onChanged.addListener(async (changes, area) => {
2 if (area !== "local" || !("accessToken" in changes)) return;
3 const signedIn = !!changes.accessToken.newValue;
4 await chrome.contextMenus.update("save-to-reader", { visible: signedIn });
5});
Execution context: the service worker, registered at the top level. visible: false removes the item from the menu entirely; enabled: false shows it greyed out. Greyed out is right when the user can fix the condition and should know the feature exists; hidden is right when the item is irrelevant.
What neither can do is change per click based on the page’s contents — there is no hook that runs before the menu opens. Chrome’s contextMenus API has no onShown event; Firefox’s menus.onShown does, and it is the one place where content-dependent menus are possible without guesswork. The Chrome pattern for page-dependent menus — updating on tab activation and navigation — is in dynamic context menu generation based on page content.
Cross-browser variation
- Chrome / Edge:
chrome.contextMenussupports all three filters,visibleandenabled. There is noonShownevent, so content-dependent items must be updated ahead of the right-click. - Firefox:
browser.menus(withcontextMenusas an alias) supports the same filters plus extra contexts such astabandbookmark, andmenus.onShown/menus.refresh()for per-click updates. - Safari: supports
contextMenuswith a smaller set of contexts;targetUrlPatternssupport has been inconsistent. PreferdocumentUrlPatternsand handler-side checks for Safari builds. - All three: items are limited per extension — Chrome caps top-level items at
chrome.contextMenus.ACTION_MENU_TOP_LEVEL_LIMITfor the action menu, and multiple page-menu items are grouped under the extension’s name automatically.
Verification
- Right-click selected text, a link, an image and empty page space; confirm each item appears only where intended.
- Confirm the registered filters from the service worker console:
1chrome.contextMenus.create({ id: "probe", title: "probe", contexts: ["page"] }, () => {
2 console.log(chrome.runtime.lastError?.message ?? "created");
3 chrome.contextMenus.remove("probe");
4});
Execution context: the service worker console. There is no getAll for menus in Chrome, so the practical audit is to log your own creation calls; a “duplicate id” error here reveals an item created twice.
- Visit
chrome://extensionsand right-click; items restricted bydocumentUrlPatternsto web pages should be absent. - Toggle the controlling setting and confirm
visiblechanges take effect on the next right-click.
FAQ
Can I filter by the selected text’s content?
No — there is no pattern filter for selection text. Show the item for any selection and validate in the handler, or on Firefox use menus.onShown to update the title with the selection before the menu renders.
Why does my item appear under a sub-menu with my extension’s name?
Because the extension registered more than one item for that context. Chrome groups them automatically. Registering a single item keeps it at the top level.
Do filters work for the toolbar action’s menu?
The action context has its own menu and ignores URL patterns. Use visible for conditional items there.
Is there a limit on how many items I can register?
There is no small hard cap for page-context items, but every extension’s items share one right-click menu with the browser’s own entries and other extensions’. More than two or three items for any single context is a design problem regardless of what the API allows — group the rest under a parent item, as described in nested and radio context menu items.
Related
- Dynamic context menu generation based on page content — content-dependent menus.
- Handling selection and link context menu clicks — what to do once an item is chosen.
- Rebuilding context menus after worker restarts — when and where to create items.
- Context menus and right-click actions — the parent guide.