Chrome Extensions have a lot of features and functionalities that help developers create extensions and tools with great user experience.

In this tutorial, we'll go over how to add keyboard shortcuts in a Chrome extension. This tutorial will cover how it can be done for both Manifest Versions 2 (MV2) and 3 (MV3).

Suggested Read: Chrome Extension Tutorial: Migrating to Manifest V3 from V2

You can find the full code for this tutorial on this GitHub repository.


Creating The Extension

In case you already have an extension ready, you can skip this step. We'll quickly go over how to create a Chrome extension.

Suggested Read: Learn how to create a Chrome extension in the tutorial Chrome Extension Tutorial — Replace Images in Any Website with Pikachu.

To create a Chrome extension, first, create a directory that will hold all the extension files in it:

mkdir new-extension
cd new-extension

Then, create a manifest.json file with the following content:

{
    "name": "Commands",
    "version": "0.0.1",
    "description": "Assign Keyboard Commands",
    "manifest_version": 3,
    "icons": {
        "16": "/assets/icon-16.png",
        "32": "/assets/icon-32.png",
        "48": "/assets/icon-48.png",
        "128": "/assets/icon-128.png"
    },
}

If you're creating a Manifest V2 extension, make sure to change the manifest version:

"manifest_version": 2,

Also, make sure to download the icons from the GitHub repository first, or add your own icons.

Then, go to chrome://extensions/ on your Chrome browser, enable Developer Mode if you haven't from the top right, click on "Load unpacked" and choose the folder that you created for your extension. Once done, the extension will be added to your Chrome browser.


What Are Chrome Commands

Chrome commands allow an extension to register commands, or keyboard shortcuts, to provide ease of use for the users of that extension to perform certain actions through those shortcuts. An extension can have at most 4 commands.

There are two kinds of commands. A standard command and an action command. A standard command runs in the background script or service worker and has no access to the current page or tab opened. Standard commands trigger the event chrome.commands.onCommand, and you can listen to it in your background script or service worker.

Action commands map the shortcuts to your extension's action (if MV3) or browser action or page action (if MV2). Action commands can have access to the current tab and can execute a script on the current page. Action commands do not trigger the event chrome.commands.onCommand. Instead, in MV3 they trigger chrome.action.onClicked , and in MV2 they trigger chrome.browserAction.onClicked or chrome.pageAction.onClicked.

Commands are defined in an extension's manifest.json under the commands key. Standard commands are defined in the following format:

"commands": {
	"firstCommand": {
    	"suggested_key": "CTRL+F",
        "description": "My first command"
    }
}

