This crocodile imagines how cool it is to build and run extensions (photo by Kyaw Tun on Unsplash)

All articles about browser extensions:

  1. Browser Extensions: When Your Product Needs It
  2. Browser Extensions: How to Build and Run (you're here)

Extensions can have several different parts: background scripts, content scripts, UI elements, options page, and so on.

But in this article I won’t cover any of that because it isn’t necessary for your first extension.
What is necessary? Only one file — manifest.json

Disclaimer: all the examples in this article and later should work in Firefox and all the browsers based on Chromium (Google Chrome, Opera, Yandex Browser, you name it).
We’ll cover other browsers later.

Before we start writing our first manifest.json, we need to create an empty folder for an extension —eventually, it will be the place for all extension files.

$ mkdir my-first-extension
$ cd my-first-extension

Now we have a place for manifest.json. What is this?

developer.google.com says:

Every extension has a JSON-formatted manifest file, named manifest.json, that provides important information.

Not too much information, is it? Let’s go to MDN, maybe there is a better explanation:

The manifest.json file is the only file that every extension using WebExtension APIs must contain.

Using manifest.json, you specify basic metadata about your extension such as the name and version, and can also specify aspects of your extension’s functionality, such as background scripts, content scripts, and browser actions.

It is a JSON-formatted file, with one exception: it is allowed to contain “//"-style comments.

Excellent!

We don’t want to write this on our own, let’s just copy it from the example of the first extension from MDN:

Looks quite complex, doesn't it? Google’s example is a bit shorter:

Just 4 fields! We’ll go a bit further and remove a description. So, there are only 3 mandatory fields: name, version and manifest_version.

Ok, name and version are quite trivial — it’s all about your extension. It’s your creature, you decide how to call it and which version to set (but pay attention at version, it must be a string even if it may seem a bit odd). What the hell is manifest_version?

In short, it’s 2.
Chrome used to use version 1 of manifests but stopped after Chrome 18 was released (in 2012). They supported both versions for a while but stopped in 2014, so only version 2 works now.
Firefox is a bit more explicit about it, it says:

This key specifies the version of manifest.json used by this extension.
Currently, this must always be 2.

In the end, we have something like this:

Save it to the folder you’ve created before, and your first extension is ready to be run in a browser.

Loading extension in Chrome

To run the extension in Chrome, you should:

  1. Go tochrome://extensions to open the Extension Management page
  2. Enable Developer Mode in the top-right corner
  3. Click Load Unpacked
  4. Select the extension directory
Done!

Then, if you changed something in the extension and need to load the latest version, just press the button :

Manual reload works after I changed version to 2.2.2
It's also possible to setup hot-reloading if you use webpack.
There's a plugin for it, so every time you change something, the extension will reload new sources automatically

Loading extension in Firefox

It’s a bit trickier with Firefox. Let’s start with a simple example.

To load an extension in Firefox you should:

  1. Go to about:debugging
  2. Click the big button “Load Temporary Add-on”
  3. Select the manifest.json of your extension
Works for Firefox too!

There is the only one difference with Chrome: the word “temporary”.

“Temporary add-on” means that as soon as you restart your browser, it disappears.

It’s fine for simple debugging, but not for all cases. It doesn’t work if you need to test that an extension persists its state when you restart the browser.

To avoid this, we should pack the extension. There are two ways to do this:

  1. Old-school and incorrect. Unfortunately, it's still popular with developers who are used to this method.
  2. Recommended and long.

Incorrect way of packing Firefox extensions

At first glance, packing should be a straightforward process, because browser extension is nothing more than a simple zip archive.
All you need to do is to go to the folder with manifest.json and compress all the files (in our case it's just one file). Then you should be able to install this "extension".

But not too fast.

To install the extension, we should open "Add-ons Manager" (about:addons) and drag & drop the file. And it doesn't work:

What's wrong? We don't know. As you see, this error dialog doesn't explain much.

The rule #1:
Browser Console is your best friend when you debug extensions in Firefox

If you don't know what Browser Console is, MDN will help:

The Browser Console is like the Web Console, but applied to the whole browser rather than a single content tab.

So it logs the same sorts of information as the Web Console - network requests, JavaScript, CSS, and security errors and warnings, and messages explicitly logged by JavaScript code. However, rather than logging this information for a single content tab, it logs information for all content tabs, for add-ons, and for the browser's own code.

Ctrl(Cmd)+Shift+J and we see:

addons.xpi-utils	WARN	Add-on undefined is not correctly signed.

It used to be possible to go to about:config and allow unsigned extensions by setting xpinstall.signatures.required to false. This way doesn't work in Firefox anymore, and you can allow unsigned extensions only in Firefox Developer Edition.
But even in this case, Firefox won't install it straight away – it'll show one more time the error we saw before.

Why does it happen now?
Rule #1 suggests to use Browser Console:

Invalid XPI: Error: Cannot find id for addon /Users/elergy/pr/my-first-extension/manifest.json.zip(resource://gre/modules/addons/XPIInstall.jsm:1319:19) JS Stack trace: loadManifest@XPIInstall.jsm:1319:19

Basically, it says that to install an unlisted extension, we should specify its identifier. This ID should be either a GUID or a string formatted like an email address (myfirstextension@evgenii.info). Also, it should be unique (means no other extensions should use it).

Try once again, and finally, this dialog appears:

After you add it, it will work even after a restart – you can store any data you need and rely on the assumption that it's available all the time.

Let's move to the correct way, which will work even in normal Firefox.

Good news is that Mozilla has their own tooling for development that makes all common scenarios much easier. It's an npm module web-ext.

With web-ext you can:

  • run your extension from the terminal in a separate instance of Firefox
  • validate the sources
  • build a package
  • and (the most important part for us) sign it!

The link above has all the necessary documentation, but it's enough to just install it (npm i -g web-ext) and use web-ext --help then.

The command web-ext sign has only 2 required parameters and more than 10 optional. The required ones are api-key and api-secret.

To obtain these parameters, you need to have an account in https://addons.mozilla.org/. This is true even if you don't plan to publish your extension. It's free so don't worry (e.g. you need to pay $5 fee for the Google Store account if you wish to publish Chrome extensions). After you log in, go to "Tools -> Manage API Keys" and generate new credentials:

Don't share it with anyone!

Now you can use it to run web-ext sign:

This command will pack an extension, sign and download it, so you can install it locally without any problems:

Building web extension from /Users/elergy/pr/my-first-extension
No extension ID specified (it will be auto-generated)
Validating add-on [............................................................]
Validation results: https://addons.mozilla.org/en-US/developers/upload/901fda737c1e4f0aa43cba791cb3c090
Downloading signed files: 100% 
Downloaded:
    ./web-ext-artifacts/my_first_extension-1.0.0-an+fx.xpi
Extension ID: {b6500315-00c6-40e1-9f6a-acf857aedddb}
SUCCESS

Development is easier with web-ext

What if you need just run the latest version of your extension?
In Chrome, you could just press the button "", what's about Firefox?

Their web-ext has a very useful command web-ext run.

This command starts Firefox with a new profile, which means that it won't have any of your history and any other extensions. But most importantly, web-ext will watch all the changes in the current directory and will reload the extension every time you change something:

The version changes as soon as I update the manifest

Summary

In this article, I've shown the minimal working version of manifest.json for Chrome and Firefox.
We know how to load an extension to the browser, how to keep it persistent between restarts and how to reload the extension after something was changed.

In addition, I mentioned a very useful tool web-ext that makes building extensions for Firefox much easier. It takes care of a build process and allows you to reload extensions automatically every time you change the code.
Chrome doesn't support it out of the box, but there is a webpack plugin that can help you to have something very similar.

In the next articles we'll start building something more useful than an empty extension and will talk about other parts, such as content scripts and UI elements.