Firefox extension .xpi file structure: description, contents, creation, and installation
I put a lot of stuff in searching an easy way to develop a Firefox extension, but I am unable to create an extension. Kindly tell me the file structure of Firefox extensions and an easy way to install the extension.
.xpi
file format (Extension Packaging)
The .xpi
files that are used as containers for Mozilla (Firefox, Thunderbird, etc.) extensions are merely zip archives that have had the file extension changed to .xpi
with the files added to the archive using either "deflate" compression, or uncompressed. If you use any other type of compression, other than "deflate", or "store" (uncompressed), you will get an error like:
This add-on cannot be installed because it appears to be corrupted
The files start in the root directory of the zip compressed archive (i.e. there is not an empty first level directory which then contains the files).
The contents of the archive could be only a few files to any number of files. The files that must be included depend on the type of add-on which you are packaging. If you are planning on using the Add-on SDK, then you probably don't need to know the format for these files, as much of it is abstracted by using the jpm
tool. If you have no idea what I am talking about, you may want to read up on the different types of add-ons for Firefox (WebExtensions, Add-on SDK, Bootstrap/Restartless, and Overlay/Legacy/XUL).
WebExtensions:
At a minimum, you will have a manifest.json file which describes the extension. You will, almost certainly, have additional files. The chrome.manifest, install.rdf, and package.json files used in other types of add-ons are not used in WebExtension add-ons. You should not have those files.
Add-on SDK:
The .xpi file for a Firefox Add-on SDK extension should be created by executing jpm xpi
. Add-on SDK extensions are described in a package.json file. When you run jpm xpi
your add-on is translated to being a Bootstrap/Restartless add-on. This is done by translating the package.json file into a install.rdf, creating a chrome.manifest file and adding some wrappers to the JavaScript. You should not try to perform this process yourself, unless doing so is necessary for your add-on to function (which would be quite rare).
Bootstrap/Restartless and Overlay/legacy:
At a minimum, you have install.rdf, and chrome.manifest files. Bootstrap/Restartless add-ons will also have a bootstrap.js file. There will almost always be additional files. These types of add-ons do not use a package.json, nor a manifest.json.
My very simple Bootstrap/Restartless extension, Print Button is Print (changes the print button to print instead of print preview), has the following structure:
Archive contains:
bootstrap.js
chrome/
chrome/content/
chrome/content/options.xul
chrome/skin/
chrome/skin/printer-typeC128.png
chrome/skin/printer-typeC32.png
chrome/skin/printer-typeC48.png
chrome/skin/printer-typeC64.png
chrome.manifest
install.rdf
license.txt
Total 12 entries (42360 bytes)
- There are the required install.rdf and chrome.manifest files.
- The file bootstrap.js is required for Bootstrap/Restartless extensions. It contains the code that is run when the extension is installed, removed, enabled, disabled, or upon Firefox startup or shutdown. This extension is simple enough such that all the JavaScript code is contained in bootstrap.js.
- There is a file chrome/content/options.xul which is a XUL definition of the options dialog.
- The license.txt just explains that the extension was relased under the Mozilla Public License, v2.0.
- The
.png
files are the icon for this extension at various resolutions.
Creating the .xpi file
You can use whatever method you desire to create the .zip file, which is renamed to .xpi. Keep in mind the requirement that the only compression method that is supported is "deflate", but files can also be added to the archive uncompressed. Your top level files (e.g. which ever you have of manifest.json (WebExtensions), or everything else: chrome.manifest, and install.rdf) should be in the root directory of the archive, not in a subdirectory.
To create the .xpi
file I use a batch file, which uses a combination of DOS and Unix/Linux (actually Cygwin) commands:
mkxpi.bat:
rm -f [email protected]
zip -1 -r [email protected] * [email protected]
pause
This removes any old version of the .xpi
file. It then creates a new .xpi
file using, -1
, minimal compression (speed of access is more important than saving space), which forces only storing uncompressed or using "deflate". The new .xpi will contain all files and subdirectories *
, but ignoring all the files in the xpi.ignore text file ([email protected]
). Ignoring files is used because I have other things in the directory (e.g. .git
directory, .bak
files auto-created from editor, etc.). Once the .xpi
file is created the script executes pause
so I can verify which files were included, that there were no errors, etc., instead of just having the window disappear and assuming that everything is fine.
My xpi.ignore file is a bit long, as it accumulates cruft from various projects and is rarely cleaned out:
*.com
*.class
*.dll
*.exe
*.o
*.so
*.7z
*.dmg
*.gz
*.iso
*.jar
*.rar
*.tar
*.zip
*.log
*.sql
*.sqlite
*.svg
*/.DS_Store
*/.DS_Store?
*/._*
._*
*/.Spotlight-V100
.Spotlight-V100
*/.Trashes
.Trashes
*/ehthumbs.db
*/Thumbs.db
*.ORIG
*.bak
*OLD*
OLD/*
*/OLD/*
*.OLD
*.OLD[0-9]
*/OLD/*
*/OLD[0-9]/*
*.unknown
*.unknown[0-9]
*.updated
*.updated[0-9]
*/Copy *
*/OLD
*/OLD*
*/OLD[0-9]
*/OLD[0-9][0-9]
*/test/*
*/not in xpi/*
*/tmp
*.tmp
*/foo
*.foo
*checkpoint
.git
*/.git
.gitignore
*/.gitignore
xpi.ignore
mkclean.bat
mkclean.bat.DONTRUN
mkxpi.bat
*.xpi
*/devtools-toolbox-window.ico
*/devtools-webconsole.ico
*/JSConsoleWindow.ico
*/main-window.ico
*/places.ico
*/viewSource.ico
Installing extensions
As normal extensions:
In order to install an extension as a normal add-on into a branded Release or Beta version of Firefox it must be signed by Mozilla. This is done by submitting it to AMO. You can install unsigned extensions as normal add-ons into other versions of Firefox (e.g. Firefox Developer Edition, Firefox Nightly, Unbranded Beta, or Unbranded Release) by setting xpinstall.signatures.required
to false
in about:config
.
If you choose, in a particular installation of Firefox, you can completely disable the add-on signing requirement. For more information, you can see my answer: How can I disable signature checking for Firefox add-ons?
Installing an extension (i.e. the .xpi
file) can be a simple matter of dragging and dropping it onto a Firefox window running the profile in which you desire it installed. For development/testing, you can have the extension be in a directory on your local drive by using a Firefox extension proxy file (create a file named as the extension's <em:id>
(in install.rdf for Bootstrap/Restartless and Overlay/Legacy) in the profile's extensions directory containing one line with the complete path to the directory containing the extension's files). Depending on what your goal is (one profile, all profiles, all users, which OS, etc.), there are other options as to how to install extensions.
As temporary add-ons:
The only type of extension which can not be installed as a temporary add-on is Overlay/Legacy. Such extensions require the browser to be restarted after the install prior to being functional. As such, they can not be temporary.
To install an extension as a temporary, navigate to about:debugging
. From that page, click on Load Temporary Add-on, then navigate popup to the appropriate folder and select either an .xpi file, or any file in the directory. If you select a file other than an .xpi file, it is assumed that the directory contains unpacked add-on files which will be automatically identified.
Generate a signed .xpi
- Install web-ext with NPM, maybe you will need root privileges:
npm install --global web-ext
- Go to https://addons.mozilla.org/es/developers/addon/api/key/ and generate a new API KEY.
- Go to your extension folder, open a terminal and execute:
web-ext sign --api-key=$AMO_JWT_ISSUER --api-secret=$AMO_JWT_SECRET
where $AMO_JWT_IUSSER and $AMO_JWT_SECRET are the keys you generated in the previous step.