This article will explain you how to use the most useful Chrome APIs by creating a simple Chrome extension called Block Site.
Agenda:
- the APIs you will learn in this tutorial are called "storage" and "tabs"
- the full list of APIs available to Chrome extensions is here
- Block Site, which we are about to create to learn and practice the two APIs, is available here
Introduction
Block Site is a simple Chrome extension that improves your productivity by blocking the access to time-consuming websites (like during the working hours) as you specify in Options. Block Site is by default disabled and doesn't block any website until you say so in Options.
Options refers to "options_page" which the extension can have. To open Options, generally you right-click on the extension icon in the toolbar and choose Options from the menu. The same will apply to Block Site.
"storage" API will be needed to store the websites user wants to block the access to, to boost his productivity, and it will also be used to store an option to quickly disable or enable the blocking at any given moment.
"tabs" API will be needed to get the tab url we are trying to open, and based on whether the domain is on the blocked list or not, we close the tab instantly, or let it proceed.
Let's now create the extension Block Site.
First steps
$ mkdir block-site
$ cd block-site
$ touch manifest.json
We have now created a new folder called block-site
and prepared a file manifest.json
which every extension has to have.
Update manifest.json
to contain following:
{
"manifest_version": 2,
"name": "Block Site",
"description": "Blocks access to time-consuming websites (as you specify) to improve your productivity.",
"version": "1.0",
"options_page": "options.html",
"permissions": ["storage", "tabs"],
"background": {
"scripts": ["background.js"],
"persistent": false
}
}
The structure of this file is explained here. Any other fields besides "manifest_version", "name" and "version", are optional and we add them as needed.
Explanation:
"options_page" is a page to open when you right-click the extension in the toolbar, and select Options from the menu. In our case, we will use this page to set a list of websites to block, and also to easily enable/disable the blocking.
"permissions" lists permissions needed by the extension. It can be requesting API access as in our case, or also a match pattern. More about possible options here. Permissions are requested by the extension when installing. Based on the requested permissions, Chrome may display a warning as explained here.
"background" sets the script to be run in the background. In our case, it will be a place where we put the main logic to stop the blocked websites from opening. Because Chrome extensions are event-based, background script is a good place to put event-based scripts, especially if they don't require an UI (like blocking the websites). It's also a good place to put any heavy calculation that could slow down the UI. As you can see, the background is set to be no persistent. That means, when not needed, the script is unloaded from memory. More about background scripts here.
Create Options page (use "storage")
Create options.html
and give it a simple markup like this:
<!DOCTYPE html>
<html>
<head>
<title>Block Site</title>
</head>
<body>
<h1>Block Site</h1>
<textarea id="textarea" rows="10" cols="30" spellcheck="false"></textarea>
<br>
<button id="save">Save</button>
<strong>Enabled?</strong><input id="checkbox" type="checkbox">
<script src="options.js"></script>
</body>
</html>
The UI is pretty simple. We have 3 elements:
-
#textarea
to specify the websites to block -
#save
button to save the modified#textarea
-
#checkbox
to enable or disable the blocking
Create options.js
and give it this content:
const textarea = document.getElementById("textarea");
const save = document.getElementById("save");
const checkbox = document.getElementById("checkbox");
save.addEventListener("click", () => {
const blocked = textarea.value.split("\n").map(s => s.trim()).filter(Boolean);
chrome.storage.local.set({ blocked });
});
checkbox.addEventListener("change", (event) => {
const enabled = event.target.checked;
chrome.storage.local.set({ enabled });
});
window.addEventListener("DOMContentLoaded", () => {
chrome.storage.local.get(["blocked", "enabled"], function (local) {
const { blocked, enabled } = local;
if (Array.isArray(blocked)) {
textarea.value = blocked.join("\n");
checkbox.checked = enabled;
}
});
});
We can see chrome.storage.local
being used, which is made available by having the "storage" permission.
When we click on #save
, we save the list of blocked sites in #textarea
under the key blocked
. Before saving them, we remove any empty lines or trailing whitespaces.
Example on how the list of blocked sites in #textarea
can look like:
facebook.com
instagram.com
youtube.com
twitter.com
reddit.com
When we click on #checkbox
, we save the boolean under the key enabled
, to tell if the blocking should be enabled or not.
When the page is loaded, we read blocked
and enabled
, and set the UI accordingly.
A closer look on "storage"
Using "storage" made chrome.storage.local
available, but what is it actually? And is that all?
It turns out, "storage" gives us access one step further, to chrome.storage
which is documented here.
chrome.storage
is similar to localStorage
, in terms of its API and storage limitations. The main benefit comes from it being asynchronous and having an onChanged
listener that can be used to synchronize the UI or otherwise react to changes in data.
chrome.storage
gives us 3 storage areas:
-
chrome.storage.local
that is best for storing the data locally -
chrome.storage.sync
that supports to store and synchronize (although very limited in size) the data across other computers where the extension is installed and same Google Account is used -
chrome.storage.managed
which is like read-only area for administrator purposes only
The most commonly used storage out of these three is chrome.storage.local
.
The most common methods across these storages are get
, set
, and remove
. See the documentation here.
Create Background script (use "tabs")
Now when we have the Options page ready, which can set blocked
(array of websites to block) and enabled
(boolean if blocking should be applied or not), it is time to work with these in the background.
Create background.js
and give it this content:
chrome.runtime.onInstalled.addListener(function () {
chrome.storage.local.get(["blocked", "enabled"], function (local) {
if (!Array.isArray(local.blocked)) {
chrome.storage.local.set({ blocked: [] });
}
if (typeof local.enabled !== "boolean") {
chrome.storage.local.set({ enabled: false });
}
});
});
chrome.tabs.onUpdated.addListener(function (tabId, changeInfo) {
const url = changeInfo.pendingUrl || changeInfo.url;
if (!url || !url.startsWith("http")) {
return;
}
const hostname = new URL(url).hostname;
chrome.storage.local.get(["blocked", "enabled"], function (local) {
const { blocked, enabled } = local;
if (Array.isArray(blocked) && enabled && blocked.find(domain => hostname.includes(domain))) {
chrome.tabs.remove(tabId);
}
});
});
At the top we can see chrome.runtime
being used. More about this API here. List of all available APIs here. And list of all possible permissions here.
As we can see, not every API requires a permission. Some APIs are generally available in extensions, like chrome.runtime
.
chrome.runtime.onInstalled.addListener
calls a given callback any time the extension is installed or updated. What we do here, we simply check if blocked
and enabled
are of a correct format, and if not, we reset them.
The more interesting, is the use of chrome.tabs
. Most of this API is generally available as well.
A closer look on "tabs"
chrome.tabs
which is described here, opens many options like creating a new tab, updating an existing tab, or reacting to various events about tabs. Most of the API is generally available, and doesn't require a "tabs" permission.
We are using "tabs" permission to get the access to url
and pendingUrl
inside the onUpdated
event. This way, we can detect if the address we try to open matches any website from the blocked
list, and if yes, we close the tab instantly as a way of blocking the access.
pendingUrl
is quite new (available since Chrome 79), and it captures the url we indent to open before the tab committed to it. pendingUrl
precedes url
. url
is more like a fallback. One tab can go through many events.
To close the tab that would navigate to a blocked site, we use chrome.tabs.remove
and provide it with a tabId
.
Testing Block Site
Block Site is now ready to test.
Open chrome://extensions
in a new tab and navigate to block-site
folder to load the extension. If no errors were made, extension should be successfully loaded.
Open any website you'd like to block, see that it works as usual.
Now, right-click on Block Site icon and select Options to open. Type in the website you would like to block and hit Save and Enabled.
Now, try to open the blocked site again. It should not be possible! Try disabling the blocking via Options and playing around with the used APIs by checking the values from the console.
Thank you very much for reading the article. I hope you enjoyed it and it left you full of excitement to continue learning. Cheers!
A summary of used resources:
- Manifest file
- List of all Chrome APIs
- Chrome Runtime API
- Chrome Storage API
- Chrome Tabs API
- All possible permissions and all possible warnings related to them
- All about Background scripts
- Block Site on GitHub - give it a like pls! :)
Here are the extensions I made on Web Store:
- Skip Ad (article here)
- My Notes
- Block Site
Top comments (4)
Great read and very helpful when I want to create my own version of this extension! I think if I'm going to implement this thru a time-based blocking instead of like a switch-based blocking 😅
Also, please don't close the tab! I feel like someone is hacking me 😂
Cool JM! :D Let me know what you then create! Time-based blocking sounds great. Like from 9-3 let's say. Should be pretty easy to extend and make it work that way.
Thanks!
Great article! Really helpful for my own learning!
Great Tutorial. Thank you. I found one issue though, in the if condition in background.js should it not be domain.find(domain => domain.includes(hostman) ?