Declare Firefox extensions and settings

Hello everyone!

I’m on a quest to declaratively manage as much of my system as possible. As part of this journey, I recently tackled Firefox’ configuration including extensions/add-ons and preferences. Because I’m fairly new to Nix and NixOS myself, it took me quite a while to figure out the details. That’s why I would like to share my results so far with anyone who’s in a similar situation like me and wants a quick and basic reference. It is simple and easy to read with minimal prior knowledge of Nix required and doesn’t depend on third-party Flakes.

EXTENSIONS
I’m using uBlock Origin, Privacy Badger and 1Password extensions, but the syntax for setting up other add-ons is the same.

FINDING EXTENSION IDs
If you’re having trouble finding a specific extension’s ID, you can always temporarily install it imperatively (the normal non-Nix way via the Firefox menu) and check the entries in the Add-ons section of about:support.

FINDING AVAILABLE PREFERENCES
For available preferences check the about:config page and for policy options the templates list provided by Mozilla.
If you want to adapt and expand on the settings used below and your customizations aren’t applied as expected, you can check the about:policies page for errors. (A list of supported preferences that start with certain prefixes can be found here.)

I hope this basic guide will be useful for some users who are new to NixOS and want to configure Firefox the Nix way. Please let me know if you have any suggestions/additions for improvement.

Here’s the contents of my current firefox.nix file that can be imported to your existing configuration:

{ config, pkgs, ... }:

  let
    lock-false = {
      Value = false;
      Status = "locked";
    };
    lock-true = {
      Value = true;
      Status = "locked";
    };
  in
{
  programs = {
    firefox = {
      enable = true;
      languagePacks = [ "de" "en-US" ];

      /* ---- POLICIES ---- */
      # Check about:policies#documentation for options.
      policies = {
        DisableTelemetry = true;
        DisableFirefoxStudies = true;
        EnableTrackingProtection = {
          Value= true;
          Locked = true;
          Cryptomining = true;
          Fingerprinting = true;
        };
        DisablePocket = true;
        DisableFirefoxAccounts = true;
        DisableAccounts = true;
        DisableFirefoxScreenshots = true;
        OverrideFirstRunPage = "";
        OverridePostUpdatePage = "";
        DontCheckDefaultBrowser = true;
        DisplayBookmarksToolbar = "never"; # alternatives: "always" or "newtab"
        DisplayMenuBar = "default-off"; # alternatives: "always", "never" or "default-on"
        SearchBar = "unified"; # alternative: "separate"

        /* ---- EXTENSIONS ---- */
        # Check about:support for extension/add-on ID strings.
        # Valid strings for installation_mode are "allowed", "blocked",
        # "force_installed" and "normal_installed".
        ExtensionSettings = {
          "*".installation_mode = "blocked"; # blocks all addons except the ones specified below
          # uBlock Origin:
          "uBlock0@raymondhill.net" = {
            install_url = "https://addons.mozilla.org/firefox/downloads/latest/ublock-origin/latest.xpi";
            installation_mode = "force_installed";
          };
          # Privacy Badger:
          "jid1-MnnxcxisBPnSXQ@jetpack" = {
            install_url = "https://addons.mozilla.org/firefox/downloads/latest/privacy-badger17/latest.xpi";
            installation_mode = "force_installed";
          };
          # 1Password:
          "{d634138d-c276-4fc8-924b-40a0ea21d284}" = {
            install_url = "https://addons.mozilla.org/firefox/downloads/latest/1password-x-password-manager/latest.xpi";
            installation_mode = "force_installed";
          };
        };
  
        /* ---- PREFERENCES ---- */
        # Check about:config for options.
        Preferences = { 
          "browser.contentblocking.category" = { Value = "strict"; Status = "locked"; };
          "extensions.pocket.enabled" = lock-false;
          "extensions.screenshots.disabled" = lock-true;
          "browser.topsites.contile.enabled" = lock-false;
          "browser.formfill.enable" = lock-false;
          "browser.search.suggest.enabled" = lock-false;
          "browser.search.suggest.enabled.private" = lock-false;
          "browser.urlbar.suggest.searches" = lock-false;
          "browser.urlbar.showSearchSuggestionsFirst" = lock-false;
          "browser.newtabpage.activity-stream.feeds.section.topstories" = lock-false;
          "browser.newtabpage.activity-stream.feeds.snippets" = lock-false;
          "browser.newtabpage.activity-stream.section.highlights.includePocket" = lock-false;
          "browser.newtabpage.activity-stream.section.highlights.includeBookmarks" = lock-false;
          "browser.newtabpage.activity-stream.section.highlights.includeDownloads" = lock-false;
          "browser.newtabpage.activity-stream.section.highlights.includeVisited" = lock-false;
          "browser.newtabpage.activity-stream.showSponsored" = lock-false;
          "browser.newtabpage.activity-stream.system.showSponsored" = lock-false;
          "browser.newtabpage.activity-stream.showSponsoredTopSites" = lock-false;
        };
      };
    };
  };
}
26 Likes

