Listing Configured Shortcuts with commands.getAll

Read the extension's current keyboard shortcuts with chrome.commands.getAll: what each command object contains, detecting unassigned shortcuts, rendering a shortcut list in options, and keeping it fresh after users rebind keys.

Published October 2, 2026 Updated October 2, 2026 7 min read
Table of Contents

The manifest suggests Ctrl+Shift+S for “Save page”, so the onboarding page proudly tells users to press it — but on this user’s machine another extension already claimed that combination, Chrome silently left the command unassigned, and the advertised shortcut does nothing. The manifest’s suggested_key is only a suggestion. What actually happens when a user presses keys depends on what the browser assigned at install and what the user changed later, and the only reliable source is chrome.commands.getAll(). Every piece of UI that mentions a shortcut should read it from there. This guide shows what the API returns and how to build shortcut lists and hints on top of it. It belongs to keyboard shortcuts and commands.

What getAll returns

chrome.commands.getAll() resolves to an array of Command objects, one for each command declared under commands in the manifest — including the special _execute_action (and _execute_side_panel where supported). Each has a name (the manifest key), a description (from the manifest, localised if you used __MSG_ keys), and a shortcut string with the currently assigned combination in the platform’s display format, such as Ctrl+Shift+S or ⇧⌘S. If the command has no shortcut — because none was suggested, the suggestion conflicted at install, or the user cleared it — shortcut is an empty string. There is no event when a user changes a shortcut, so code that displays shortcuts re-reads them when its UI opens.

From manifest suggestion to actual shortcutThe manifest suggests keys per platform; at install the browser assigns them unless they conflict; the user may change them in the shortcuts page; commands.getAll returns the current result, which the popup, options and onboarding display.manifest commandssuggested_keyInstall-time assignmentskips conflictsUser editsshortcuts pagecommands.getAll()Command[]name, description, shortcut"" = unassignedneeds a promptUI hintspopup, options, onboarding
The manifest proposes; the browser and the user decide; getAll tells you the outcome.

Step-by-step: shortcut lists from the source of truth

1. Declare commands with good descriptions

1"commands": {
2  "_execute_action": { "suggested_key": { "default": "Alt+Shift+R", "mac": "Alt+Shift+R" } },
3  "save-page":       { "suggested_key": { "default": "Ctrl+Shift+S", "mac": "Command+Shift+S" }, "description": "__MSG_cmdSavePage__" },
4  "toggle-reader":   { "suggested_key": { "default": "Alt+Shift+F" }, "description": "__MSG_cmdToggleReader__" },
5  "open-notes":      { "description": "__MSG_cmdOpenNotes__" }
6}

Execution context: the manifest. Descriptions appear on the browser’s shortcut page and in getAll results, so they are user-facing copy — localise them. open-notes has no suggestion, so it starts unassigned; users can still bind it. Only four commands may carry suggested keys, as explained in the _execute_action command and the four-shortcut limit.

2. Read the current shortcuts

1// shortcuts.js — usable from any extension page
2export async function getShortcuts() {
3  const commands = await chrome.commands.getAll();
4  return commands.map((c) => ({
5    name: c.name,
6    label: c.name === "_execute_action" ? chrome.i18n.getMessage("cmdOpenPopup") : c.description,
7    shortcut: c.shortcut || null,
8  }));
9}

Execution context: extension pages (popup, options, side panel, onboarding tab) or the service worker. _execute_action has no description of its own in some versions, so supply a label. Normalising empty strings to null makes “unassigned” explicit in UI code. Content scripts cannot call chrome.commands; send them the data from the worker if they need it.

Command states and what to showCommands that are assigned, unassigned because of a conflict, unassigned by design, or cleared by the user, with what getAll returns and what the UI should display for each.StategetAll shortcutShow in UIAssigned as suggested"Ctrl+Shift+S"Key chipsSuggestion conflicted"""Not set — choose keys"No suggestion"""Not set — choose keys"User rebound"Alt+S"Their choice
An empty shortcut is a prompt to configure, not an error.

3. Render a shortcut list in options

 1// options.js
 2const list = document.querySelector("#shortcut-list");
 3async function renderShortcuts() {
 4  const items = await getShortcuts();
 5  list.replaceChildren(...items.map(({ label, shortcut }) => {
 6    const row = document.createElement("li");
 7    const name = Object.assign(document.createElement("span"), { textContent: label });
 8    const keys = shortcut ? keyChips(shortcut) : Object.assign(document.createElement("em"), { textContent: chrome.i18n.getMessage("shortcutNotSet") });
 9    row.append(name, keys);
10    return row;
11  }));
12}
13renderShortcuts();
14document.addEventListener("visibilitychange", () => { if (!document.hidden) renderShortcuts(); });

Execution context: the options page. Re-rendering when the page becomes visible picks up changes the user made in the browser’s shortcut page in another tab — there is no change event to listen for. Render key combinations as <kbd> chips so they are visually distinct and readable by screen readers.