Where suggested_key can be an object or a string (we'll get to that in a bit) and description is required.

Action commands are defined in the same way but under the _execute_action key in MV3, or _execute_browser_action or _execute_page_action keys in MV2:

"commands": {
	"_execute_action": {
    	"suggested_key": "CTRL+F",
        "description": "My first command"
    }
}

description for action commands is optional.

Suggested Key

Some of the keys that are allowed to be used are: Ctrl, Alt, Shift, 0...9, and A...Z. For a full list of keys, you can see them in the chrome.commands reference.

suggested_key can either be a string or an object. If a string, then the same shortcut will be applied for all Operating Systems. In this case, certain keys will be replaced with their equivalent on the Operating Systems. This means that Ctrl on Mac OS will be substituted by Command.

To specify the shortcut for an operating system, you should pass an object where the key is the operating system's name. The allowed keys are default which applies the shortcut to all operating systems not listed in the object, chromeos, linux, mac, and windows.

If you want to use Ctrl on Mac instead of Command, you can use MacCtrl for the value of mac in the object:

"suggested_key": {
	"default": "Ctrl+F",
    "mac": "MacCtrl+F"
}

This will make the shortcut Ctrl+F for all operating systems, but for Mac instead of the conventional Command substitute, it will use the Ctrl key on Mac.

It should be noted that the value for the keyboard shortcuts is case-sensitive. If you set the value to CTRL+F instead of Ctrl+F, the shortcut will not be registered.

Global Shortcuts

By default, keyboard shortcuts are executed only when the Chrome browser is in focus. To allow shortcuts to work even when Chrome is in the background, you can make the command "global" by adding the global key in the definition:

"commands": {
	"first": {
    	"suggested_key": "Ctrl+Shift+F",
        "description": "First command",
        "global": true
    }
}

Global shortcuts are very limited in the amount of keys you can use in suggested_key to make sure your shortcut does not overlap with a system shortcut. For that reason, the format should be Ctrl+Shift+[0...9].

If the shortcut is already defined and you later set it to global, upon testing it I noticed that the shortcut will not be updated. To update it, you will need to remove and reinstall the extension.


Adding an Action Command

We'll first add an action command. The action command will toggle "Dark Mode" on the current website. We'll do it in a very basic way, which is just changing the background color to black and text color to white, as this is not the purpose of this tutorial.

Define The Command

Let's first define the action command. It will be triggered at the press of Ctrl+Shift+L.

MV3

For MV3, the definition will be:

"commands": {
	"_execute_action": {
		"suggested_key": "Ctrl+Shift+L",
		"description": "Toggle dark body"
	},
}

As we are adding an Action command, we need to also add an action key:

"action": {},

MV2

For MV2 we just need to change _execute_action to _execute_browser_action:

"commands": {
	"_execute_browser_action": {
            "suggested_key": "Ctrl+Shift+L",
            "description": "Toggle dark body"
	},
}

and add a browser_action key:

"browser_action": {},

This will define our command that toggles dark mode at the press of Ctrl+Shift+L. Make sure that the suggested_key value is correct as it is case-sensitive. If it's incorrect, the shortcut will not be registered.

Add Event Listeners

Next, we need to listen to when these keys are pressed. Inside the listener, we need to execute a Javascript code that will just toggle the background color and text color of the document's body.

MV3

To do that, first, add a service worker:

"background": {
   "service_worker": "background.js"
},

Then, in the service worker or background script, we'll add a listener to handle the press of the keys. In the service worker, we'll add a listener to chrome.action.onClicked event:

chrome.action.onClicked.addListener((tab) => {
    //TODO toggle dark mode in the tab
});

Inside the listener, we'll use chrome.scripting.executeScript. executeScript accepts an object of options including tabId for the id of the tab to execute the script on and function for the Javascript function to execute. Add in the service worker the following function:

function toggleDark () {
    if (!document.body.getAttribute('data-ext-dark')) {
        document.body.setAttribute('data-ext-dark', true);
        document.body.style.backgroundColor = '#000';
        document.body.style.color = '#fff';
    } else {
        document.body.setAttribute('data-ext-dark', false);
        document.body.style.backgroundColor = '#fff';
        document.body.style.color = '#000';
    }
}

Then, inside the listener we created earlier, we'll use executeScript to execute the function on the current tab:

chrome.scripting.executeScript({
    target: {tabId: tab.id},
    function: toggleDark
});

MV2

If you're creating an MV2 extension, you need to add a background script instead:

"background": {
   "scripts": ["background.js"],
   "persistent": false
 },

In  the background script, we'll add a listener to chrome.browserAction.onClicked event:

chrome.browserAction.onClicked.addListener((tab) => {
    //TODO toggle dark mode in the tab
});

chrome.scripting is not compatible with MV2. Instead, we'll use chrome.tabs.executeScript.  This method accepts 2 arguments, the first one the id of the tab to execute the script on, and the second is an object of options including the file option which allows you to specify a Javascript file to inject into the page and execute. So, create a new file called toggleDark.js in the root of the extension with the following content:

if (!document.body.getAttribute('data-ext-dark') || document.body.getAttribute('data-ext-dark') === 'false') {
    document.body.setAttribute('data-ext-dark', true);
    document.body.style.backgroundColor = '#000';
    document.body.style.color = '#fff';
} else {
    document.body.setAttribute('data-ext-dark', false);
    document.body.style.backgroundColor = '#fff';
    document.body.style.color = '#000';
}

And in the listener we created earlier, use chrome.tabs.executeScript to run the code in toggleDark.js:

chrome.browserAction.onClicked.addListener((tab) => {
    chrome.tabs.executeScript(tab.id, {
        file: 'toggleDark.js'
    });
});

Adding Permissions

Now we're ready to handle when the shortcut is pressed. There's one last thing we need to do which is add permissions. Permissions are defined in manifest.json and include a set of permissions that the user has to allow for the extension to run.

MV3

If you've been following along with the MV3 instructions, then you'll need two permissions: activeTab to access the current tab and scripting to use the chrome.scripting API. So, add the following in manifest.json:

"permissions": ["activeTab", "scripting"],

MV2

As for MV2, we'll need the activeTab permission to access the current tab and tabs to use the chrome.tabs API. So, add the following in manifest.json:

"permissions": ["activeTab", "tabs"],

Testing

Let's test our code. First, go to chrome://extensions and refresh the extension. Then, in an open tab (if you're creating an MV2 extension you'll need to refresh the page), press Ctrl+Shift+L, and the document's body background color will change to black and the text to white. Remember that we're taking a simple approach to dark mode, so obviously it will not actually look good. If you keep pressing the same shortcut, it will toggle between light and dark mode.

If it doesn't work, make sure that Ctrl+Shift+P in your manifest.json is written correctly, as this is case-sensitive. Also, it should be noted that if other extensions that were installed previously use this shortcut, they'll have precedence to use the shortcut.  Similarly, if the shortcut is used by the browser or your operating system, then it will not be triggered. So, if the command does not work for you, try changing the shortcut in the manifest, then refresh the extension and try again.


Adding a Standard Command

Next, we'll add a standard command. The standard command we'll create will use the shortcut Ctrl+Shift+P to print to the console of the service worker or background script "Hello there!". It will run as long as Chrome is in focus, regardless of what page or tab is open.

This section is the same for both MV3 and MV2, so you can follow along regardless of what version your extension is.

In manifest.json, add another command under the commands key:

"commands": {
	//...
    "hello": {
       "suggested_key": "Ctrl+Shift+P",
       "description": "Say Hello"
    },
}

Then, in the service worker or background script, add a listener to the chrome.commands.onCommand event:

chrome.commands.onCommand.addListener((command) => {
    //TODO handle event
});

In the listener, we'll first check which command is triggered. This is necessary once you have more than one standard command, as they all trigger this same event.

The command argument passed is the name of the command. So, if the user presses Ctrl+Shift+P, the value for command will be hello.

If the command is hello, we'll log to the console "Hello there!":

if (command === 'hello') {
	console.log("Hello there!");
}

That's it. Go to chrome://extensions and refresh the extension. Then, for MV3 extensions click on "Inspect views service worker"

For MV2 extensions click on "Inspect views background page"

This will open the devtools for the service worker or background script. Click on the Console tab. Now, try clicking the shortcut Ctrl+Shift+P and you'll see "Hello there!" logged in the console.

If it doesn't work, make sure that Ctrl+Shift+P in your manifest.json is written correctly, as this is case-sensitive. Also, it should be noted that if other extensions that were installed previously use this shortcut, they'll have precedence to use the shortcut.  Similarly, if the shortcut is used by the browser or your operating system, then it will not be triggered. So, if the command does not work for you, try changing the shortcut in the manifest, then refresh the extension and try again.


Making a Command Global

In this section, we'll create a global standard command that when the user presses Ctrl+Shift+8, it will log to the console of the service worker or background script the current date.

This section is the same for both MV3 and MV2, so you can follow along regardless of what version your extension is.

To make a command global, we just need to add the key global to its definition in manifest.json and set it to true:

"date": {
	"suggested_key": "Ctrl+Shift+8",
	"description": "Show Date",
    "global": true
}

Then, in your service worker or background script, add an else block to the previous if statement that checked if the command name was "hello". In the else block, we'll log to the console the current date:

chrome.commands.onCommand.addListener((command) => {
    if (command === 'hello') {
        console.log("Hello there!");
    } else {
        console.log("Date: " + (new Date).toDateString());
    }
});

Our new command is ready. During development, I noticed that the global command, whether it's a newly added command or an older command that we added the global key to it, later on, the shortcut was not added or updated unless the extension was removed and re-loaded. So, make sure to do that.

Then, open the console for the service worker or background script as mentioned in the previous section. If you now change to any window that's not your Chrome browser and press Ctrl+Shift+8, then go back to the console for the service worker or background script, you'll see that the date is logged.

Again, if the command does not work, make sure that the command in the manifest is correct as it is case-sensitive. Also, if this command is used by your system for another shortcut, you'll need to change it to something else.


Conclusion: Next Steps

As you have noticed while going through this tutorial, if an extension that was added prior to yours uses the same shortcut, your shortcuts will not be registered, so they will not work. To avoid that, you can check if your shortcuts are all added on installing the extension, and if any are missing you can notify the user that they can set the shortcuts in their Chrome browser on chrome://extensions/shortcuts. For more information and an example on how to do that, check the example on chrome.command reference.