Developing extensions for different browsers is mostly similar, however, there are a few differences you should look out for.

This article lists the difference between developing Chrome extensions and Firefox Add-Ons, helping you understand how to make sure your extension is compatible with both browsers. In the end I will also include the difference when packaging and publishing the extensions on the different platforms.


Manifest

Here are the key differences in the manifest.json file:

  1. Firefox has a developer key in the manifest, which is an object that includes name and url. Chrome doesn't.
  2. If you are using the Storage API and want to test your extension in your browser by loading it from your machine, Firefox requires the browser_specific_settings key to work, otherwise the Storage API will not work. An example of it would be:
"browser_specific_settings": {
  "gecko": {
    "id": "addon@example.com",
    "strict_min_version": "42.0"
  }
}

Manifest V3

Currently, Chrome is pushing towards using Manifest V3 which has been controversial for a few reasons. As for Firefox in a blog post in 2019, Mozilla indicated that they will also support Manifest V3, however, they're not obligated to implement every part of it. Mozilla is intending to keep a lot of functions and APIs that Chrome is discarding in V3.

Suggested Read: Learn how to migrate your Chrome extension from Manifest V2 to V3!


API

In Chrome, the API namespace is chrome.*, whereas for Firefox it's browser.*. Firefox claims that it supports chrome.*, but it's preferred to use browser.*.

The main difference between the two, however, is that chrome.* only supports callbacks when handling asynchronous events, whereas browser.* supports both callbacks and promises.

Here's an example of how you'd query tabs in Chrome:

chrome.tabs.query({active: true}, function (tabs) {
    console.log(tabs[0].title);
});

And here's the same example in Firefox when using Promises:

browser.tabs.query({active: true})
	.then ((tabs) => console.log(tabs[0].title))
    .catch ((err) => console.error(err))

Mozilla, however, offers a polyfill that allows you to use Promises in all browser extensions. You can check it out here.


Functions Differences

Some functions have different signatures or behavior for each of the browsers:

  1. Chrome states in their chrome.notifications API documentation that for chrome.notifications.create the parameter iconUrl is required, whereas for Firefox it's optional.
  2. For the tabs API in the functions insertCSS and executeScript, Firefox resolves the URLs being passed relative to the current page, whereas Chrome resolves the URLs being passed as relative to the root of the extension. To resolve this, use chrome.runtime.getURL (or replace chrome with browser for Firefox) to get the fully-qualified URL for a file in the extension.
  3. tabs.query is not allowed in Firefox without the tabs permission in the manifest.json but it's allowed in Chrome if the tab matches the host permissions in manifest.json.
  4. The declarativeContent API that Chrome has is not yet implemented in Firefox.

Some Additional Differences

  1. URLs in CSS files in Firefox are resolved relative to the CSS file, whereas in Chrome they're resolved relative to the current page.
  2. Firefox does not allow functions like alert, confirm or prompt in background scripts.
  3. Chrome allows passing relative URLs when making a request (for example, /user), however, Firefox requires absolute URLs.

Packaging and Publishing Extensions Experience

When packaging the extension to publish it, in Chrome the manifest.json file should be in the root of the package. Whereas in Firefox the extension should be encapsulated in a directory that contains manifest.json at the root of it.

Here's an example of how a Chrome extension package structure would look like:

root
|
|
|_ _ _ manifest.json

And here's how it would look like in a Firefox extension package:

root
|
|
|_ _ _ my-extension
       |
       |
       |_ _ _ manifest.json

When it comes to publishing your extension, Google requires a one time fee of $25 (at the time of writing this) to create a developer account. Once you do, you don't need to make any additional payments when adding more extensions. With Firefox, you don't need to pay anything to publish an extension.

Once you have a developer account on both platforms, you can upload your extension.

When uploading your extension on Chrome, you will be asked to enter a lot of information regarding the name of the extension, description, a variety of images in different sizes, and other information that the user will see when downloading your extension. You will also be required to enter a few details regarding privacy and handling user data based on the permissions you're asking for in manifest.json. You also can enter a Google Analytics code that helps you track your extension and its users more thoroughly. Once you are done, the review process can take some time before your extension is published on the Chrome Webstore.

When uploading your extension on Firefox, you will be asked first to enter a few information regarding remote code execution, privacy and other security information as well. Then, you will get to enter almost the same information as for Chrome regarding the name, description, etc... However, Firefox requires less images and isn't as strict about the sizing as Chrome. Firefox does not allow adding a Google Analytics tracking code to track your extension. Once you're done, your extension will be published right away.

As for updating your extension, for Chrome you just need to upload the latest package, and if there are no changes in the permissions, you don't really need to enter any other information. If you have any changes in the permissions you might need to fill out more privacy and security related information. Once you're done, your extension will be reviewed and if it's approved it will be published.

For Firefox, when updating you will need to enter the same information as before regarding security and remote execution. You will also be asked to add  Changelog information for your users to know what changed. Once you're done, your extension will be published right away.