4. Turn a shortcut string into key chips

1function keyChips(shortcut) {
2  const wrap = document.createElement("span");
3  wrap.className = "keys";
4  const parts = shortcut.includes("+") ? shortcut.split("+") : [...shortcut];   // mac strings like "⇧⌘S" have no "+"
5  for (const p of parts) wrap.append(Object.assign(document.createElement("kbd"), { textContent: p }));
6  wrap.setAttribute("aria-label", shortcut);
7  return wrap;
8}

Execution context: extension pages. On Windows and Linux the shortcut comes back as Ctrl+Shift+S; on macOS Chrome returns symbol form such as ⇧⌘S. Handling both keeps one renderer. See Mac and Windows modifier keys in commands.

Detecting and fixing an unassigned shortcutOn install the worker calls getAll, finds save-page unassigned because of a conflict, and records it; the onboarding page shows a notice with a button that opens the browser's shortcut page; when the user returns, the page re-reads getAll and shows the new binding.Service workerOnboarding pageShortcuts pageonInstalled → getAll(): save-page = ""open onboarding"Choose a shortcut" → open pageuser sets Alt+Svisibilitychange → getAll()
Detect at install, prompt in UI, re-read on return.

5. Detect conflicts at install

1// sw.js
2chrome.runtime.onInstalled.addListener(async ({ reason }) => {
3  if (reason !== chrome.runtime.OnInstalledReason.INSTALL) return;
4  const missing = (await chrome.commands.getAll())
5    .filter((c) => !c.shortcut && SUGGESTED.has(c.name))
6    .map((c) => c.name);
7  if (missing.length) await chrome.storage.local.set({ unassignedShortcuts: missing });
8});

Execution context: the service worker. SUGGESTED is the set of command names that had a suggested_key in your manifest — if one of those is empty right after install, it lost a conflict. Storing the list lets the onboarding page explain what happened instead of advertising keys that do nothing. See resolving keyboard shortcut conflicts.

1document.querySelector("#change-shortcuts").addEventListener("click", () => {
2  const url = navigator.userAgent.includes("Firefox") ? "about:addons" : "chrome://extensions/shortcuts";
3  chrome.tabs.create({ url });
4});

Execution context: an extension page. tabs.create can open chrome://extensions/shortcuts even though links to it from web pages are blocked. Firefox manages shortcuts from the add-ons manager (“Manage Extension Shortcuts” in the gear menu). See letting users rebind extension shortcuts.

7. Never hard-code shortcuts in copy

Every tooltip, onboarding line and help page that mentions a shortcut should render from getAll(), or omit the shortcut when it is unassigned. A static “Press Ctrl+Shift+S” in a locale file will eventually be wrong for some users. See showing shortcut hints in extension UI.

8. Share the list with content scripts

1// sw.js
2chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
3  if (msg.type !== "get-shortcuts") return;
4  chrome.commands.getAll().then(sendResponse);
5  return true;
6});

Execution context: the service worker answering content scripts. Injected overlays that want to mention a shortcut (“Press Alt+Shift+F to close reader view”) cannot call chrome.commands themselves. A small message handler gives them the same current data the extension pages use, and returning true keeps the response channel open for the async reply.

Common mistakes

  • Trusting suggested_key. Conflicts leave commands unassigned.
  • Hard-coded shortcut text. Wrong after rebinding.
  • Caching shortcuts forever. There is no change event; re-read when UI opens.
  • Calling chrome.commands from a content script. Not available there.
  • Not handling Mac symbol strings. Splitting on + fails.

Cross-browser variation

  • Chrome / Edge: commands.getAll as described; Mac returns symbol strings.
  • Firefox: browser.commands.getAll returns the same shape; Firefox also offers commands.update and commands.reset so an extension can change its own shortcuts, and commands.onChanged reports changes.
  • Safari: supports commands with getAll; shortcuts are configured differently — test what the API returns on macOS.

Verification

  1. Install with a conflicting shortcut already in use and confirm the command reports "" and the onboarding notice appears.
  2. Rebind a command in the browser’s shortcut page, return to options and confirm the list updates.
  3. Check the rendering on macOS and Windows.
  4. Confirm no UI shows a shortcut that getAll reports as unassigned.

FAQ

Is there an event when the user changes a shortcut?

Not in Chrome. Firefox has commands.onChanged. In Chrome, re-read on UI open or visibility change.

Can my extension set its own shortcut programmatically?

Not in Chrome — users change shortcuts. Firefox supports commands.update.

Does getAll require a permission?

No. Any extension with a commands key can call it.

Why is _execute_action in the list without a description?

It is a reserved command; give it a label yourself in the UI.

Can I tell whether a shortcut is global?

Not from getAll. Global scope is chosen by the user on the shortcut page; the API reports only the key combination.

Other UI/UX Patterns & Interactive Components Resources