Build your first Chrome extension with me
Simplifying everything you need to know about building Chrome extensions
Introduction:
If you're interested in building your Chrome extension, you're in the right place! In this blog post, I'll guide you through the process of building your first Chrome extension, step-by-step, and I'll try to cover all the fundamental concepts and best practices you need to know to get a smooth start in the world of Chrome extension development.
What are Chrome extensions?
Let's start by defining the word 'extension'.
An extension is a software component that adds some specific functionality to an existing application or program. Extensions are typically designed to be lightweight, easy to install, and easy to use.
Now, that we have a much clearer idea about what an extension is, we can talk about what Chrome extensions are.
A Chrome extension is a type of extension specifically designed to work within the Google Chrome web browser. Chrome extensions are written in web technologies such as HTML, CSS, and JavaScript, and they can modify or enhance the functionality of the browser in various ways, such as by adding new features, modifying the appearance of web pages, or interacting with other web applications. Chrome extensions can be installed from the Chrome Web Store or other sources, and they run entirely within the Chrome browser, without requiring any separate installation or setup.
What are the prerequisites for you to be able to build one?
To build a Chrome extension, you should have a basic understanding of web technologies such as HTML, CSS, JavaScript, and JSON. You should also have some knowledge of the Chrome extension architecture and the APIs provided by Chrome.
We can continue with this session if you are confident in your expertise in these areas (or if you at least have an intermediate understanding of what they are and how to use them).
Otherwise, I would suggest bookmarking this article and learning those first. Below are some beginner-friendly tutorials that can help you get started:
Basic components of a Chrome extension:
A typical Chrome extension's file structure looks like this:
-manifest.json
-background.js
-content.js
-popup.html
-style.css
-popup.js
-assets
-icon.png
Let's try to understand what each of these files does.
Manifest File: The manifest.json file is a required file for all Chrome extensions. It is a JSON file that provides essential information about the extension to Chrome, including its name, version, author, and permissions. In layman's terms, it is a registry of everything that is inside your Chrome extension, as well as how your extension can interact with Chrome's APIs.
Here's a detailed overview of the various sections and properties of the 'manifest.json' file:
Manifest version, name, and version: The version of the manifest file format that your extension uses is specified in this file as well as the name of your extension which appears in the Chrome Web Store and the browser and also the version number of your extension. You can also add a short description of your extension.
Icons: This is an array of image files that represent your extension's icon in various sizes.
Browser action or Page action: This specifies whether your extension will add a button to the browser toolbar (browser action) or the context menu of web pages (page action).
Background or Service Workers: This specifies a background script that will run in the background of the browser and perform tasks that do not require user interaction.
Content scripts: This specifies one or more scripts that will be injected into web pages when the extension is active.
Permissions: This specifies the permissions that your extension requires to function properly, such as the ability to access the user's browsing history or modify web pages.
Web-accessible resources: This specifies any files that your extension makes available to web pages, such as images or CSS stylesheets.
Knowing about these properties should give you a good head start for now. We shall learn more about this in our tutorial.
Background Script: In Chrome extensions, the background script is a special type of script that runs in the background of the browser, even when the extension is not actively being used. It is typically used to perform tasks that do not require user interaction, such as monitoring network activity, managing state information, and performing periodic updates or maintenance tasks.
In newer versions of Chrome, the background script can also be implemented using service workers.
Content Script(s): A content script is a JavaScript file that runs in the context of a web page when that page is loaded or interacted with by the user. Content scripts are typically used to modify or enhance the behavior of web pages in some way, such as adding new functionality, modifying existing elements, or injecting additional content into the page.
An extension may have more than one content script.
Popup: The popup.html, popup.js, and style.css files are used to create the popup window in a Chrome extension. When a user clicks on the extension icon in the browser toolbar, the popup window is displayed, providing a way for the user to interact with the extension.
The popup.html file contains the HTML markup for the popup window. The popup.js file contains the JavaScript code for rendering the logic, and the style.css file contains the CSS styles that are applied to the popup window.
Assets: The 'Assets' folder contains all the resources that you may use in your extension, for example, logo, icons etc. You can name this folder anything you want.
Which script can do what?
Before we get started with our tutorial, we must understand the sphere of influence of all the different kinds of script files in our extension. The image below will help us understand it better:
The content script(s) have access to the main web page (highlighted by the light grayish lime green part). The popup script has access to the popup window (highlighted by the gray part). The popup script also has access to a limited subset of the extension APIs and functionality, specifically those that are related to the user interface and popup window itself. The background script, on the other hand, has access to the full range of Chrome extension APIs, as well as access to the web page DOMs and network requests.
YouTube Info Saver:
Now, that we have covered the basics, we are all set to start building our Chrome extension.
Our extension will be able to save information about a YouTube video such as its title and creator's name etc. We'll use Chrome's Local Storage to save and retrieve this information.
Let's start by defining our 'manifest.json' file:
{
"name": "YouTube Info Saver",
"version": "1.0",
"description": "Saving information about YouTube videos",
"icons": {
"128": "assets/icon.png"
},
"host_permissions": ["https://*.youtube.com/*"],
"permissions": ["storage", "tabs"],
"background": {
"service_worker": "background.js"
},
"content_scripts": [
{
"matches": ["https://*.youtube.com/*"],
"js": ["content.js"]
}
],
"action": {
"default_title": "YouTube Info Saver",
"default_icon": {
"16": "assets/ext-icon.png",
"24": "assets/ext-icon.png",
"32": "assets/ext-icon.png"
},
"default_popup": "popup.html"
},
"manifest_version": 3
}
If you're not familiar with the formal JSON syntax, no worries. Just read this documentation and you should be able to wrap your head around it pretty easily.
Let's walk through this code line by line:
"name": "YouTube Info Saver"
,"version": "1.0"
,"description": "Saving information about YouTube videos"
: Setting the name, version, and description of the Chrome extension."icons": { "128": "assets/icon.png" }
: This specifies the icon to be used for the extension."host_permissions": ["https://*.
youtube.com/*
"]
: This specifies the URL patterns for which the extension will have access to the host page. In this case, the extension will be able to access any page on theyoutube.com
domain."permissions": ["storage", "tabs"]
: This specifies the permissions that the extension requires. In this case, the extension needs to be able to access the browser's storage and tabs."background": { "service_worker": "background.js" }
: This specifies the background script for the extension, which will run in the background even when the extension is not actively being used."content_scripts": [ { "matches": ["https://*.
youtube.com/*
"], "js": ["content.js"] } ]
: This specifies the content script for the extension, which will run on pages that match the specified URL pattern."action": { "default_title": "YouTube Info Saver",
"default_icon": { },
"default_popup": "popup.html" }
: This specifies the browser action for the extension i.e., when the extension icon is clicked, which includes the default title, icon, and the popup window."manifest_version": 3
: This specifies the version of the manifest format being used, with version 3 being the most recent as of now.
To get a detailed look at all the fields which can be used in the manifest file, visit this documentation.
Next, let's write code for our 'background.js' file:
chrome.tabs.onUpdated.addListener((tabId, tab) => {
if (tab.url && tab.url.includes("youtube.com/watch")) {
const queryParameters = tab.url.split("?")[1];
const urlParameters = new URLSearchParams(queryParameters);
chrome.tabs.sendMessage(tabId, {
type: "GET_VIDEO_INFO",
videoId: urlParameters.get("v"),
});
}
});
No panicking! Let me break down what this code does.
This code is a JavaScript function that uses the Chrome Extension API to listen for updates to the currently active tab in the Chrome browser.
The addListener()
method is used to add a listener function that will be called whenever a tab's status or information changes. The function takes three arguments: tabId
, changeInfo
, and tab
. tabId
is the ID of the tab that was updated. changeInfo
is an object that contains information about the changes that occurred in the tab, such as whether it finished loading or its URL changed. A tab
is an object that contains information about the tab itself, such as its URL and title.
Read more about Chrome's tab API to understand it better.
After an update to the current tab is detected, we check whether the updated tab's URL contains the string “youtube.com/watch” by accessing the URL
property of the tab
object and using the includes()
method to check if the string is present.
If the condition is fulfilled, we then extract the video ID from the URL using the split method to split the URL into an array at the “?” character, and then we use the URLSearchParams()
method to create a new object from the query string. The get()
method is then used to extract the value of the “v” parameter, which is the video ID.
I can sense your confusion! Let's try to simplify it. We can take the URL of a YouTube video given below as an example:
“https://www.youtube.com/watch?v=BwDBeNhG9ls”
Every YouTube video's URL has this part “youtube.com/watch?v=” in common. The value of the remaining part is unique for every video, and that's what we want to extract as the video's ID.
So, we split the URL at the character “?” and then extract the unique value after “v=” as the video's ID.
Finally, chrome.tabs.sendMessage()
is called to send a message to the content script associated with the tab. This message contains two properties: type
, which is set to “GET_VIDEO_INFO”, and videoId
, which contains the ID of the video extracted from the URL. The content script can then use this information to perform some action, such as retrieving information about the video or manipulating the DOM of the page.
Let's get to the content script now!
(() => {
let videoId;
let videoTitle;
let creatorName;
})();
First, we declare these three variables, which we'll use later in the code. You must be noticing those weird brackets at the start and end. That's the syntax for an immediately declared and invoked function (IIFE). While not necessary it is generally recommended to avoid global namespace pollution.
By wrapping your code in an IIFE, you create a local scope for your variables and functions. This helps prevent naming conflicts with other scripts that may be running on the same page and also keeps your global namespace clean.
Next, we'll add a listener in our content script to listen for any messages from the background script:
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.type === "GET_VIDEO_INFO") {
videoId = message.videoId;
videoTitle = document.querySelector("h1.style-scope.ytd-watch-metadata yt-formatted-string").innerText;
creatorName = document.querySelector("div.ytd-channel-name a").innerText;
}
});
Read more about chrome.runtime.onMessage
.
The message
parameter represents the message received from the sender. In this case, the function checks if the message type is "GET_VIDEO_INFO"
.
If the message type is "GET_VIDEO_INFO"
, the function extracts information about the video, including its videoId
, videoTitle
, and creatorName
. These values are obtained from the DOM using the document.querySelector
calls to select specific elements on the page.
So, far we've written code for sending a message from the background script to the content script whenever a tab is updated and the updated tab's URL is that of a YouTube video. Furthermore, we've written code in the content script for handling that message.
Next, we'll move toward our popup script.
It's not needed to add the popup script in the manifest file. We just have to link it to the 'popup.html' file using a script
tag.
const saveButton = document.getElementById("save-info");
const displayButton = document.getElementById("display-info");
const message = document.getElementById("message");
First, we'll grab the HTML elements that we need to render the logic. Then we use the chrome.tabs.query
to get the currently active tab's URL.
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
const currentTab = tabs[0];
if (currentTab.url.includes("https://www.youtube.com/watch")) {
saveButton.classList.remove("hidden");
saveButton.classList.add("display-button");
displayButton.classList.remove("hidden");
displayButton.classList.add("display-button");
} else {
message.classList.remove("hidden");
message.classList.add("message");
}
});
Based, on whether the current tab's URL is that of a YouTube video or not, we display elements on our popup window. Read more about chrome.tabs.query
.
Now, if the user is on a YouTube page and clicks on the saveButton
on the popup window, we will send a message to the content script to save the video's data (that the content script will already have extracted on receiving a message from the background script) in Chrome's local storage.
Read more about Chrome's storage here.
saveButton.addEventListener("click", function () {
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
chrome.tabs.sendMessage(tabs[0].id, { action: "saveInfo" })
});
});
We've sent a message to the content script. Now, we must write some code in the content script to handle this message:
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.action === "saveInfo") {
chrome.storage.local.set({ [videoId]: { videoTitle, creatorName } })
}
});
The content script on receiving this message will save the information related to the currently playing video in Chrome's local storage. We can later retrieve this information from the local storage using the chrome.storage.local.get()
, and do something with it like displaying it on the popup window.
And that's pretty much it! We've developed a functioning albeit simple Chrome extension. You can take what you've learned here (I hope you've learned something!) and build much more complex and sophisticated Chrome extensions.
Conclusion:
In conclusion, building a Chrome extension can seem daunting at first, but with the right tools and guidance, anyone can create one. I hope that by following this tutorial, you've learned the basics of building a simple Chrome extension, from creating a manifest file to adding functionality with JavaScript.
You can continue to build on what you've learned by exploring other features of the Chrome extension API, such as adding user interfaces, modifying web pages, or integrating with other services. The possibilities are endless, and by building your extension, you can tailor your browsing experience to your needs.
Remember to always test your code and follow best practices for security and user privacy. And most importantly, have fun and enjoy the process of creating something that can make your web browsing experience more personalized and efficient.
You can access the complete code for YouTube Info Saver here. Also, check out another extension that I've created named Chat2PDF which takes conversations with ChatGPT, converts them into PDF files with specific formatting for paragraphs, lists, tables, code snippets etc, and saves them.
Lastly, do leave a comment if you would like to. Happy coding!