But what's a plain Web Extension?

This article is part of a series.

Safari Web Extensions envelop a ball of HTML, javascript, CSS and JSON in an Xcodeproj and shove it into Safari. That same ball can be shoved into Firefox and Chrome… sort of. It has the same cross-browser issues that all other web development does and has always had.

This post covers references on how to check an extension in Firefox and what’s in the manifest.json


Being Cross Compatible

When reading about chrome extensions keep in mind that firefox and safari use browser: among other difference

Checking Extension in Firefox

More info: https://extensionworkshop.com/documentation/develop/getting-started-with-web-ext/

brew install web-ext #or npm
web-ext --version
cd /PATH/TO/StandaloneExtension
web-ext run

See also about typing “about:debugging” and “about:addons” into the FireFox toolbar.

What kinds of things can a Browser Extension do?

Finding out what a web extension does starts with reading its manifest.json Good news, a manifest.json can have // comments!

Here are some things you might find in a V3 one:

Extension Identity

Start with the basic meta information. The minimum viable manifest.json comprises of just. manifest_version, version, and name.

    "manifest_version": 3,
    "default_locale": "en",

    "name": "__MSG_extension_name__",
    "description": "__MSG_extension_description__",
    "version": "1.0",
    "homepage_url": "https://example.com",

If you see __MSG_extension_name__ look for a default_locale and _locales folder because that’s internationalization and localization variable. More info on i18n conventions

Icon images can be a collection of pngs or a single svg.

    "icons": {

    "icons": {
        "48": "images/icon-48.png",
        "96": "images/icon-96.png",
        "512": "images/icon-512.png",
        "1024": "images/icon-1024.png"

Background Processes

Background processes happen behind the scenes, but are not allowed to keep running indefinitely. Chrome and safari prefer the service-worker API if available. Firefox falls back to the non-persistent background script.

    "background": {
        "service_worker": "background.js",
        "scripts" : ["background.js"],
        "persistent" : false

Background processes frequently get paired with storage or [unlimitedStorage][prm_ulm] permission, which then take advantage of storage.local, storage.managed or storage.session.

Content Scripts

Content scripts have a more constrained range of abilities than background scripts, but they run inside a loaded page’s DOM.

    "content_scripts": [{
        "js": [ "content.js" ],
        "matches": [ "*://*.whynotestflight.com/*" ]

Several match patterns can be used to dial in what websites the script should try to load itself into, including <all_urls>.

Tool Bar Actions

Add an item to the browser tool bars with an action

    "action": {
        "default_popup": "popup.html",
        "default_icon": {
            "512": "images/toolbar-icon-512.svg"

Custom Browser Pages

Replace new tab page or others. Comparable to chrome_url_overrides

    "browser_url_overrides": {
        "newtab": "new_tab_page.html"

Content Blockers

declarative_net_request controls how network requests get handled and is the basis for content blockers.

    "declarative_net_request": {
        "rule_resources": [
                "id": "MyRuleset",
                "enabled": true,
                "path": "rules.json"

Example rules.json that blocks all images on all websites. Rule sets can be static (life time of extension) or dynaimc (session conditional). more

        "id" : 1,
        "priority" : 1,
        "action" : { "type" : "block" },
        "condition" :
            "urlFilter" : "*.",
            "resourceTypes" : [ "image" ]

Controlling Abilities

An extension writer can request more or less abilities for their extension. Most browsers will then let the installer know how much more the extension wants to do.


An extension can ask for install time permissions(permissions) or runtime permissions(optional_permission, a subset). The desired permissions get listed in an array.

"permissions": [ "activeTab", "storage", "scripting" ],

Some common permissions:

There are a handful of non-api permissions (like activeTab and [clipboardWrite]), but the bulk are about fine-grained dialing in of what parts of the web extensions API the extension actually needs.

Content Security Policy

content_security_policy tightens or loosens what scripts from where can do. For example, the below would enable the use of web assembly.

"content_security_policy": {
  "extension_pages": "script-src 'self' 'wasm-unsafe-eval'"

Manifest V3 gives a lot less latitude to extensions drawing in scripts the installer might not be able to reason about (remote, dynamic, etc) For example, it only allows ’none’, ‘self’, and ‘wasm-unsafe-eval’ as script source parameters. The default policy for V3:

"script-src 'self'; upgrade-insecure-requests;"

In general terms:

More details from Mozilla from W3C

Externally Connectable

“Externally connectable controls which other extensions and web pages can communicate with an extension using runtime.connect() and runtime.sendMessage() message passing. If externally_connectable is not specified, all extensions can communicate with each other but not with web pages.” page

   "externally_connectable": {
        "matches": [ "*://*.example.com/*" ]

Load Resources

web_accessible_resources determines what resources an extension can use depending on what page. Below example lets the script load files with .xml and .xsl extensions from a directory “xsl” in the extension’s root when the page in the activeTab has the .xml extension.

    "web_accessible_resources": [
            "resources" : ["xsl/*.xml","xsl/*.xsl"],
            "matches": ["*://*/*.xml"]


This catalogs a lot of the things that can be controlled, next post, how to use them.

This article is part of a series.