In this tutorial, we’ll go over how to create a Firefox add-on that allows the user to create alarms at specified times with custom text. We’ll cover how to create a basic add-on, adding a popup and an options page, adding permissions, storing information, sending notifications, and creating a background script for sending the notifications.
Setting Up Our Firefox Add-on
The first step to create a Firefox add-on is to create the
manifest.json file. This file is the only file required for a Firefox add-on. The basic format of a
manifest.json file should include the following keys:
name: the name of the add-on in slug format — such as
version: the current version of the add-on. When updating anything in the extension, you’ll need to update this version, so it’s recommended to start low.
manifest_version: at the time of writing, Firefox only supports Manifest V2, so the value for this should be
2. However, if future support for V3 is added, the value can be
These’re the mandatory fields for any add-on. The following two are optional but recommended:
description: a short description for your add-on that explains its purpose.
icons: a list of icons of different sizes. These icons will be used in the settings, toolbar of the browser, and other places as well. Recommended sizes to add are
For our add-on, let’s start by creating a folder named
firefox-alarms-addon. Then add a
manifest.json with the following content:
"name": "personalized-alarms", "version": "0.0.1", "description": "Create personalized alarms", "manifest_version": 2, "icons": "16": "assets/images/icon16.png", "32": "assets/images/icon32.png", "48": "assets/images/icon48.png", "128": "assets/images/icon128.png"
As you can see, the
icons key is an object with keys of the file size and the path to it. The path is relative to the root of the add-on, which is where
manifest.json resides. For this tutorial, I’m using an icon downloaded from iconscout by Twitter Emoji where I can download the different sizes needed as well.
If you’re following along, grab these files from our repo and place them in the appropriate directory (
That’s all that’s needed to create a Firefox add-on!
Loading the Add-on in Firefox
To test our Firefox add-on and be able to debug it later on before uploading it to Mozilla’s Developer Hub, open Firefox, then choose Add-ons and Themes from the right menu, or using the shortcut ctrl + shift + A. Then, Click on the “Settings” icon next to Manage Your Extensions and choose Debug Add-ons.
A new page’ll open for Temporary Extensions.
Click on Load Temporary Add-on button and choose the
manifest.json file you just created. If everything was done correctly, you’ll see the newly created add-on with some information about it and the icon we specified in the
Adding a Popup
Firefox add-ons can be made accessible via different methods, and one of them is by adding a popup page. When adding a popup page, the icon for your extension will show up in the toolbar and once the user clicks on it, the popup page you specify will show up.
We’ll use the popup page to show the user the list of upcoming alarms and a link to add a new alarm that takes the user to the options page (which we’ll talk about in the next section).
popup.html file in the project root with the following content:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Personalized Alarms</title> <link href="assets/css/bootstrap.min.css" rel="stylesheet" /> </head> <body class="p-3"> <h1>Upcoming Alarms</h1> <ul class="list-group" id="alarmsList"> </ul> <div class="mt-3"> <a href="#" class="link-primary" id="optionsLink">Add an Alarm</a> </div> <script src="http://www.sitepoint.com/assets/js/jquery.min.js"></script> </body> </html>
As you can see, it’s just an HTML document. We’ve also added
assets/css and linked it here, and
assets/js/jquery.min.js and linked it as well. These two libraries are just to make things easier, but you don’t have to actually use them. You can grab them from our repo here and here.
In the content of the page, we’ll show the list of alarms that are upcoming and a link to the options page.
The next step to make a popup work is to add the following in
"browser_action": "default_popup": "popup.html", "browser_style": true
browser_action is an object that has a number of options, but the only mandatory one is
default_popup, which is the relative path to the popup from the add-on root directory.
browser_style isn’t mandatory, but it’s recommended that it be set to
true. This means that Firefox will inject the browser’s default styles to make sure the add-on’s popup styling is similar to the rest of the browser.
That’s all that’s required to add a popup. Go to the Temporary Add-ons page that we went to before and click on the Reload button for the add-on. This will make Firefox check
manifest.json for any changes and apply them.
Once you do, you’ll be able to see an icon of your extension in the toolbar menu.
If you click on it, you can see the popup page we just created.
Two things are still left in our popup to make it fully functional: using storage to get the upcoming alarms, and making the “Add an Alarm” link take the user to the options page.
Storage in browser extensions allows us to store data relevant to the extension or the user, either locally on the machine, or in sync based on their account. Local storage stores information locally in the browser, which means that if the user is logged in to Firefox with the same email from another machine, this stored information will not be present there. Sync storage stores information for the current user logged, which allows this information to be available wherever the user is logged in.
Sync storage should be used for certain settings the user wants to have available everywhere, whereas local storage should be used for information or options that are relevant for the current browser installation only.
In our example, we’ll make alarms available everywhere the user is logged in, so we’ll store them in sync storage. But let’s say we want to add a “temporary disable” option that mutes the alarms for a while. In that case it would probably be more suitable to use local storage.
Storage can be accessed easily through the Storage API through get and set methods, but first, we need to request permission to use
storage in our add-on. This can be done inside
"permissions": [ "storage" ],
When the user installs your add-on, they’ll get to see what permissions you require and need them to accept to install your add-on.
There’s one other thing we need to add in order to be able to test the add-on locally: an explicit add-on ID to be able to use the storage. To do so, add this in the
manifest.json as well:
"browser_specific_settings": "gecko": "id": "email@example.com", "strict_min_version": "42.0"
This is just to be able to test it locally. Once we publish it, we’ll remove this from the manifest.
The next thing we’ll do is create a new
assets/js/popup.js file, which will get the alarms from storage and display them.
To get items from the storage, you can use browser.storage.sync.get or browser.storage.local.get. This depends on whether you’re storing the information in sync storage or local storage. In our case, we’re storing alarms in sync storage, so we’ll use
browser.storage.sync.get. It should be noted that all methods under
browser.storage.local.* have the same signature and accept/return the same types.
browser.storage.sync.get takes one parameter: an array of strings that are the keys of the data we’re retrieving. These keys are defined when we set the storage (which we’ll talk about in the next section). This function returns a promise that resolves to a
results object containing the keys we specified in the first parameters and their values, if they exist.
Note: if you’re making the add-on compatible with Chrome, be sure to check out the “Making Add-ons Compatible with Chrome” section.
assets/js/popup.js with the following content:
$(document).ready(() => const listElement = $('#alarmsList'); browser.storage.sync.get(['alarms']) .then((result) => if (result.alarms && result.alarms.length) //loop over the alarms and display them result.alarms.forEach((alarm) => appendItem(alarm.content, alarm.time); ); else //show no items available appendItem('No alarms are available'); ); function appendItem(content, badgeContent = null) listElement.append(` <li class="list-group-item d-flex justify-content-between align-items-center"> $content $badgeContent ? `<span class="badge bg-primary rounded-pill">$badgeContent</span>` : '' </li> `); );
You’ll also need to include this file in
... <script src="http://www.sitepoint.com/assets/js/popup.js"></script> </body> </html>
When the document is ready, we’re using
browser.storage.sync.get to get the alarms created by the user. We’re then checking if there are any alarms. If there are, we’re looping over them and displaying them using the
appendItem helper function, which just appends an HTML list element
#alarmsList. If there are no alarms available, we’re just showing “no items available”.
If we reload the add-on now, you’ll notice a new installation of the add-on has been added. This is because we explicitly specified the ID in the
manifest.json. You can remove the old one to avoid conflict.
You’ll notice that nothing has changed in our popup, since we don’t have any alarms added yet. We’ll do this in the next section.
Adding an Options Page
To allow your users to customize or edit options or settings in the add-on, you create an HTML page that holds the options and the logic behind setting or changing them. Then you link to it in the
In our add-on, we’ll use the Options page to allow the user to create alarms. Let’s first create the file
options.html. You can create it anywhere in the add-on project directory. We’ll create it in the root of the project with the following content:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Options</title> <link href="assets/css/bootstrap.min.css" rel="stylesheet" /> </head> <body class="p-3"> <h1>Add Alarm</h1> <form> <div class="form-group"> <label for="name">Alarm Name</label> <input type="text" class="form-control" name="name" id="name" placeholder="Wake up" /> </div> <div class="form-group"> <label for="time">Time</label> <input type="time" class="form-control" name="time" id="time" /> </div> <button type="submit" class="btn btn-primary mt-3"> Add a New Alarm </button> </form> <script src="http://www.sitepoint.com/assets/js/jquery.min.js"></script> <script src="assets/js/options.js"></script> </body> </html>
Here, we’re just displaying a form with two input fields: “Alarm Name”, which will be the text displayed in the alarm when the notification is sent, and “Time”, which is the time to set the alarm at.
We’ll need to create
assets/js/options.js, which will listen for the
submit event for the
form and set
alarms in the sync storage, adding a new alarm to the array.
Similarly to our use of the
get method, to set the storage we can use browser.storage.sync.set or browser.storage.local.set, depending on whether we’re storing the data just locally or in sync between all logged-in instances. Since we’re storing our alarms in
sync, we’ll use
set method takes one parameter that’s an object of keys and values. The key is what we use to retrieve the value later on, just like we did earlier with
assets/js/options.js with the following content:
$(document).ready(() => const nameElm = $('#name'); const timeElm = $('#time'); const formElm = $('form'); formElm.on('submit', () => $('.alert').remove(); //remove previous success alerts, if any //get existing alarms browser.storage.sync.get(['alarms']) .then((result) => let alarms = result.alarms; const alarmName = nameElm.val().trim() + '_' + (Math.random() * 100); if (!alarms) alarms = ; alarms.push( content: nameElm.val().trim(), time: timeElm.val(), alarmName ); //set alarms in the storage browser.storage.sync.set(alarms) .then(() => //TODO schedule notification formElm.prepend('<div class="alert alert-success">Alarm added successfully</div>'); nameElm.val(''); timeElm.val(''); ); ); return false; //disable default form submit action ); );
On form submission, we’re first retrieving stored alarms, if there are any. Then, we’re pushing the new alarm we’re creating through the form to the
alarms array. Notice how we’re also creating an
alarmName variable. We’ll be using this variable to create a unique alarm, then cancel it when the user deletes it. Finally, we’re using
browser.storage.sync.set to set the new
You might also notice that we added a
TODO comment, which is where we’ll schedule notifications in the next section.
Our options page is now ready. To make it available, we first need to add the following to
"options_ui": "page": "options.html", "browser_style": false
This tells Firefox where to find our Options page. We’re also setting
false because we don’t want Firefox’s styling to override the Bootstrap styling.
Second, we’ll now make the link in the popup take the user to the options page. To do this, we use the method browser.runtime.openOptionsPage() in a new event listener attached to
#optionsLink. We’ll add the following to
$(document).ready(() => ... // New code here $('#optionsLink').on('click', () => browser.runtime.openOptionsPage(); ); function appendItem(content, badgeContent = null) ... );
Now, when the user clicks the “Add an Alarm” link, it will take them to the Options page.
Go to the Temporary Add-ons page, and click the reload button. Now, our options page will be registered.
Let’s test it out. Open the popup and click on “Add an Alarm”. It should take you to the Preferences tab in the add-on’s page, and the content will be the content we added in the
Now, try to add a test alarm with any name and time and click on “Add an Alarm”. You should be able to see it in the popup after that.
We still need to make one change to
assets/js/popups.js, which is to show alarms whose time is later than the current time. Change the call to
browser.storage.sync.get to the following:
browser.storage.sync.get(['alarms']) .then((result) => if (result.hasOwnProperty('alarms') && result.alarms) //get current time const minutes = (new Date).getMinutes().toString().padStart(2, '0'); const hours = (new Date).getHours().toString().padStart(2, '0'); const now = new Date('1970-01-01T' + hours + ':' + minutes + 'Z').getTime(); //loop over the alarms and display them result.alarms.forEach((alarm) => const alarmTime = new Date('1970-01-01T' + alarm.time + 'Z').getTime(); if (alarmTime > now) appendItem(alarm.content, alarm.time); ); else //show no items available appendItem('No alarms are available'); );
This checks for each alarm if its time is greater than the current time and then displays it. The reason we’re formatting the time as
'1970-01-01T' + alarm.time + 'Z' is because we’re creating the alarms independent of the date. This is just to make the tutorial simpler. We’re also padding
minutes with zeros when they’re one digit when calculating the current time, since the required format for
new Date should have two digits for both numbers.
If you check now, you’ll notice that the previous alarm we added is shown or not depending on when its time is. You can also test adding a new alarm at another time to see whether it appears in the popup or not.
How to Create a Firefox Add-on