The _execute_action Command and the Four-Shortcut Limit
How MV3's reserved _execute_action command opens the popup, why only four commands can have suggested keys, and how to decide which actions earn a default shortcut.
Table of Contents
The commands key looks like a free-form map of actions to keys, and two rules make it anything but. First, _execute_action is reserved: it does not fire commands.onCommand, it opens your popup (or fires action.onClicked if there is no popup). Second, only four commands may declare a suggested_key — define more and the extra suggestions are silently dropped, leaving commands with no shortcut at all until the user assigns one. This guide is part of keyboard shortcuts and commands.
How reserved and ordinary commands differ
Step-by-step
1. Give the popup a shortcut with _execute_action
1{
2 "action": { "default_popup": "popup.html" },
3 "commands": {
4 "_execute_action": {
5 "suggested_key": { "default": "Alt+Shift+R", "mac": "Alt+Shift+R" },
6 "description": "__MSG_cmdOpenPopup__"
7 }
8 }
9}
Execution context: parsed at install. There is no listener to write — pressing the shortcut opens the popup exactly as a click would, including granting activeTab. This is also the keyboard entry point screen-reader users rely on, as described in testing extension UI with a screen reader.
2. Handle ordinary commands in the worker
1{
2 "commands": {
3 "save-page": { "suggested_key": { "default": "Alt+Shift+S" }, "description": "__MSG_cmdSavePage__" },
4 "toggle-view": { "suggested_key": { "default": "Alt+Shift+V" }, "description": "__MSG_cmdToggleView__" }
5 }
6}
1chrome.commands.onCommand.addListener(async (command, tab) => {
2 if (command === "save-page") return savePage(tab);
3 if (command === "toggle-view") return toggleView(tab);
4});
Execution context: the service worker, registered at the top level so a shortcut that wakes a sleeping worker is not lost. The tab argument is the active tab when the shortcut was pressed; a command counts as a user gesture and grants activeTab for it.
3. Stay within four suggested keys
_execute_action plus three named commands is the maximum that ship with a default. Anything beyond that must be declared without a suggested_key.
1{
2 "commands": {
3 "_execute_action": { "suggested_key": { "default": "Alt+Shift+R" }, "description": "…" },
4 "save-page": { "suggested_key": { "default": "Alt+Shift+S" }, "description": "…" },
5 "toggle-view": { "suggested_key": { "default": "Alt+Shift+V" }, "description": "…" },
6 "next-article": { "suggested_key": { "default": "Alt+Shift+N" }, "description": "…" },
7 "prev-article": { "description": "__MSG_cmdPrevArticle__" },
8 "archive-article": { "description": "__MSG_cmdArchive__" }
9 }
10}
Execution context: parsed at install. The last two commands appear on the shortcuts page with no key assigned; the user can bind them there. A fifth suggested_key would not error — it would just be ignored, which is worse, because nothing tells you.
4. Tell users where to assign the rest
Unassigned commands are invisible unless you point at them. Link to the shortcuts page from your options page.
1document.querySelector("#shortcuts").addEventListener("click", () => {
2 chrome.tabs.create({ url: "chrome://extensions/shortcuts" });
3});
Execution context: the options page. Extension pages may open chrome://extensions/shortcuts with tabs.create, even though they cannot script it. The Firefox equivalent and the rebinding flow are in letting users rebind extension shortcuts.
5. Show the effective shortcut in your UI
What you suggested and what the user has are different things: the browser may have refused the suggestion because of a conflict, or the user may have changed it.
1const commands = await chrome.commands.getAll();
2const open = commands.find((c) => c.name === "_execute_action");
3hint.textContent = open?.shortcut
4 ? `Tip: press ${open.shortcut} to open Reader.`
5 : "Tip: assign a shortcut to open Reader from the keyboard.";
Execution context: any extension page. shortcut is an empty string when no key is bound — including when your suggestion collided with a browser shortcut and was not applied. Displaying the real value avoids telling users to press a key that does nothing.
Choosing which actions earn a default key
Three suggested keys is a small budget, and the choice shapes how the extension feels. The candidates worth a default share three properties: they are used often, they are used while the user’s hands are on the keyboard, and they act on the current page without further input.
“Save this page” qualifies. “Open settings” does not — it is rare, and the popup is one click away. “Next article” qualifies for a reader extension where it is pressed dozens of times a session. “Export all data” does not, and binding it to a key invites accidents.
Key choice is the other half. Ctrl+Shift+<letter> and Alt+Shift+<letter> are the conventional extension spaces; plain Ctrl+<letter> collides with browser and page shortcuts almost everywhere, and the browser will refuse many of them. Avoid letters that common web applications use with the same modifiers — Alt+Shift+ combinations collide less often with site shortcuts than Ctrl+Shift+ ones do. The conflict problem is explored in resolving keyboard shortcut conflicts.
Global commands and the popup’s own keys
Two more distinctions round out the model.
A command may declare "global": true, which makes the shortcut work even when the browser is not focused — a media-control extension might want play/pause from anywhere on the desktop. Global shortcuts are limited to Ctrl+Shift+[0–9] on most platforms, are not available on ChromeOS, and are a much larger imposition on the user’s system than a browser-scoped key. They are the exception, and they share the same four-suggestion budget. The rules are set out in implementing global keyboard shortcuts safely.
Keys inside the popup are a separate matter entirely. Once the popup is open, it is an ordinary document and ordinary keydown handlers work — j/k to move through a list, Enter to open, / to focus search. These do not go through commands, do not count toward any limit and cannot conflict with browser shortcuts, because the popup has focus.
1// popup.js — in-popup keys, no manifest entry needed
2document.addEventListener("keydown", (e) => {
3 if (e.target instanceof HTMLInputElement) return; // do not hijack typing
4 if (e.key === "j") moveSelection(+1);
5 if (e.key === "k") moveSelection(-1);
6 if (e.key === "/") { e.preventDefault(); searchInput.focus(); }
7});
Execution context: the popup document. The input guard is essential: without it, typing a j into the search box moves the selection instead. A good pattern is one manifest command to open the popup, and single-key shortcuts once inside — the global budget stays small and the in-popup vocabulary can be as rich as the UI needs.
Cross-browser variation
- Chrome / Edge:
_execute_actionopens the popup;_execute_side_panelopens the side panel (Chrome 116+). Four suggested keys maximum. Users manage shortcuts atchrome://extensions/shortcuts. - Firefox: supports
_execute_action(and the older_execute_browser_actionin MV2),_execute_sidebar_action, and the same four-suggestion guidance.browser.commands.updatelets the extension change a binding itself — Chrome has no equivalent. - Safari: supports
commandswith suggested keys; the popup-opening reserved command works. Shortcut management is less discoverable for users, so the in-UI hint matters more. - All three:
macinsuggested_keylets you useCommandon macOS;Ctrlin the manifest maps toCommandon macOS automatically unless you specifyMacCtrl.
Verification
- After a fresh install, list the effective bindings:
1(await chrome.commands.getAll()).map((c) => [c.name, c.shortcut || "(none)"]);
2// [["_execute_action","⌥⇧R"], ["save-page","⌥⇧S"], ["toggle-view","⌥⇧V"], ["next-article","⌥⇧N"],
3// ["prev-article","(none)"], ["archive-article","(none)"]]
Execution context: any extension context. Any of the first four showing (none) means the suggestion conflicted and was not applied.
- Press the
_execute_actionshortcut and confirm the popup opens with noonCommandlog. - Press each named shortcut on a page and confirm the handler runs with a populated
tab.url(theactiveTabgrant). - Open the options page and confirm the hint shows the real binding.
FAQ
Can I have more than four shortcuts?
You can declare any number of commands; only four may suggest a key. The rest are assignable by the user.
Does _execute_action work without a popup?
Yes — it then fires action.onClicked, exactly as a click on the toolbar button would.
Why did my suggested key not apply?
Most often a conflict with a browser shortcut or another extension that claimed it first. The browser silently leaves your command unbound.
Related
- Implementing global keyboard shortcuts safely — the global-scope variant.
- Resolving keyboard shortcut conflicts — when a suggested key is taken.
- Letting users rebind extension shortcuts — the path for the fifth command onward.
- Keyboard shortcuts and commands — the parent guide.