Really nice, will play with that, but what about profiles?:wink:

I use really many profiles and I have different extension needs for all of them;)

1 Like

Home-manager has a firefox module with profile support. Would be nice with a unification here.

1 Like

My issue atm is not being able to configure extensions (as in, extension-specifc preferences) declaratively. Wonder if that’s even possible.

2 Likes

Thank for the guide! Instead of temporarily installing the extension to get the ID, you can view the source of the extension in the addon store and search for "guid"

2 Likes

When I attempted something like this, I found that extension settings are usually stored inside an SQLite database at which point I stopped bothering.

2 Likes

Thank you all for your feedback and your suggestions/questions. I modified the code so that it works with home-manager too (see below). It also includes a minimal configuration for profiles that should be easily adaptable and further expandable.

According to the Home-Manager manual (https://nix-community.github.io/home-manager/options.html#opt-programs.firefox.profiles._name_.extensions), it is possible to declare profile-specific extensions with the help of the Nix User Repository NUR, but I don’t know whether it can be done without relying on third-party Flakes. Since a) my initial goal was to provide a basic first setup that is easy to read for beginners like me and b) I haven’t used the NUR yet, I decided not to include it here.

Same goes for declaratively configuring extension-specific preferences as asked by @Adrielus. After reading @Atemu’s post I didn’t investigate further.

Maybe someone more knowledgeable can help us out here. :slight_smile:

Anyway, here’s my modified firefox.nix that can be imported to home.nix:

{ config, pkgs, ... }:

  let
    lock-false = {
      Value = false;
      Status = "locked";
    };
    lock-true = {
      Value = true;
      Status = "locked";
    };
  in
{
  programs = {
    firefox = {
      enable = true;
      package = pkgs.wrapFirefox pkgs.firefox-unwrapped {
        extraPolicies = {
          DisableTelemetry = true;
          # add policies here...

          /* ---- EXTENSIONS ---- */
          ExtensionSettings = {
            "*".installation_mode = "blocked"; # blocks all addons except the ones specified below
            # uBlock Origin:
            "uBlock0@raymondhill.net" = {
              install_url = "https://addons.mozilla.org/firefox/downloads/latest/ublock-origin/latest.xpi";
              installation_mode = "force_installed";
            };
            # add extensions here...
          };
  
          /* ---- PREFERENCES ---- */
          # Set preferences shared by all profiles.
          Preferences = { 
            "browser.contentblocking.category" = { Value = "strict"; Status = "locked"; };
            "extensions.pocket.enabled" = lock-false;
            "extensions.screenshots.disabled" = lock-true;
            # add global preferences here...
          };
        };
      };

      /* ---- PROFILES ---- */
      # Switch profiles via about:profiles page.
      # For options that are available in Home-Manager see
      # https://nix-community.github.io/home-manager/options.html#opt-programs.firefox.profiles
      profiles ={
        profile_0 = {           # choose a profile name; directory is /home/<user>/.mozilla/firefox/profile_0
          id = 0;               # 0 is the default profile; see also option "isDefault"
          name = "profile_0";   # name as listed in about:profiles
          isDefault = true;     # can be omitted; true if profile ID is 0
          settings = {          # specify profile-specific preferences here; check about:config for options
            "browser.newtabpage.activity-stream.feeds.section.highlights" = false;
            "browser.startup.homepage" = "https://nixos.org";
            "browser.newtabpage.pinned" = [{
              title = "NixOS";
              url = "https://nixos.org";
            }];
            # add preferences for profile_0 here...
          };
        };
        profile_1 = {
          id = 1;
          name = "profile_1";
          isDefault = false;
          settings = {
            "browser.newtabpage.activity-stream.feeds.section.highlights" = true;
            "browser.startup.homepage" = "https://ecosia.org";
            # add preferences for profile_1 here...
          };
        };
      # add profiles here...
      };
    };
  };
}
2 Likes

yeah, I think it should be possible to generate some activation script which edits the db, but figuring that out sounds pretty hard

your always fighting against software that DOES NOT WANT to be configured in the nix way.

if Mozilla actually started to work with nix more closely and embraced a declarative configurations methods

then this stuff would be easy…

however, getting any of the large open source projects to realise that this stuff is important… is almost impossible…

if someone knows how to get developers to at Mozilla or any other large open source project to ‘listen’… please let me know.

will will probably have to wait to the 10% monkey conjecture kicks in …

1 Like

it’s not even about working with nix… all software would have to do is store configurations as plain text!

2 Likes

you have my 100% agreement…

and for my next trick the ‘Microsoft windows registry’…

I hope together we can reverse the imperative configuration crisis.

1 Like

Fab settings. I log into firefox and this seems to set me up as I want on any device - thus the settings are how I want them everywhere and making them fixed in nix while a beautiful example of how things should be, is for me not necessary but mega informative. I am not sure I saw the setting in
privacy and security
under “use custom settings for history”
to make it false (uncheck) “clear history when firefox closes”
which I seemed to need to keep myself logged into sites after reboot.
Is there a way in nixos configuration.nix to make an automatic firefox login ? eg after re-installation of nixos from scratch. I guess not in view of passoword requirements but I was wondering about passkeys…

I thought Firefox has done it pretty well. It also provide the api to configure extensions declaratively. If the extension doesn’t use such api, you should ask the dev of the extension to do that.

1 Like

hey, firefox isn’t the worse offender by far, it’s just the topic of this thread…

Don’t blame the players, blame the game.

The world doesn’t care about reproducible or declarative systems…

However, the future is shaped by optimists… so here I am from 2038.

you have to understand the being a nixxer is rather like saying the earth rotates around the sun, and everyone out there is CONVINCED that the sun rotates around the earth…

https://newsroom.ucla.edu/releases/the-truth-about-galileo-and-his-conflict-with-the-catholic-church)

Some other the scientists that tried to change the game… they burnt at the STEAK.

let the sink in for a moment.

1 Like

where can I read more about said api?

1 Like

Nice, there’s some helpful stuff in here. Just thought I’d offer this snippet from my browser config, thought it might make things easier for people following this thread who are interested in declarative Firefox config:

    programs.firefox.policies = {
      ExtensionSettings = with builtins;
        let extension = shortId: uuid: {
          name = uuid;
          value = {
            install_url = "https://addons.mozilla.org/en-US/firefox/downloads/latest/${shortId}/latest.xpi";
            installation_mode = "normal_installed";
          };
        };
        in listToAttrs [
          (extension "tree-style-tab" "treestyletab@piro.sakura.ne.jp")
          (extension "ublock-origin" "uBlock0@raymondhill.net")
          (extension "bitwarden-password-manager" "{446900e4-71c2-419f-a6a7-df9c091e268b}")
          (extension "tabliss" "extension@tabliss.io")
          (extension "umatrix" "uMatrix@raymondhill.net")
          (extension "libredirect" "7esoorv3@alefvanoon.anonaddy.me")
          (extension "clearurls" "{74145f27-f039-47ce-a470-a662b129930a}")
        ];
        # To add additional extensions, find it on addons.mozilla.org, find
        # the short ID in the url (like https://addons.mozilla.org/en-US/firefox/addon/!SHORT_ID!/)
        # Then, download the XPI by filling it in to the install_url template, unzip it,
        # run `jq .browser_specific_settings.gecko.id manifest.json` or
        # `jq .applications.gecko.id manifest.json` to get the UUID
    };
  };
2 Likes

You don’t need to get the UUID from the xpi. You can install it then find the UUID in about:debugging#/runtime/this-firefox.

1 Like

Right, as I said in my original post, the ID can be found in about:support#addons as well.

I like this concise form. Thanks for sharing!

1 Like