Browser extensions are a cool way to build on top of the basic parts of frontend web development: HTML, CSS, and JavaScript. In fact, they were my own on-ramp to frontend web development. What I really like about them is that all you need in order to get started building extensions is the stuff you make webpages with, and then by throwing in browser APIs, you can make really useful apps!
For this tutorial, we're going to make a Chrome extension that rearranges the tabs in your browser so they're in order by URL. That way, if you end up with a whole lot of tabs open from a bunch of different websites, you can use this extension to put all the tabs from the same site together in Chrome's top bar so they're easier to navigate.
This tutorial assumes understanding of the basics of JavaScript, and it's recommended to know about callback functions and the Array.prototype.sort
method. It is geared toward people who are getting started with writing browser extensions.
Add an extension manifest
The first thing every browser extension needs is a manifest. A manifest is a JSON file, and it's sort of like "the blueprint of the app", telling you things like what picture to use as your extension's icon, which code the extension runs, and which parts of Chrome the app needs access to, such as web history or bookmarks.
If you are following along in this tutorial, make a folder titled tab-sorter
, put a folder under it called app
, and inside the app
folder, add the file manifest.json
with this
code in it:
{
"manifest_version": 2,
"name": "Tab Sorter",
"version": "1.0.0",
"description": "Rearrange your tabs by URL"
}
We now have a manifest giving us the name of our extension, its version, and a little description. In fact, with this, we actually technically already have an extension we can load into Chrome, so let's try that! First go to the URL chrome://extensions
, which is where you can manage your extensions in Chrome. Then, flip the switch that says developer mode:
Then, you will see a button in the top left that says Load Unpacked, which lets you load an extension from one of your folders. Click it and select the folder tab-sorter/app
, and now in the top bar you should be able to see an icon with a T to the right of your URL bar. That's the extension you're making.
The extension doesn't do anything yet, except make a dropdown of options appear, so let's change that by giving it a popup, an HTML page that appears when you click on the extension icon. In the app
folder add this HTML to a file titled popup.html
:
<html>
<head></head>
<body>
<h1>We're running a Chrome extension!</h1>
</body>
</html>
We have a page that can appear in a popup! But if we just reloaded the extension now, it wouldn't actually do anything with popup.html
. In order to use that HTML file as our popup, we need to add popup.html to the manifest in a browser action, which is one way of specifying which code your extension will use.
In manifest.json
, add the browser_action
to the file and it should now look like this:
{
"manifest_version": 2,
"name": "Tab Sorter",
"version": "1.0.0",
"description": "Rearrange your tabs by domain name",
+ "browser_action": {
+ "default_popup": "popup.html"
+ }
}
The browser_action.default_popup
field tells us that when we click on our app's icon, popup.html's content will appear in a little window under the browser's top bar. Now in chrome://extensions
, click through the "load unpacked" flow again, or just click the swirly arrow icon in your extension's panel on the page to reload the extension. Then, we should see our popup!
Now we have a popup, and just like in a regular HTML webpage, we can run JavaScript in our popup to make the app do all the same things we can do in a regular HTML/CSS/JS web app.
But unlike in regular webpages, in extensions we can use browser APIs to be able to work with different parts of the browser, such as the user's tabs. We'll get started on that in the next section!
Before we do that, though, can't have an app without an icon! Our icon can be any picture that has the same height and width, and luckily at this page, Google made a free set of icons to use, under the permissive MIT license! Download the one that looks like a couple arrows going left and right in 128x128 size, save it to tab-sorter/app/128.png
. Then, change the browser_action
section of your manifest to:
"browser_action": {
- "default_popup": "popup.html"
+ "default_popup": "popup.html",
+ "default_icon": {
+ "128": "128.png"
+ }
}
Now if you reload your extension one more time, you should see this:
Our progress so far is in Commit 1
Now let's get our extension to work with browser tabs!
Explore the chrome.tabs API
In order to sort our tabs, we need an API that allows us to "talk to" the Google Chrome tabs. The actions we want to be able to do are:
- List all the tabs in the current browser window
- See what website each tab is on so we can sort the tabs by URL
- Move the tabs around in the browser's top bar so they're in alphabetical URL order
Google Chrome gives you a ton of APIs letting you work with different browser features, and you can see the whole list here! The one we want is chrome.tabs
, and you can find its documentation here!
If you look through the table of contents for one of Chrome's APIs like tabs
, you can see the different types of JavaScript objects the API works with, methods in the API you can use, and events you can make your Chrome extension listen for and respond to.
Let's take a look at the Tab
type, which is the data type we'll be working with. A Tab
object tells us information about a single tab in our browser. A few of those pieces of information are:
-
id
, a number uniquely identifying the tab -
windowId
, which tells us which window the tab is in -
highlighted
, a boolean that tells us whether or not a given tab is the one we're looking at - and the main field we want,
url
, which tells us what URL the tab is on
Since we found the browser tab object and we know it has a url
field, the pseudocode for our extension will look something like:
let tabs = getTheTabs();
sortTheTabs(by Tab.url);
for (let i = 0; i < tabs.length; i++) {
moveTabTo(that tabs index inside the sorted array);
}
So the pieces of pseudocode we need to turn into real code are now getTheTabs
, moveTabTo
, and a by Tab.url
function for sorting tabs by their URL. Let's start by looking for a function for getting all the tabs in the browser window we're using.
Listing the tabs
In the Methods section in the API's table of contents, there's a lot of methods, like getting an individual tab by its ID number, opening and closing tabs, navigating to a different URL, and even changing a tab's CSS using insertCSS
.
The method we want, for getting a list of all the tabs in the current window, is chrome.tabs.query
, and its function signature is:
function query(queryInfo, callback)
The queryInfo
parameter is a JavaScript object that gives us options for narrowing down which tabs we want to get. So to get only the tabs in the browser window the user is currently in (like if the user had more than one Chrome window open), our queryInfo
object would look like this:
{windowId: chrome.windows.WINDOW_ID_CURRENT}
Then we have the callback
parameter. tabs.query
, and a lot of other Google Chrome API methods, are asynchronous. In order to be sure we're not blocking the JavaScript runtime while we're getting data from thr browser, when we run chrome.tabs.query
or similar methods, we have Chrome start getting the tabs we're asking for with queryInfo
, then let JavaScript keep running more code. Then, when Chrome gives us our list of tabs, the callback function runs to let us work with the tabs.
So instead of our code looking like:
let tabs = chrome.tabs.query({windowId: chrome.windows.WINDOW_ID_CURRENT});
// sort the tabs
// rearrange the tabs
it will look more like:
chrome.tabs.query({windowId: chrome.windows.WINDOW_ID_CURRENT}, (tabs) => {
// sort the tabs
// rearrange the tabs
});
Let's give chrome.tabs.query
a try by having our popup show a bulleted list all the tabs we're on! In the body of popup.html
, add the script tag:
<script type="text/javascript" src="popup.js"></script>
and then make a new file popup.js
with this code:
chrome.tabs.query({windowId: chrome.windows.WINDOW_ID_CURRENT}, (tabs) => {
document.write(`<h3>The tabs you're on are:</h3>`);
document.write('<ul>');
for (let i = 0; i < tabs.length; i++) {
document.write(`<li>${tabs[i].url}</li>`);
}
document.write('</ul>');
});
Go to chrome://extensions
, reload the extension one more time, click the popup, and you will get:
Why are all our list items saying undefined
? The reason why that is, is that we're not able to see what URL is on the tabs because our app doesn't request permission to use tab data. We need to set that permission in our extension's manifest.
Go to your manifest.json
, and add the line permissions: ["tabs"]
{
"manifest_version": 2,
"name": "Tab Sorter",
"version": "1.0.0",
"description": "Rearrange your tabs by domain name",
+ "permissions": ["tabs"],
"browser_action": {
"default_popup": "popup.html"
}
}
On browser extension stores, users are able to see what permissions an extension needs access to. So before Chrome lets an extension get access to what URLs are in each tab, the tabs
permission needs to be listed in the manifest. With that permission now in our manifest, reload your Chrome extension one more time, and you should see:
All right! We can now get all our tabs! Now our app's overall pseudocode has just two more blanks to fill in: sorting the tabs, and moving tabs to rearrange them. So now our pseudocode looks like:
chrome.tabs.query({windowId: chrome.windows.WINDOW_ID_CURRENT}, (tabs) => {
sortTheTabs(by Tab.url);
for (let i = 0; i < tabs.length; i++) {
moveTabTo(tabs[i], i);
}
});
Our progress so far is in Commit 2.
Sorting the tabs
Now that we have our Tab
s, the next thing to figure out is how to sort them so they go in order of their URLs.
As we saw in the last section, we have a Tab.url
field we can use to see each tab's URL. So to sort our array of tabs in alphabetical URL order, we can use JavaScript's core Array.prototype.sort
method.
If you haven't used Array.prototype.sort
before, it allows you to rearrange the items in an array using the order you want. For example, if you ran:
let a = ['JavaScript', 'C++', 'Go'];
a.sort();
console.log(a);
Then the strings in the array would now be in the order ['C++', 'Go', 'JavaScript']
, sorted in alphabetical order.
Since we're sorting objects instead of strings or numbers, though, we'll need to also pass in a comparison function that takes in two tabs and tells us which one should go earlier in the array. Running it will look like this:
tabs.sort(byAlphabeticalURLOrder);
According to the rules of writing Array.prototype.sort
, when two items in the array are compared with a comparison function:
- The items in the array are moved so the first item is before the second item if the function returns a number less than 0.
- The items in the array are moved so the first item is after the second item if the function returns a number greater than 0.
- If the comparison function returns exactly 0, the two items are considered to have equal values and stay put where they are in the array.
So when two tabs are passed into our comparison function:
- If the first tab's URL comes before the second tab's URL alphabetically, then we return -1 so the first tab comes earlier in the array.
- If the second tab's URL comes before the first tab's URL alphabetically, then we return 1 so the second tab comes earlier in the array.
- If the two tabs have identical URLs, we return 0, and they stay in the same order they were already in.
So let's turn this into code. Add this function at the top of popup.js
function byAlphabeticalURLOrder(tab1, tab2) {
if (tab1.url < tab2.url) {
return -1;
} else if (tab1.url > tab2.url) {
return 1;
}
return 0;
}
We've got our comparison function! Now let's try using it in our listing function we made in the last section:
chrome.tabs.query({windowId: chrome.windows.WINDOW_ID_CURRENT}, (tabs) => {
+ tabs.sort(byAlphabeticalURLOrder);
+
document.write(`<h3>The tabs you're on are:</h3>`);
document.write('<ul>');
for (let i = 0; i < tabs.length; i++) {
document.write(`<li>${tabs[i].url}</li>`);
}
document.write('</ul>');
});
Reload your extension in chrome://extensions
, open the extension's popup again, and now it should look like this!
Great, we have our sorted list of tabs! Now to make it so the tabs rearrange in the top bar of the browser, we need just one more Chrome function!
Our progress so far is in Commit 3
Moving the tabs
Looking back in the docs for the Chrome Tabs API, we can see that the API gives us exactly the function we want for moving the tabs in the top bar, chrome.tabs.move
! And the function signature is:
function move(tabIds, moveProperties, callback);
- For the
tabIds
parameter, each tab has an ID number,Tab.id
, uniquely identifying it. So if we're moving the tab with the ID number 250, we would domove(250, moveProperties, callback);
-
moveProperties
is an object describing where to move the tab to. So we could move the tab with ID number 250 to be the leftmost tab in our browser window by callingmove(250, {index: 0}, callback);
. Notice that tab indices are zero-indexed, so the leftmost tab in the window has index 0. - Finally, we reach the optional
callback
. Like withquery
, themove
function is asynchronous, so if we want something to happen right after the tab is moved, we run that in the callback function.
Let's give move a try by moving just the tab that's first in alphabetical URL order, to be the leftmost tab in your window. Edit popup.js
like this:
chrome.tabs.query({windowId: chrome.windows.WINDOW_ID_CURRENT}, (tabs) => {
tabs.sort(byAlphabeticalURLOrder);
+ chrome.tabs.move(tabs[0].id, {index: 0});
-
- document.write(`<h3>The tabs you're on are:</h3>`);
- document.write('<ul>');
- for (let i = 0; i < tabs.length; i++) {
- document.write(`<li>${tabs[i].url}</li>`);
- }
- document.write('</ul>');
});
To try this out, reload your extension in chrome://extensions
, and move that tab so it's the rightmost tab in the browser. Now, click your browser extension's icon and that tab should move so it's the first tab from the left.
Because we had already run tabs.sort
, all the tabs in the tabs
array are now in the order we want them in on the top bar of the browser; the first tab in the array should be the leftmost one in the top bar, the second tab in the array is supposed to be the second tab in the top bar, and so on!
So if we have all the tabs in the order we want them in the array, we can move all of them into alphabetical URL order with this loop:
chrome.tabs.query({windowId: chrome.windows.WINDOW_ID_CURRENT}, (tabs) => {
tabs.sort(byAlphabeticalURLOrder);
- chrome.tabs.move(tabs[0].id, {index: 0});
+ for (let i = 0; i < tabs.length; i++) {
+ chrome.tabs.move(tabs[i].id, {index: i});
+ }
});
Reload the extension just one more time in chrome://extensions
, click its icon, and your tabs should all now be rearranged in alphabetical order by URL!
Though one thing you might notice is a little off is that if, say, twitter.com and www.google.com are in your top bar, Twitter will come first in the rearranged tabs, even though Google alphabetically comes before Twitter. This is because "www" comes after "Twitter" in the URL. So if we were making this at a real company, a possible next user experience step might be to tweak our comparison function to ignore https://
and www.
.
I'll leave that, and brainstorming other ideas on improving user experience, as a challenge to do; I encourage you to keep experimenting with this app and the chrome.tabs
API, but for now, we've got a sweet MVP (minimum viable product)!
If this is your first time writing a browser extension, congratulations, and I hope you want to go build some more! I definitely recommend checking out the API index for Google Chrome to see the many other kinds of APIs your browser extensions can work with! π
The final product for this tutorial is in Commit 4.
Top comments (2)
Thanks for this great article! I'm a novice in developing Chrome tabs and this post is a really good starting point. I'm wondering how can we change the look of tabs: I still didn't find anything about that. Can you give some guidance on this?
Thank you!
Sorry, I've just realized I had missed the mention of insertCSS method :)