bedrock Documentation
             Release 1.0
                 Mozilla
               Oct 12, 2023
                                                                                                   CONTENTS
1 Contents                                                                                                           3
  1.1 Installing Bedrock . . . . . . . . . . . . . . . . . . . . . .   . . . . . . . . . . . . . . . . . . . . . .   3
  1.2 Localization . . . . . . . . . . . . . . . . . . . . . . . . .   . . . . . . . . . . . . . . . . . . . . . . 10
  1.3 Developing on Bedrock . . . . . . . . . . . . . . . . . . .      . . . . . . . . . . . . . . . . . . . . . . 27
  1.4 How to contribute . . . . . . . . . . . . . . . . . . . . . .    . . . . . . . . . . . . . . . . . . . . . . 45
  1.5 Continuous Integration & Deployment . . . . . . . . . . .        . . . . . . . . . . . . . . . . . . . . . . 49
  1.6 Front-end testing . . . . . . . . . . . . . . . . . . . . . . .  . . . . . . . . . . . . . . . . . . . . . . 53
  1.7 Managing Redirects . . . . . . . . . . . . . . . . . . . . .     . . . . . . . . . . . . . . . . . . . . . . 58
  1.8 Newsletters . . . . . . . . . . . . . . . . . . . . . . . . . .  . . . . . . . . . . . . . . . . . . . . . . 61
  1.9 Contentful CMS (Content Management System) Integration           . . . . . . . . . . . . . . . . . . . . . . 64
  1.10 Sitemaps . . . . . . . . . . . . . . . . . . . . . . . . . . .  . . . . . . . . . . . . . . . . . . . . . . 76
  1.11 Using External Content Cards Data . . . . . . . . . . . . .     . . . . . . . . . . . . . . . . . . . . . . 77
  1.12 Banners . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78
  1.13 Mozilla.UITour . . . . . . . . . . . . . . . . . . . . . . . .  . . . . . . . . . . . . . . . . . . . . . . 79
  1.14 Send to Device Widget . . . . . . . . . . . . . . . . . . . .   . . . . . . . . . . . . . . . . . . . . . . 80
  1.15 Firefox Download Buttons . . . . . . . . . . . . . . . . . .    . . . . . . . . . . . . . . . . . . . . . . 81
  1.16 Firefox Accounts Helpers . . . . . . . . . . . . . . . . . .    . . . . . . . . . . . . . . . . . . . . . . 82
  1.17 Funnel cakes and Partner Builds . . . . . . . . . . . . . . .   . . . . . . . . . . . . . . . . . . . . . . 88
  1.18 A/B Testing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
  1.19 Mozilla VPN Subscriptions . . . . . . . . . . . . . . . . .     . . . . . . . . . . . . . . . . . . . . . . 93
  1.20 Attribution . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
  1.21 Architectural Decision Records . . . . . . . . . . . . . . .    . . . . . . . . . . . . . . . . . . . . . . 114
  1.22 Browser Support . . . . . . . . . . . . . . . . . . . . . . .   . . . . . . . . . . . . . . . . . . . . . . 124
                                                                                                                     i
ii
                                                                          bedrock Documentation, Release 1.0
bedrock is the project behind www.mozilla.org. It is as shiny, awesome, and open-source as always. Perhaps even a
little more.
bedrock is a web application based on Django, a Python web application framework.
Patches are welcome! Feel free to fork and contribute to this project on Github.
CONTENTS                                                                                                        1
bedrock Documentation, Release 1.0
2                                  CONTENTS
                                                                                                         CHAPTER
                                                                                                              ONE
                                                                                                  CONTENTS
1.1 Installing Bedrock
1.1.1 Installation Methods
There are two primary methods of installing bedrock: Docker and Local. Whichever you choose you’ll start by getting
the source
$ git clone https://github.com/mozilla/bedrock.git
$ cd bedrock
After these basic steps you can choose your install method below. Docker is the easiest and recommended way, but
local is also possible and may be preferred by people for various reasons.
You should also install our git pre-commit hooks. Our setup uses the pre-commit framework. Install the framework
using the instructions on their site depending on your platform, then run pre-commit install. After that it will check
your Python, JS, and CSS files before you commit to save you time waiting for the tests to run in our CI (Continuous
Integration) before noticing a linting error.
Docker Installation
Note: This method assumes you have Docker installed for your platform. If not please do that now or skip to the
Local Installation section.
This is the simplest way to get started developing for bedrock. If you’re on Linux or Mac (and possibly Windows 10
with the Linux subsystem) you can run a script that will pull our production and development docker images and start
them:
$ make clean run
Note: You can start the server any other time with:
$ make run
You should see a number of things happening, but when it’s done it will output something saying that the server is
running at localhost:8000. Go to that URL in a browser and you should see the mozilla.org home page. In this mode
                                                                                                                     3
bedrock Documentation, Release 1.0
the site will refresh itself when you make changes to any template or media file. Simply open your editor of choice and
modify things and you should see those changes reflected in your browser.
Note: It’s a good idea to run make pull from time to time. This will pull down the latest Docker images from our
repository ensuring that you have the latest dependencies installed among other things. If you see any strange errors
after a git pull then make pull is a good thing to try for a quick fix.
If you don’t have or want to use Make you can call the docker and compose commands directly
$ docker-compose pull
$ [[ ! -f .env ]] && cp .env-dist .env
Then starting it all is simply
$ docker-compose up app assets
All of this is handled by the Makefile script and called by Make if you follow the above directions. You DO NOT
need to do both.
These directions pull and use the pre-built images that our deployment process has pushed to the Docker Hub. If you
need to add or change any dependencies for Python or Node then you’ll need to build new images for local testing. You
can do this by updating the requirements files and/or package.json file then simply running:
$ make build
Note: For Apple Silicon / M1 users
If you find that when you’re building you hit issues with Puppeteer not installing, these will help:
     • Set up a Rosetta Terminal.
     • Follow these Puppeter installation tips:.
Asset bundles
If you make a change to media/static-bundles.json, you’ll need to restart Docker.
Note: Sometimes stopping Docker doesn’t actually kill the images. To be safe, after stopping docker, run docker
ps to ensure the containers were actually stopped. If they have not been stopped, you can force them by running
docker-compose kill to stop all containers, or docker kill <container_name> to stop a single container, e.g.
docker kill bedrock_app_1.
4                                                                                               Chapter 1. Contents
                                                                                 bedrock Documentation, Release 1.0
Local Installation
These instructions assume you have Python, pip, and NodeJS installed. If you don’t have pip installed (you probably
do) you can install it with the instructions in the pip docs.
Bedrock currently uses Python 3.9.10. The recommended way to install and use that version is with pyenv and to create
a virtualenv using pyenv-virtualenv that will isolate Bedrock’s dependencies from other things installed on the system.
The following assumes you are on MacOS, using zsh as your shell and Homebrew as your package manager. If you are
not, there are installation instructions for a variety of platforms and shells in the READMEs for the two pyenv projects.
Install Python 3.9.10 with pyenv
   1. Install pyenv itself
       $ brew install pyenv
2. Configure your shell to init pyenv on start - this is noted in the project’s own docs, in more detail, but omits that
setting PYENV_ROOT and adding it to the path is needed:
$ echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.zshrc
$ echo 'export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.zshrc
$ echo 'eval "$(pyenv init -)"' >> ~/.zshrc
3. Restart your login session for the changes to profile files to take effect - if you’re not using zsh, the pyenv docs have
other routes
$ zsh -l
   4. Install the latest Python 3.9.x (eg 3.9.10), then test it’s there:
       $ pyenv install 3.9.10
       If you’d like to make Python 3.9.10 your default globally, you can do so with:
       $ pyenv global 3.9.10
       If you only want to make Python 3.9.10 available in the current shell, while you set up the Python virtualenv
       (below), you can do so with:
       $ pyenv shell 3.9.10
   5. Verify that you have the correct version of Python installed:
       $ python --version
       Python 3.9.10
Note: At the time of writing, Python 3.9.10 was the 3.9 release that worked with least complication across the
core team’s local-development platforms, incl both Intel and Apple Silicon Macs. It’s also the version of 3.9 in the
slim-bullseye image used for the Dockerized version.
Install a plugin to manage virtualenvs via pyenv and create a virtualenv for Bedrock’s dependencies
   1. Install pyenv-virtualenv
1.1. Installing Bedrock                                                                                                    5
bedrock Documentation, Release 1.0
       $ brew install pyenv-virtualenv
2. Configure your shell to init pyenv-virtualenv on start - again, this is noted in the pyenv-virtualenv project’s
own documentation, in more detail. The following will slot in a command that will work as long as you have pyenv-
virtualenv installed:
$ echo 'eval "$(pyenv virtualenv-init -)"' >> ~/.zshrc
    3. Restart your login session for the changes to profile files to take effect
       $ zsh -l
    4. Make a virtualenv we can use - in this example we’ll call it bedrock but use whatever you want
       $ pyenv virtualenv 3.9.10 bedrock
Use the virtualenv
    1. Switch to the virtualenv - this is the command you will use any time you need this virtualenv
       $ pyenv activate bedrock
2. If you’d like to auto activate the virtualenv when you cd into the bedrock directory, and deactivate it when you exit
the directory, you can do so with:
$ echo 'bedrock' > .python-version
    3. Securely upgrade pip
       $ pip install --upgrade pip
    4. Install / update dependencies
       $ make install-local-python-deps
Note: If you are on OSX and some of the compiled dependencies fails to compile, try explicitly setting the arch flags
and try again. The following are relevant to Intel Macs only. If you’re on Apple Silicon, 3.9.10 should ‘just work’:
$ export ARCHFLAGS="-arch i386 -arch x86_64"
$ make install-local-python-deps
If you are on Linux, you may need at least the following packages or their equivalent for your distro:
python3-dev libxslt-dev
Sync the database and all of the external data locally. This gets product-details, security-advisories, credits, release
notes, localizations, legal-docs etc:
$ bin/bootstrap.sh
Install the node dependencies to run the site. This will only work if you already have Node.js and npm installed:
6                                                                                              Chapter 1. Contents
                                                                              bedrock Documentation, Release 1.0
$ npm install
Note: Bedrock uses npm to ensure that Node.js packages that get installed are the exact ones we meant (similar to pip
hash checking mode for python). Refer to the npm documentation for adding or upgrading Node.js dependencies.
Note: As a convenience, there is a make preflight command which automatically brings your installed Python and
NPM dependencies up to date and also fetches the latest DB containing the latest site content. This is a good thing to
run after pulling latest changes from the main branch.
1.1.2 Run the tests
Now that we have everything installed, let’s make sure all of our tests pass. This will be important during development
so that you can easily know when you’ve broken something with a change.
Docker
We manage our local docker environment with docker-compose and Make. All you need to do here is run:
$ make test
If you don’t have Make you can simply run docker-compose run test.
If you’d like to run only a subset of the tests or only one of the test commands you can accomplish that with a command
like the following:
$ docker-compose run test py.test bedrock/firefox
This example will run only the unit tests for the firefox app in bedrock. You can substitute py.test bedrock/
firefox with most any shell command you’d like and it will run in the Docker container and show you the output. You
can also just run bash to get an interactive shell in the container which you can then use to run any commands you’d
like and inspect the file system:
$ docker-compose run test bash
Local
From the local install instructions above you should still have your virtualenv activated, so running the tests is as simple
as:
$ py.test lib bedrock
To test a single app, specify the app by name in the command above. e.g.:
$ py.test bedrock/firefox
1.1. Installing Bedrock                                                                                                    7
bedrock Documentation, Release 1.0
1.1.3 Make it run
Docker
You can simply run the make run script mentioned above, or use docker-compose directly:
$ docker-compose up app assets
Local
To make the server run, make sure your virtualenv is activated, and then run the server:
$ npm start
If you are not inside a virtualenv, you can activate it by doing:
$ pyenv activate bedrock
Prod Mode
There are certain things about the site that behave differently when running locally in dev mode using Django’s de-
velopment server than they do when running in the way it runs in production. Static assets that work fine locally can
be a problem in production if referenced improperly, and the normal error pages won’t work unless DEBUG=False and
doing that will make the site throw errors since the Django server doesn’t have access to all of the built static assets.
So we have a couple of extra Docker commands (via make) that you can use to run the site locally in a more prod-like
way.
First you should ensure that your .env file is setup the way you need. This usually means adding DEBUG=False and
DEV=False, though you may want DEV=True if you want the site to act more like www-dev.allizom.org in that all
feature switches are On and all locales are active for every page. After that you can run the following:
$ make run-prod
This will run the latest bedrock image using your local bedrock files and templates, but not your local static assets. If
you need an updated image just run make pull.
If you need to include the changes you’ve made to your local static files (images, css, js, etc.) then you have to build
the image first:
$ make build-prod run-prod
Pocket Mode
By default, Bedrock will serve the content of www.mozilla.org. However, it is also possible to make Bedrock serve
the content pages for Pocket (getpocket.com). This is done, ultimately, by setting a SITE_MODE env var to the value
of Pocket.
For local development, setting this env var is already supported in the standard ways to run the site:
     • Docker: make run-pocket and make run-pocket prod
     • Local run/Node/webpack and Django runserver: npm run in-pocket-mode
     • SITE_MODE=Pocket ./manage.py runserver for plain ol’ Django runserver, in Pocket mode
8                                                                                               Chapter 1. Contents
                                                                             bedrock Documentation, Release 1.0
For demos on servers, remember to set the SITE_MODE env var to be the value you need (Pocket or Mozorg – or
nothing, which is the same as setting Mozorg)
Documentation
This is a great place for coders and non-coders alike to contribute! Please note most of the documentation is currently
in reStructuredText but we also support Markdown files.
If you see a typo or similarly small change, you can use the “Edit in GitHub” link to propose a fix through GitHub.
Note: you will not see your change directly committed to the main branch. You will commit the change to a separate
branch so it can be reviewed by a staff member before merging to main.
If you want to make a bigger change or find a Documentation issue on the repo, it is best to edit and preview locally
before submitting a pull request. You can do this with Docker or Local installations. Run the commands from your root
folder. They will build documentation and start a live server to auto-update any changes you make to a documentation
file.
Docker:
$ make docs
Local:
$ pip install -r requirements/docs.txt
$ make livedocs
1.1.4 Localization
Localization (or L10n) files were fetched by the bootstrap.sh command your ran earlier and are included in the docker
images. If you need to update them or switch to a different repo or branch after changing settings you can run the
following command:
$ ./manage.py l10n_update
You can read more details about how to localize content here.
1.1.5 Feature Flipping (aka Switches)
Environment variables are used to configure behavior and/or features of select pages on bedrock via a template helper
function called switch(). It will take whatever name you pass to it (must be only numbers, letters, and dashes),
convert it to uppercase, convert dashes to underscores, and lookup that name in the environment. For example:
switch('the-dude') would look for the environment variable SWITCH_THE_DUDE. If the value of that variable is
any of “on”, “true”, “1”, or “yes”, then it will be considered “on”, otherwise it will be “off”.
You can also supply a list of locale codes that will be the only ones for which the switch is active. If the page is viewed
in any other locale the switch will always return False, even in DEV mode. This list can also include a “Locale Group”,
which is all locales with a common prefix (e.g. “en-US, en-GB” or “zh-CN, zh-TW”). You specify these with just the
prefix. So if you used switch('the-dude', ['en', 'de']) in a template, the switch would be active for German
and any English locale the site supports.
You may also use these switches in Python in views.py files (though not with locale support). For example:
1.1. Installing Bedrock                                                                                                   9
bedrock Documentation, Release 1.0
from bedrock.base.waffle import switch
def home_view(request):
      title = 'Staging Home' if switch('staging-site') else 'Prod Home'
      ...
Testing
If the environment variable DEV is set to a “true” value, then all switches will be considered “on” unless they are
explicitly “off” in the environment. DEV defaults to “true” in local development and demo servers.
To test switches locally:
    1. Set DEV=False in your .env file.
    2. Enable the switch in your .env file.
    3. Restart your web server.
To configure switches/env vars for a demo branch. Follow the demo-site instructions here.
Traffic Cop
Currently, these switches are used to enable/disable Traffic Cop experiments on many pages of the site. We only add
the Traffic Cop JavaScript snippet to a page when there is an active test. You can see the current state of these switches
and other configuration values in our configuration repo.
To work with/test these experiment switches locally, you must add the switches to your local environment. For example:
# to switch on firstrun-copy-experiment you'd add the following to your ``.env`` file
SWITCH_FIRSTRUN_COPY_EXPERIMENT=on
To do the equivalent in one of the bedrock apps see the www-config documentation.
Notes
A shortcut for activating virtual envs in zsh or bash is . venv/bin/activate. The dot is the same as source.
There’s a project called pew that provides a better interface for managing/activating virtual envs, so you can use that if
you want. Also if you need help managing various versions of Python on your system, the pyenv project can help.
1.2 Localization
The site is fully localizable. Localization files are not shipped with the code distribution, but are available in sepa-
rate GitHub repositories. The proper repos can be cloned and kept up-to-date using the l10n_update management
command:
$ ./manage.py l10n_update
If you don’t already have a data/www-l10n directory, this command will clone the git repo containing the .ftl transla-
tion files (either the dev or prod files depending on your DEV setting). If the folder is already present, it will update the
repository to the latest version.
10                                                                                                Chapter 1. Contents
                                                                               bedrock Documentation, Release 1.0
1.2.1 Fluent
Bedrock’s Localization (l10n) system is based on Project Fluent. This is a departure from a standard Django project
that relies on a gettext work flow of string extraction from template and code files, in that it relies on developers directly
editing the default language (English in our case) Fluent files and using the string IDs created there in their templates
and views.
The default files for the Fluent system live in the l10n directory in the root of the bedrock project. This directory
houses directories for each locale the developers directly implement (mostly simplified English “en”, and “en-US”).
The simplified English files are the default fallback for every string ID on the site and should be strings that are plain
and easy to understand English, as free from colloquialisms as possible. The translators are able to easily understand
the meaning of the string, and can then add their own local flair to the ideas.
Note: Much of this documentation also applies to the use of Fluent with Pocket templates, with the main differences
being that:
     • the English Pocket Fluent directory is called l10n-pocket/
     • there are no activation-threshold checks for Pocket templates - we expect all strings to be translated in one go,
        because they are done via a vendor
.ftl files
When adding translatable strings to the site you start by putting them all into an .ftl file in the l10n/en/ directory with
a path that matches or is somehow meaningful for the expected location of the template or view in which they’ll be
used. For example, strings for the mozorg/mission.html template would go into the l10n/en/mozorg/mission.
ftl file. Locales are activated for a particular .ftl file, not template or URL, so you should use a unique file for most
URLs, unless they’re meant to be translated and activated for new locales simultaneously.
You can have shared .ftl files that you can load into any template render, but only the first .ftl file in the list of the ones
for a page render will determine whether the page is active for a locale.
Activation of a locale happens automatically once certain rules are met. A developer can mark some string IDs as
being “Required”, which means that the file won’t be activated for a locale until that locale has translated all of those
required strings. The other rule is a percentage completion rule: a certain percentage (configurable) of the strings IDs
in the “en” file must be translated in the file for a locale before it will be marked as active. We’ll get into how exactly
this works later.
Translating with .ftl files
The Fluent file syntax is well documented on the Fluent Project’s site. We use “double hash” or “group” comments
to indicate strings required for activation. A group comment only ends when another group comment starts however,
so you should either group your required strings at the bottom of a file, or also have a “not required” group comment.
Here’s an example:
### File for example.html
## Required
example-page-title = The Page Title
# this is a note the applies only to example-page-desc
example-page-desc = This page is a test.
                                                                                                          (continues on next page)
1.2. Localization                                                                                                              11
bedrock Documentation, Release 1.0
                                                                                                (continued from previous page)
##
example-footer = This string isn't as important
Any group comment (a comment that starts with “##”) that starts with “Required” (case does not matter) will start a
required strings block, and any other group comment will end it.
Once you have your strings in your .ftl file you can place them in your template. We’ll use the above .ftl file for a simple
Jinja template example:
<!doctype html>
<html>
<head>
      <title>{{ ftl('example-page-title') }}</title>
</head>
<body>
      <h1>{{ ftl('example-page-title') }}</h1>
      <p>{{ ftl('example-page-desc') }}</p>
      <footer>
           <p>{{ ftl('example-footer') }}</p>
      </footer>
</body>
</html>
FTL (Fluent Translation List) String IDs
Our convention for string ID creation is the following:
   1. String IDs should be all lower-case alphanumeric characters.
   2. Words should be separated with hyphens.
   3. IDs should be prefixed with the name of the template file (e.g.                      firefox-new-skyline for
       firefox-new-skyline.html)
   4. If you need to create a new string for the same place on a page and to transition to it as it is translated, you can
       add a version suffix to the string ID: e.g. firefox-new-skyline-main-page-title-v2.
   5. The ID should be as descriptive as possible to make sense to the developer, but could be anything as long as it
       adheres to the rules above.
Using brand names
Common Mozilla brand names are stored in a global brands.ftl file, in the form of terms. Terms are useful for keeping
brand names separated from the rest of the translations, so that they can be managed in a consistent way across all
translated files, and also updated easily in a global context. In general the brand names in this file remain in English
and should not be translated, however locales still have the choice and control to make adjustments should it suit their
particular language.
Only our own brands should be managed this way, brands from other companies should not. If you are concerned that
the brand is a common word and may be translated, leave a comment for the translators.
Note:          We are trying to phase out use of { -brand-name-firefox-browser } please use {
-brand-name-firefox } browser.
12                                                                                               Chapter 1. Contents
                                                                               bedrock Documentation, Release 1.0
-brand-name = Firefox
example-about = About { -brand-name }.
example-update-successful = { -brand-name } has been updated.
# "Safari" here refers to the competing web browser
example-compare = How does { -brand-name } compare to Safari?
Important: When adding a new term to brands.ftl, the new term should also be manually added to the mozilla-
l10n/www-l10n repo for all locales. The reason for this is that if a term does not exist for a particular locale, then it
does not fall back to English like a regular string does. Instead, the term variable name is displayed on the page.
Variables
Single hash comments are applied only to the string immediately following them. They should be used to provide
additional context for the translators including:
   1. What the values of variables are.
   2. Context about where string appears on the page if it is not visible or references other elements on the page.
   3. Explanations of English idioms and jargon that may be confusing to non-native speakers.
# Variables:
#     $savings (string) - the percentage saved from the regular price, not including the %␣
 ˓→Examples: 50, 70
example-bundle-savings = Buy now for { $savings }% off.
# Context: Used as an accessible text alternative for an image
example-bookmark-manager-alt = The bookmark manager window in { -brand-name-firefox }.
# Context: This lists the various websites and magazines who have mentioned Firefox␣
 ˓→Relay.
# Example: "As seen in: FORBES magainze and LifeHacker"
example-social-proof = As seen in:
example-privacy-on-every = Want privacy on every device?
# "You got it" here is a casual answer to the previous question, "Want privacy on every␣
 ˓→device?"
example-you-got-it = You got it. Get { -brand-name-firefox } for mobile.
HTML with attributes
When passing HTML tags with attributes into strings for translation, remove as much room for error as possible by
putting all the attributes and their values in a single variable. (This is most common with links and their href attributes
but we do occasionally pass classes with other tags.)
# Variables:
#     $attrs (attrs) - link to https://www.mozilla.org/about/
example-created = { -brand-name-firefox } was created by <a {$attrs}>{ -brand-name-
 ˓→mozilla }</a>.
                                                                                                     (continues on next page)
1.2. Localization                                                                                                         13
bedrock Documentation, Release 1.0
                                                                                               (continued from previous page)
# Variables:
#     $class (string) - CSS class used to replace brand name with wordmark logo
example-firefox-relay = Add <span { $class }">{ -brand-name-firefox-relay }</span>
{% set created_attrs = 'href="%s" data-cta-type="link" data-cta-text="created by Mozilla"
 ˓→'|safe|format(url('mozorg.about.index')) %}
<p>{{ ftl('example-created', attrs=created_attrs) }}</p>
{{ ftl('example-firefox-relay', class_name='class="mzp-c-wordmark mzp-t-wordmark-md mzp-
 ˓→t-product-relay"') }}
Obsolete strings
When new strings are added to a page sometimes they update or replace old strings. Obsolete strings & IDs should
be removed from ftl files immediately if they are not being used as a fallback. If they are being kept as a fallback they
should be removed after 1-2 months.
Fallback
If you need to create a new string for the same place on a page and would like to keep the old one as a fallback, you can
add a version suffix to the new string ID: e.g. firefox-new-skyline-main-page-title-v2.
example-block-title-v2 = Security, reliability and speed — on every device, anywhere you␣
 ˓→go.
# Obsolete string
example-block-title = Security, reliability and speed — from name you can trust.
The ftl helper function has the ability to accept a fallback string ID and is described in the next section.
Remove
If the new string is fundamentally different a new string ID should be created and the old one deleted.
For example, if the page is going from talking about the Google Translate extension to promoting our own Firefox
Translate feature the old strings are not appropriate fall backs.
The old strings and IDs should be deleted:
example-translate-title = The To Google Translate extension makes translating the page␣
 ˓→you’re on easier than ever.
example-translate-content = Google Translate, with over 100 languages* at the ready, is␣
 ˓→used by millions of people around the world.
The new strings should have different IDs and not be versioned:
example-translate-integrated-title = { -brand-name-firefox } now comes with an␣
 ˓→integrated translation tool.
example-translate-integrated-content = Unlike some cloud-based alternatives, { -brand-
 ˓→name-firefox } translates text locally, so the content you’re translating doesn’t␣
 ˓→leave your machine.
14                                                                                              Chapter 1. Contents
                                                                                 bedrock Documentation, Release 1.0
The ftl_has_messages jinja helper would be useful here and is described in the next section.
The ftl helper function
The ftl() function takes a string ID and returns the string in the current language, or simplified english if the string
isn’t translated. If you’d like to use a different string ID in the case that the primary one isn’t translated you can specify
that like this:
ftl('primary-string-id', fallback='fallback-string-id')
When a fallback is specified it will be used only if the primary isn’t translated in the current locale. English locales
(e.g. en-US, en-GB) will never use the fallback and will print the simplified english version of the primary string if not
overridden in the more specific locale.
You can also pass in replacement variables into the ftl() function for use with fluent variables. If you had a variable
in your fluent file like this:
welcome = Welcome, { $user }!
You could use that in a template like this:
<h2>{{ ftl('welcome', user='Dude') }}<h2>
For our purposes these are mostly useful for things that can change, but which shouldn’t involve retranslation of a string
(e.g. URLs or email addresses).
You may also request any other translation of the string (or the original English string of course) regardless of the
current locale.
<h2>{{ ftl('welcome', locale='en', user='Dude') }}<h2>
This helper is available in Jinja templates and Python code in views. For use in a view you should always call it in the
view itself:
# views.py
from lib.l10n_utils import render
from lib.l10n_utils.fluent import ftl
def about_view(request):
       ftl_files = 'mozorg/about'
       hello_string = ftl('about-hello', ftl_files=ftl_files)
       render(request, 'about.html', {'hello': hello_string}, ftl_files=ftl_files)
If you need to use this string in a view, but define it outside of the view itself, you can use the ftl_lazy variant which
will delay evaluation until render time. This is mostly useful for defining messages shared among several views in
constants in a views.py or models.py file.
Whether you use this function in a Python view or a Jinja template it will always use the default list of Fluent files
defined in the FLUENT_DEFAULT_FILES setting. If you don’t specify any additional Fluent files via the fluent_files
keyword argument, then only those default files will be used.
1.2. Localization                                                                                                          15
bedrock Documentation, Release 1.0
The ftl_has_messages helper function
Another useful template tool is the ftl_has_messages() function. You pass it any number of string IDs and it will
return True only if all of those message IDs exist in the current translation. This is useful when you want to add a new
block of HTML to a page that is already translated, but don’t want it to appear untranslated on any page.
{% if ftl_has_messages('new-title', 'new-description') %}
   <h3>{{ ftl('new-title') }}</h3>
   <p>{{ ftl('new-description') }}</p>
{% else %}
   <h3>{{ ftl('title') }}</h3>
   <p>{{ ftl('description') }}</p>
{% endif %}
If you’d like to have it return true when any of the given message IDs exist in the translation instead of requiring all of
them, you can pass the optional require_all=False parameter and it will do just that.
There is a version of this function for use in views called has_messages. It works exactly the same way but is meant
to be used in the view Python code.
# views.py
from lib.l10n_utils import render
from lib.l10n_utils.fluent import ftl, has_messages
def about_view(request):
      ftl_files = 'mozorg/about'
      if has_messages('about-hello-v2', 'about-title-v2',
                             ftl_files=ftl_files):
            hello_string = ftl('about-hello-v2', ftl_files=ftl_files)
            title_string = ftl('about-title-v2', ftl_files=ftl_files)
      else:
            hello_string = ftl('about-hello', ftl_files=ftl_files)
            title_string = ftl('about-title', ftl_files=ftl_files)
      render(request, 'about.html', {'hello': hello_string, 'title': title_string}, ftl_
 ˓→files=ftl_files)
Specifying Fluent files
You have to tell the system which Fluent files to use for a particular template or view. This is done in either the page()
helper in a urls.py file, or in the call to l10n_utils.render() in a view.
Using the page() function
If you just need to render a template, which is quite common for bedrock, you will probably just add a line like the
following to your urls.py file:
urlpatterns = [
      page('about', 'about.html'),
      page('about/contact', 'about/contact.html'),
]
To tell this page to use the Fluent framework for l10n you just need to tell it which file(s) to use:
16                                                                                                Chapter 1. Contents
                                                                             bedrock Documentation, Release 1.0
urlpatterns = [
      page('about', 'about.html', ftl_files='mozorg/about'),
      page('about/contact', 'about/contact.html', ftl_files=['mozorg/about/contact',
 ˓→'mozorg/about']),
]
The system uses the first (or only) file in the list to determine which locales are active for that URL. You can pass a
string or list of strings to the ftl_files argument. The files you specify can include the .ftl extension or not, and
they will be combined with the list of default files which contain strings for global elements like navigation and footer.
There will also be files for reusable widgets like the newsletter form, but those should always come last in the list.
Using the class-based view
Bedrock includes a generic class-based view (CBV) that sets up l10n for you. If you need to do anything fancier than
just render the page, then you can use this:
from lib.l10n_utils import L10nTemplateView
class AboutView(L10nTemplateView):
      template_name = 'about.html'
      ftl_files = 'mozorg/about'
Using that CBV will do the right things for l10n, and then you can override other useful methods (e.g.
get_context_data) to do what you need. Also, if you do need to do anything fancy with the context, and you
find that you need to dynamically set the fluent files list, you can easily do so by setting ftl_files in the context
instead of the class attribute.
from lib.l10n_utils import L10nTemplateView
class AboutView(L10nTemplateView):
      template_name = 'about.html'
      def get_context_data(self, **kwargs):
            ctx = super().get_context_data(**kwargs)
            ftl_files = ['mozorg/about']
            if request.GET.get('fancy'):
                  ftl_files.append('fancy')
            ctx['ftl_files'] = ftl_files
            return ctx
A common case is needing to use FTL files when one template is used, but not with another. In this case you would have
some logic to decide which template to use in the get_template_names() method. You can set the ftl_files_map
class variable to a dict containing a map of template names to the list of FTL files for that template (or a single file
name if that’s all you need).
# views.py
from lib.l10n_utils import L10nTemplateView
# class-based view example
class AboutView(L10nTemplateView):
      ftl_files_map = {
                                                                                                      (continues on next page)
1.2. Localization                                                                                                          17
bedrock Documentation, Release 1.0
                                                                                                (continued from previous page)
            'about_es.html': ['about_es']
            'about_new.html': ['about']
      }
      def get_template_names(self):
            if self.request.locale.startswith('en'):
                 template_name = 'about_new.html'
            elif self.request.locale.startswith('es'):
                 template_name = 'about_es.html'
            else:
                 # FTL system not used
                 template_name = 'about.html'
            return [template_name]
If you need for your URL to use multiple Fluent files to determine the full list of active locales, for example when
you are redesigning a page and have multiple templates in use for a single URL depending on locale, you can use the
activation_files parameter. This should be a list of FTL filenames that should all be used when determining the full list
of translations for the URL. Bedrock will gather the full list for each file and combine them into a single list so that the
footer language switcher works properly.
Another common case is that you want to keep using an old template for locales that haven’t yet translated the strings
for a new one. In that case you can provide an old_template_name to the class and include both that template and
template_name in the ftl_files_map. Once you do this the view will use the template in template_name only for
requests for an active locale for the FTL files you provided in the map.
from lib.l10n_utils import L10nTemplateView
class AboutView(L10nTemplateView):
      template_name = 'about_new.html'
      old_template_name = "about.html"
      ftl_files_map = {
            "about_new.html": ["about_new", "about_shared"],
            "about.html": ["about", "about_shared"],
      }
In this example when the about_new FTL file is active for a locale, the about_new.html template will be rendered.
Otherwise the about.html template would be used.
Using in a view function
Lastly there’s the good old function views. These should use l10n_utils.render directly to render the template with
the context. You can use the ftl_files argument with this function as well.
from lib.l10n_utils import render
def about_view(request):
      render(request, 'about.html', {'name': 'Duder'}, ftl_files='mozorg/about')
18                                                                                               Chapter 1. Contents
                                                                                bedrock Documentation, Release 1.0
Fluent File Configuration
In order for a Fluent file to be extracted through automation and sent out for localization, it must first be configured to
go through one or more distinct pipelines. This is controlled via a set of configuration files:
      • Vendor, locales translated by an agency, and paid for by Marketing (locales covered by staff are also included in
         this group).
      • Pontoon, locales translated by Mozilla contributors.
      • Special templates, for locales with dedicated templates that don’t go through the localization process (not cur-
         rently used).
Each configuration file consists of a pre-defined set of locales for which each group is responsible for translating. The
locales defined in each file should not be changed without first consulting the with L10n team, and such changes should
not be a regular occurrence.
To establish a localization strategy for a Fluent file, it needs to be included as a path in one or more configuration files.
For example:
[[paths]]
        reference = "en/mozorg/mission.ftl"
        l10n = "{locale}/mozorg/mission.ftl"
You can read more about configuration files in the L10n Project Configuration docs.
Important: Path definitions in Fluent configuration files are not source order dependent. A broad definition using a
wild card can invalidate all previous path definitions for example. Paths should be defined carefully to avoid exposing
.ftl files to unintended locales.
Using a combination of vendor and pontoon configuration offers a flexible but specific set of options to choose from
when it comes to defining an l10n strategy for a page. The available choices are:
    1. Staff locales.
    2. Staff + select vendor locales.
    3. Staff + all vendor locales.
    4. Staff + vendor + pontoon.
    5. All pontoon locales (for non-marketing content only).
When choosing an option, it’s important to consider that vendor locales have a cost associated with them, and pontoon
leans on the goodwill of our volunteer community. Typically, only non-marketing content should go through Pontoon
for all locales. Everything that is marketing related should feature one of the staff/vendor/pontoon configurations.
Fluent File Activation
Fluent files are activated automatically when processed from the l10n team’s repo into our own based on a couple of
rules.
    1. If a fluent file has a group of required strings, all of those strings must be present in the translation in order for it
         to be activated.
    2. A translation must contain a minimum percent of the string IDs from the English file to be activated.
If both of these conditions are met the locale is activated for that particular Fluent file. Any view using that file as its
primary (first in the list) file will be available in that locale.
1.2. Localization                                                                                                           19
bedrock Documentation, Release 1.0
Deactivation
If the automated system activates a locale but we for some reason need to ensure that this page remains unavailable in
that locale, we can add this locale to a list of deactivated locales in the metadata file for that FTL file. For example, say
we needed to make sure that the mozorg/mission.ftl file remained inactive for German, even though the translation is
already done. We would add de to the inactive_locales list in the metadata/mozorg/mission.json file:
{
    "active_locales": [
       "de",
       "fr",
       "en-GB",
       "en-US",
    ],
    "inactive_locales": [
       "de"
    ],
    "percent_required": 85
}
This would ensure that even though de appears in both lists, it will remain deactivated on the site. We could just remove
it from the active list, but automation would keep attempting to add it back, so for now this is the best solution we have,
and is an indication of the full list of locales that have satisfied the rules.
Alternate Rules
It’s also possible to change the percentage of string completion required for activation on a per-file basis. In the same
metadata file as above, if a percent_required key exists in the JSON data (see above) it will be used as the minimum
percent of string completion required for that file in order to activate new locales.
Note: Once a locale is activated for a Fluent file it will NOT be automatically deactivated, even if the rules change.
If you need to deactivate a locale you should follow the Deactivation instructions.
Activation Status
You can determine and use the activation status of a Fluent file in a view to make some decisions; what template to
render for example. The way you would do that is with the ftl_file_is_active function. For example:
# views.py
from lib.l10n_utils import L10nTemplateView
from lib.l10n_utils.fluent import ftl_file_is_active
# class-based view example
class AboutView(L10nTemplateView):
      ftl_files_map = {
            'about.html': ['about']
            'about_new.html': ['about_new', 'about']
      }
      def get_template_names(self):
            if ftl_file_is_active('mozorg/about_new'):
                                                                                                          (continues on next page)
20                                                                                                  Chapter 1. Contents
                                                                             bedrock Documentation, Release 1.0
                                                                                                (continued from previous page)
                template_name = 'about_new.html'
           else:
                template_name = 'about.html'
           return [template_name]
# function view example
def about_view(request):
      if ftl_file_is_active('mozorg/about_new'):
           template = 'mozorg/about_new.html'
           ftl_files = ['mozorg/about_new', 'mozorg/about']
      else:
           template = 'about.html'
           ftl_files = ['mozorg/about']
      render(request, template, ftl_files=ftl_files)
Active Locales
To see which locales are active for a particular .ftl file you can either look in the metadata file for that .ftl file, which
is the one with the same path but in the metadata folder instead of a locale folder in the www-l10n repository. Or if
you’d like something a bit nicer looking and more convenient there is the active_locales management command:
$ ./manage.py l10n_update
$ ./manage.py active_locales mozorg/mission
There are 91 active locales for mozorg/mission.ftl:
- af
- an
- ar
- ast
- az
- be
- bg
- bn
...
You get an alphabetically sorted list of all of the active locales for that .ftl file. You should run ./manage.py
l10n_update as shown above for the most accurate and up-to-date results.
1.2. Localization                                                                                                          21
bedrock Documentation, Release 1.0
String extraction
The string extraction process for both new .ftl content and updates to existing .ftl content is handled through automation.
On each commit to main a command is run that looks for changes to the l10n/ and l10n-pocket/ directories. If a
change is detected, it will copy those files into a new branch in mozilla-l10n/www-l10n and then a bot will open a pull
request containing those changes. Once the pull request has been reviewed and merged by the L10n team, everything
is done.
To view the state of the latest automated attempt to open an L10N PR, see:
     • Mozorg L10N PR action
     • Pocket L10N PR action
(We also just try to open L10N PRs every 3 hours, to catch any failed jobs that are triggered by a commit to main)
CSS
If a localized page needs some locale-specific style tweaks, you can add the style rules to the page’s stylesheet like this:
html[lang="it"] #features li {
   font-size: 20px;
}
html[dir="rtl"] #features {
   float: right;
}
If a locale needs site-wide style tweaks, font settings in particular, you can add the rules to /media/css/l10n/
{{LANG}}/intl.css. Pages on Bedrock automatically includes the CSS in the base templates with the l10n_css
helper function. The CSS may also be loaded directly from other Mozilla sites with such a URL: //mozorg.cdn.
mozilla.net/media/css/l10n/{{LANG}}/intl.css.
Open Sans, the default font on mozilla.org, doesn’t offer non-Latin glyphs. intl.css can have @font-face rules to
define locale-specific fonts using custom font families as below:
     • X-LocaleSpecific-Light: Used in combination with Open Sans Light. The font can come in 2 weights: normal
       and optionally bold
     • X-LocaleSpecific: Used in combination with Open Sans Regular. The font can come in 2 weights: normal and
       optionally bold
     • X-LocaleSpecific-Extrabold: Used in combination with Open Sans Extrabold. The font weight is 800 only
Here’s an example of intl.css:
@font-face {
   font-family: X-LocaleSpecific-Light;
   font-weight: normal;
   font-display: swap;
   src: local(mplus-2p-light), local(Meiryo);
}
@font-face {
   font-family: X-LocaleSpecific-Light;
   font-weight: bold;
   font-display: swap;
                                                                                                         (continues on next page)
22                                                                                                 Chapter 1. Contents
                                                                           bedrock Documentation, Release 1.0
                                                                                              (continued from previous page)
    src: local(mplus-2p-medium), local(Meiryo-Bold);
}
@font-face {
    font-family: X-LocaleSpecific;
    font-weight: normal;
    font-display: swap;
    src: local(mplus-2p-regular), local(Meiryo);
}
@font-face {
    font-family: X-LocaleSpecific;
    font-weight: bold;
    font-display: swap;
    src: local(mplus-2p-bold), local(Meiryo-Bold);
}
@font-face {
    font-family: X-LocaleSpecific-Extrabold;
    font-weight: 800;
    font-display: swap;
    src: local(mplus-2p-black), local(Meiryo-Bold);
}
Localizers can specify locale-specific fonts in one of the following ways:
     • Choose best-looking fonts widely used on major platforms, and specify those with the src:           local(name)
       syntax
     • Find a best-looking free Web font, add the font files to /media/fonts/, and specify those with the src:
       url(path) syntax
     • Create a custom Web font to complement missing glyphs in Open Sans, add the font files to /media/fonts/
       l10n/, and specify those with the src: url(path) syntax. M+ 2c offers various international glyphs and
       looks similar to Open Sans, while Noto Sans is good for the bold and italic variants. You can create subsets of
       these alternative fonts in the WOFF and WOFF2 formats using a tool found on the Web. See Bug 1360812 for
       the Fulah (ff) locale’s example
Developers should use the .open-sans mixin instead of font-family: 'Open Sans' to specify the default font
family in CSS. This mixin has both Open Sans and X-LocaleSpecific so locale-specific fonts, if defined, will be applied
to localized pages. The variant mixins, .open-sans-light and .open-sans-extrabold, are also available.
1.2.2 All
Locale-specific Templates
While the ftl_has_messages template function is great in small doses, it doesn’t scale particularly well. A template
filled with conditional copy can be difficult to comprehend, particularly when the conditional copy has associated CSS
and/or JavaScript.
In instances where a large amount of a template’s copy needs to be changed, or when a template has messaging targeting
one particular locale, creating a locale-specific template may be a good choice.
Locale-specific templates function simply by naming convention. For example, to create a version of /firefox/new.
html specifically for the de locale, you would create a new template named /firefox/new.de.html. This template
1.2. Localization                                                                                                        23
bedrock Documentation, Release 1.0
can either extend /firefox/new.html and override only certain blocks, or be entirely unique.
When a request is made for a particular page, bedrock’s rendering function automatically checks for a locale-specific
template, and, if one exists, will render it instead of the originally specified (locale-agnostic) template.
Note: Creating a locale-specific template for en-US was not possible when this feature was introduced, but it is now.
So you can create your en-US-only template and the rest of the locales will continue to use the default.
Specifying Active Locales in Views
Normally we rely on activation tags in our translation files (.lang files) to determine in which languages a page will be
available. This will almost always be what we want for a page. But sometimes we need to explicitly state the locales
available for a page. The impressum page for example is only available in German and the template itself has German
hard-coded into it since we don’t need it to be translated into any other languages. In cases like these we can send a list
of locale codes with the template context and it will be the final list. This can be accomplished in a few ways depending
on how the view is coded.
For a plain view function, you can simply pass a list of locale codes to l10n_utils.render in the context using the name
active_locales. This will be the full list of available translations. Use add_active_locales if you want to add languages
to the existing list:
def french_and_german_only(request):
      return l10n_utils.render(request, 'home.html', {'active_locales': ['de', 'fr'])
If you don’t need a custom view and are just using the page() helper function in your urls.py file, then you can similarly
pass in a list:
page('about', 'about.html', active_locales=['en-US', 'es-ES']),
Or if your view is even more fancy and you’re using a Class-Based-View that inherits from LangFilesMixin (which it
must if you want it to be translated) then you can specify the list as part of the view Class definition:
class MyView(LangFilesMixin, View):
      active_locales = ['zh-CN', 'hi-IN']
Or in the urls.py when using a CBV:
url(r'about/$', MyView.as_view(active_locales=['de', 'fr'])),
The main thing to keep in mind is that if you specify active_locales that will be the full list of localizations available
for that page. If you’d like to add to the existing list of locales generated from the lang files then you can use the
add_active_locales name in all of the same ways as active_locales above. It’s a list of locale codes that will be added
to the list already available. This is useful in situations where we would have needed the l10n team to create an empty
.lang file with an active tag in it because we have a locale-specific-template with text in the language hard-coded into
the template and therefore do not otherwise need a .lang file.
24                                                                                                 Chapter 1. Contents
                                                                            bedrock Documentation, Release 1.0
1.2.3 Adding new L10N integrations
Bedrock, as a platform, can operate in different modes, and it is possible (necessary, even) to support multiple L10N
pipelines, so that each mode of operation can have its own distinct Fluent files and translation strategy.
As of Summer 2022, there are two separate L10N integrations within Bedrock:
     • Mozilla.org (“Mozorg mode”)
     • Pocket Marketing Pages (“Pocket mode”)
These integrations are similar in their approach, but not identical in how they run. They use different translations
strategies, which requires slightly different data flows.
Moving L10N data (essentially Fluent .ftl files) happens via various automation steps, which aren’t captured here, as
they are more about infrastructure and operations. However, what follows outlines the steps needed to add a new L10N
integration (for “newintegration”) to Bedrock.
    1. FILE SETUP (Bedrock developer)
Add a directory for the source (en) Fluent strings that will need translation.
Note: For source Fluent files currently. . .
     • . . . Mozorg uses ./l10n/
     • . . . Pocket uses ./l10n-pocket/
Add the following files:
./l10n-newintegration/
./l10n-newintegration/en/ # This is where source Fluent templates go for 'newintegration'
./l10n-newintegration/en/configs/pontoon.toml # If using community/Pontoon translations␣
 ˓→at all
./l10n-newintegration/en/configs/vendor.toml # If using a paid-for translation service␣
 ˓→such as Smartling
./l10n-newintegration/en/configs/special-templates.toml                        # Only needed to exclude␣
 ˓→certain files from all community AND vendor translation. e.g. we use staff translation␣
 ˓→only.
./l10n-newintegration/l10n-pontoon.toml # If using community/Pontoon translations at all
./l10n-newintegration/l10n-vendor.toml # If using a paid-for translation service such␣
 ˓→as Smartling
./data/l10n-newintegration-team/ # leave this empty - it will be populated via a git␣
 ˓→sync using data FROM the l10n team
For the exact content of each .toml or .json file, see the examples in ./l10n/ and ./l10n-pocket/ for inspiration -
they’re not too hard to work out. The .toml files outside of /en/ basically point to the ones in /en/configs/ and are
a ‘gateway’ through which we spec which config files are relevant to which translation stragegy (community or vendor
- or neither if it’s staff-only translation).
    2. REPO SETUP (Bedrock and/or L10N team admin)
You will need to set up one or two new repos, to hold the translation files as part of the pipeline.
i. A repo in where the files are sent to in https://github.com/mozilla-l10n/ for the L10N team’s automation
to pick up.
1.2. Localization                                                                                                  25
bedrock Documentation, Release 1.0
For example, Mozorg uses github.com/mozilla-l10n/www-l10n/ and Pocket uses github.com/mozilla-l10n/
www-pocket-l10n/. Your new github.com/mozilla-l10n/www-newintegration-l10n/ repo will be needed
regardless of who does the actual translation work.
    ii. An optional repo where files are post-processed following translation.
If relevant,       this will live       in    github.com/mozmeao/          -   for   example     github.com/mozmeao/
www-newintegration-l10n/
Important: If you are not using Pontoon/community translations, you do NOT need to create this repo. Why?
If the translations are done by the community (via Pontoon), there is a possibility that not enough of the strings will
be translated in order to render the content in the relevant locale. We run a CI task to determine whether a locale has
enough translated strings to be considered ‘active’. At the moment, only Mozorg uses this pattern. The Pocket-mode
translations do not have their activation measured because their translations come entirely from a vendor and we expect
the Pocket strings to be 100% translated.
    3. CI SETUP (Bedrock dev)
Only relevant if using Pontoon community translations. Details of how MozMarRobot is hooked are best gleaned from
looking at https://gitlab.com/mozmeao/www-fluent-update.
In short, once new translations land in the string-source repo (e.g.                       github.com/mozilla-l10n/
www-newintegration-l10n) they are cloned over to the activation-check repo github.com/mozmeao/
www-newintegration-l10n/ by CI and later pulled into Bedrock from there.
    4. CONFIGURE SETTINGS (Bedrock dev).
You’ll also have to update settings so that when the site is in ‘newintegration’ mode, it knows which L10N-related local
folders and remote repos to use. Look in settings/__init__.py to see what we did for Pocket mode.
You’ll also have to set up new env vars to provide the new repo and filepath settings’ values, which will mean updating
github.com/mozmeao/www-config/ and possibly getting new secrets provisioned in Kubernetes if you need to use
a separate auth token for github.com/mozilla-l10n/. (You may not.)
Note that if you are not using community/Pontoon translations - and therefore you don’t need to use an intermediary
repo to calculate activation status - you can just use the mozilla-l10n/www-newintegration-l10n repo for both
outbound and inbound translations - look at the Pocket Mode setting for an example of this.
    5. EXPAND L10N UPDATE SCRIPT (Bedrock dev).
Uploading strings for translation
Uploading `en-locale source strings from Bedrock to the github.com/mozilla-l10n/ repos is handled by
bedrock/bin/open-ftl-pr.sh. This file requires no specific code changes to support a new integration as long
as you have already set up a SITE_MODE for ‘newintegration’.
However, you do need to add a new entry to bedrock/.gitlab-ci.yml – copy the update-l10n step, in a similar
way to how it’s been duplicated for update-pocket-l10n.
Downloading translated strings
Update the configuration dict at the top of bedrock/lib/l10n_utils/management/commands/l10n_update.py
so that when that management command is run, it will pull down the appropriate translations for “newintegration”.
Tip: to test drive things, you can fork the real repos and test against your forks by specifying them via local env vars.
    6. VENDOR SETUP (L10N Team)
The vendor (e.g. Smartling) will need to add the new string-source repo (github.com/mozilla-l10n/
www-newintegration-l10n) to its configuration. Once this is done new translations from the vendor will be added
26                                                                                               Chapter 1. Contents
                                                                         bedrock Documentation, Release 1.0
to that repo, and synced down to Bedrock. This step is out of our hands, but the vendor’s technical contact should be
able to make it happen.
    7. PONTOON SETUP (L10N Team)
Details to come for setting up community translations using Pontoon. (Contributions about this aspect are welcome!)
1.3 Developing on Bedrock
1.3.1 Managing Dependencies
For Python we use pip-compile from pip-tools to manage dependencies expressed in our requirements files.
pip-compile is wrapped up in Makefile commands, to ensure we use it consistently.
If you add a new Python dependency (eg to requirements/prod.in or requirements/dev.in) you can generate
a pinned and hash-marked addition to our requirements files just by running:
make compile-requirements
and committing any changes that are made. Please re-build your docker image and test it with make build test to
be sure the dependency does not cause a regression.
Similarly, if you upgrade a pinned dependency in an *.in file, run make compile-requirements then rebuild, test
and commit the results
To check for stale Python dependencies (basically pip list -o but in the Docker container):
make check-requirements
For Node packages we use NPM, which should already be installed alongside Node.js.
Front-end Dependencies
Our team maintains a few dependencies that we serve on Bedrock’s front-end.
     • @mozilla-protocol/core: Bedrock’s primary design system
     • @mozmeao/cookie-helper: A complete cookies reader/writer framework
     • @mozmeao/dnt-helper: Do Not Track (DNT) helper
     • @mozmeao/trafficcop: Used for A/B testing page variants
Because they are all published on NPM, install the packages and keep up-to-date with the latest version of each de-
pendency by running an npm install. For further documentation on installing NPM packages, check out the official
documentation.
1.3. Developing on Bedrock                                                                                        27
bedrock Documentation, Release 1.0
1.3.2 Asset Management and Bundling
Bedrock uses Webpack to manage front-end asset processing and bundling. This includes processing and minifying
JavaScript and SCSS/CSS bundles, as well as managing static assets such as images, fonts, and other common file
types.
When developing on bedrock you can start Webpack by running make run when using Docker, or npm start when
running bedrock locally.
Once Webpack has finished compiling, a local development server will be available at localhost:8000. When Webpack
detects changes to a JS/SCSS file, it will automatically recompile the bundle and then refresh any page running locally
in the browser.
Webpack Configuration
We have two main Webpack config files in the root directory:
The webpack.static.config.js file is responsible for copying static assets, such as images and fonts, from the
/media/ directory over to the /assets/ directory. This is required so Django can serve them correctly.
The webpack.config.js file is responsible for processing JS and SCSS files in the /media/ directory and compiling
them into the /assets/ directory. This config file also starts a local development server and watches for file changes.
We use two separate config files to keep responsibilities clearly defined, and to make the configs both shorter and easier
to follow.
Note: Because of the large number of files used in bedrock, only JS and SCSS files managed by webpack.config.js
are watched for changes when in development mode. This helps save on memory consumption. The implication of this
is that files handled by webpack.static.config.js are only copied over when Webpack first runs. If you update an
image for example, then you will need to stop and restart Webpack to pick up the change. This is not true for JS and
SCSS files, which will be watched for change automatically.
Asset Bundling
Asset bundles for both JS and SCSS are defined in ./media/static-bundles.json. This is the file where you can
define the bundle names that will get used in page templates. For example, a CSS bundle can be defined as:
"css": [
      {
             "files": [
                  "css/firefox/new/basic/download.scss"
             ],
             "name": "firefox_new_download"
      }
]
Which can then be referenced in a page template using:
{{ css_bundle('firefox_new_download') }}
A JS bundle can be defied as:
28                                                                                              Chapter 1. Contents
                                                                              bedrock Documentation, Release 1.0
"js": [
      {
            "files": [
                 "protocol/js/protocol-modal.js",
                 "js/firefox/new/basic/download.js"
            ],
            "name": "firefox_new_download"
      }
]
Which can then be referenced in a page template using:
{{ js_bundle('firefox_new_download') }}
Once you define a bundle in static-bundles.json, the webpack.config.js file will use these as entrypoints for
compiling JS and CSS and watching for changes.
1.3.3 Writing JavaScript
Bedrock’s Webpack configuration supports some different options for writing JavaScript:
Default Configuration
Write example-script.js using ES5 syntax and features. Webpack will bundle the JS as-is, without any additional
pre-processing.
Babel Configuration
Write example-script.es6.js using ES2015+ syntax. Webpack will transpile the code to ES5 using Babel. This
is useful when you want to write modern syntax but still support older browsers.
Important: Whilst Babel will transpile most modern JS syntax to ES5 when suitable fallbacks exist, it won’t auto-
matically include custom polyfills for everything since these can start to greatly increase bundle size. If you want to
use Promise or async/await functions for example, then you will need to load polyfills for those. This can be done
either at the page level, or globally in lib.js if it’s something that multiple pages would benefit from. But please pick
and choose wisely, and be concious of performance.
For pages that are served to Firefox browsers only, such as /whatsnew, it is also possible to write native ES2015+
syntax and serve that directly in production. Here there is no need to include the .es6.js file extension. Instead, you
can simply use .js instead. The rules that which files you can do this in are defined in our ESLint config.
1.3. Developing on Bedrock                                                                                            29
bedrock Documentation, Release 1.0
1.3.4 Writing URL Patterns
URL patterns should be the entire URL you desire, minus any prefixes from URLs files importing this one, and including
a trailing slash. You should also give the URL a name so that other pages can reference it instead of hardcoding the
URL. Example:
path("channel/", channel, name="mozorg.channel")
If you only want to render a template and don’t need to do anything else in a custom view, Bedrock comes with a handy
shortcut to automate all of this:
from bedrock.mozorg.util import page
page("channel/", "mozorg/channel.html")
You don’t need to create a view. It will serve up the specified template at the given URL (the first parameter. see the
Django docs for details). You can also pass template data as keyword arguments:
page("channel/", "mozorg/channel.html",
        latest_version=product_details.firefox_versions["LATEST_FIREFOX_VERSION"])
The variable latest_version will be available in the template.
1.3.5 Finding Templates by URL
General Structure
Bedrock follows the Django app structure and most templates are easy to find by matching URL path segments to
folders and files within the correct app.
URL: https://www.mozilla.org/en-US/firefox/features/private-browsing/
Template path: bedrock/bedrock/firefox/templates/firefox/features/private-browsing.html
To get from URL to template path:
     • Ignore https://www.mozilla.org and the locale path segment /en-US. The next path segment is the app
        name /firefox.
     • From the root folder of bedrock, find the app’s template folder at bedrock/{app}/templates/{app}
     • Match remaining URL path segments (/features/private-browsing) to the template folder’s structure (/
        features/private-browsing.html)
Note: mozorg is the app name for the home page and child pages related to Mozilla Corporation (i.e. About, Contact,
Diversity).
30                                                                                            Chapter 1. Contents
                                                                           bedrock Documentation, Release 1.0
Whatsnew and Firstrun
These pages are specific to Firefox browsers, and only appear when a user updates or installs and runs a Firefox browser
for the first time. The URL and template depend on what Firefox browser and version are in use.
Note: There may be extra logic in the app’s views.py file to change the template based on locale or geographic
location as well.
Firefox release
Version number is digits only.
Whatsnew URL: https://www.mozilla.org/en-US/firefox/99.0/whatsnew/
Template path: https://github.com/mozilla/bedrock/tree/main/bedrock/firefox/templates/firefox/whatsnew
Firstrun URL: https://www.mozilla.org/en-US/firefox/99.0/firstrun/
Template path: https://github.com/mozilla/bedrock/blob/main/bedrock/firefox/templates/firefox/firstrun/firstrun.html
Firefox Nightly
Version number is digits and a1.
Whatsnew URL: https://www.mozilla.org/en-US/firefox/99.0a1/whatsnew/
Template path:
https://github.com/mozilla/bedrock/blob/main/bedrock/firefox/templates/firefox/nightly/whatsnew.html
Firstrun URL: https://www.mozilla.org/en-US/firefox/nightly/firstrun/
Template path: https://github.com/mozilla/bedrock/tree/main/bedrock/firefox/templates/firefox/nightly
Firefox Developer
Version number is digits and a2.
Whatsnew URL: https://www.mozilla.org/en-US/firefox/99.0a2/whatsnew/
Template path:
https://github.com/mozilla/bedrock/blob/main/bedrock/firefox/templates/firefox/developer/whatsnew.html
Firstrun URL: https://www.mozilla.org/en-US/firefox/99.0a2/firstrun/
Template path:
https://github.com/mozilla/bedrock/blob/main/bedrock/firefox/templates/firefox/developer/firstrun.html
1.3. Developing on Bedrock                                                                                           31
bedrock Documentation, Release 1.0
Release Notes
Release note templates live here: https://github.com/mozilla/bedrock/tree/main/bedrock/firefox/templates/firefox/
releases
Note: Release note content is pulled in from an external data source.
    • Firefox release: https://www.mozilla.org/en-US/firefox/99.0.1/releasenotes/
    • Firefox Developer and Beta: https://www.mozilla.org/en-US/firefox/100.0beta/releasenotes/
    • Firefox Nightly: https://www.mozilla.org/en-US/firefox/101.0a1/releasenotes/
    • Firefox Android: https://www.mozilla.org/en-US/firefox/android/99.0/releasenotes/
    • Firefox iOS: https://www.mozilla.org/en-US/firefox/ios/99.0/releasenotes/
1.3.6 Optimizing Images
Images can take a long time to load and eat up a lot of bandwidth. Always take care to optimize images before uploading
them to the site.
The script img.sh can be used to optimize images locally on the command line:
   1. Before you run it for the first time you will need to run npm install to install dependencies
   2. Add the image files to git’s staging area git add *
   3. Run the script ./bin/img.sh
   4. The optimized files will not automatically be staged, so be sure to add them before commiting
The script will:
    • optimize JPG and PNG files using tinypng (
               – this step is optional since running compression on the same images over and over degrades them)
               – you will be prompted to add a TinyPNG API key
    • optimize SVG images locally with svgo
    • check that SVGs have a viewbox (needed for IE support)
    • check that images that end in -high-res have low res versions as well
1.3.7 Embedding Images
Images should be included on pages using one of the following helper functions.
32                                                                                             Chapter 1. Contents
                                                                         bedrock Documentation, Release 1.0
Primary image helpers
The following image helpers support the most common features and use cases you may encounter when coding pages:
static()
For a simple image, the static() function is used to generate the image URL. For example:
<img src="{{ static('img/firefox/new/firefox-wordmark-logo.svg') }}" alt="Firefox">
will output an image:
<img src="/media/img/firefox/new/firefox-wordmark-logo.svg" alt="Firefox">
resp_img()
For responsive images, where we want to specify multiple different image sizes and let the browser select which is best
to use.
The example below shows how to serve an appropriately sized, responsive red panda image:
resp_img(
      url="img/panda-500.png",
      srcset={
           "img/panda-500.png": "500w",
           "img/panda-750.png": "750w",
           "img/panda-1000.png": "1000w"
      },
      sizes={
           "(min-width: 1000px)": "calc(50vw - 200px)",
           "default": "calc(100vw - 50px)"
      }
)
This would output:
<img src="/media/img/panda-500.png"
        srcset="/media/img/panda-500.png 500w,/media/img/panda-750.png 750w,/media/img/
 ˓→panda-1000.png 1000w"
        sizes="(min-width: 1000px) calc(50vw - 200px),calc(100vw - 50px)" alt="">'
In the above example we specified the available image sources using the srcset parameter. We then used sizes to
say:
     • When the viewport is greater than 1000px wide, the panda image will take up roughly half of the page width.
     • When the viewport is less than 1000px wide, the panda image will take up roughly full page width.
The default image src is what we specified using the url param. This is also what older browsers will fall back to
using. Modern browsers will instead pick the best source option from srcset (based on both the estimated image size
and screen resolution) to satisfy the condition met in sizes.
1.3. Developing on Bedrock                                                                                          33
bedrock Documentation, Release 1.0
Note: The value default in the second sizes entry above should be used when you want to omit a media query.
This makes it possible to provide a fallback size when no other media queries match.
Another example might be to serve a high resolution alternative for a fixed size image:
resp_img(
     url="img/panda.png",
     srcset={
           "img/panda-high-res.png": "2x"
     }
)
This would output:
<img src="/media/img/panda.png" srcset="/media/img/panda-high-res.png 2x" alt="">
Here we don’t need a sizes attribute, since the panda image is fixed in size and small enough that it won’t need to
resize along with the browser window. Instead the srcset image includes an alternate high resolution source URL,
along with a pixel density descriptor. This can then be used to say:
    • When a browser specifies a device pixel ratio of 2x or greater, use panda-high-res.png.
    • When a browser specifies a device pixel ration of less than 2x, use panda.png.
The resp_img() helper also supports localized images by setting the 'l10n' parameter to True`:
resp_img(
     url="img/panda-500.png",
     srcset={
           "img/panda-500.png": "500w",
           "img/panda-750.png": "750w",
           "img/panda-1000.png": "1000w"
     },
     sizes={
           "(min-width: 1000px)": "calc(50vw - 200px)",
           "default": "calc(100vw - 50px)"
     },
     optional_attributes={
           "l10n": True
     }
)
This would output (assuming de was your locale):
<img src="/media/img/l10n/de/panda-500.png"
       srcset="/media/img/l10n/de/panda-500.png 500w,/media/img/l10n/de/panda-750.png 750w,
 ˓→/media/img/l10n/de/panda-1000.png 1000w"
       sizes="(min-width: 1000px) calc(50vw - 200px),calc(100vw - 50px)" alt="">'
Finally, you can also specify any other additional attributes you might need using optional_attributes:
resp_img(
     url="img/panda-500.png",
     srcset={
                                                                                               (continues on next page)
34                                                                                          Chapter 1. Contents
                                                                           bedrock Documentation, Release 1.0
                                                                                               (continued from previous page)
            "img/panda-500.png": "500w",
            "img/panda-750.png": "750w",
            "img/panda-1000.png": "1000w"
      },
      sizes={
            "(min-width: 1000px)": "calc(50vw - 200px)",
            "default": "calc(100vw - 50px)"
      },
      optional_attributes={
            "alt": "Red Panda",
            "class": "panda-hero",
            "height": "500",
            "l10n": True,
            "loading": "lazy",
            "width": "500"
      }
)
picture()
For responsive images, where we want to serve different images, or image types, to suit different display sizes.
The example below shows how to serve a different image for desktop and mobile sizes screens:
picture(
      url="img/panda-mobile.png",
      sources=[
            {
                  "media": "(max-width: 799px)",
                  "srcset": {
                       "img/panda-mobile.png": "default"
                  }
            },
            {
                  "media": "(min-width: 800px)",
                  "srcset": {
                       "img/panda-desktop.png": "default"
                  }
            }
      ]
)
This would output:
<picture>
      <source media="(max-width: 799px)" srcset="/media/img/panda-mobile.png">
      <source media="(min-width: 800px)" srcset="/media/img/panda-desktop.png">
      <img src="/media/img/panda-mobile.png" alt="">
</picture>
In the above example, the default image src is what we specifed using the url param. This is also what older browsers
will fall back to using. We then used the sources parameter to specify one or more alternate image <source> elements,
1.3. Developing on Bedrock                                                                                                35
bedrock Documentation, Release 1.0
which modern browsers can take advantage of. For each <source>, media lets us specify a media query as a condition
for when to load an image, and srcset lets us specify one or more sizes for each image.
Note: The value default in the srcset entry above should be used when you want to omit a descriptor. In this
example we only have one entry in srcset (meaning it will be chosen immediately should the media query be satisfied),
hence we omit a descriptor value.
A more complex example might be when we want to load responsively sized, animated gifs, but also offer still images
for users who set (prefers-reduced-motion: reduce):
picture(
      url="img/dancing-panda-500.gif",
      sources=[
           {
                "media": "(prefers-reduced-motion: reduce)",
                "srcset": {
                     "img/sleeping-panda-500.png": "500w",
                     "img/sleepinng-panda-750.png": "750w",
                     "img/sleeping-panda-1000.png": "1000w"
                },
                "sizes": {
                     "(min-width: 1000px)": "calc(50vw - 200px)",
                     "default": "calc(100vw - 50px)"
                }
           },
           {
                "media": "(prefers-reduced-motion: no-preference)",
                "srcset": {
                     "img/dancing-panda-500.gif": "500w",
                     "img/dancing-panda-750.gif": "750w",
                     "img/dancing-panda-1000.gif": "1000w"
                },
                "sizes": {
                     "(min-width: 1000px)": "calc(50vw - 200px)",
                     "default": "calc(100vw - 50px)"
                }
           }
      ]
)
This would output:
<picture>
      <source media="(prefers-reduced-motion: reduce)"
                srcset="/media/img/sleeping-panda-500.png 500w,/media/img/sleeping-panda-750.
 ˓→png 750w,/media/img/sleeping-panda-1000.png 1000w"
                sizes="(min-width: 1000px) calc(50vw - 200px),calc(100vw - 50px)">
      <source media="(prefers-reduced-motion: no-preference)"
                srcset="/media/img/dancing-panda-500.gif 500w,/media/img/dancing-panda-750.
 ˓→gif 750w,/media/img/dancing-panda-1000.gif 1000w"
                sizes="(min-width: 1000px) calc(50vw - 200px),calc(100vw - 50px)">
      <img src="/media/img/dancing-panda-500.gif" alt="">
</picture>
36                                                                                         Chapter 1. Contents
                                                                         bedrock Documentation, Release 1.0
In the above example we would default to loading animated gifs, but if a user agent specified
(prefers-reduced-motion: reduce) then the browser would load static png files instead. Multiple image
sizes are also supported for each <source> using srcset and sizes.
Another type of use case might be to serve different image formats, so capable browsers can take advantage of more
efficient encoding:
picture(
      url="img/red-panda.png",
      sources=[
            {
                 "type": "image/webp",
                 "srcset": {
                     "img/red-panda.webp": "default"
                 }
            }
      ]
)
This would output:
<picture>
      <source type="image/webp" srcset="/media/img/red-panda.webp">
      <img src="/media/img/red-panda.png" alt="">
</picture>
In the above example we use sources to specify an alternate image with a type attribute of image/webp. This lets
browsers that support WebP to download red-panda.webp, whilst older browsers would download red-panda.png.
Like resp_img(), the picture() helper also supports L10n images and other useful attributes via the
optional_attributes parameter:
picture(
      url="img/panda-mobile.png",
      sources=[
            {
                 "media": "(max-width: 799px)",
                 "srcset": {
                     "img/panda-mobile.png": "default"
                 }
            },
            {
                 "media": "(min-width: 800px)",
                 "srcset": {
                     "img/panda-desktop.png": "default"
                 }
            }
      ],
      optional_attributes={
            "alt": "Red Panda",
            "class": "panda-hero",
            "l10n": True,
            "loading": "lazy",
      }
)
1.3. Developing on Bedrock                                                                                     37
bedrock Documentation, Release 1.0
Which image helper should you use?
This is a good question. The answer depends entirely on the image in question. A good rule of thumb is as follows:
     • Is the image a vector format (e.g. .svg)?
               – If yes, then for most cases you can simply use static().
     • Is the image a raster format (e.g. .png or .jpg)?
               – Is the same image displayed on both large and small viewports? Does the image need to scale as the
                  browser resizes? If yes to both, then use resp_img() with both srcset and sizes.
               – Is the image fixed in size (non-responsive)? Do you need to serve a high resolution version? If yes to
                  both, then use resp_img() with just srcset.
     • Does the source image need to change depending on a media query (e.g serve a different image on both desktop
        and mobile)? If yes, then use picture() with media and srcset.
     • Is the image format only supported in certain browsers? Do you need to provide a fallback? If yes to both, then
        use picture() with type and srcset.
Secondary image helpers
The following image helpers are less commonly used, but exist to support more specific use cases. Some are also
encapsulated as features inside inside of primary helpers, such as l1n_img().
l10n_img()
Images that have translatable text can be handled with l10n_img():
<img src="{{ l10n_img('firefox/os/have-it-all/messages.jpg') }}">
The images referenced by l10n_img() must exist in media/img/l10n/, so for above example, the im-
ages could include media/img/l10n/en-US/firefox/os/have-it-all/messages.jpg and media/img/l10n/
es-ES/firefox/os/have-it-all/messages.jpg.
qrcode()
This is a helper function that will output SVG data for a QR Code at the spot in the template where it is called. It caches
the results to the data/qrcode_cache directory, so it only generates the SVG data one time per data and box_size
combination.
qrcode("https://accounts.firefox.com", 30)
The first argument is the data you’d like to encode in the QR Code (usually a URL), and the second is the “box size”.
It’s a parameter that tells the generator how large to set the height and width parameters on the XML SVG tag, the units
of which are “mm”. This can be overriden with CSS so you may not need to use it at all. The box_size parameter is
optional.
38                                                                                               Chapter 1. Contents
                                                                            bedrock Documentation, Release 1.0
1.3.8 Using Large Assets
We don’t want to (and if large enough GitHub won’t let us) commit large files to the bedrock repo. Files such as large
PDFs or very-high-res JPG files (e.g. leadership team photos), or videos are not well-tracked in git and will make every
checkout after they’re added slower and this diffs less useful. So we have another domain at which we upload these
files: assets.mozilla.net
This domain is simply an AWS S3 bucket with a CloudFront CDN (Content Delivery Network) in front of it. It is
highly available and fast. We’ve made adding files to this domain very simple using git-lfs. You simply install git-lfs,
clone our assets.mozilla.net repo, and then add and commit files under the assets directory there as usual. Open a
pull request, and once it’s merged it will be automatically uploaded to the S3 bucket and be available on the domain.
For example, if you add a file to the repo under assets/pdf/the-dude-abides.pdf, it will be available as https:
//assets.mozilla.net/pdf/the-dude-abides.pdf. Once that is done you can link to that URL from bedrock as you would
any other URL.
1.3.9 Writing Migrations
Bedrock uses Django’s built-in Migrations framework for its database migrations, and has no custom database routing,
etc. So, no big surprises here – write things as you regularly would.
However, as with any complex system, care needs to be taken with schema changes that drop or rename database
columns. Due to the way the rollout process works (ask for details directly from the team), an absent column can cause
some of the rollout to enter a crashloop.
To avoid this, split your changes across releases, such as below.
For column renames:
     • Release 1: Add your new column
     • Release 2: Amend the codebase to use it instead of the old column
     • Release 3: Clean up - drop the old, deprecated column, which should not be referenced in code at this point.
For column drops:
     • Release 1: Update all code that uses the relevant column, so that nothing interacts with it any more.
     • Release 2: Clean up - drop the old, deprecated column.
With both paths, check for any custom schema or data migrations that might reference the deprecated column.
1.3.10 Writing Views
You should rarely need to write a view for mozilla.org. Most pages are static and you should use the page function
documented above.
If you need to write a view and the page is translated or translatable then it should use the l10n_utils.render()
function to render the template.
from lib import l10n_utils
from django.views.decorators.http import require_safe
@require_safe
def my_view(request):
                                                                                                     (continues on next page)
1.3. Developing on Bedrock                                                                                                39
bedrock Documentation, Release 1.0
                                                                                                 (continued from previous page)
      # do your fancy things
      ctx = {"template_variable": "awesome data"}
      return l10n_utils.render(request, "app/template.html", ctx)
Make sure to namespace your templates by putting them in a directory named after your app, so instead of tem-
plates/template.html they would be in templates/blog/template.html if blog was the name of your app.
The require_safe ensures that only GET or HEAD requests will make it through to your view.
If you prefer to use Django’s Generic View classes we have a convenient helper for that. You can use it either to create
a custom view class of your own, or use it directly in a urls.py file.
# app/views.py
from lib.l10n_utils import L10nTemplateView
class FirefoxRoxView(L10nTemplateView):
      template_name = "app/firefox-rox.html"
# app/urls.py
urlpatterns = [
      # from views.py
      path("firefox/rox/", FirefoxRoxView.as_view()),
      # directly
      path("firefox/sox/", L10nTemplateView.as_view(template_name="app/firefox-sox.html")),
]
The L10nTemplateView functionality is mostly in a template mixin called LangFilesMixin which you can use with
other generic Django view classes if you need one other than TemplateView. The L10nTemplateView already ensures
that only GET or HEAD requests will be served.
Variation Views
We have a generic view that allows you to easily create and use a/b testing templates. If you’d like to have either separate
templates or just a template context variable for switching, this will help you out. For example.
# urls.py
from django.urls import path
from bedrock.utils.views import VariationTemplateView
urlpatterns = [
      path("testing/",
             VariationTemplateView.as_view(template_name="testing.html",
                                                      template_context_variations=["a", "b"]),
             name="testing"),
]
This will give you a context variable called variation that will either be an empty string if no param is set, or a if
?v=a is in the URL, or b if ?v=b is in the URL. No other options will be valid for the v query parameter and variation
will be empty if any other value is passed in for v via the URL. So in your template code you’d simply do the following:
{% if variation == 'b' %}<p>This is the B variation of our test. Enjoy!</p>{% endif %}
40                                                                                                Chapter 1. Contents
                                                                              bedrock Documentation, Release 1.0
If you’d rather have a fully separate template for your test, you can use the template_name_variations argument to
the view instead of template_context_variations.
# urls.py
from django.urls import path
from bedrock.utils.views import VariationTemplateView
urlpatterns = [
       path("testing/",
              VariationTemplateView.as_view(template_name="testing.html",
                                                       template_name_variations=["1", "2"]),
              name="testing"),
]
This will not provide any extra template context variables, but will instead look for alternate template names. If the
URL is testing/?v=1, it will use a template named testing-1.html, if v=2 it will use testing-2.html, and for
everything else it will use the default. It simply puts a dash and the variation value between the template file name and
file extension.
It is theoretically possible to use the template name and template context versions of this view together, but that would
be an odd situation and potentially inappropriate for this utility.
You can also limit your variations to certain locales. By default the variations will work for any localization of the page,
but if you supply a list of locales to the variation_locales argument to the view then it will only set the variation
context variable or alter the template name (depending on the options explained above) when requested at one of said
locales. For example, the template name example above could be modified to only work for English or German like so
# urls.py
from django.urls import path
from bedrock.utils.views import VariationTemplateView
urlpatterns = [
       path("testing/",
              VariationTemplateView.as_view(template_name="testing.html",
                                                       template_name_variations=["1", "2"],
                                                       variation_locales=["en-US", "de"]),
              name="testing"),
]
Any request to the page in for example French would not use the alternate template even if a valid variation were given
in the URL.
Note: If you’d like to add this functionality to an existing Class-Based View, there is a mixin that implements this
pattern that should work with most views: bedrock.utils.views.VariationMixin.
1.3. Developing on Bedrock                                                                                               41
bedrock Documentation, Release 1.0
Geo Template View
Now that we have our CDN configured properly, we can also just swap out templates per request country. This is very
similar to the above, but it will simply use the proper template for the country from which the request originated.
from bedrock.base.views import GeoTemplateView
class CanadaIsSpecialView(GeoTemplateView):
      geo_template_names = {
            "CA": "mozorg/canada-is-special.html",
      }
      template_name = "mozorg/everywhere-else-is-also-good.html"
For testing purposes while you’re developing or on any deployment that is not accessed via the production domain
(www.mozilla.org) you can append your URL with a geo query param (e.g. /firefox/?geo=DE) and that will take
precedence over the country from the request header.
Other Geo Stuff
There are a couple of other tools at your disposal if you need to change things depending on the location of the user.
You can use the bedrock.base.geo.get_country_from_request function in a view and it will return the country
code for the request (either from the CDN or the query param, just like above).
from bedrock.base.geo import get_country_from_request
def dude_view(request):
      country = get_country_from_request(request)
      if country == "US":
            # do a thing for the US
      else:
            # do the default thing
The other convenience available is that the country code, either from the CDN or the query param, is avilable in any
template in the country_code variable. This allows you to change anything about how the template renders based on
the location of the user.
{% if country_code == "US" %}
      <h1>GO MURICA!</h1>
{% else %}
      <h1>Yay World!</h1>
{% endif %}
Reference:
     • Officially assigned list of ISO country codes.
42                                                                                             Chapter 1. Contents
                                                                             bedrock Documentation, Release 1.0
1.3.11 Coding Style
Bedrock uses the following open source tools to follow coding styles and conventions, as well as applying automatic
code formatting:
    • ruff for Python style, code quality rules, and import ordering.
    • black for Python code formatting.
    • Prettier for JavaScript code formatting.
    • ESLint for JavaScript code quality rules.
    • Stylelint for Sass/CSS style and code quality rules.
For front-end HTML & CSS conventions, bedrock uses Mozilla’s Protocol design system for building components.
You can read the Protocol documentation site for more information.
Mozilla also has some more general coding styleguides available, although some of these are now rather outdated:
    • Mozilla Python Style Guide
    • Mozilla HTML Style Guide
    • Mozilla JS Style Guide
    • Mozilla CSS Style Guide
1.3.12 Test coverage
When the Python tests are run, a coverage report is generated, showing which lines of the codebase have tests that
execute them, and which do not. You can view this report in your browser at file:///path/to/your/checkout/
of/bedrock/python_coverage/index.html.
When adding code, please aim to provide solid test coverage, using the coverage report as a guide. This doesn’t
necessarily mean every single line needs a test, and 100% coverage doesn’t mean 0% defects.
1.3.13 Configuring your Code Editor
Bedrock includes an .editorconfig file in the root directory that you can use with your code editor to help maintain
consistent coding styles. Please see editorconfig.org. for a list of supported editors and available plugins.
1.3.14 Working with Protocol Design System
Bedrock uses the Protocol Design System to quickly produce consistent, stable components. There are different meth-
ods – depending on the component – to import a Protocol component into our codebase.
One method involves two steps:
   1. Adding the correct markup or importing the appropriate macro to the page’s HTML file.
   2. Importing the necessary Protocol styles to a page’s SCSS file.
The other method is to import CSS bundles onto the HTML file. However, this only works for certain components,
which are listed below in the respective section.
1.3. Developing on Bedrock                                                                                       43
bedrock Documentation, Release 1.0
Styles and Components
The base templates in Bedrock have global styles from Protocol that apply to every page. When we need to extend
these styles on a page-specific basis, we set up Protocol in a page-specific SCSS file.
For example, on a Firefox product page, we might want to use Firefox logos or wordmarks that do not exist on every
page.
To do this, we add Protocol mzp- classes to the HTML:
// bedrock/bedrock/firefox/templates/firefox/{specific-page}.html
<div class="mzp-c-wordmark mzp-t-wordmark-md mzp-t-product-firefox">
      Firefox Browser
</div>
Then we need to include those Protocol styles in the page’s SCSS file:
/* bedrock/media/css/firefox/{specific-page}.scss */
/* if we need to use protocol images, we need to set the $image-path variable */
$image-path: '/media/protocol/img';
/* mozilla is the default theme, so if we want a different one, we need to set the
 ˓→$brand-theme variable */
$brand-theme: 'firefox';
/* the lib import is always essential: it provides access to tokens, functions, mixins,␣
 ˓→and theming */
@import '~@mozilla-protocol/core/protocol/css/includes/lib';
/* then you add whatever specific protocol styling you need */
@import '~@mozilla-protocol/core/protocol/css/components/logos/wordmark';
@import '~@mozilla-protocol/core/protocol/css/components/logos/wordmark-product-firefox';
Note: If you create a new SCSS file for a page, you will have to include it in that page’s CSS bundle by updating
static-bundles.json file.
Macros
The team has created several Jinja macros out of Protocol components to simplify the usage of components housing
larger blocks of code (i.e. Billboard). The code housing the custom macros can be found in our protocol macros file.
These Jinja macros include parameters that are simple to define and customize based on how the component should
look like on a given page.
To use these macros in files, we simply import a macro to the page’s HTML code and call it with the desired arguments,
instead of manually adding Protocol markup. We can import multiple macros in a comma-separated fashion, ending
the import with with context:
// bedrock/bedrock/firefox/templates/firefox/{specific-page}.html
{% from "macros-protocol.html" import billboard with context %}
{{ billboard(
      title='This is Firefox.',
                                                                                                   (continues on next page)
44                                                                                           Chapter 1. Contents
                                                                           bedrock Documentation, Release 1.0
                                                                                             (continued from previous page)
      ga_title='This is Firefox',
      desc='Firefox is an awesome web browser.',
      link_cta='Click here to install',
      link_url=url('firefox.new')
   )}}
Because not all component styles are global, we still have to import the page-specific Protocol styles in SCSS:
/* bedrock/media/css/firefox/{specific-page}.scss */
$brand-theme: 'firefox';
@import '~@mozilla-protocol/core/protocol/css/includes/lib';
@import '~@mozilla-protocol/core/protocol/css/components/billboard';
Import CSS Bundles
We created pre-built CSS bundles to be used for some components due to their frequency of use. This method only
requires an import into the HTML template. Since it’s a separate CSS bundle, we don’t need to import that component
in the respective page CSS. The CSS bundle import only works for the following components:
     • Split
     • Card
     • Picto
     • Callout
     • Article
     • Newsletter form
     • Emphasis box
Include a CSS bundle in the template’s page_css block along with any other page-specific bundles, like so:
{% block page_css %}
      {{ css_bundle('protocol-split') }}
      {{ css_bundle('protocol-card') }}
      {{ css_bundle('page-specific-bundle') }}
{% endblock %}
1.4 How to contribute
Before diving into code it might be worth reading through the Developing on Bedrock documentation, which contains
useful information and links to our coding guidelines for Python, Django, JavaScript and CSS.
1.4. How to contribute                                                                                                  45
bedrock Documentation, Release 1.0
1.4.1 Git workflow
When you want to start contributing, you should create a branch from main. This allows you to work on different
project at the same time:
$ git switch main
$ git switch -c topic-branch
To keep your branch up-to-date, assuming the mozilla repository is the remote called mozilla:
$ git switch main
$ git pull --ff-only
More on Why you should use –ff-only. To make this the default update your Git config as described in the article.
$ git switch topic-branch
$ git rebase main
If you need more Git expertise, a good resource is the Git book.
Once you’re done with your changes, you’ll need to describe those changes in the commit message.
1.4.2 Git commit messages
Commit messages are important when you need to understand why something was done.
     • First, learn how to write good git commit messages.
     • All commit messages must include a bug number. You can put the bug number on any line, not only the first one.
     • If you use the syntax bug xxx, Github will reference the commit into Bugzilla. With fix bug xxx, it will even
       close the bug once it goes into main.
If you’re asked to change your commit message, you can use these commands:
$ git commit --amend
-f is doing a force push because you modified the history
$ git push -f my-remote topic-branch
1.4.3 Submitting your work
In general, you should submit your work with a pull request to main. If you are working with other people or you want
to put your work on a demo server, then you should be working on a common topic branch.
Once your code has been positively reviewed, it will be deployed shortly after. So if you want feedback on your code but
it’s not ready to be deployed, you should note it in the pull request, or use a Draft PR. Also make use of an appropriate
label, such as Do Not Merge.
46                                                                                              Chapter 1. Contents
                                                                           bedrock Documentation, Release 1.0
1.4.4 Squashing your commits
Should your pull request contain more than one commit, sometimes we may ask you to squash them into a single
commit before merging. You can do this with git rebase.
As an example, let’s say your pull request contains two commits. To squash them into a single commit, you can follow
these instructions:
$ git rebase -i HEAD~2
You will then get an editor with your two commits listed. Change the second commit from pick to fixup, then save and
close. You should then be able to verify that you only have one commit now with git log.
To push to GitHub again, because you “altered the history” of the repo by merging the two commits into one, you’ll
have to git push -f instead of just git push.
1.4.5 Deploying your code
These are the websites that Bedrock is usually deployed to as part of development.
Demo sites
Bedrock as a platform can run in two modes: Mozorg Mode (for content that will appear on mozilla.org) and Pocket
Mode (for content that will end up on getpocket.com).
To support this, we have two separate sets of URLs we use for demos. To get code up to one of those URLs, push it to
the specified branch on github.com/mozilla/bedrock:
    • Mozorg:
               – Branch mozorg-demo-1 -> https://www-demo1.allizom.org/
               – Branch mozorg-demo-2 -> https://www-demo2.allizom.org/
               – Branch mozorg-demo-3 -> https://www-demo3.allizom.org/
               – Branch mozorg-demo-4 -> https://www-demo4.allizom.org/
               – Branch mozorg-demo-5 -> https://www-demo5.allizom.org/
               – Branch mozorg-demo-6 -> https://www-demo6.allizom.org/
               – Branch mozorg-demo-7 -> https://www-demo7.allizom.org/
               – Branch mozorg-demo-8 -> https://www-demo8.allizom.org/
               – Branch mozorg-demo-9 -> https://www-demo9.allizom.org/
    • Pocket:
               – Branch pocket-demo-1 -> https://www-demo1.tekcopteg.com/
               – Branch pocket-demo-2 -> https://www-demo2.tekcopteg.com/
               – Branch pocket-demo-3 -> https://www-demo3.tekcopteg.com/
               – Branch pocket-demo-4 -> https://www-demo4.tekcopteg.com/
               – Branch pocket-demo-5 -> https://www-demo5.tekcopteg.com/
For example, for Mozorg:
1.4. How to contribute                                                                                            47
bedrock Documentation, Release 1.0
$ git push -f mozilla my-demo-branch:mozorg-demo-2
Or for Pocket:
$ git push -f mozilla my-demo-branch:pocket-demo-1
Deployment notification and logs
At the moment, there is no way to view logs for the deployment unless you have access to Google Cloud Platform.
If you do have access, the Cloud Build dashboard shows the latest builds, and Cloud Run will link off to the relevant
logs.
There are Mozilla Slack notifications in #www-notify that show the status of demo builds. (Work is ticketed to make
those notifications richer in data.)
Env vars
Rather than tweak env vars via a web UI, they are set in config files. Both Mozorg and Pocket mode have specific
demo-use-only env var files, which are only used by our GCP demo setup. They are:
    • bedrock/gcp/bedrock-demos/cloudrun/mozorg-demo.env.yaml
    • bedrock/gcp/bedrock-demos/cloudrun/pocket-demo.env.yaml
If you need to set/add/remove an env var, you can edit the relevant file on your feature branch, commit it and push it
along with the rest of the code, as above. There is a small risk of clashes, but these can be best avoided if you keep up
to date with bedrock/main and can be resolved easily.
Secret values
Remember that the env vars files are public because they are in the Bedrock codebase, so sensitive values should not
be added there, even temporarily.
If you need to add a secret value, this currently needs to be added at the GCP level by someone with appropriate
permissions to edit and apply the Terraform configuration, and to edit the trigger YAML spec to pass through the new
secret. Currently Web-SRE and the backend team have appropriate GCP access and adding a secret is relatively quick.
(We can make this easier in the future if there’s sufficient need, of course.)
Note: Always-on vs auto-sleep demo servers
The demo servers are on GCP Cloud Run, and by default they will be turned off if there is no traffic for 15 minutes.
After this time, the demo app will be woken up if it receives a request.
Normally, a ‘cold start’ will not be a problem. However, if the branch you are demoing does things that alter the
database (i.e contains migrations), then you may find the restarted demo app crashes because the new migrations have
not been applied after a cold start.
The best current way to avoid that happening is:
    • In your branch’s demo-env-vars YAML file, set LOCAL_DB_UPDATE=True so that the Dev DB is not pulled down
       to the demo app
    • Ask one of the backend team to set the Demo app to always be awake by setting ‘Minimum instances’ to 1 for
       the relevant Cloud Run service and restarting it. The app will always be on and will not sleep, so won’t need
       a cold start. Once you have completed the feature work, please ask the backenders to restore the default sleepy
       behaviour. As an example with mozorg-demo-1:
           – To make it always-on: gcloud run services update mozorg-demo-1 --min-instances 1
           – To revert it to auto-sleeping: gcloud run services update mozorg-demo-1 --min-instances 0
48                                                                                              Chapter 1. Contents
                                                                            bedrock Documentation, Release 1.0
(We’ll try to make this a self-serve thing as soon as we can).
DEPRECATED: Heroku Demo Servers
Demos are now powered by Google Cloud Platform (GCP), and no longer by Heroku.
However, the Github Action we used to push code to Heroku may still be enabled. Pushing a branch to one of the
demo/* branches of the mozilla/bedrock repo will trigger this. However, note that URLs that historically used to point
to Heroku will be pointed to the new GCP demos services instead, so you will have to look at Heroku’s web UI to see
what the URL of the relevant Heroku app is.
To push to launch a demo on Heroku:
$ git push -f mozilla my-demo-branch:demo/1
Pushing to production
We’re doing pushes as soon as new work is ready to go out.
Code flows automatically to Dev, amd manually to Stage and to Production. See Continuous Integration & Deployment
for details.
After doing a push, those who are responsible for implementing changes need to update the bugs that have been pushed
with a quick message stating that the code was deployed.
If you’d like to see the commits that will be deployed before the push run the following command:
$ ./bin/open-compare.py
This will discover the currently deployed git hash, and open a compare URL at github to the latest main. Look at
open-compare.py -h for more options.
We automate pushing to production via tagged commits (see Continuous Integration & Deployment)
1.5 Continuous Integration & Deployment
Bedrock runs a series of automated tests as part of continuous integration workflow and deployment pipeline. You can
learn more about each of the individual test suites by reading their respective pieces of documentation:
     • Python unit tests (see Run the tests).
     • JavaScript unit tests (see Front-end testing).
     • Redirect tests (see Testing redirects).
     • Functional tests (see Front-end testing).
1.5. Continuous Integration & Deployment                                                                           49
bedrock Documentation, Release 1.0
1.5.1 Deployed site URLs
Note that a deployment of Bedrock will actually trigger two separate deployments: one serving all of mozilla.org
and another serving certain parts of getpocket.com
Dev
    • Mozorg URL: https://www-dev.allizom.org/
    • Pocket Marketing pages URL: https://dev.tekcopteg.com/
    • Bedrock locales: dev repo
    • Bedrock Git branch: main, deployed on git push
Staging
    • Mozorg URL: https://www.allizom.org/
    • Pocket Marketing pages URL: https://www.tekcopteg.com/
    • Bedrock locales: prod repo
    • Bedrock Git branch: stage, deployed on git push
Production
    • Mozorg URL: https://www.mozilla.org/
    • Pocket Marketing pages URL: https://getpocket.com/
    • Bedrock locales: prod repo
    • Bedrock Git branch: prod, deployed on git push with date-tag
You can check the currently deployed git commit by checking /revision.txt on any of these URLs.
1.5.2 Tests in the lifecycle of a change
Below is an overview of the tests during the lifecycle of a change to bedrock:
Local development
The change is developed locally, and page specific integration tests can be executed against a locally running in-
stance of the application. If testing changes to the website as a whole is required, then pushing changes to the special
run-integration-tests branch (see below) is much faster than running the full test suite locally.
50                                                                                             Chapter 1. Contents
                                                                           bedrock Documentation, Release 1.0
Pull request
Once a pull request is submitted, a Unit Tests Github Action will run both the Python and JavaScript unit tests, as well
as the suite of redirect headless HTTP(s) response checks.
Push to main branch
Whenever a change is pushed to the main branch, a new image is built and deployed to the dev environment, and the
full suite of headless and UI tests are run. This is handled by the pipeline, and is subject to change according to the
settings in the Github Action workflow defined in bedrock/.github/workflows/integration_tests.yml.
The tests for the dev environment are currently configured as follows:
     • Chrome (latest) via local Selenium grid.
     • Firefox (latest) via local Selenium grid.
     • Internet Explorer 11 (smoke tests) via Sauce Labs.
     • Internet Explorer 9 (sanity tests) via Sauce Labs.
     • Headless tests.
Note that now we have Mozorg mode and Pocket mode, we actually stand up two dev, two stage and two test deployments
and we run the appropriate integration tests against each deployment: most tests are written for Mozorg, but there are
some for Pocket mode that also get run.
Note: The deployment workflow runs like this
1. A push to the main/stage/prod/run-integration-tests branch of mozilla/bedrock triggers a webhook ping
to the (private) mozilla-sre-deploy/deploy-bedrock repo.
2. A Github Action (GHA) in mozilla-sre-deploy/deploy-bedrock builds a “release”-ready Bedrock container
image, which it stores in a private container registry (private because our infra requires container-image access to be
locked down). Using the same commit, the workflow also builds an equivalent set of public Bedrock container images,
which are pushed to Docker Hub.
    3. The GHA deploys the relevant container image to the appropriate environment.
4. The GHA pings a webhook back in mozilla/bedrock to run integration tests against the environment that has just
been deployed.
Push to stage branch
Whenever a change is pushed to the stage branch, a production docker image is built, published to Docker Hub, and
deployed to a public staging environment. Once the new image is deployed, the full suite of UI tests is run against it
again, but this time with the addition of the headless download tests.
1.5. Continuous Integration & Deployment                                                                             51
bedrock Documentation, Release 1.0
Push to prod branch (tagged)
When a tagged commit is pushed to the prod branch, a production container image (private, see above) is built, and
a set of public images is also built and pushed to Docker Hub if needed (usually this will have already happened as a
result of a push to the main or stage branch). The production image is deployed to each production deployment.
Push to prod cheat sheet
    1. Check out the main branch
    2. Make sure the main branch is up to date with mozilla/bedrock main
    3. Check that dev deployment is green:
              1. View the Integration Tests Github Action and look at the run labelled Run Integration tests for
                  main
    4. Check that stage deployment is also green (Run Integration tests for stage)
    5. Tag and push the deployment by running bin/tag-release.sh --push
Note: By default the tag-release.sh script will push to the origin git remote. If you’d like for it to push to a
different remote name you can either pass in a -r or --remote argument, or set the MOZ_GIT_REMOTE environment
variable. So the following are equivalent:
$ bin/tag-release.sh --push -r mozilla
$ MOZ_GIT_REMOTE=mozilla bin/tag-release.sh --push
And if you’d like to just tag and not push the tag anywhere, you may omit the --push parameter.
1.5.3 What Is Currently Deployed?
You can look at the git log of the main branch to find the last commit with a date-tag on it (e.g. 2022-05-05): this
commit will be the last one that was deployed to production. You can also use the whatsdeployed.io service to get a
nice view of what is actually currently deployed to Dev, Stage, and Prod:
1.5.4 Instance Configuration & Switches
We have a separate repo for configuring our primary instances (dev, stage, and prod). The docs for updating configu-
rations in that repo are on their own page, but there is a way to tell what version of the configuration is in use on any
particular instance of bedrock. You can go to the /healthz-cron/ URL on an instance (see prod for example) to see
the current commit of all of the external Git repos in use by the site and how long ago they were updated. The info
on that page also includes the latest version of the database in use, the git revision of the bedrock code, and how long
ago the database was updated. If you recently made a change to one of these repos and are curious if the changes have
made it to production, this is the URL you should check.
52                                                                                               Chapter 1. Contents
                                                                           bedrock Documentation, Release 1.0
1.5.5 Updating Selenium
There are several components for Selenium, which are independently versioned. The first is the Python client, and this
can be updated via the test dependencies. The other components are the Selenium versions used in both SauceLabs and
the local Selenium grid. These versions are selected automatically based on the required OS / Browser configuration,
so they should not need to be updated or specified independently.
1.5.6 Adding test runs
Test runs can be added by creating a new job in bedrock/.github/workflows/integration_tests.yml with the
desired variables and pushing that branch to Github. For example, if you wanted to run the smoke tests in IE10 (using
Saucelabs) you could add the following clause to the matrix:
- LABEL: test-ie10-saucelabs
   BROWSER_NAME: internet explorer
   BROWSER_VERSION: "10.0"
   DRIVER: SauceLabs
   PYTEST_PROCESSES: "8"
   PLATFORM: Windows 8
   MARK_EXPRESSION: smoke
You can use Sauce Labs platform configurator to help with the parameter values.
1.5.7 Pushing to the integration tests branch
If you have commit rights to our Github repo (mozilla/bedrock) you can simply push your branch to the branch named
run-integration-tests, and the app will be deployed and the full suite of integration tests for that branch will be
run. Please announce in our Slack channel (#www on mozilla.slack.com) that you’ll be doing this so that we don’t get
conflicts. Also remember that you’ll likely need to force push, as there may be commits on that branch which aren’t in
yours – so, if you have the mozilla/bedrock remote set as mozilla:
$ git push -f mozilla $(git branch --show-current):run-integration-tests
1.6 Front-end testing
Bedrock runs a suite of front-end Jasmine behavioral/unit tests, which use Karma as a test runner. We also have a suite
of functional tests using Selenium and pytest. This allows us to emulate users interacting with a real browser. All these
test suites live in the tests directory.
The tests directory comprises of:
     • /functional contains pytest tests.
     • /pages contains Python page objects.
     • /unit contains the Jasmine tests and Karma config file.
1.6. Front-end testing                                                                                                53
bedrock Documentation, Release 1.0
1.6.1 Installation
First follow the installation instructions for bedrock, which will install the dependencies required to run the various
front-end test suites.
1.6.2 Running Jasmine tests using Karma
To perform a single run of the Jasmine test suite using Firefox and Chrome, first make sure you have both browsers
installed locally, and then activate your bedrock virtual env.
$ pyenv activate bedrock
You can then run the tests with the following command:
$ npm run test
This will run all our front-end linters and formatting checks before running the Jasmine test suite. If you only want to
run the tests themselves, you can run:
$ npm run karma
See the Jasmine documentation for tips on how to write JS behavioral or unit tests. We also use Sinon for creating test
spies, stubs and mocks.
1.6.3 Running functional tests
Note: Before running the functional tests, please make sure to follow the bedrock installation docs, including the
database sync that is needed to pull in external data such as event/blog feeds etc. These are required for some of the
tests to pass. To run the tests using Firefox, you must also first download geckodriver and make it available in your
system path. You can alternatively specify the path to geckodriver using the command line (see the pytest-selenium
documentation for more information).
To download geckodriver and have it ready to run in your system, there are a couple of ways:
     • Download its latest release and add it to your system path:
             cd /path/to/your/downloaded/files/
             mv geckodriver /usr/local/bin/
     • If you’re on MacOS, download it directly using Homebrew, which automatically places it in your system path:
             brew install geckodriver
To run the full functional test suite against your local bedrock instance in Mozorg mode:
$ py.test --base-url http://localhost:8000 --driver Firefox --html tests/functional/
 ˓→results.html tests/functional/
This will run all test suites found in the tests/functional directory and assumes you have bedrock running at
localhost on port 8000. Results will be reported in tests/functional/results.html.
To run the full functional test suite against your local bedrock instance in Pocket mode, things are slightly different,
because of the way things are set up in order to allow CI to test both Mozorg Mode and Pocket Mode at the same time.
54                                                                                             Chapter 1. Contents
                                                                                  bedrock Documentation, Release 1.0
You need to define a temporary environment variable (needed by the pocket_base_url fixture) and scope pytest to only
run Pocket tests:
$ BASE_POCKET_URL=http://localhost:8000 py.test -m pocket_mode --driver Firefox --html␣
 ˓→tests/functional/results.html tests/functional/
This will run all test suites found in the tests/functional directory that have the pytest “mark” of pocket_mode and
assumes you have bedrock running in Pocket mode at localhost on port 8000. Results will be reported in tests/
functional/results.html.
Note: If you omit the --base-url command line option in Mozorg mode (ie, not in Pocket mode) then a local instance
of bedrock will be started, however the tests are not currently able to run against bedrock in this way.
By default, tests will run one at a time. This is the safest way to ensure predictable results, due to bug 1230105. If you
want to run tests in parallel (this should be safe when running against a deployed instance), you can add -n auto to
the command line. Replace auto with an integer if you want to set the maximum number of concurrent processes.
Note: There are some functional tests that do not require a browser. These can take a long time to run, especially if
they’re not running in parallel. To skip these tests, add -m 'not headless' to your command line.
To run a single test file you must tell py.test to execute a specific file e.g. tests/functional/test_newsletter.py:
$ py.test --base-url http://localhost:8000 --driver Firefox --html tests/functional/
 ˓→results.html tests/functional/firefox/new/test_download.py
To run a single test you can filter using the -k argument supplied with a keyword e.g.                                  -k
test_download_button_displayed:
$ py.test --base-url http://localhost:8000 --driver Firefox --html tests/functional/
 ˓→results.html tests/functional/firefox/new/test_download.py -k test_download_button_
 ˓→displayed
You can also easily run the tests against any bedrock environment by specifying the --base-url argument. For
example, to run all functional tests against dev:
$ py.test --base-url https://www-dev.allizom.org --driver Firefox --html tests/
 ˓→functional/results.html tests/functional/
Note: For the above commands to work, Firefox needs to be installed in a predictable location for your operating
system. For details on how to specify the location of Firefox, or running the tests against alternative browsers, refer to
the pytest-selenium documentation.
For more information on command line options, see the pytest documentation.
1.6. Front-end testing                                                                                                  55
bedrock Documentation, Release 1.0
Running tests in Sauce Labs
You can also run tests in Sauce Labs directly from the command line. This can be useful if you want to run tests against
Internet Explorer when you’re on Mac OSX, for instance.
    1. Sign up for an account at https://saucelabs.com/opensauce/.
    2. Log in and obtain your Remote Access Key from user settings.
    3. Run a test specifying SauceLabs as your driver, and pass your credentials.
For example, to run the home page tests using Internet Explorer via Sauce Labs:
$ SAUCELABS_USERNAME=thedude SAUCELABS_API_KEY=123456789 SAUCELABS_W3C=true SELENIUM_
 ˓→EXCLUDE_DEBUG=logs py.test --base-url https://www-dev.allizom.org --driver SauceLabs --
 ˓→capability browserName 'internet explorer' --capability platformName 'Windows 10' --
 ˓→html tests/functional/results.html tests/functional/test_home.py
1.6.4 Writing Selenium tests
Tests usually consist of interactions and assertions. Selenium provides an API for opening pages, locating elements,
interacting with elements, and obtaining state of pages and elements. To improve readability and maintainability of the
tests, we use the Page Object model, which means each page we test has an object that represents the actions and states
that are needed for testing.
Well written page objects should allow your test to contain simple interactions and assertions as shown in the following
example:
def test_sign_up_for_newsletter(base_url, selenium):
      page = NewsletterPage(base_url, selenium).open()
      page.type_email('noreply@mozilla.com')
      page.accept_privacy_policy()
      page.click_sign_me_up()
      assert page.sign_up_successful
It’s important to keep assertions in your tests and not your page objects, and to limit the amount of logic in your page
objects. This will ensure your tests all start with a known state, and any deviations from this expected state will be
highlighted as potential regressions. Ideally, when tests break due to a change in bedrock, only the page objects will
need updating. This can often be due to an element needing to be located in a different way.
Please take some time to read over the Selenium documentation for details on the Python client API.
Destructive tests
By default all tests are assumed to be destructive, which means they will be skipped if they’re run against a sensitive
environment. This prevents accidentally running tests that create, modify, or delete data on the application under test.
If your test is nondestructive you will need to apply the nondestructive marker to it. A simple example is shown
below, however you can also read the pytest markers documentation for more options.
import pytest
@pytest.mark.nondestructive
def test_newsletter_default_values(base_url, selenium):
      page = NewsletterPage(base_url, selenium).open()
      assert '' == page.email
                                                                                                     (continues on next page)
56                                                                                             Chapter 1. Contents
                                                                           bedrock Documentation, Release 1.0
                                                                                               (continued from previous page)
      assert   'United States' == page.country
      assert   'English' == page.language
      assert   page.html_format_selected
      assert   not page.text_format_selected
      assert   not page.privacy_policy_accepted
Smoke tests
Smoke tests are considered to be our most critical tests that must pass in a wide range of web browsers, including
Internet Explorer 11. The number of smoke tests we run should be enough to cover our most critical pages where
legacy browser support is important.
import pytest
@pytest.mark.smoke
@pytest.mark.nondestructive
def test_download_button_displayed(base_url, selenium):
      page = DownloadPage(selenium, base_url, params='').open()
      assert page.is_download_button_displayed
You can run smoke tests only by adding -m smoke when running the test suite on the command line.
Waits and Expected Conditions
Often an interaction with a page will cause a visible response. While Selenium does its best to wait for any page loads
to be complete, it’s never going to be as good as you at knowing when to allow the test to continue. For this reason, you
will need to write explicit waits in your page objects. These repeatedly execute code (a condition) until the condition
returns true. The following example is probably the most commonly used, and will wait until an element is considered
displayed:
from selenium.webdriver.support import expected_conditions as expected
from selenium.webdriver.support.ui import WebDriverWait as Wait
Wait(selenium, timeout=10).until(
      expected.visibility_of_element_located(By.ID, 'my_element'))
For convenience, the Selenium project offers some basic expected conditions, which can be used for the most common
cases.
1.6.5 Debugging Selenium
Debug information is collected on failure and added to the HTML report referenced by the --html argument. You can
enable debug information for all tests by setting the SELENIUM_CAPTURE_DEBUG environment variable to always.
1.6. Front-end testing                                                                                                    57
bedrock Documentation, Release 1.0
1.6.6 Guidelines for writing functional tests
    • Try and keep tests organized and cleanly separated. Each page should have its own page object and test file, and
      each test should be responsible for a specific purpose, or component of a page.
    • Avoid using sleeps - always use waits as mentioned above.
    • Don’t make tests overly specific. If a test keeps failing because of generic changes to a page such as an image
      filename or href being updated, then the test is probably too specific.
    • Avoid string checking as tests may break if strings are updated, or could change depending on the page locale.
    • When writing tests, try and run them against a staging or demo environment in addition to local testing. It’s also
      worth running tests a few times to identify any intermittent failures that may need additional waits.
See also the Web QA style guide for Python based testing.
1.6.7 Testing Basket email forms
When writing functional tests for front-end email newsletter forms that submit to Basket, we have some special case
email addresses that can be used just for testing:
   1. Any newsletter subscription request using the email address “success@example.com” will always return success
      from the basket client.
   2. Any newsletter subscription request using the email address “failure@example.com” will always raise an excep-
      tion from the basket client.
Using the above email addresses enables newsletter form testing without actually hitting the Basket instance, which
reduces automated newsletter spam and improves test reliability due to any potential network flakiness.
1.6.8 Headless tests
There are targeted headless tests for the download pages. These tests and are run as part of the pipeline to ensure that
download links constructed via product details are well formed and return valid 200 responses.
1.7 Managing Redirects
We have a redirects app in bedrock that makes it easier to add and manage redirects. Due to the size, scope, and history
of mozilla.org we have quite a lot of redirects. If you need to add or manage redirects read on.
1.7.1 Add a redirect
You should add redirects in the app that makes the most sense. For example, if the source URL is /firefox/... then
the bedrock.firefox app is the best place. Redirects are added to a redirects.py file within the app. If the app
you want to add redirects to doesn’t have such a file, you can create one and it will automatically be discovered and
used by bedrock as long as said app is in the INSTALLED_APPS setting (see bedrock/mozorg/redirects.py as an
example).
Once you decide where it should go you can add your redirect. To do this you simply add a call to the bedrock.
redirects.util.redirect helper function in a list named redirectpatterns in redirects.py. For example:
58                                                                                             Chapter 1. Contents
                                                                          bedrock Documentation, Release 1.0
from bedrock.redirects.util import redirect
redirectpatterns = [
     redirect(r'^rubble/barny/$', '/flintstone/fred/'),
]
This will make sure that requests to /rubble/barny/ (or with the locale like /pt-BR/rubble/barny/) will get a
301 response sending users to /flintstone/fred/.
The redirect() function has several options. Its signature is as follows:
def redirect(pattern, to, permanent=True, locale_prefix=True, anchor=None, name=None,
                query=None, vary=None, cache_timeout=12, decorators=None):
     """
     Return a url matcher suited for urlpatterns.
     pattern: the regex against which to match the requested URL.
     to: either a url name that `reverse` will find, a url that will simply be returned,
          or a function that will be given the request and url captures, and return the
          destination.
     permanent: boolean whether to send a 301 or 302 response.
     locale_prefix: automatically prepend `pattern` with a regex for an optional locale
          in the URL. This locale (or None) will show up in captured kwargs as 'locale'.
     anchor: if set it will be appended to the destination URL after a '#'.
     name: if used in a `urls.py` the redirect URL will be available as the name
          for use in calls to `reverse()`. Does _NOT_ work if used in a `redirects.py` file.
     query: a dict of query params to add to the destination URL.
     vary: if you used an HTTP header to decide where to send users you should include␣
 ˓→that
          header's name in the `vary` arg.
     cache_timeout: number of hours to cache this redirect. just sets the proper `cache-
 ˓→control`
          and `expires` headers.
     decorators: a callable (or list of callables) that will wrap the view used to␣
 ˓→redirect
          the user. equivalent to adding a decorator to any other view.
     Usage:
     urlpatterns = [
          redirect(r'projects/$', 'mozorg.product'),
          redirect(r'^projects/seamonkey$', 'mozorg.product', locale_prefix=False),
          redirect(r'apps/$', 'https://marketplace.firefox.com'),
          redirect(r'firefox/$', 'firefox.new', name='firefox'),
          redirect(r'the/dude$', 'abides', query={'aggression': 'not_stand'}),
     ]
     """
1.7. Managing Redirects                                                                                   59
bedrock Documentation, Release 1.0
1.7.2 Differences
This all differs from urlpatterns in urls.py files in some important ways. The first is that these happen first. If
something matches in a redirects.py file it will always win the race if another URL in a urls.py file would also have
matched. Another is that these are matched before any locale prefix stuff happens. So what you’re matching against in
the redirects files is the original URL that the user requested. By default (unless you set locale_prefix=False) your
patterns will match either the plain URL (e.g. /firefox/os/) or one with a locale prefix (e.g. /fr/firefox/os/). If
you wish to include this locale in the destination URL you can simply use python’s string format() function syntax. It is
passed to the format method as the keyword argument locale (e.g. redirect('^stuff/$', '{locale}whatnot/
')). If there was no locale in the URL the {locale} substitution will be an empty string. Similarly if you wish
to include a part of the original URL in the destination, just capture it with the regex using a named capture (e.g.
r'^stuff/(?P<rest>.*)$' will let you do '/whatnot/{rest}').
1.7.3 Utilities
There are a couple of utility functions for use in the to argument of redirect that will return a function to allow you
to match something in an HTTP header.
ua_redirector
bedrock.redirects.util.ua_redirector is a function to be used in the to argument that will use a regex to
match against the User-Agent HTTP header to allow you to decide where to send the user. For example:
from bedrock.redirects.util import redirect, ua_redirector
redirectpatterns = [
      redirect(r'^rubble/barny/$',
                   ua_redirector('firefox(os)?', '/firefox/', '/not-firefox/'),
                   cache_timeout=0),
]
You simply pass it a regex to match, the destination URL (substitutions from the original URL do work) if the regex
matches, and another destination URL if the regex does not match. The match is not case sensitive unless you add the
optional case_sensitive=True argument.
Note: Be sure to include the cache_timeout=0 so that you won’t be bitten by any caching proxies sending all users
one way or the other. Do not set the Vary: User-Agent header; this will not work in production.
header_redirector
This is basically the same as ua_redirector but works against any header. The arguments are the same as above
except that thre is an additional first argument for the name of the header:
from bedrock.redirects.util import redirect, header_redirector
redirectpatterns = [
      redirect(r'^rubble/barny/$',
                   header_redirector('cookie', 'been-here', '/firefox/', '/firefox/new/'),
                                                                                                     (continues on next page)
60                                                                                               Chapter 1. Contents
                                                                                  bedrock Documentation, Release 1.0
                                                                                                    (continued from previous page)
                    vary='cookie'),
]
1.7.4 Testing redirects
A suite of tests exists for redirects, which is intended as a reference of the redirects we expect to work on
www.mozilla.org. This will become a base for implementing these redirects in the bedrock app and allow us to test
them before release.
Installation
First follow the installation instructions for bedrock, which will guide you through installing pip and setting up a virtual
environment for the tests. The additional requirements can then be installed by using the following commands:
$ source venv/bin/activate
$ pip install -r requirements/dev.txt
Running the tests
If you wish to run the full set of tests, which requires a deployed instance of the site (e.g. www.mozilla.org) you can
set the --base-url command line option:
$ py.test --base-url https://www.mozilla.org tests/redirects/
By default, tests will run one at a time. If you intend to run the suite against a remote instance of the site (e.g. production)
it will run a lot quicker by running the tests in parallel. To do this, you can add -n auto to the command line. Replace
auto with an integer if you want to set the maximum number of concurrent processes.
1.8 Newsletters
Bedrock includes support for signing up for and managing subscriptions and preferences for Mozilla newsletters.
Many pages have a form to sign-up for the default newsletters, “Mozilla Foundation” and “Firefox & You”. Other pages
have more specific sign up forms, such as the contribute page, or Mozilla VPN wait-list page.
1.8.1 Features
     • Ability to subscribe to a newsletter from a web form. Many pages on the site might include this form.
     • Whole pages devoted to subscribing to one newsletter, often with custom text, branding, and layout.
     • Newsletter preference center - allow user to change their email preferences (e.g. language, HTML vs. text), as
        well as which newsletters they’re subscribed to, etc. Access is limited by requiring a user-specific token in the
        URL (it’s a UUID). The full URL is included as a link in each newsletter sent to the user. Users can also recover
        a link to their token by visiting the newsletter recovery page and entering their email address.
1.8. Newsletters                                                                                                               61
bedrock Documentation, Release 1.0
1.8.2 Newsletters
Newsletters have a variety of characteristics. Some of these are implemented in Bedrock, others are transparent to
Bedrock but implemented in the basket back-end that provides our interface to the newsletter vendor.
     • Public name - the name that is displayed to users, e.g. “Firefox Weekly Tips”.
     • Internal name - a short string that is used internal to Bedrock and basket to identify a newsletter. Typically these
       are lowercase strings of words joined by hyphens, e.g. “firefox-tips”. This is what we send to basket to identify
       a newsletter, e.g. to subscribe a user to it.
     • Show publicly - pages like the newsletter preferences center show a list of unsubscribed newsletters and allow
       subscribing to them. Some newsletters aren’t included in that list by default (though they are shown if the user is
       already subscribed, to let them unsubscribe). If the user has a Firefox Account, there are also some other related
       newsletters that will always be shown in the list.
     • Languages - newsletters are available in a particular set of languages. Typically when subscribing to a newsletter,
       a user can choose their preferred language. We should try not to let them subscribe to a newsletter in a language
       that it doesn’t support.
       The backend only stores one language for the user though, so whenever the user submits one of our forms,
       whatever language they last submitted is what is saved for their preference for everything.
     • Welcome message - each newsletter can have a canned welcome message that is sent to a user when they subscribe
       to it. Newsletters should have both an HTML and a text version of this.
     • Drip campaigns - some newsletters implement so-called drip campaigns, in which a series of canned messages
       are dribbled out to the user over a period of time. E.g. 1 week after subscribing, they might get message 1; a
       week later, message 2, and so on until all the canned messages have been sent.
       Because drip campaigns depend on the sign-up date of the user, we’re careful not to accidentally change the
       sign-up date, which could happen if we sent redundant subscription commands to our backend.
1.8.3 Bedrock and Basket
Bedrock is the user-facing web application. It presents an interface for users to subscribe and manage their subscriptions
and preferences. It does not store any information. It gets all newsletter and user-related information, and makes up-
dates, via web requests to the Basket server. These requests are made typically made by Bedrock’s front-end JavaScript
modules.
The Basket server implements an HTTP API for the newsletters. The front-end (Bedrock) can make calls to it to retrieve
or change users’ preferences and subscriptions, and information about the available newsletters. Basket implements
some of that itself, and other functions by calling the newsletter vendor’s API. Details of that are outside the scope of
this document, but it’s worth mentioning that both the user token (UUID) and the newsletter internal name mentioned
above are used only between Bedrock and Basket.
See the Basket docs for more information.
62                                                                                                Chapter 1. Contents
                                                                              bedrock Documentation, Release 1.0
1.8.4 URLs
Here are a few important mozorg newsletter URLs. Some of these were established before Bedrock came along, and
so are unlikely to be changed.
    • /newsletter/ - Subscribe to ‘mozilla-and-you’ newsletter (public name: “Firefox & You”)
    • /newsletter/existing/{USERTOKEN}/ - User management of their preferences and subscriptions.
    • /newsletter/confirm/{USERTOKEN}/ - URL someone lands on when they confirm their email address after
       initially subscribing.
    • /newsletter/country/{USERTOKEN}/ - Allows users to change their country.
    • /newsletter/recovery/ - Allows users to recover a link containing their token so they can manage their
       subscriptions.
    • /newsletter/updated/ - A page users are redirected to after updating their details, or unsubscribing.
Note: URLs that contain {USERTOKEN} will have their path rewritten on page load so that they no longer contain the
token e.g. /newsletter/existing/{USERTOKEN}/ will be rewritten to just /newsletter/existing/. This helps
to prevent accidental sharing of user tokens in URLS and also against referral information leakage.
1.8.5 Footer sign-up
In some common templates, you can customize the footer sign-up form by overriding the email_form template block.
For example, to have no sign-up form:
{% block email_form %}{% endblock %}
The default is:
{% block email_form %}{{ email_newsletter_form() }}{% endblock %}
This will render a sign-up for “Firefox & You”. You can pass parameters to the macro email_newsletter_form
to change that. For example, the newsletters parameter controls which newsletter is signed up for, and title can
override the text:
{% block email_form %}
      {{ email_newsletter_form('app-dev',
                                       'Sign up for more news about the Firefox Marketplace.') }}
{% endblock %}
The newsletters parameter, the first positional argument, can be either a list of newsletter IDs or a comma separated list
of newsletters IDs:
{% block email_form %}
      {{ email_newsletter_form('mozilla-foundation, mozilla-and-you') }}
{% endblock %}
Pages can control whether country or language fields are included by passing include_language=[True|False]
and/or include_country=[True|False].
1.8. Newsletters                                                                                                      63
bedrock Documentation, Release 1.0
1.9 Contentful CMS (Content Management System) Integration
1.9.1 Overview
Contentful is a headless CMS. It stores content for our website in a structured format. We request the content from
Contentful using an API. Then the content gets made into Protocol components for display on the site.
We define the structure Contentful uses to store the data in content models. The content models are used to create a
form for editors to fill out when they want to enter new content. Each chunk of content is called an entry.
For example: we have a content model for our “card” component. That model creates a form with fields like heading,
link, blurb, and image. Each card that is created from the model is its own entry.
We have created a few different types of content models. Most are components that correspond to components in our
design system. The smallest create little bits of code like buttons. The larger ones group together several entries for the
smaller components into a bigger component or an entire page.
For example: The Page: General model allows editors to include a hero entry, body entry, and callout entry. The
callout layout entry, in turn, includes a CTA (Call To Action) entry.
One advantage of storing the content in small chunks like this is that is can be reused in many different pages. A callout
which focuses on the privacy related reasons to download Firefox could end up on the Private Browsing, Ad Tracker
Blocking, and Fingerprinter Blocking pages. If our privacy focused tagline changes from “Keep it secret with Firefox”
to “Keep it private with Firefox” it only needs to be updated in one entry.
So, when looking at a page on the website that comes from Contentful you are typically looking at several different
entries combined together.
On the bedrock side, the data for all entries is periodically requested from the API and stored in a database.
When a Contentful page is requested the code in api.py transforms the information from the database into a group of
Python dictionaries (these are like key/value pairs or an object in JS).
This data is then passed to the page template (either Mozilla or for Firefox themed as appropriate). The page template
includes some files which take the data and feed it into macros to create Protocol components. These are the same
macros we use on non-Contentful pages. There are also includes which will import the appropriate JS and CSS files
to support the components.
Once rendered the pages get cached on the CDN as usual.
1.9.2 Contentful Apps
Installed on Environment level. Make sure you are in the environment you want to edit before accessing an app. Use
Apps link in top navigation of Contentful Web App to find an environment’s installed apps.
Compose
Compose provides a nicer editing experience. It creates a streamlined view of pages by combining multiple entries into
a single edit screen and allowing field groups for better organization.
Any changes made to Compose page entries in a specific environment are limited to that environment. If you are in a
sandbox environment, you should see an /environments/sandbox-name path at the end of your Compose URL.
64                                                                                              Chapter 1. Contents
                                                                             bedrock Documentation, Release 1.0
Known Limitations
    • Comments are not available on Compose entries
    • It is not possible to edit embedded entries in Rich Text fields in Compose app. Selecting the “edit” option in the
      dropdown opens the entry in the Contentful web app.
Merge
Merge provides a UI for comparing the state of Content Models across two environments. You can select what changes
you would like to migrate to a new environment.
Known Limitations
    • Does not migrate Help Text (under Appearance Tab)
    • Does not migrate any apps used with those Content Models
    • Does not migrate Content Entries or Assets
    • It can identify when Content Models should be available in Compose, but it cannot migrate the field groups
Others
    • Launch allows creation of “releases”, which can help coordinate publishing of multiple entries
    • Workflows standardizes process for a specific Content Model. You can specify steps and permissions to regulate
      how content moves from draft to published.
1.9.3 Content Models
Emoji legend for content models
    • this component is a page, it will include meta data for the page, a folder, and slug
    • this is a layout wrapper for another component
    • this component includes editable content, not just layout config
    • this component is suitable for inclusion as an inline entry in a rich text field
    • this component can be embedded without a layout wrapper
Naming conventions for content models
Note: For some fields it is important to be consistent because of how they are processed in bedrock. For all it is
important to make the editor’s jobs easier.
Name
      This is for the internal name of the entry. It should be set as the Entry title, required, and unique.
1.9. Contentful CMS (Content Management System) Integration                                                          65
bedrock Documentation, Release 1.0
Preview (and Preview Title, Preview Blurb, Preview Image)
      These will be used in search results and social media sites. There’s also the potential to use them for aggregate
      pages on our own sites. Copy configuration and validation from an existing page.
Heading (and Heading Level)
      Text on a page which provides context for information that follows it. Usually made into a H1-H4 in bedrock.
      Not: header, title, or name.
Image (and Image Size, Image Width)
      Not: picture, photo, logo, or icon (unless we are specifically talking about a logo or icon.)
Content
      Multi-reference
Product Icon
      Copy configuration and validation from an existing page.
Theme
      Copy configuration and validation from an existing page.
Body (Body Width, Body Vertical Alignment, Body Horizontal Alignment)
      Rich text field in a Component. Do not use this for multi reference fields, even if the only content on the page is
      other content entries. Do not use MarkDown for body fields, we can’t restrict the markup. Copy configuration
      and validation from an existing page.
Rich Text Content
      Rich text field in a Compose Page
CTA
      The button/link/dropdown that we want a user to interact with following some content. Most often appearing in
      Split and Callout components.
Page
Pages in bedrock are created from page entries in Contentful’s Compose App.
Homepage
      The homepage needs to be connected to bedrock using a Connect component (see Legacy) and page meta data
      like title, blurb, image, etc come from bedrock.
General
      Includes hero, text, and callout. The simplified list and order of components is intended to make it easier for
      editors to put a page together.
Versatile
      No pre-defined template. These pages can be constructed from any combination of layout and component entries.
Resource Center
      Includes product, category, tags, and a rich text editor. These pages follow a recognizable format that will help
      orient users looking for more general product information (i.e. VPN).
The versatile and general templates do not need bedrock configuration to be displayed. Instead, they should appear
automatically at the folder and slug specified in the entry. These templates do include fields for meta data.
66                                                                                              Chapter 1. Contents
                                                                              bedrock Documentation, Release 1.0
Layout
These entries bring a group of components together. For example: 3 picto blocks in a picto block layout. They also
include layout and theme options which are applied to all of the components they bring together. For example: centering
the icons in all 3 picto blocks.
These correspond roughly to Protocol templates.
The one exception to the above is the Layout: Large Card, which exists to attach a large display image to a regular card
entry. The large card must still be included in the Layout: 5 Cards.
Component
We’re using this term pretty loosely. It corresponds roughly to a Protocol atom, molecule, or organism.
These entries include the actual content, the bits that people write and the images that go with it.
If they do not require a layout wrapper there may also be some layout and theme options. For example, the text
components include options for width and alignment.
Embed
These pre-configured content pieces can go in rich text editors when allowed (picto, split, multi column text. . . ).
Embeds are things like logos, where we want tightly coupled style and content that will be consistent across entries. If
a logo design changes, we only need to update it in one place, and all uses of that embed will be updated.
Adding a new Page
     • Create the content model
          – Ensure the content model name starts with page (i.e. pageProductJournalismStory)
          – Add an SEO reference field which requires the SEO Metadata content type
          – In Compose, go to Page Types and click “Manage Page Types” to make your new content model available
            to the Compose editor.
               ∗ If you have referenced components, you can choose whether they will be displayed as expanded by
                  default.
               ∗ Select “SEO” field for “Page Settings” field
          – If the page is meant to be localised, ensure all fields that need localisation have the “Enable localization of
            this field” checkbox checked in content model field settings
     • Update bedrock/contentful/constants
          – Add content type constant
          – Add constant to default array
          – If page is for a single locale only, add to SINGLE_LOCALE_CONTENT_TYPES
          – If page is localised, add to LOCALISATION_COMPLETENESS_CHECK_CONFIG with an array of lo-
            calised fields that need to be checked before the page’s translation can be considered complete
     • Update bedrock/contentful/api.py
          – If you’re adding new embeddable content types, expand list of renderer helpers configured for the RichTex-
            tRenderer in the ContentfulAPIWrapper
1.9. Contentful CMS (Content Management System) Integration                                                             67
bedrock Documentation, Release 1.0
         – Update ContentfulAPIWrapper.get_content() to have a clause to handle the new page type
    • Create a custom view to pass the Contentful data to a template
Adding a new Component
Example: Picto
   1. Create the content model in Contentful.
          • Follow the naming conventions.
          • You may need two models if you are configuring layout separately.
   2. Add the new content model to the list of allowed references in other content models (At the moment this is just
      the “content” reference field on pages).
   3. In bedrock create CSS and JS entries in static-bundles for the new component.
   4. In api.py write a def for the component.
   5. In api.py add the component name, def, and bundles to the CONTENT_TYPE_MAP.
   6. Find or add the macro to macros-protocol.
   7. Import the macro into all.html and add a call to it in the entries loop.
Note: Tips:
    • can’t define defaults in Contentful, so set those in your Python def.
    • for any optional fields make sure you check the field exists before referencing the content.
Adding a new Embed
Example: Wordmark.
   1. Create the content model in Contentful.
          • Follow the naming conventions.
   2. Add the new content model to rich text fields (like split and text).
   3. In bedrock include the CSS in the Sass file for any component which may use it (yeah, this is not ideal, hopefully
      we will have better control in the future).
   4. Add a def to api.py to render the piece (like _make_wordmark).
Note: Tips:
    • can’t define defaults in Contentful, so set those in your Python def.
    • for any optional fields make sure you check the field exists before referencing the content.
68                                                                                             Chapter 1. Contents
                                                                               bedrock Documentation, Release 1.0
Adding a rich text field in a component
Disable everything then enable: B, I, UL, OL, Link to URL, and Inline entry. You will want to enable some some
Headings as well, H1 should be enabled very rarely. Enable H2-H4 using your best judgement.
1.9.4 Adding support for a new product icon, size, folder
Many content models have drop downs with identical content. For example: the Hero, Callout, and Wordmark models
all include a “product icon”. Other common fields are width and folder.
There are two ways to keep these lists up to date to reflect Protocol updates:
    1. By opening and editing the content models individually in Contentful
    2. Scripting updates using the API
At the moment it’s not too time consuming to do by hand, just make sure you are copy and pasting to avoid introducing
spelling errors.
We have not tried scripting updates with the API yet. One thing to keep in mind if attempting this is that not all widths
are available on all components. For example: the “Text: Four columns” component cannot be displayed in small
content widths.
1.9.5 Rich Text Rendering
Contentful provides a helper library to transform the rich text fields in the API into HTML content.
In places were we disagree with the rendering or want to enhance the rendering we can provide our own renderers on the
bedrock side. They can be as simple as changing <b> tags to <strong> tags or as complex as inserting a component.
A list of our custom renderers is passed to the RichTextRenderer helper at the start of the ContentfulPage class in api.py.
The renderers themselves are also defined in api.py
Note:
     • Built-in nodes cannot be extended or customized: Custom node types and marks are not allowed. Embed entry
       types are required to extend rich text functionality. (i.e. if you need more than one style of blockquote)
1.9.6 L10N
Smartling - our selected approach
When setting up a content model in Contentful, fields can be designated as available for translation.
Individual users can be associated with different languages, so when they edit entries they see duplicate fields for each
language they can translate into. In addition - and in the most common case - these fields are automatically sent to
Smartling to be translated there.
Once text for translation lands in Smartling, it is batched up into jobs for human translation. When the work is complete,
Smartling automatically updates the relevant Contentful entries with the translations, in the appropriate fields.
Note that those translations are only visible in Contentful if you select to view that locale’s fields, but if they are present
in Contentful’s datastore (and that locale is enabled in the API response) they will be synced down by Bedrock.
On the Bedrock side, the translated content is pulled down the same way as the default locale’s content is, and is stored
in a locale-specific ContentfulEntry in the database.
1.9. Contentful CMS (Content Management System) Integration                                                                 69
bedrock Documentation, Release 1.0
In terms of ‘activation’, or “Do we have all the parts to show this Contentful content”?, Contentful content is not
evaluated in the same way as Fluent strings (where we will show a page in a given locale if 80% of its Fluent strings
have been translated, falling back to en-US where not).
Instead, we check that all of the required fields present in the translated Entry have non-null data, and if so, then the
entire page is viable to show in the given locale. (ie, we look at fields, not strings. It’s a coarser level of granularity
compared to Fluent, because the data is organised differently - most of Contentful-sourced content will be rich text, not
individual strings).
The check about whether or not a Contentful entry is ‘active’ or ‘localisation complete’ happens during the main sync
from Contentful. Note that there is no fallback locale for Contentful content other than a redirect to the en-US version
of the page - either the page is definitely available in a locale, or it’s not at all available in that locale.
Notes:
     • The batching of jobs in Smartling is still manual, even though the data flow is automated. We need to keep an
       eye on how onerous this is, plus what the cost exposure could be like if we fully automate it.
     • The Smartling integration is currently only set to use Mozilla.org’s 10 most popular locales, in addition to en-US.
     • No localisation of Contentful content happens via Pontoon.
     • The Smartling setup is most effectively leveraged with Compose-based pages rather than Connect-based com-
       ponents, and the latter may require some code tweaks.
     • Our Compose: SEO field in Contentful is configured for translation (and in use on the VPN Resource Center).
       All Compose pages require this field. If a Compose page type is not meant to be localised, we need to stop these
       SEO-related fields from going on to Smartling.
Fluent
NB: Not selected for use, but notes retained for reference
Instead of using the language translation fields in Contentful to store translations we could designate one of the locales
to contain a fluent string ID. Bedrock could then use the string IDs and the English content to create Fluent files for
submission into our current translation system.
Creation of the string IDs could be automated using Contentful’s write API.
To give us the ability to use fallback strings the Contentful field could accept a comma separated list of values.
This approach requires significant integration code on the bedrock side but comes with the benefit of using our current
translation system, including community contributions.
No English Equivalent
NB: Not selected for use, but notes retained for reference
Components could be created in the language they are intended to display in. The localized content would be written
in the English content fields.
The down sides of this are that we do not know what language the components are written in and could accidentally
display the wrong language on any page. It also means that localized content cannot be created automatically by English
editors and translations would have to be manually associated with URLs.
This is the approach that will likely be used for the German and French homepages since that content is not going to
be used on English pages and creating a separate homepage with different components is valuable to the German and
French teams.
70                                                                                                    Chapter 1. Contents
                                                                            bedrock Documentation, Release 1.0
1.9.7 Assets
Images that are uploaded in Contentful will be served to site visitors from the Contentful CDN. The cost of using the
CDN are not by request so we don’t have to worry about how many times an image will be requested.
Using the Contentful CDN lets us use their Images API to format our images.
In theory, a large high quality image is uploaded in Contentful and then bedrock inserts links to the CDN for images
which are cropped to fit their component and resized to fit their place on the page.
Because we cannot rely on the dimensions of the image uploaded to Contentful as a guide for displaying the image -
bedrock needs to be opinionated about what size images it requests based on the component and its configuration. For
example, hero images are fixed at 800px wide. In the future this could be a user configurable option.
1.9.8 Preview
Content previews are configured under Settings > Content preview on a per-content model basis. At the moment
previews are only configured for pages, and display on demo5.
Once the code is merged into bedrock they should be updated to use the dev server.
Specific URLs will only update every 5 minutes as the data is pulled from the API but pages can be previewed up to
the second at the contentful-preview URL. This preview will include “changed” and “draft” changes (even if there is
an error in the data) not just published changes.
For previewing on localhost, see Development Practices, below.
1.9.9 Roles/Permissions
In general we are trusting people to check their work before publishing and very few guard rails have been installed.
We have a few roles with different permissions.
Admin
      Organization
          • Define roles and permission
          • Manage users
          • Change master and sandbox environment aliases
          • Create new environments
      Master environment
          • Edit content model
          • Create, Edit, Publish, Archive, Delete content
          • Install/Uninstall apps
Developer
      Organization
          • Create new environments
      Master environment
          • Create, Edit, Publish, Archive content
      Sandbox environments (any non-master environment)
          • Edit content model
1.9. Contentful CMS (Content Management System) Integration                                                       71
bedrock Documentation, Release 1.0
          • Create, Edit, Publish, Archive, Delete content
          • Install/Uninstall apps
Editor (WIP)
       Master environment (through Compose)
          • Create, Edit, Publish, Archive content
1.9.10 Development practices
This section outlines tasks generally required if developing features against Contentful.
Get bedrock set up locally to work with Contentful
In your .env file for Bedrock, make sure you have the followign environment variables set up.
     • CONTENTFUL_SPACE_ID - this is the ID of our Contentful integration
     • CONTENTFUL_SPACE_KEY - this is the API key that allows you access to our space. Note that two types of key
       are available: a Preview key allows you to load in draft content; the Delivery key only loads published contnet.
       For local dev, you want a Preview key.
     • SWITCH_CONTENTFUL_HOMEPAGE_DE should be set to True if you are working on the German Contentful-
       powered homepage
     • CONTENTFUL_ENVIRONMENT Contentful has ‘branches’ which it calls environments. master is what we use in
       production, and sandbox is generally what we use in development. It’s also possible to reference a specific
       environment - e.g. CONTENTFUL_ENVIRONMENT=sandbox-2021-11-02
To get values for these vars, please check with someone on the backend team.
If you are working on the Contentful Sync backed by the message-queue (and if you don’t know what this is, you don’t
need it for local dev), you will also need to set the following env vars:
     • CONTENTFUL_NOTIFICATION_QUEUE_URL
     • CONTENTFUL_NOTIFICATION_QUEUE_REGION
     • CONTENTFUL_NOTIFICATION_QUEUE_ACCESS_KEY_ID
     • CONTENTFUL_NOTIFICATION_QUEUE_SECRET_ACCESS_KEY
How to preview your changes on localhost
When viewing a page in Contentful, it’s possible to trigger a preview of the draft page. This is typically rendered on
www-dev.allizom.org. However, that’s only useful for code that’s already in main. If you want to preview Contentful
content on your local machine - e.g. you’re working on a feature branch that isn’t ready for merging - do the following:
72                                                                                             Chapter 1. Contents
                                                                             bedrock Documentation, Release 1.0
Existing (master) Content Types
In the right-hand sidebar of the editor page in Contentful:
     • Find the Preview section
     • Select Change and pick Localhost Preview
     • Click Open preview
New (non-master) Content Types
In bedrock:
     • Update class ContentfulPreviewView(L10nTemplateView) in Mozorg Views with a render case for your
       new content type
In the right-hand sidebar of the editor page in Contentful:
     • Click Info tab
     • Find Entry ID section and copy the value
Manually create preview URL in browser:
     • http://localhost:8000/en-US/contentful-preview/{entry_id}/
Note that previewing a page will require it to be pulled from Contentful’s API, so you will need CONTENTFUL_SPACE_ID
and CONTENTFUL_SPACE_KEY set in your .env. It may take a few seconds to get the data.
Also note that when you select Localhost preview, the choice sticks, so you should set it back to Preview on web
when you’re done.
How to update/refresh the sandbox environment
It helps to think of Contentful ‘environments’ as simply branches of a git-like repo full of content. You can take a par-
ticular environment and branch off it to make a new environment for WIP (Work in Progress) or experimental content,
using the original one as your starting point. On top of this, Contentful has the concept of aliases for environments and
we use two aliases in our setup:
     • master is used for production and is an alias currently pointing to the V1 environment. It is pretty stable and
       access to it is limited.
     • sandbox is used for development and more team members have access to edit content. Again, it’s an alias and
       is pointed at an environment (think, branch) with a name in the format sandbox-YYYY-MM-DD.
While updating master is something that we generally don’t do (at the moment only a product owner and/or admin
would do this), updating the sandbox happens more often, typically to populate it with data more recently added to
master. To do this:
     • Go to Settings > Environments
     • Ensure we have at least one spare environment slot. If we don’t delete the oldest sandbox-XXXX-XX-XX envi-
       ronment.
     • Click the blue Add Environment button, to the right. Name it using the sandbox-YYYY-MM-DD pattern and
       base it on whatever environment is aliased to master - this will basically create a new ‘branch’ with the content
       currently in master.
     • In the Environment Aliases section of the main page, find sandbox and click Change alias target, then select the
       sandbox-XXXX-XX-XX environment you just made.
1.9. Contentful CMS (Content Management System) Integration                                                            73
bedrock Documentation, Release 1.0
Which environment is connected to where?
master is the environment used in Bedrock production, stage, dev and test sandbox may, in the future, be made the
default environment for dev. It’s also the one we should use for local development.
If you develop a new feature that adds to Contentful (e.g. page or component) and you author it in the sandbox, you
will need to re-create it in master before the corresponding bedrock changes hit production.
Troubleshooting
If you run into trouble on an issue, be sure to check in these places first and include the relevant information in requests
for help (i.e. environment).
Contentful Content Model & Entries
     • What environment are you using?
     • Do you have the necessary permissions to make changes?
     • Do you see all the entry fields you need? Do those fields have the correct value options?
Bedrock API (api.py)
     • What environment are you using?
     • Can you find a Python function definition for the content type you need?
     • Does it structure data as expected?
# example content type def
def get_section_data(self, entry_obj):
      fields = entry_obj.fields()
      # run `print(fields)` here to verify field values from Contentful
      data = {
            "component": "sectionHeading",
            "heading": fields.get("heading"),
      }
      # run `print(data)` here to verify data values from Bedrock API
      return data
Bedrock Render (all.html)
     • Can you find a render condition for the component you need?
/* example component condition */
{% elif entry.component == 'sectionHeading' %}
     • If the component calls a macro:
74                                                                                                 Chapter 1. Contents
                                                                              bedrock Documentation, Release 1.0
               – Does it have all the necessary parameters?
               – Is it passing the expected values as arguments?
    • If the component is custom HTML:
               – Is the HTML structure correct?
               – Are Protocol-specific class names spelled correctly?
    • Is the component CSS available?
    • Is the component JS available?
Note: Component CSS and JS are defined in a CONTENT_TYPE_MAP from the Bedrock API (api.py).
Bedrock Database
Once content is synced into your local database, it can be found in the contentful_contentfulentry table. All the depen-
dencies to explore the data are installed by default for local development.
Using sqlite (with an example query to get some info about en-US pages):
./manage.py dbshell
select id, slug, data from contentful_contentfulentry where locale='en-US';
Close the sqlite shell with .exit
Using Django shell (with an example query to get data from first entry of “pageProductJournalismStory” type):
./manage.py shell
from bedrock.contentful.models import ContentfulEntry
product_stories = ContentfulEntry.objects.filter(content_type="pageProductJournalismStory
 ˓→", localisation_complete=True, locale="en-US")
product_stories[0].data            # to see the data stored for the first story in the results
Close the Djanjo shell with exit() or CTRL+D
1.9.11 Useful Contentful Docs
https://www.contentful.com/developers/docs/references/images-api/#/reference/resizing-&-cropping/
specify-focus-area
https://www.contentful.com/developers/docs/references/content-delivery-api/
https://contentful.github.io/contentful.py/#filtering-options
https://github.com/contentful/rich-text-renderer.py             https://github.com/contentful/rich-text-renderer.py/blob/
a1274a11e65f3f728c278de5d2bac89213b7470e/rich_text_renderer/block_renderers.py
1.9. Contentful CMS (Content Management System) Integration                                                           75
bedrock Documentation, Release 1.0
1.9.12 Assumptions we still need to deal with
     • image sizes
1.9.13 Legacy
Since we decided to move forward the the Compose App, we no longer need the Connect content model. The EN-US
homepage is currently still using Connect. Documentation is here for reference.
     • this component is referenced by ID in bedrock (at the moment that is just the homepage but could be used to
       connect single components for display on non-contentful pages. For example: the latest feature box on /new)
Connect
These are the highest level component. They should be just a name and entry reference.
The purpose of the connect is to create a stable ID that can be referenced in bedrock to be included in a jinja template.
Right now we only do this for the homepage. This is because the homepage has some conditional content above and
below the Contentful content.
Using a connect component to create the link between jinja template and the Contentful Page entry means an entire
new page can be created and proofed in Contentful before the bedrock homepage begins pulling that content in.
In other contexts a connect content model could be created to link to entries where the ID may change. For example: the
“Latest Firefox Features: section of /new could be moved to Contentful using a connect component which references
3 picto blocks.
Because the ID must be added to a bedrock by a dev, only devs should be able to make new connect entries.
1.10 Sitemaps
bedrock serves a root sitemap at /sitemap.xml, which links to localised sitemaps for each supported locale.
The sitemap data is (re)generated on a schedule by www-sitemap-generator and then is pulled into bedrock’s database,
from which the XML sitemaps are rendered.
1.10.1 Quick summary
What does www-sitemap-generator do?
www-sitemap-generator, ultimately, produces an updated sitemap.json file if it detects changes in pages since
the last time the sitemap was generated. It does this by loading every page and checking its ETag. This sitemap.json
data is key to sitemap rendering by bedrock.
The update process is run on a schedule via our Gitlab CI setup.
Note: www-sitemap-generator uses the main bedrock release Docker image as its own base container image,
which means it has access to all of bedrock’s code and data-loading utils.
Bear this in mind when looking at management commands in bedrock; update_sitemaps is actually only called by
www-sitemap-generator even though it (currently) lives in bedrock
76                                                                                             Chapter 1. Contents
                                                                             bedrock Documentation, Release 1.0
When is the sitemap data pulled into bedrock?
Bedrock’s clock pod regularly runs bin/run-db-update.sh, which calls the update_sitemaps_data management
command. This is what pulls in data from the www-sitemap-generator git repo and refreshes the SitemapURL
records in Bedrock’s database. It is from these SitemapURL records that the XML sitemap tree is rendered by bedrock.
1.11 Using External Content Cards Data
The www-admin repo contains data files and images that are synced to bedrock and available for use on any page. The
docs for updating said data is available via that repo, but this page will explain how to use the cards data once it’s in
the bedrock database.
1.11.1 Add to a View
The easiest way to make the data available to a page is to add the page_content_cards variable to the template
context:
from bedrock.contentcards.models import get_page_content_cards
def view_with_cards(request):
      locale = l10n_utils.get_locale(request)
      ctx = {'page_content_cards': get_page_content_cards('home', locale)}
      return l10n_utils.render(request, 'sweet-words.html', ctx)
The get_page_content_cards returns a dict of card data dicts for the given page (home in this case) and locale. The
dict keys are the names of the cards (e.g. card_1). If the page_content_cards context variable is available in the
template, then the content_card() macro will discover it automatically.
Note: The get_page_content_cards function is not all that clever as far as l10n is concerned. If you have translated
the cards in the www-admin repo that is great, but you should have cards for every locale for which the page is active
or the function will return an empty dict. This is especially tricky if you have multiple English locales enabled (en-
US, en-CA, en-GB, etc.) and want the same cards to be used for all of them. You’d need to do something like if
locale.startswith('en-'): then use en-US in the function call.
Alternately you could just wrap the section of the template using cards to be optional in an {% if
page_content_cards %} statement, and that way it will not show the section at all if the dict is empty if there
are no cards for that page and locale combination.
1.11.2 Add to the Template
Once you have the data in the template context, using a card is simple:
{% from "macros-protocol.html" import content_card with context %}
{{ content_card('card_1') }}
This will insert the data from the card_1.en-US.md file from the www-admin repo into the template via the card()
macro normally used for protocol content cards.
If you don’t have the page_content_cards variable in the template context and you don’t want to create or modify a
view, you can fetch the cards via a helper function in the template itself, but you have to pass the result to the macro:
1.11. Using External Content Cards Data                                                                                77
bedrock Documentation, Release 1.0
{% from "macros-protocol.html" import content_card with context %}
{% set content_cards = get_page_content_cards('home', LANG) %}
{{ content_card('card_1', content_cards) }}
1.12 Banners
1.12.1 Creating page banners
Any page on bedrock can incorporate a top of page banner as a temporary feature. An example of such a banner is the
MOFO (Mozilla Foundation) fundraising form that gets shown on the home page several times a year.
Banners can be inserted into any page template by using the page_banner block. Banners can also be toggled on and
off using a switch:
{% block page_banner %}
   {% if switch('fundraising-banner') %}
      {% include 'includes/banners/fundraiser.html' %}
   {% endif %}
{% endblock %}
Banner templates should extend the base banner template, and content can then be inserted using banner_title and
banner_content blocks:
{% extends 'includes/banners/base.html' %}
{% block banner_title %}We all love the web. Join Mozilla in defending it.{% endblock %}
{% block banner_content %}
      <!-- insert custom HTML here -->
{% endblock %}
CSS styles for banners should be located in media/css/base/banners/, and should extend common base banner
styles:
@import 'includes/base';
To initiate a banner on a page, include js/base/banners/mozilla-banner.js in your page bundle and then initiate
the banner using a unique ID. The ID will be used as a cookie identifier should someone dismiss a banner and not wish
to see it again.
(function() {
      'use strict';
      function onLoad() {
            window.Mozilla.Banner.init('fundraising-banner');
      }
      window.Mozilla.run(onLoad);
})();
78                                                                                          Chapter 1. Contents
                                                                            bedrock Documentation, Release 1.0
By default, page banners will be rendered directly underneath the primary page navigation. If you want to render a
banner flush at the top of the page, you can pass a secondary renderAtTopOfPage parameter to the init() function
with a boolean value:
(function() {
      'use strict';
      function onLoad() {
            window.Mozilla.Banner.init('fundraising-banner', true);
      }
      window.Mozilla.run(onLoad);
})();
L10n for page banners
Because banners can technically be shown on any page, they need to be broadly translated, or alternatively limited to
the subset of locales that have translations. Each banner should have its own .ftl associated with it, and accessible to
the template or view it gets used in.
1.13 Mozilla.UITour
1.13.1 Introduction
Mozilla.UITour is a JS library that exposes an event-based Web API for communicating with the Firefox browser
chrome. It can be used for tasks such as opening menu panels, highlighting buttons, or querying Firefox Account
signed-in state. It is supported in Firefox 29 onward, but some API calls are only supported in later versions.
For security reasons Mozilla.UITour will only work on white-listed domains and over a secure connection. The list
of allowed origins can be found here: https://searchfox.org/mozilla-central/source/browser/app/permissions
The Mozilla.UITour library is maintained on Mozilla Central.
Important: The API is supported only on the desktop versions of Firefox. It doesn’t work on Firefox for Android and
iOS.
1.13.2 Local development
To develop or test using Mozilla.UITour locally you need to create some custom preferences in about:config.
     • browser.uitour.testingOrigins (string) (value: local address e.g. http://127.0.0.1:8000)
     • browser.uitour.requireSecure (boolean) (value: false)
Note that browser.uitour.testingOrigins can be a comma separated list of domains, e.g.
       ‘http://127.0.0.1:8000, https://www-demo2.allizom.org’
1.13. Mozilla.UITour                                                                                                 79
bedrock Documentation, Release 1.0
Important: Prior to Firefox 36, the testing preference was called browser.uitour.whitelist.add.testing
(Bug 1081772). This old preference does not accept a comma separated list of domains, and you must also exclude the
domain protocol e.g. https://. A browser restart is also required after adding an allowed domain.
If you are working on Firefox Accounts integration, you can use the identity.fxaccounts.autoconfig.uri config
property to change the Accounts server. For example, to change it to stage environment use this value: https:/
/accounts.stage.mozaws.net/. Restart the browser and make sure the configuration updated. identity.
fxaccounts.remote.root preference should now point to https://accounts.stage.mozaws.net. If it has not
changed for some reason, update it manually. Ref: https://mozilla-services.readthedocs.io/en/latest/howtos/run-fxa.
html
1.13.3 JavaScript API
The UITour API documentation can be found in the Mozilla Source Tree Docs.
1.14 Send to Device Widget
The Send to Device widget is a single form which facilitates the sending of a download link from a desktop browser to
a mobile device. The form allows sending via email.
Important: This widget should only be shown to a limited set of locales who are set up to receive the emails. For those
locales not in the list, direct links to the respective app stores should be shown instead. If a user is on iOS or Android,
CTA buttons should also link directly to respective app stores instead of showing the widget. This logic should be
handled on a page-by-page basis to cover individual needs.
Note: A full list of supported locales can be found in settings/base.py under SEND_TO_DEVICE_LOCALES, which
can be used in the template logic for each page to show the form.
1.14.1 Usage
    1. Make sure necessary files are in your CSS/JS bundles:
     • 'css/protocol/components/send-to-device.scss'
     • 'js/base/send-to-device.es6.js'
    2. Include the macro in your page template:
       {{ send_to_device() }}
    3. Initialize the widget:
       In your page JS, initialize the widget using:
       import SendToDevice from '/media/js/base/send-to-device.es6';
       const form = new SendToDevice();
       form.init();
80                                                                                                Chapter 1. Contents
                                                                           bedrock Documentation, Release 1.0
       By default the init() function will look for a form with an HTML id of send-to-device. If you need
       to pass another id, you can do so directly:
       const form = new SendToDevice('my-custom-form-id');
       form.init();
Configuration
The Jinja macro supports parameters as follows (* indicates a required parameter)
 Parame-         Definition                                 Format          Example
 ter name
 platform*       Platform ID for the receiving device. De-  String          ‘all’, ‘android’, ‘ios’
                 faults to ‘all’.
 mes-            ID for the email that should be received.  String          ‘default’, ‘fx-mobile-download-desktop’,
 sage_set*       Defaults to ‘default’.                                     ‘download-firefox-rocket’
 dom_id*         HTML form ID. Defaults to ‘send-to-        String          ‘send-to-device’
                 device’.
 class_name CSS class name for form orientation.            String          ‘horizontal’, ‘vertical’
                 Defaults to ‘vertical’
 in-             Should the widget contain a title. De-     Boolean         ‘True’, ‘False’
 clude_title     faults to ‘True’.
 title_text      Provides a custom string for the form ti-  Localizable     ‘Send Firefox Lite to your smartphone or
                 tle, overriding the default.               string          tablet’ .
 input_label Provides a custom label for the input          Localizable     ‘Enter your email’ .
                 field, overriding the default.             string
 le-             Provides a custom legal note for email     Localizable     ‘The intended recipient of the email must
 gal_note_emailuse.                                         String.         have consented.’
 spin-           Hex color for the form spinner. Defaults   String          ‘#fff’
 ner_color       to ‘#000’.
 but-            Optional button CSS class string. De-      String          ‘mzp-t-product mzp-t-dark’
 ton_class       faults to ‘mzp-t-product’
1.15 Firefox Download Buttons
There are two Firefox download button helpers in bedrock to choose from. The first is a lightweight button that links
directly to the /firefox/download/thanks/ page. Its sole purpose is to facilitate downloading the main release
version of Firefox.
{{ download_firefox_thanks() }}
The second type of button is more heavy weight, and can be configured to download any build of Firefox (e.g. Release,
Beta, Developer Edition, Nightly). It can also offer functionality such as direct (in-page) download links, so it comes
with a lot more complexity and in-page markup.
{{ download_firefox() }}
1.15. Firefox Download Buttons                                                                                       81
bedrock Documentation, Release 1.0
1.15.1 Which button should I use?
A good rule of thumb is to always use download_firefox_thanks() for regular landing pages (such as /firefox/
new/) where the main release version of Firefox is the product being offered. For pages pages that require direct
download links, or promote pre-release products (such as /firefox/channel/) then download_firefox() should
be used instead.
1.15.2 Documentation
See helpers.py for documentation and supported parameters for both buttons.
1.15.3 External referrers
Generally we encourage other websites in the Mozilla ecosystem to link to the /firefox/new/ page when prompting
visitors to download Firefox, since it provides a consistent user experience and also benefits SEO (Search Engine Op-
timization). In some circumstances however sites may want to provide a download button that initiates a file download
automatically when clicked. For cases like this, sites can link to the following URL:
https://www.mozilla.org/firefox/download/thanks/?s=direct
Important: Including the s=direct query parameter here will ensure that Windows download attribution is collected
and recorded correctly in Telemetry. Also, make sure to not include the locale in the URL, so that bedrock can serve
the most suitable language based on the visitor’s browser preference.
Note: This download URL will not automatically trigger a download in older Internet Explorer browsers. If that’s
important to your visitors, then you can use a conditional comment to provide a different link.
<!--[if !IE]><!-->
      <a href="https://www.mozilla.org/firefox/download/thanks/?s=direct">Download Firefox
 ˓→</a>
<!--<![endif]-->
<!--[if IE]>
      <a href="https://www.mozilla.org/firefox/new/">Download Firefox</a>
<![endif]-->
1.16 Firefox Accounts Helpers
Marketing pages often promote the creation of a Firefox Account (FxA) as a common call to action (CTA). This is
typically accomplished using either a sign-up form, or a prominent link/button. Other products such as Mozilla VPN
use similar Firefox Account auth flows to manage subscriptions. To accomplish these tasks, bedrock templates can take
advantage of a series of Python helpers which can be used to standardize product referrals, and make supporting these
auth flows easier.
Note: See the attribution docs (Firefox Accounts attribution) for more a detailed description of the analytics functions
these helpers provide.
82                                                                                             Chapter 1. Contents
                                                                          bedrock Documentation, Release 1.0
1.16.1 Firefox Account Sign-up Form
Use the fxa_email_form macro to display a Firefox Account signup form on a page.
Usage
To use the form in a Jinja template, first import the fxa_email_form macro:
{% from "macros.html" import fxa_email_form with context %}
The form can then be invoked using:
{{ fxa_email_form(entrypoint='mozilla.org-firefox-accounts') }}
The macro’s respective JavaScript and CSS dependencies should also be imported in the page:
Javascript:
import FxaForm from './path/to/fxa-form.es6.js';
FxaForm.init();
The above JS is also available as a pre-compiled bundle, which can be included directly in a template:
{{ js_bundle('fxa_form') }}
CSS:
@import '../path/to/fxa-form';
The JavaScript files will automatically handle things such as adding metrics parameters for Firefox desktop browsers.
The CSS file contains some default styling for the sign-up form.
1.16. Firefox Accounts Helpers                                                                                    83
bedrock Documentation, Release 1.0
Configuration
The sign-up form macro accepts the following parameters (* indicates a required parameter)
  Param-        Definition                                             Format                   Example
  eter
  name
  entry-        Unambiguous identifier for which page of the site is   mozilla.org-directory-   ‘mozilla.org-
  point*        the referrer.                                          page                     firefox-accounts’
  entry-        Used to identify experiments.                          Experiment ID            ‘whatsnew-
  point_experiment                                                                              headlines’
  entry-        Used to track page variations in multivariate tests.   Variant identifier       ‘b’
  point_variation
                Usually just a number or letter but could be a short
                keyword.
  style         An optional parameter used to invoke an alternatively  String                   ‘trailhead’
                styled page at accounts.firefox.com.
  class_name Applies a CSS class name to the form. Defaults to:        String                   ‘fxa-email-form’
                ‘fxa-email-form’
  form_title The main heading to be used in the form (optional         Localizable string       ‘Join Firefox’ .
                with no default).
  intro_text Introductory copy to be used in the form. Defaults to     Localizable string       ‘Enter your email
                a well localized string.                                                        address to get
                                                                                                started.’ .
  but-          Button copy to be used in the form. Defaults to a well Localizable string       ‘Sign Up’ .
  ton_text      localized string.
  but-          CSS class names to be applied to the submit button.    String of one or more    ‘mzp-c-button
  ton_class                                                            CSS class names          mzp-t-primary
                                                                                                mzp-t-product’
  utm_campaign  Used to identify specific marketing campaigns. De-     Campaign           name  ‘trailhead-fxa-
                faults to fxa-embedded-form                            prepended to default     embedded-form’
                                                                       value
  utm_term Used for paid search keywords.                              Brief keyword            ‘existing-users’
  utm_contentDeclared when more than one piece of content (on a        Description of content,  ‘get-the-rest-of-
                page or at a URL) links to the same place, to distin-  or name of experiment    firefox’
                guish between them.                                    treatment
Invoking the macro will automatically include a set of default UTM (Urchin Tracking Module) parameters as hidden
form input fields:
     • utm_source is automatically assigned the value of the entrypoint parameter.
     • utm_campaign is automatically set as the value of fxa-embedded-form. This can be prefixed with a custom
        value by passing a utm_campaign value to the macro. For example, utm_campaign='trailhead' would result
        in a value of trailhead-fxa-embedded-form.
     • utm_medium is automatically set as the value of referral.
Note: When signing into FxA using this form on a Firefox Desktop browser, it will also activate the Sync feature.
84                                                                                            Chapter 1. Contents
                                                                        bedrock Documentation, Release 1.0
1.16.2 Firefox Account Links
Use the fxa_button helper to create a CTA button or link to https://accounts.firefox.com/.
Usage
{{ fxa_button(entrypoint='mozilla.org-firefox-accounts', button_text='Sign In') }}
Note: There is also a fxa_link_fragment helper which will construct a valid href property. This is useful when
constructing an inline link inside a paragraph, for example.
Note: When signing into FxA using this link on a Firefox Desktop browser, it will also activate the Sync feature.
For more information on the available parameters, read the “Common FxA Parameters” section further below.
1.16.3 Firefox Monitor Links
Use the monitor_fxa_button helper to link to https://monitor.firefox.com/ via a Firefox Accounts auth flow.
Usage
{{ monitor_fxa_button(entrypoint=_entrypoint, button_text='Sign Up for Monitor') }}
For more information on the available parameters, read the “Common FxA Parameters” section further below.
1.16.4 Pocket Links
Use the pocket_fxa_button helper to link to https://getpocket.com/ via a Firefox Accounts auth flow.
Usage
{{ pocket_fxa_button(entrypoint='mozilla.org-firefox-pocket', button_text='Try Pocket Now
 ˓→', optional_parameters={'s': 'ffpocket'}) }}
For more information on the available parameters, read the “Common FxA Parameters” section below.
1.16.5 Common FxA (Firefox Account) Parameters
The fxa_button, pocket_fxa_button, and monitor_fxa_button helpers all support the same standard parame-
ters:
1.16. Firefox Accounts Helpers                                                                                    85
bedrock Documentation, Release 1.0
  Param-      Definition                                    Format        Example
  eter
  name
  entry-      Unambiguous identifier for which page of      ‘mozilla.org- ‘mozilla.org-firefox-pocket’
  point*      the site is the referrer. This also serves as firefox-
              a value for ‘utm_source’.                     pocket’
  but-        The button copy to be used in the call to ac- Localizable   ‘Try Pocket Now’
  ton_text* tion.                                           string
  class_nameA class name to be applied to the link (typi-   String of one ‘pocket-main-cta-button’
              cally for styling with CSS).                  or more class
                                                            names
  is_button_class
              A boolean value that dictates if the CTA      Boolean       True or False
              should be styled as a button or a link. De-
              faults to ‘True’.
  in-         A boolean value that dictates if metrics pa-  Boolean       True or False
  clude_metrics
              rameters should be added to the button href.
              Defaults to ‘True’.
  op-         An dictionary of key value pairs containing   Dictionary    {‘s’: ‘ffpocket’}
  tional_parameters
              additional parameters to append the the href.
  op-         An dictionary of key value pairs containing   Dictionary    {‘data-cta-text’: ‘Try Pocket Now’,
  tional_attributes
              additional data attributes to include in the                ‘data-cta-type’: ‘activate pocket’,’data-
              button.                                                     cta-position’: ‘primary’}
Note: The fxa_button helper also supports an additional action parameter, which accepts the values signup,
signin, and email for configuring the type of authentication flow.
1.16.6 Mozilla VPN (Virtual Private Network) Links
Use the vpn_subscribe_link helpers to create a VPN subscription link via a Firefox Accounts auth flow.
Usage
{{ vpn_subscribe_link(entrypoint='www.mozilla.org-vpn-product-page', link_text='Get␣
 ˓→Mozilla VPN') }}
Common VPN Parameters
Both helpers for Mozilla VPN support the same parameters (* indicates a required parameter)
86                                                                                          Chapter 1. Contents
                                                                            bedrock Documentation, Release 1.0
  Param-      Definition                                        Format           Example
  eter
  name
  entry-      Unambiguous identifier for which page of the      ‘www.mozilla.org-‘www.mozilla.org-vpn-product-page’
  point*      site is the referrer. This also serves as a value page-name’
              for ‘utm_source’.
  link_text* The link copy to be used in the call to action.    Localizable      ‘Get Mozilla VPN’
                                                                string
  class_nameA class name to be applied to the link (typi-       String of one    ‘vpn-button’
              cally for styling with CSS).                      or more class
                                                                names
  lang        Page locale code. Used to query the right sub-    Locale string    ‘de’
              scription plan ID in conjunction to country
              code.
  coun-       Country code provided by the CDN. Used to         Two digit, up-   ‘DE’
  try_code determine the appropriate subscription plan          percase coun-
              ID.                                               try code
  bun-        Generate a link that will bundle both Mozilla     Boolean          True, False
  dle_relay VPN and Firefox Relay in a single subscrip-
              tion. Defaults to False.
  op-         An dictionary of key value pairs containing       Dictionary       {‘utm_campaign’:         ‘vpn-product-
  tional_parameters
              additional parameters to append the the href.                      page’}
  op-         An dictionary of key value pairs containing       Dictionary       {‘data-cta-text’:   ‘VPN Sign In’,
  tional_attributes
              additional data attributes to include in the but-                  ‘data-cta-type’: ‘fxa-vpn’, ‘data-cta-
              ton.                                                               position’: ‘navigation’}
The vpn_subscribe_link helper has an additional plan parameter to support linking to different subscription plans.
    Parameter name         Definition                                          Format         Example
    plan                   Subscription plan ID. Defaults to 12-month plan.    ‘12-month’     ‘12-month’ or ‘monthly’
1.16.7 Firefox Sync and UITour
Since Firefox 80 the FxA link and email form macros use UITour to show the Firefox Accounts page and log the
browser into Sync or an Account. For non-Firefox browsers or if UITour is not available, the flow uses normal links
that allow users to log into FxA as a website only without connecting the Firefox Desktop client. This UITour flow
allows the Firefox browser to determine the correct FxA server and authentication flow (this includes handling the
China Repack build of Firefox). This transition was introduced to later migrate Firefox Desktop to an OAuth based
client authentication flow.
The script that handles this logic is /media/js/base/fxa-link.js, and will automatically apply to any link with
a js-fxa-cta-link class name. The current code automatically detects if you are in the supported browser for this
flow and updates links to drive them through the UITour API. The UITour showFirefoxAccounts action supports
flow id parameters, UTM parameters and the email data field.
1.16. Firefox Accounts Helpers                                                                                        87
bedrock Documentation, Release 1.0
1.16.8 Testing Signup Flows
Testing the Firefox Account signup flows on a non-production environment requires some additional configuration.
Configuring bedrock:
Set the following in your local .env file:
FXA_ENDPOINT=https://accounts.stage.mozaws.net/
For Mozilla VPN links you can also set:
VPN_ENDPOINT=https://stage.guardian.nonprod.cloudops.mozgcp.net/
VPN_SUBSCRIPTION_URL=https://accounts.stage.mozaws.net/
Note: The above values for staging are already set by default when Dev=True, which will also apply to demo servers.
You may only need to configure your .env file if you wish to change a setting to something else.
1.17 Funnel cakes and Partner Builds
1.17.1 Funnel cakes
In addition to being an American delicacy funnel cakes are what we call special builds of Firefox. They can come with
extensions preinstalled and/or a custom first-run experience.
      “The whole funnelcake system is so marred by history at this point I don’t know if anyone fully understands
      what it’s supposed to do in all situations” - pmac
Funnelcakes are configured by the Release Engineering team. You can see the configs in the funnelcake git repo
Currently bedrock only supports funnelcakes for “stub installer platforms”. Which means they are windows only.
However, funnelcakes can be made for all platforms so bedrock support may expand.
We signal to bedrock that we want a funnelcake when linking to the download page by appending the query variable f
with a value equal to the funnelcake number being requested.
https://www.mozilla.org/en-US/firefox/download/thanks/?f=137
Bedrock checks to see if the funnelcake is configured (this is handled in the www-config repo)
FUNNELCAKE_135_LOCALES=en-US
FUNNELCAKE_135_PLATFORMS=win,win64
Bedrock then converts that into a request to download a file like so:
Windows:
https://download.mozilla.org/?product=firefox-stub-f137&os=win&lang=en-US
Mac (You can see the mac one does not pass the funnelcake number along.):
https://download.mozilla.org/?product=firefox-latest-ssl&os=osx&lang=en-US
Someone in Release Engineering needs to set up the redirects on their side to take the request from here.
88                                                                                            Chapter 1. Contents
                                                                            bedrock Documentation, Release 1.0
Places things can go wrong
As with many technical things, the biggest potential problems are with people:
    • Does it have executive approval?
    • Did legal sign off?
    • Has it had a security review?
On the technical side:
    • Is the switch enabled?
    • Is the variable being passed?
1.17.2 Partner builds
Bedrock does not have an automated way of handling these, so you’ll have to craft your own download button:
<a href="https://download.mozilla.org/?product=firefox-election-edition&os=win&lang=en-US
 ˓→">
Download</a>
Bugs that might have useful info:
    • https://bugzilla.mozilla.org/show_bug.cgi?id=1450463
    • https://bugzilla.mozilla.org/show_bug.cgi?id=1495050
PRs that might have useful code:
    • https://github.com/mozilla/bedrock/pull/5555
1.18 A/B Testing
1.18.1 Convert experiments
Conversion rate optimization (CRO) experiments on bedrock can be run using a third-party tool called Convert. Convert
experiments are for relatively simple multivariate experiments, such as testing changes to headlines, images, or button
copy.
The Convert script is not included in part of bedrock’s base bundle for performance reasons. To use Convert on a page,
you can load the script behind a feature flag, which can be turned on / off for only the duration of an experiment. The
script should be loaded inside the experiments block in your template:
{% block experiments %}
      {% if switch('experiment-convert-page-name', ['en-US']) %}
           {{ js_bundle('convert') }}
      {% endif %}
{% endblock %}
Convert A/B tests can be implemented using the Convert dashboard and editor. Convert experiments should be coded
and tested against staging, before being reviewed and scheduled to run in production.
1.18. A/B Testing                                                                                                    89
bedrock Documentation, Release 1.0
QA for Convert experiments
The process for QA’ing Convert experiments is as follows:
    1. Bedrock feature switch should be activated on staging.
    2. Experiment is built and configured to run on https://www.allizom.org/*
    3. In the Github issue for an experiment, someone will request review by an engineer.
An engineer reviewing the experiment will:
    1. Verify that the experiment is not configured to run on https://www.mozilla.org/ (production) yet.
    2. Activate the experiment to run on stage.
During review, the engineer will compare the following to the experiment plan:
    1. The experiment’s logic.
    2. Any JS included (in Convert editor’s JS field).
    3. Any CSS included (in Convert editor’s CSS field).
    4. The target audience is configured.
    5. The goals are configured.
    6. The distribution percentages are configured.
    7. The target URLs are configured.
Once the engineer is satisfied, the engineer (or someone else with write privileges) will:
    1. Add https://www.mozilla.org/* to the list of URLs the experiment can run on.
    2. Reset the experiment (eliminating any data gathered during QA).
    3. Enable the bedrock feature switch in production.
    4. Activate (or schedule) the experiment.
After an experiment is finished, the feature switch should be deactivated in production.
Note: * should be replaced by the exact URL pathname for the experiment page.
1.18.2 Traffic Cop experiments
More complex experiments, such as those that feature full page redesigns, or multi-page user flows, should be imple-
mented using Traffic Cop. Traffic Cop small javascript library which will direct site traffic to different variants in a/b
experiments and make sure a visitor always sees the same variation.
It’s possible to test more than 2 variants.
Traffic Cop sends users to experiments and then we use Google Analytics (GA) to analyze which variation is more
successful. (If the user has DNT (Do Not Track) enabled they do not participate in experiments.)
All a/b tests should have a mana page detailing the experiment and recording the results.
90                                                                                              Chapter 1. Contents
                                                                             bedrock Documentation, Release 1.0
Coding the variants
Traffic cop supports two methods of a/b testing. Executing different on page javascript or redirecting to the same URL
with a query string appended. We mostly use the redirect method in bedrock. This makes testing easier.
Create a variation view for the a/b test.
The view can handle the URL redirect in one of two ways:
   1. the same page, with some different content based on the variation variable
   2. a totally different page
Content variation
Useful for small focused tests.
This is explained on the variation view page.
New page
Useful for large page changes where content and assets are dramatically different.
Create the variant page like you would a new page. Make sure it is noindex and does not have a canonical URL.
{% block canonical_urls %}<meta name="robots" content="noindex,follow">{% endblock %}
Configure as explained on the variation view page.
Traffic Cop
Create a .js file where you initialize Traffic Cop and include that in the experiments block in the template that will be
doing the redirection. Wrap the extra js include in a switch.
{% block experiments %}
  {% if switch('experiment-berlin-video', ['de']) %}
      {{ js_bundle('firefox_new_berlin_experiment') }}
  {% endif %}
{% endblock %}
Switches
See the traffic cop section of the switch docs for instructions.
1.18. A/B Testing                                                                                                     91
bedrock Documentation, Release 1.0
Recording the data
Note: If you are measuring installs as part of your experiment be sure to configure custom stub attribution as well.
Including the data-ex-variant and data-ex-name in the analytics reporting will add the test to an auto generated
report in GA (Google Analytics). The variable values may be provided by the analytics team.
if (href.indexOf('v=a') !== -1) {
      window.dataLayer.push({
           'data-ex-variant': 'de-page',
           'data-ex-name': 'Berlin-Campaign-Landing-Page'
      });
} else if (href.indexOf('v=b') !== -1) {
      window.dataLayer.push({
           'data-ex-variant': 'campaign-page',
           'data-ex-name': 'Berlin-Campaign-Landing-Page'
      });
}
Make sure any buttons and interaction which are being compared as part of the test and will report into GA.
Viewing the data
The data-ex-name and data-ex-variant are encoded in Google Analytics as custom dimensions 69 and 70.
Create a custom report.
Set the “Metrics Group” to include Sessions. Configure additional metrics depending on what the experiment was
measuring (downloads, events, etc.)
Set the “Dimension Drilldowns to have cd69 in the top position and cd70 in the drilldown position.
View the custom report and drilldown into the experiment with the matching name.
Tests
Write some tests for your a/b test. This could be simple or complex depending on the experiment.
Some things to consider checking:
    • Requests for the default (non variant) page call the correct template.
    • Requests for a variant page call the correct template.
    • Locales excluded from the test call the correct (default) template.
92                                                                                           Chapter 1. Contents
                                                                              bedrock Documentation, Release 1.0
A/B Test PRs that might have useful code to reuse
     • https://github.com/mozilla/bedrock/pull/5736/files
     • https://github.com/mozilla/bedrock/pull/4645/files
     • https://github.com/mozilla/bedrock/pull/5925/files
     • https://github.com/mozilla/bedrock/pull/5443/files
     • https://github.com/mozilla/bedrock/pull/5492/files
     • https://github.com/mozilla/bedrock/pull/5499/files
1.18.3 Avoiding experiment collisions
To ensure that Traffic Cop doesn’t overwrite data from any other externally controlled experiments (for example Ad
campaign tests, or in-product Firefox experiments), you can use the experiment-utils helper to decide whether or not
Traffic Cop should initiate.
import TrafficCop = from '@mozmeao/trafficcop';
import { isApprovedToRun } from '../../base/experiment-utils.es6';
if (isApprovedToRun()) {
      const cop = new TrafficCop({
            id: 'experiment-name',
            variations: {
                  'entrypoint_experiment=experiment-name&entrypoint_variation=a': 10,
                  'entrypoint_experiment=experiment-name&entrypoint_variation=b': 10
            }
      });
      cop.init();
}
The isApprovedToRun() function will check the page URL’s query parameters against a list of well-known experi-
mental params, and return false if any of those params are found. It will also check for some other cases where we
do not want to run experiments, such as if the page is being opened in an automated testing environment.
1.19 Mozilla VPN Subscriptions
The Mozilla VPN landing page displays both pricing and currency information that is dependant on someone’s physical
location in the world (using geo-location). If someone is in the United States, they should see pricing in $USD, and
if someone is in Germany they should see pricing in Euros. The page is also available in multiple languages, which
can be viewed independently of someone’s physical location. So someone who lives in Switzerland, but is viewing the
page in German, should still see pricing and currency displayed in Swiss Francs (CHF).
Additionally, it is important that we render location specific subscription links, as purchasing requires a credit card that
is registered to each country where we have a plan available. We are also legally obligated to prevent both purchasing
and/or downloading of Mozilla VPN in certain countries. In countries where VPN is not yet available, we also rely on
geo-location to hide subscription links, and instead to display a call to action to encourage prospective customers to
sign up to the VPN wait list.
1.19. Mozilla VPN Subscriptions                                                                                          93
bedrock Documentation, Release 1.0
To facilitate all of the above, we rely on our CDN to return an appropriate country code that relates to where a visitor’s
request originated from (see Geo Template View). We use that country code in our helpers and view logic for the VPN
landing page to decide what to display in the pricing section of the page (see Mozilla VPN Links).
1.19.1 Server architecture
Bedrock is configured so that when dev=True, VPN subscription links will point to the Firefox Accounts (FxA) staging
environment. When dev=False, they will point to the Fxa production environment.
So our different environments are mapped like so:
     • http://localhost:8000 -> https://accounts.stage.mozaws.net/
     • https://www-dev.allizom.org/products/vpn/ -> https://accounts.stage.mozaws.net/
     • https://www.allizom.or/products/vpn/ -> https://accounts.firefox.com/
     • https://www.mozilla.org/products/vpn -> https://accounts.firefox.com/
This allows the product and QA teams to routinely test changes and new VPN client releases on https://www-dev.
allizom.org/products/vpn/, prior to being available in production.
1.19.2 Adding new countries for VPN
When launching VPN in new countries there is a set process to follow.
Launch steps
    1. All the code changes below should be added behind a feature switch.
    2. Once the PR is reviewed and merged, the product QA team should be notified and they can then perform testing
       on https://www-dev.allizom.org/products/vpn/. Often the QA team will request a date for code to be ready for
       testing to begin.
    3. Code can be pushed to production ahead of time (but will be disabled behind the feature switch by default).
    4. Once QA gives the green light on launch day, the feature switch can then be enabled in production.
    5. QA will then do a final round of post-launch QA to verify subscriptions / purchasing works in the new countries
       in production.
Code changes
Reference: officially assigned list of ISO country codes. Reference: list of ISO 4217 currency codes`_
The majority of config changes need to happen in bedrock/settings/base.py:
    1. Add new pricing plan configs to VPN_PLAN_ID_MATRIX for any new countries that require newly created plan
       IDs (these will be provided by the VPN team). Separate plan IDs for both dev and prod are required for each
       new currency / language combination (this is because the product QA team need differently configured plans on
       dev to routinely test things like renewal and cancellation flows). Meta data such as price, total price and saving
       for each plan / currency should also be provided.
       Example pricing plan config for $USD / English containing both 12-month and monthly plans:
94                                                                                              Chapter 1. Contents
                                                                           bedrock Documentation, Release 1.0
            VPN_PLAN_ID_MATRIX = {
                  "usd": {
                       "en": {
                             "12-month": {
                                  "id": "price_1J0Y1iKb9q6OnNsLXwdOFgDr" if DEV else "price_
             ˓→1Iw85dJNcmPzuWtRyhMDdtM7",
                                  "price": "US$4.99",
                                  "total": "US$59.88",
                                  "saving": 50,
                                  "analytics": {"brand": "vpn", "plan": "vpn", "currency":
             ˓→"USD", "discount": "60.00", "price": "59.88", "period": "yearly"},
                             },
                             "monthly": {
                                  "id": "price_1J0owvKb9q6OnNsLExNhEDXm" if DEV else "price_
             ˓→1Iw7qSJNcmPzuWtRMUZpOwLm",
                                  "price": "US$9.99",
                                  "total": None,
                                  "saving": None,
                                  "analytics": {"brand": "vpn", "plan": "vpn", "currency":
             ˓→"USD", "discount": "0", "price": "9.99", "period": "monthly"},
                             },
                       }
                  },
                  # repeat for other currency / language configs.
            }
            See the Begin Checkout section of the analytics docs for more a detailed description of what should
            be in the analytics objects.
   2. Map each new country code to one or more applicable pricing plans in VPN_VARIABLE_PRICING.
      Example that maps the US country code to the pricing plan config above:
            VPN_VARIABLE_PRICING = {
                  "US": {
                       "default": VPN_PLAN_ID_MATRIX["usd"]["en"],
                  },
                  # repeat for other country codes.
            }
   3. Once every new country has a mapping to a pricing plan, add each new country code to the list of supported
      countries in VPN_COUNTRY_CODES. Because new countries need to be added behind a feature switch, you may
      want to create a new variable temporarily for this until launched, such as VPN_COUNTRY_CODES_WAVE_VI. You
      can then add these to VPN_COUNTRY_CODES in products/views.py using a simple function like so:
            def vpn_available(request):
                  country = get_country_from_request(request)
                  country_list = settings.VPN_COUNTRY_CODES
                  if switch("vpn-wave-vi"):
                       country_list = settings.VPN_COUNTRY_CODES + settings.VPN_COUNTRY_
             ˓→CODES_WAVE_VI
                  return country in country_list
1.19. Mozilla VPN Subscriptions                                                                                 95
bedrock Documentation, Release 1.0
            The function could then be used in the landing page view like so:
            vpn_available_in_country = vpn_available(request),
   4. If you now test the landing page locally, you should hopefully see the newly added pricing for each new country
      (add the ?geo=[INSERT_COUNTRY_CODE] param to the page URL to mock each country). If all is well, this is
      the perfect time to add new unit tests for each new country. This will help give you confidence that the right plan
      ID is displayed for each new country / language option.
      def test_vpn_subscribe_link_variable_12_month_us_en(self):
            """Should contain expected 12-month plan ID (US / en-US)"""
            markup = self._render(
                 plan="12-month",
                 country_code="US",
                 lang="en-US",
            )
            self.assertIn("?plan=price_1Iw85dJNcmPzuWtRyhMDdtM7", markup)
      def test_vpn_subscribe_link_variable_monthly_us_en(self):
            """Should contain expected monthly plan ID (US / en-US)"""
            markup = self._render(
                 plan="monthly",
                 country_code="US",
                 lang="en-US",
            )
            self.assertIn("?plan=price_1Iw7qSJNcmPzuWtRMUZpOwLm", markup)
   5. Next, update VPN_AVAILABLE_COUNTRIES to the new total number of countries where VPN is available. Again,
      because this needs to be behind a feature switch you may want a new temporary variable that you can use in
      products/views.py:
      available_countries = settings.VPN_AVAILABLE_COUNTRIES
      if switch("vpn-wave-vi"):
            available_countries = settings.VPN_AVAILABLE_COUNTRIES_WAVE_VI
   6. Finally, there is also a string in l10n/en/products/vpn/shared.ftl that needs updating to include the new
      countries. This should be a new string ID, and behind a feature switch in the template:
      vpn-shared-available-countries-v6 = We currently offer { -brand-name-mozilla-vpn }␣
       ˓→in Austria, Belgium, Canada, Finland, France, Germany, Ireland, Italy, Malaysia,␣
       ˓→the Netherlands, New Zealand, Singapore, Spain, Sweden, Switzerland, the UK, and␣
       ˓→the US.
      {% if switch('vpn_wave_vi') %}
            {{ ftl('vpn-shared-available-countries-v6', fallback='vpn-shared-available-
       ˓→countries-v5') }}
      {% else %}
            {{ ftl('vpn-shared-available-countries-v5') }}
      {% endif %}
   7. After things are launched in production and QA has verified that all is well, don’t forget to file an issue to tidy
      up the temporary variables and switch logic.
96                                                                                             Chapter 1. Contents
                                                                             bedrock Documentation, Release 1.0
1.19.3 Excluded countries
For a list of country codes where we are legally obligated                        to prevent purchasing VPN,            see
VPN_EXCLUDED_COUNTRY_CODES in bedrock/settings/base.py.
For a list of country codes where we are also required to prevent downloading the VPN client, see
VPN_BLOCK_DOWNLOAD_COUNTRY_CODES.
1.20 Attribution
Attribution is the practice of recording the main touch points that a website visitor encounters on their path to down-
loading or signing up for one of our products. It often involves a multi-step user journey, sometimes across multiple
properties, but the goal is to end up with informative data that tells us where the user of a product initially came from,
and what their journey looked like along the way.
These documents define how attribution works for the different products on our websites.
1.20.1 Mozorg analytics
Google Tag Manager (GTM)
In mozorg mode, bedrock uses Google Tag Manager (GTM) to manage and organize its Google Analytics solution.
GTM (Google Tag Manager) is a tag management system that allows for easy implementation of Google Analytics
(GA) tags and other 3rd party marketing tags in a nice GUI (Graphical User Interface) experience. Tags can be added,
updated, or removed directly from the GUI. GTM allows for a “one source of truth” approach to managing an analytics
solution in that all analytics tracking can be inside GTM.
Bedrock’s GTM solution is CSP (Content Security Policy) compliant and does not allow for the injection of custom
HTML or JavaScript but all tags use built in templates to minimize any chance of introducing a bug into Bedrock.
The GTM DataLayer
How an application communicates with GTM is via the dataLayer object, which is a simple JavaScript array GTM
instantiates on the page. Bedrock will send messages to the dataLayer object by means of pushing an object literal
onto the dataLayer. GTM creates an abstract data model from these pushed objects that consists of the most recent
value for all keys that have been pushed to the dataLayer.
The only reserved key in an object pushed to the dataLayer is event which will cause GTM to evaluate the firing
conditions for all tag triggers.
DataLayer push example
If we wanted to track clicks on a carousel and capture what the image was that was clicked, we might write a dataLayer
push like this:
dataLayer.push({
      'event': 'carousel-click',
      'image': 'house'
});
1.20. Attribution                                                                                                       97
bedrock Documentation, Release 1.0
In the dataLayer push there is an event value to have GTM evaluate the firing conditions for tag triggers, making it
possible to fire a tag off the dataLayer push. The event value is descriptive to the user action so it’s clear to someone
coming in later what the dataLayer push signifies. There is also an image property to capture the image that is clicked,
in this example it’s the house picture.
In GTM, a tag could be setup to fire when the event carousel-click is pushed to the dataLayer and could consume
the image value to pass on what image was clicked.
The Core DataLayer object
For the passing of contextual data on the user and page to GTM, we’ve created what we call the Core DataLayer Object.
This object passes as soon as all required API calls for contextual data have completed. Unless there is a significant
delay to when data will be available, please pass all contextual or meta data on the user or page here that you want to
make available to GTM.
Conditional banners
When a banner is shown:
dataLayer.push({
      'eLabel': 'Banner Impression',
      'data-banner-name': '<banner name>', //ex. Fb-Video-Compat
      'data-banner-impression': '1',
      'event': 'non-interaction'
});
When an element in the banner is clicked:
dataLayer.push({
      'eLabel': 'Banner Clickthrough',
      'data-banner-name': '<banner name>', //ex. Fb-Video-Compat
      'data-banner-click': '1',
      'event': 'in-page-interaction'
});
When a banner is dismissed:
dataLayer.push({
      'eLabel': 'Banner Dismissal',
      'data-banner-name': '<banner name>', //ex. Fb-Video-Compat
      'data-banner-dismissal': '1',
      'event': 'in-page-interaction'
});
98                                                                                              Chapter 1. Contents
                                                                             bedrock Documentation, Release 1.0
A/B tests
if(href.indexOf('v=a') !== -1) {
      window.dataLayer.push({
            'data-ex-variant': 'de-page',
            'data-ex-name': 'Berlin-Campaign-Landing-Page'
      });
} else if (href.indexOf('v=b') !== -1) {
      window.dataLayer.push({
            'data-ex-variant': 'campaign-page',
            'data-ex-name': 'Berlin-Campaign-Landing-Page'
      });
}
GTM listeners & data attributes
GTM also uses click and form submit listeners to gather context on what is happening on the page. Listeners push to
the dataLayer data on the specific element that triggered the event, along with the element object itself.
Since GTM listeners pass the interacted element object to the dataLayer, the use of data attributes works very well when
trying to identify key elements that you want to be tracked and for storing data on that element to be passed into Google
Analytics. We use data attributes to track clicks on all downloads, buttons elements, and nav, footer, and CTA/button
link elements.
Important: When adding any new elements to a Bedrock page, please follow the below guidelines to ensure accurate
analytics tracking.
For all generic CTA links and <button> elements, add these data attributes (* indicates a required attribute):
              Data Attribute           Expected Value (lowercase)
              data-cta-type *          Link type (e.g. navigation, footer, or button)
              data-cta-text            name or text of the link
              data-cta-position        Location of CTA on the page (e.g. primary, secondary, header)
For Firefox download buttons, add these data attributes (* indicates a required attribute).                     Note that
data-download-name and data-download-version should be included for download buttons that serve multiple
platforms. For mobile specific store badges, they are not strictly required.
  Data Attribute                Expected Value
  data-link-type *              download
  data-download-os *            Desktop, Android, iOS
  data-download-name            Windows 32-bit, Windows 64-bit, macOS, Linux 32-bit, Linux 64-bit, iOS,
                                Android
  data-download-version win, win64, osx, linux, linux64, ios, android
  data-download-locationprimary, secondary, nav, other
For all links to accounts.firefox.com use these data attributes (* indicates a required attribute):
1.20. Attribution                                                                                                     99
bedrock Documentation, Release 1.0
 Data           Expected Value
 Attribute
 data-cta-type  fxa-servicename (e.g. fxa-sync, fxa-monitor)
 *
 data-cta-text  Name or text of the link (e.g. Sign Up, Join Now, Start Here). We use this when the link text
                is not useful, as is the case with many FxA forms that say, Continue. We replace Continue with
                Register.
                Location of CTA on the page (e.g. primary, secondary, header)
 data-cta-position
Old data-cta structure
Do not use. Included here because some old pages still use it.
data-cta-type="" and data-cta-name="" trigger a generic link / button click with the following structure:
    • Event Category: {{page ID}} Interactions
    • Event Action: {{data-cta-type}} click
    • Event Label: {{data-cta-name}}
GA4
Note: The migration to GA4 has begun but is incomplete.
Enhanced Event Measurement
Pageviews, video events, and external link clicks are being collected using GA4’s enhanced event measurement.
Some form submissions are also being collected but newsletter signups are not. (See Bug #13348)
Begin Checkout
We are using GA4’s recommended eCommerce event begin_checkout for VPN and Relay referrals to the FxA Sub-
scription Platform with purchase intent.
Note: Any link to Firefox Accounts should also be using firefox accounts attribution
datalayer-begincheckout.es6.js contains generic functions that can be called on to push the appropriate infor-
mation to the dataLayer. The script is expecting the following values:
    • item_id: Stripe Plan ID
    • brand: relay, vpn, or monitor
    • plan:
               – vpn-monthly
               – vpn-yearly
               – vpn-relay-yearly
100                                                                                         Chapter 1. Contents
                                                                         bedrock Documentation, Release 1.0
               – relay-email-monthly
               – relay-email-yearly
               – relay-phone-monthly
               – relay-phone-yearly
               – monitor-monthly
               – monitor-yearly
    • period: monthly or yearly
    • price: cost displayed at checkout, pre tax (example: 119.88)
    • currency: in 3-letter ISO 4217 format (examples: USD, EUR)
    • discount: value of the discount in the same currency as price (example: 60.00)
There are two ways to use TrackBeginCheckout:
   1) Call the function passing the values directly.
TrackBeginCheckout.getEventObjectAndSend(item_id, brand, plan, period, price, currency,␣
 ˓→discount)
   2) Pass the values as a data attribute.
The vpn_subscribe_link and relay_subscribe_link will automatically generate a data-ga-item object and
add the ga-begin-checkout class to links they create – as long as there is analytics information associated with the
plan in its lookup table.
To use this method you will need to include datalayer-begincheckout-init.es6.js in the page bundle.
<a href="{{ fxa link }}"
     class="ga-begin-checkout"
     data-ga-item="{
            'id' : 'price_1Iw7qSJNcmPzuWtRMUZpOwLm',
            'brand' : 'vpn',
            'plan' : 'vpn',
            'period' : 'monthly',
            'price' : '9.99',
            'discount' : '0',
            'currency' : 'USD'
     }"
>
     Get monthly plan
</a>
1.20. Attribution                                                                                               101
bedrock Documentation, Release 1.0
Product Download
Important: Only Firefox and Pocket are currently supported. VPN support has not been added.
We are using a the custom event product_download to track product downloads and app store referrals for Firefox,
Pocket, and VPN. We are not using the default GA4 event file_download for a combination of reasons: it does not
trigger for the Firefox file types, we would like to collect more information than is included with the default events, and
we would like to treat product downloads as goals but not all file downloads are goals.
Note: Most apps listed in appstores.py are supported but you may still want to check that the URL you are tracking is
identified as valid in `isValidDownloadURL` and will be recognized by `getEventFromUrl.
Properties for use with product_download (not all products will have all options):
     • product (example: firefox)
     • platform (example: win64)
     • method (store, site, or adjust)
     • release_channel (example: nightly)
     • download_language (example: en-CA)
There are two ways to use TrackProductDownload:
    1) Call the function, passing it the same URL you are sending the user to:
TrackProductDownload.sendEventFromURL(downloadURL);
    2) Add a class to the link:
<a href="{{ link }}" class="ga-product-download">Link text</a>
You do NOT need to include datalayer-productdownload-init.es6.js in the page bundle, it is already included
in the site bundle.
How can visitors opt out of GA?
Visitors to the website can opt-out of loading Google Analytics on our website by enabling Do Not Track (DNT) in
their web browser. We facilitate this by using a DNT helper that our team maintains.
Glean
Currently in an evaluation phase, bedrock is now capable of running a parallel first-party analytics implementation
alongside GTM, using Mozilla’s own Glean telemetry SDK (Software Development Kit). See the Glean Book for
more developer reference documentation.
Glean is currently behind a feature switch called SWITCH_GLEAN_ANALYTICS. When the switch is enabled pages will
load the Glean JavaScript bundle, which will do things like record page hits and link click events that we want to
measure.
102                                                                                              Chapter 1. Contents
                                                                             bedrock Documentation, Release 1.0
Debugging pings
For all non-production environments, bedrock will automatically set a debug view tag for all pings. This means that
when running on localhost, on a demo, or on a staging environment, ping data will be viewable in the Glean debug
dashboard which can be used to test that pings are working correctly. All bedrock debug pings will register in the debug
dashboard with the tag name bedrock.
Filtering out non-production pings
Bedrock will also set an app_channel tag with a value of either prod or non-prod, depending on the environment.
This is present in all pings in the client_info section, and is useful for filtering out non-production data in telemetry
dashboards.
Logging pings in the console
When running bedrock locally, you can also set the following environment variable in your .env` file to automatically
log pings in the browser’s web console. This can be especially useful when making updates to analytics code.
GLEAN_LOG_PINGS=True
Defining metrics and pings
All of the data we send to the Glean pipeline is defined in YAML (Yet Another Markup Language) schema files in the
./glean/ project root directory. The metrics.yaml file defines all the different metrics types and events we record.
Note: Before running any Glean commands locally, always make sure you have first activated your virtual environment
by running pyenv activate bedrock.
When bedrock starts, we automatically run npm run glean which parses these schema files and then generates some
JavaScript library code in ./media/js/libs/glean/. This library code is not committed to the repository on pur-
pose, in order to avoid people altering it and becoming out of sync with the schema. This library code is then imported
into our Glean analytics code in ./media/js/glean/, which is where we initiate page views and capture click events.
Running npm run glean can also be performed independently of starting bedrock. It will also first lint the schema
files.
Important: All metrics and events we record using Glean must first undergo a data review before being made active
in production. Therefore anytime we make new additions to these files, those changes should also undergo review.
1.20. Attribution                                                                                                    103
bedrock Documentation, Release 1.0
Using Glean events in individual page bundles
Our analytics code for Glean lives in a single bundle in the base template, which is intended to be shared across all web
pages. This bundle automatically initializes Glean and records page hit events. It also creates some helpers that can be
used across different page bundles to record interaction events such as link clicks and form submissions.
The Mozilla.Glean.pageEvent() helper can be used to record events that are specific to a page, such as successful
form completions:
if (typeof window.Mozilla.Glean !== 'undefined') {
      window.Mozilla.Glean.pageEvent({
           label: 'newsletter-sign-up-success',
           type: 'mozilla-and-you' // type is optional
      });
}
It can also be used to record non-interaction events that are not directly initiated by a visitor:
if (typeof window.Mozilla.Glean !== 'undefined') {
      window.Mozilla.Glean.pageEvent({
           label: 'firefox-default',
           nonInteraction: true
      });
}
The Mozilla.Glean.clickEvent() helper can be used to record click events that are specific to an element in a
page, such as a link or button.
if (typeof window.Mozilla.Glean !== 'undefined') {
      window.Mozilla.Glean.clickEvent({
           label: 'firefox-download',
           type: 'macOS, release, en-US', // type is optional
           position: 'primary' // position is optional
      });
}
How can visitors opt out of Glean?
Website visitors can opt out of Glean by visiting the first party data preferences page, which is linked to in the websites
privacy notice. Clicking opt-out will set a cookie which Glean checks for before initializing on page load. In production,
the cookie that is set applies for all .mozilla.org domains, so other sites such as developer.mozilla.org can also
make use of the opt-out mechanism.
104                                                                                                Chapter 1. Contents
                                                                              bedrock Documentation, Release 1.0
1.20.2 Firefox desktop attribution
Firefox Desktop Attribution (often referred to as Stub Attribution) is a system that enables Mozilla to link website
attributable referral data (including Google Analytics data) to a user’s Firefox profile. When a website visitor lands
on www.mozilla.org and clicks to download Firefox, we pass attribution data about their visit to the Firefox installer
for inclusion in Telemetry. This is to enable Mozilla to better understand how things like changes to our website and
different marketing campaigns can affect installation rates, as well as overall product retention. The data also gives us
an insight into how many installations originate from www.mozilla.org, as opposed to elsewhere on the internet.
Scope and requirements
     • Attribution was originally only possible via the Firefox stub installer on Windows (hence the name stub attribu-
       tion), however it now also works on full installer links, and across all Windows desktop release channels.
     • Attribution is still limited to devices running a Windows OS. The flow does not yet work for macOS and Linux
       users. It also does not work for Android or iOS devices.
     • Attribution will only be passed if a website visitor has their Do Not Track (DNT) preference disabled in their
       browser. Visitors can opt-out by enabling DNT. This is covered in our privacy policy.
How does attribution work?
See the Application Logic Flow Chart for a more detailed visual representation of the steps below (Mozilla access
only).
    1. A user visits a page on www.mozilla.org. On page load, a JavaScript function collects referral and analytics data
       about from where their visit originated (see the table below for a full list of attribution data we collect).
    2. Once the attribution data is validated, bedrock then generates an attribution session ID. This ID is included in
       the user’s attribution data, and is also sent to Google Analytics as a non-interaction event.
    3. Next we send the attribution data to an authentication service that is part of bedrock’s back-end server. The data
       is validated again, then base64 encoded and returned to the client together with an signed, encrypted signature
       to prove that the data came from www.mozilla.org.
    4. The encoded attribution data and signature are then stored as cookies in the user’s web browser. The cookies
       have the IDs moz-stub-attribution-code (the attribution code) and moz-stub-attribution-sig (the
       encrypted signature). Both cookies have a 24 hour expiry.
    5. Once the user reaches a Firefox download page, bedrock then checks if both attribution cookies exist, and
       if so appends the authenticated data to the Firefox download link. The query parameters are labelled
       attribution_code and attribution_sig.
    6. When the user clicks the Firefox download link, another attribution service hosted at download.mozilla.org
       then decrypts and validates the attribution signature. If the secret matches, a unique download token is generated.
       The service then stores both the attribution data (including the Google Analytics client ID) and the download
       token in Mozilla’s private server logs.
    7. The service then passes the download token and attribution data (excluding the GA client ID) into the installer
       being served to the user.
    8. Once the user installs Firefox, the data that was passed to the installer is then stored in the users’ Telemetry
       profile.
    9. During analysis, the download token can be used to join Telemetry data with the corresponding GA data in the
       server logs.
1.20. Attribution                                                                                                     105
bedrock Documentation, Release 1.0
Attribution data
  Name             Description                                                                 Example
  utm_source       Query param identifying the referring site which sent the visitor.          utm_source=google
  utm_medium       Query param identifying the type of link, such as referral, cost per click, utm_medium=cpc
                   or email.
  utm_campaignQuery param identifying the specific marketing campaign that was seen.           utm_campaign=fast
  utm_content Query param identifying the specific element that was clicked.                   utm_content=getfirefox
  referrer         The domain of the referring site when the link was clicked.                 google.com
  ua               Simplified browser name parsed from the visitor’s User Agent string.        chrome
  experiment Query param identifying an experiment name that visitor was a cohort              taskbar
                   of.
  variation        Query param identifying the experiment variation that was seen by the
                   visitor.
  client_id        Google Analytics Client ID.                                                 1715265578.
                                                                                               1681917481
  session_id       A random 10 digit string identifier used to associate attribution data with 9770365798
                   GA session.
  dlsource         A hard-coded string ID used to distinguish mozorg downloads from            mozorg
                   archive downloads
Note: If any of the above values are not present then a default value of (not set) will be used.
Cookies
The cookies created during the attribution flow are as follows:
    Name                                Value                                   Domain               Path    Expiry
    moz-stub-attribution-code           Base64 encoded attribution string       www.mozilla.org      /       24 hours
    moz-stub-attribution-sig            Base64 encoded signature                www.mozilla.org      /       24 hours
Measuring campaigns and experiments
Firefox Desktop Attribution was originally designed for measuring the effectiveness of marketing campaigns where
the top of the funnel was outside the remit of www.mozilla.org. For these types of campaigns, stub attribution requires
zero configuration. It just works in the background and passes along any attribution data that exists.
It is also possible to measure the effectiveness of experiments on installation rates and retention. This is achieved by
adding optional experiment and variation parameters to a page URL. Additionally, these values can also be set via
JavaScript using:
Mozilla.StubAttribution.experimentName = 'experiment-name';
Mozilla.StubAttribution.experimentVariation = 'v1';
Note: When setting a experiment parameters using JavaScript like in the example above, it must be done prior to
calling Mozilla.StubAttribution.init().
106                                                                                             Chapter 1. Contents
                                                                              bedrock Documentation, Release 1.0
Return to addons.mozilla.org (RTAMO)
Return to AMO (RTAMO) is a Firefox feature whereby a first-time installation onboarding flow is initiated, that redirects
a user to install the extension they have chosen whilst browsing AMO using a different browser. RTAMO works by
leveraging the existing stub attribution flow, and checking for specific utm_ parameters that were passed if the referrer
is from AMO.
Specifically, the RTAMO feature looks for a utm_content parameter that starts with rta:, followed by an ID specific
to an extension. For example: utm_content=rta:dUJsb2NrMEByYXltb25kaGlsbC5uZXQ. The stub attribution code
in bedrock also checks the referrer before passing this on, to make sure the links originate from AMO. If RTAMO data
comes from a domain other than AMO, then the attribution data is dropped.
RTAMO initially worked for only a limited subset of addons recommended by Mozilla. This functionality was recently
expanded by the AMO team to cover all publically listed addons, under a project called Extended RTAMO (ERTAMO).
How can visitors opt out?
Visitors to the website can opt-out of desktop attribution on our website by enabling Do Not Track (DNT) in their web
browser. We facilitate this by using a DNT helper that our team maintains.
Local testing
For stub attribution to work locally or on a demo instance, a value for the HMAC key that is used to sign the attribution
code must be set via an environment variable e.g.
STUB_ATTRIBUTION_HMAC_KEY=thedude
Note: This value can be anything if all you need to do is test the bedrock functionality. It only needs to match the
value used to verify data passed to the stub installer for full end-to-end testing via Telemetry.
1.20.3 Firefox mobile attribution
For Firefox mobile app store referrals we use Adjust, a third-party attribution system for mobile apps that is designed
to measure, optimize and scale app growth. We use Adjust tracking links across www.mozilla.org when we link to app
stores for both Firefox and Firefox Focus on Android and iOS. We also often embed Adjust links in QR codes that we
display to desktop visitors.
To find out more about Adjust, see the following links:
     • What is mobile ad attribution?
     • Attribution methods
1.20. Attribution                                                                                                    107
bedrock Documentation, Release 1.0
Adjust link helpers
Whilst they are not used routinely in our pages, bedrock does have a series of Adjust link helpers that can be used to
create Adjust links for different products.
1.20.4 Firefox Accounts attribution
For products such as Mozilla VPN, Relay, and Monitor, we use Firefox Accounts (FxA) as an authentication and
subscription service. In addition to Google Analytics for basic conversion tracking, we attribute web page visits and
clicks and through to actual subscriptions and installs by passing a specific allow-list of known query parameters through
to the subscription platform. This is accomplished by adding referral data as parameters to sign up links on product
landing pages.
How does attribution work?
When using any of the Firefox Accounts Helpers in bedrock, a default set of attribution parameters are added to each
FxA sign-in / subscription link on a product landing page. Here’s what we set for Mozilla VPN, as an example:
  Name             Description                                                               Example value
  utm_source       Query param identifying the referring site which sent the visitor.        www.mozilla.
                                                                                             org-vpn-product-page
  utm_medium       Query param identifying the type of link, such as referral, cost per      referral
                   click, or email.
  utm_campaign Query param identifying the specific marketing campaign that                  vpn-product-page
                   was seen.
  entrypoint       ID for which page of the website the request originates from (used        www.mozilla.
                   for FxA funnel analysis).                                                 org-vpn-product-page
  device_id        ID that correlates to the active device being used (used for FxA          Alpha numeric string
                   funnel analysis).
  flow_id          The flow identifier. A randomly-generated opaque ID (used for             Alpha numeric string
                   FxA funnel analysis).
  flow_begin_time  The time at which a flow event occurred (used for FxA funnel              Timestamp
                   analysis).
  service          Product ID used for data analysis in BigQuery (optional).                 Alpha numeric string
When performing data analysis, the default UTM values above are what we equate to “direct” traffic (i.e. someone
came to the landing page directly then subscribed. They did not arrive from a specific marketing campaign or other
channel).
If we do detect that someone came from a marketing campaign or other form of referral, then we have logic in place
that will replace the default UTM parameters on each link with more specific referral data, so that we can attribute
subscriptions to individual campaigns.
We also support passing several other optional referral parameters:
108                                                                                                Chapter 1. Contents
                                                                             bedrock Documentation, Release 1.0
  Name                          Description                                                          Example value
  coupon                        A coupon code that can be automatically applied at checkout (case    VPN20
                                sensitive).
  entrypoint_experimentExperiment name ID.                                                           Alpha     numeric
                                                                                                     string
  entrypoint_variation Experiment variation ID                                                       Alpha     numeric
                                                                                                     string
Attribution logic
See the Application Logic Flow Chart for a visual representation of the steps below (Mozilla access only).
    1. A website visitor loads a product landing page in their web browser.
    2. A JavaScript function then checks for and validates attribution data via a list of known URL parameters (see
       tables above).
    3. If there are UTM parameters in the referral data, then those are used to replace the default values in each FxA
       link. Additionally if coupon or entrypoint_experiment params found, those are also appended.
    4. If no UTM params exist, but there is a referrer cookie set, then the cookie value is used for utm_campaign and
       utm_source is set to www.mozilla.org. This cookie is often set when we display a “Get Mozilla VPN” promo
       on another mozorg page, such as /whatsnew.
    5. If there’s no referrer cookie, we next look at document.referrer to see if the visitor came from a search engine.
       If found, we set utm_medium as organic and utm_source as the search engine name.
    6. Next, an FxA metrics function makes a flow API request to the Firefox Accounts authentication server. The
       request returns a series of metrics parameters that are used to track progress through the sign-up process. These
       “flow” parameters are also appended to each subscription link in addition to the existing attribution data.
    7. When someone clicks through and completes the subscription process, attribution data we passed through is
       emitted as event logs. This data is then joined to a person’s FxA account data during the Data Science team’s
       ETL process (Extract, Transform, Load), where data is then brought together in Big Query.
Note: UTM parameters on FxA links will only be replaced if the page URL contains both a valid utm_source and
utm_campaign parameter. All other UTM parameters are considered optional, but will still be passed through, as long
as the required parameters exist. This is to avoid mixing referral data from different campaigns.
FxA attribution referrer cookie
In situations where we want to try and track a visitor’s first entry point, say if someone lands on a /whatsnew page
and then clicks on a “Get Mozilla VPN” promo link, then we can set a referral cookie in someone’s browser when they
click a same-site link (step 4 in the list above).
The cookie can be set simply by adding the class name js-fxa-product-referral-link to a same-site link, along
with a data-referral-id attribute. When clicked, our attribution logic will use the value of data-referral-id
to augment utm_campaign when someone click through to the product page.
For example, a referral with data-referral-id="navigation" would result in the following utm parameters being
set on FxA links in the product landing page:
     • utm_source=www.mozilla.org.
1.20. Attribution                                                                                                   109
bedrock Documentation, Release 1.0
     • utm_campaign=navigation.
     • utm_medium=referral.
Mozilla VPN referral link helper
For Mozilla VPN, there’s a vpn_product_referral_link helper built specifically to help implement FxA referral
links to the VPN landing page:
{{ vpn_product_referral_link(
      referral_id='navigation',
      link_to_pricing_page=True,
      link_text='Get Mozilla VPN',
      class_name='mzp-t-secondary mzp-t-md',
      page_anchor='#pricing',
      optional_attributes= {
           'data-cta-text' : 'Get Mozilla VPN',
           'data-cta-type' : 'button',
           'data-cta-position' : 'navigation',
      }
) }}
The helper supports the following parameters:
  Parame-      Definition                                Format         Example
  ter name
  referral_idThe ID for the referring page / com-        String         ‘navigation’
               ponent. This serves as a value for
               ‘utm_campaign’.
               Link to the pricing page instead of the
  link_to_pricing_page                                   Boolean        True
               landing page (defaults to False).
  link_text The link copy to be used in the call to      Localized      ‘Get Mozilla VPN’
               action.                                   string
  class_name A class name to be applied to the link      String of one  ‘mzp-t-secondary mzp-t-md’
               (typically for styling with CSS).         or more class
                                                         names
  page_anchorAn optional page anchor for the link        String         ‘#pricing’
               destination.
               An dictionary of key value pairs con-
  optional_attributes                                    Dictionary     {‘data-cta-text’: ‘Get Mozilla VPN’,
               taining additional data attributes to in-                ‘data-cta-type’:    ‘button’, ‘data-cta-
               clude in the button.                                     position’: ‘navigation’}
The cookie has the following configuration:
               Cookie name                       Value               Domain                 Expiry
               fxa-product-referral-id           Campaign identifier www.mozilla.org        1 hour
110                                                                                       Chapter 1. Contents
                                                                           bedrock Documentation, Release 1.0
FxA flow metrics
Whilst UTM parameters are passed through to FxA links automatically for any page of the website, in order for FxA
flow metrics to be added to links, a specific JavaScript bundle needs to be manually run in the page that requires it.
The reason why it’s separate is that depending on the situation, flow metrics need to get queried and added at specific
times and conditions (more on that below).
To add FxA flow metrics to links, a page’s respective JavaScript bundle should import and initialize the
FxaProductButton script.
import FxaProductButton from './path/to/fxa-product-button.es6.js';
FxaProductButton.init();
The above JS is also available as a pre-compiled bundle, which can be included directly in a template:
{{ js_bundle('fxa_product_button') }}
When init() is called, FxA flow metrics will automatically be added to add FxA links on a page.
Important: Requests to metrics API endpoints should only be made when an associated CTA is visibly displayed on
a page. For example, if a page contains both a Firefox Accounts sign-up form and a Firefox Monitor button, but only
one CTA is displayed at any one time, then only the metrics request associated with the visible CTA should occur.
Note: For links generated using the fxa_link_fragment helper, you will also need to manually add a CSS class of
js-fxa-product-button to trigger the script.
Google Analytics guidelines
For GTM datalayer attribute values in FxA links, please use the analytics documentation.
1.20.5 Mozilla CJMS affiliate attribution
The CJMS affiliate attribution flow comprises an integration between the Commission Junction (CJ) affiliate marketing
event system, bedrock, and the Security and Privacy team’s CJ micro service (CJMS).
The system allows individuals who partner with Mozilla, via CJ, to share referral links for Mozilla with their audiences.
When people subscribe using an affiliate link, the partner can be attributed appropriately in CJ’s system.
How does attribution work?
For a more detailed breakdown you can view the full flow diagram (Mozilla access only), but at a high level the logic
that bedrock is responsible for is as follows:
   1. On pages which include the script, on page load, a JavaScript function looks for a cjevent query parameterin
       the page URL.
   2. If found, we validate the query param value and then POST it together with a Firefox Account flow_id to the
       CJMS.
1.20. Attribution                                                                                                    111
bedrock Documentation, Release 1.0
    3. The CJMS responds with an affiliate marketing ID and expiry time, which we then set as a first-party cookie.
       This cookie is used to maintain a relationship between the cjevent value and an individual flow_id, so that
       successful subscriptions can be properly attributed to CJ.
    4. If a website visitor later returns to the page with an affiliate marketing cookie already set, then we update the
       flow_id and cjevent value (if a new one exists) via PUT on their repeat visit. This ensures that the most recent
       CJ referral is attributed if/when someone decides to purchase a subscription.
    5. The CJMS then responds with an updated ID / expiry time for the affiliate marketing cookie.
How can visitors opt out?
    1. To facilitate an opt-out of attribution, we display a cookie notification with an opt-out button at the top of the
       page when the flow initiates.
    2. If someone clicks “Reject” to opt-out, we generate a new flow_id (invalidating the existing flow_id in the
       CJMS database) and then delete the affiliate marketing cookie, replacing it with a “reject” preference cookie that
       will prevent attribution from initiating on repeat visits. This preference cookie will expire after 1 month.
    3. If someone clicks “OK” or closes the opt-out notification by clicking the “X” icon, here we assume the website
       visitor is OK with attribution. We set an “accept” preference cookie that will prevent displaying the opt-out
       notification on future visits (again with a 1 month expiry) and allow attribution to flow.
Cookies
The affiliate cookie has the following configuration:
Note: To query what version of CJMS is currently deployed at the endpoint bedrock points to, you can add
__version__ at the end of the base URL to see the release number and commit hash. For example: https://stage.
cjms.nonprod.cloudops.mozgcp.net/__version__
1.20.6 Pocket mode
Google Tag Manager (GTM)
In pocket mode, bedrock also uses Google Tag Manager (GTM) to manage and organize its Google Analytics (GA4)
solution. This is mostly for marketing’s own use, and is not used by the Pocket organization.
In contrast to mozorg mode, GA in Pocket is mostly used for measuring a few key events, such as sign ups and logged-in
/ logged-out page views. Most of this event and triggering logic exists entirely inside GTM, as opposed to in bedrock
code.
Snowplow
Snowplow is the analytics tool used by the Pocket organization, which is something marketing has limited access to.
Snowplow is mostly used for tracking events in the Pocket web application, although we do also load it on the logged-out
marketing pages that are hosted by bedrock.
112                                                                                              Chapter 1. Contents
                                                                          bedrock Documentation, Release 1.0
How can visitors opt out of Pocket analytics?
Pocket website visitors can opt-out of both GA and Snowplow by changing their preferences in the One Trust Cookie
Banner we display on page load. If someone opts-out of analytics cookies, we do not load GA, however we do still load
Snowplow in a more privacy reserved mode.
Snowplow configuration with cookie consent (default):
{
     appId: SNOWPLOW_APP_ID,
     platform: 'web',
     eventMethod: 'beacon',
     respectDoNotTrack: false,
     stateStorageStrategy: 'cookieAndLocalStorage',
     contexts: {
          webPage: true,
          performanceTiming: true
     },
     anonymousTracking: false
}
Snowplow configuration without cookie consent:
{
     appId: SNOWPLOW_APP_ID,
     platform: 'web',
     eventMethod: 'post',
     respectDoNotTrack: false,
     stateStorageStrategy: 'none',
     contexts: {
          webPage: true,
          performanceTiming: true
     },
     anonymousTracking: {
          withServerAnonymisation: true
     }
}
See our Pocket analytics code for more details.
1.20. Attribution                                                                                                113
bedrock Documentation, Release 1.0
1.21 Architectural Decision Records
We record major architectural decisions for bedrock in Architecture Decision Records (ADR), as described by Michael
Nygard. Below is the list of our current ADRs.
1.21.1 1. Record architecture decisions
Date: 2019-01-07
Status
Accepted
Context
We need to record the architectural decisions made on this project.
Decision
We will use Architecture Decision Records, as described by Michael Nygard.
Consequences
See Michael Nygard’s article, linked above. For a lightweight ADR toolset, see Nat Pryce’s adr-tools.
1.21.2 2. Move CI/CD Pipelines to Gitlab
Date: 2019-10-09
Status
Superseded by 0010
Context
Our current CI/CD pipelines are implemented in Jenkins. We would like to decommission our Jenkins server by the
end of this year. We have implemented CI/CD pipelines using Gitlab in other projects, including basket, nucleus and
the snippets-service.
114                                                                                         Chapter 1. Contents
                                                                           bedrock Documentation, Release 1.0
Decision
We will move our existing CI/CD pipeline implementation from Jenkins to Gitlab.
Consequences
We will continue to use www-config to version control our Kubernetes yaml files, but we will replace the use of git-
sync-operator and its branch with self-managed instances of gitlab runner executing jobs defined in a new .gitlab-ci.yml
file leveraging what we have learned implementing similar solutions in nucleus-config, basket-config, and snippets-
config. We will also eliminate our last dependency on Deis Workflow, which we have been using for dynamic demo
deployments based on the branch name, in favor of a fixed number of pre-configured demo deployments, potentially
supplemented by Heroku Review Apps.
1.21.3 3. Use Cloudflare Workers and Convert for multi-variant testing
Date: 2019-10-09
Status
Accepted
Context
Our current method for implementing multi-variant tests involves frequent, often non-trivial code changes to our most
high traffic download pages. Prioritizing and running concurrent experiments on such pages is also often complex,
increasing the risk of accidental breakage and making longer-term changes harder to roll out. Our current tool, Traffic
Cop, also requires significant custom code to accomodate these types of situations. Accurately measuring and reporting
on the outcome of experiments is also a time consuming step of the process for our data science team, often requiring
custom instrumentation and analysis.
We would like to make our end-to-end experimentation process faster, with increased capacity, whilst also minimizing
the performance impact and volume of code churn related to experiments running on our most important web pages.
Decision
We will use Cloudflare Workers to redirect a small percentage of traffic to standalone, experimental versions of our
download pages. The worker code will live in the www-workers repository. We will implement a (vetted and approved)
third-party experimentation tool called Convert for use on those experimental pages.
Consequences
Convert experiment code will be separated from our main web pages, where the vast majority of our traffic is routed.
This will minimize code churn on our most important pages, and also reduce the performance impact and risks involved
in using a third-party experimentation tool. Using Cloudflare Workers to redirect traffic to experimental pages also has
significant performance benefits over handling redirection client-side.
In terms of features, Convert offers a custom dashboard for configuring, prioritizing, and running multi-variant tests. It
also has built-in analysis and reporting tools, which are all areas where we hope to see significant savings in time and
resources.
1.21. Architectural Decision Records                                                                                 115
bedrock Documentation, Release 1.0
1.21.4 4. Use Fluent For Localization
Date: 2019-12-16
Status
Accepted
Context
The current localization (l10n) system uses the outdated and unsupported .lang format, which our l10n team would
prefer to no longer support. Mozilla’s current l10n standard for products and websites is Fluent.
Decision
In order to update our l10n practices and technology and support from Mozilla’s existing l10n infrastructure and teams
we will decomission the .lang system in bedrock and implement one based on Fluent. We will support both during a
transition period.
Consequences
Dealing with strings and templates is very different in Fluent (see the updated bedrock docs). There will be a period
of developer training and adjustment to the new way of writing and previewing templates. The biggest change is that
strings are no longer in the templates at all, and are instead referenced by string IDs which are in Fluent files (.ftl files).
The positive side of this change is that the developer has total control over the strings in the translation files and there
are no string extraction or merge steps.
1.21.5 5. Use a Single Docker Image For All Deployments
Date: 2020-07-07
Status
Accepted
Context
We currently build an individual docker image for each deployment (dev, stage, and prod) that contains the proper data
for that environment. It would save time and testing if we only built a single image that could be promoted to each
environment and loaded with the proper data at startup.
116                                                                                               Chapter 1. Contents
                                                                             bedrock Documentation, Release 1.0
Decision
We will use a Kubernetes DaemonSet to ensure that a data updater pod is running on each node in a cluster. This pod
will keep the database and l10n files updated in a volume that will be used by the other bedrock pods to access the data.
GitHub issue
Consequences
This change means that bedrock will be more simple to run because each pod will no longer need to be responsible
for keeping its data updated, and so it will run only the bedrock web process and not also the updater daemon. It also
means that there is a risk of a bedrock pod being run on a node that hasn’t had the updater pod run yet, so there would
be no available data. We will handle this by ensuring that bedrock won’t start when the data isn’t available, and so k8s
will not send traffic to those pods until they’re successfully up and responding, and will keep trying to start pods on the
node untill they succeed.
1.21.6 6. Revise tooling for Python dependency management
Date: 2022-02-25
Status
Superseded by 0007, but the context in this ADR is still useful
Context
At the moment of revisiting our dependency-management approach, Bedrock’s Python dependencies were installed
from a hand-cut requirements/*.txt files which (sensibly) included hashes so that we could be sure about what our
Python package installer, pip, was actually installing.
However, this process was onerous:
     • We had a number of requirements files, base, prod, dev, migration (no longer required but still being pro-
       cessed at installation time) and docs - all of which had to be hand-maintained.
     • Hashes needed to be generated when adding/updating a dependency. This was done with a specific tool hashin
       and needed to be done for each requirement.
     • When pip detects hashes in a requirements file, it automatically requires hashes for all packages it installs,
       including subdependencies of dependecies mentioned in requirements/*.txt. This in turn meant that adding
       or updating a new dep often required hashing-in one or more subdeps – and at worst, a change or niggle with
       pip would result in a new subdep being implicitly required, which would then fail to install because it was not
       hashed in to the requirements file.
Other projects (both within MEAO and across Mozilla) used more sophisticated dependency management tools, in-
cluding:
     • pip-tools - which draws reqs from an input file and generates a requirements.txt complete with hashes
     • pip-compile-multi - which extends pip-tools’ behaviour to support multiple output files and shared input files
     • poetry - which combines a lockfile approach with a standalone virtual environment
     • pipenv - which similarly combines a lockfile with a virtual environment
     • conda - a language-agnostic package manager and environment management system
1.21. Architectural Decision Records                                                                                   117
bedrock Documentation, Release 1.0
    • simply pip
The ideal solution would support all of the following:
    • Simple input file format/syntax
    • Ability to pin dependencies
    • Support for installing with hash-checking of packages
    • Automatic hashing of requirements, rather than having to manually do it with hashin et al.
    • Support for multiple build configurations (eg prod, dev, docs)
    • Dependabot compatibility, so we still get alerts and updates
    • An unopinionated approach to virtualenvs – can work with and without them, so that developers can use the
       virtualenv tooling they prefer and we don’t have to use a virtualenv in our containers if we don’t want to
    • Sufficiently active maintenance of the project
    • Use/knowledge of the tooling elsewhere in the broader organisation
Decision
After evaluating the above, including pip-tools, pip-compile-multi and poetry in greater depth,
pip-compile-multi was selected.
Significant factors were how allows us to pin our top-level dependencies in a clutter-free input format, supports inher-
itance between files and miltiple output files with ease, and it automatically generates hashes for subdependencies.
Consequences
pip-compile-multi has been easily integrated into the Bedrock workflow, but there is one non-trivial downside:
Github’s Dependabot service does not play well with the combination of multiple requirements files and inheritance
between them. As such, does not currently produce reliable updates (either partial updates or some requirements files
seem to be ignored entirely). See https://github.com/dependabot/dependabot-core/issues/536
Strictly, though, we don’t need the convenience of Dependabot - we have a make command to identify stale deps and
recompiling is another, single, make command. Also, we’re more likely to compile a bunch of Dependabot PRs into
one changeset (eg with paul-mclendahand), than to merge them straight to master/main one at at time. As long as
we’re getting Github security alerts for vulnerable dependencies, we’ll be OK.
That said, if we did find we needed Dependabot compatibility, pip-tools and some extra legwork in the Makefile to
deal with prod, dev and docs deps separately would likely be a viable alternative.
1.21.7 7. Further revise tooling for Python dependency management
Date: 2022-03-02
118                                                                                             Chapter 1. Contents
                                                                           bedrock Documentation, Release 1.0
Status
Proposed
Context
While pip-compile-multi gave us plenty fot benefits (see ADR 0006) the lack of Dependabot support was an annoyance
and replacing it with alternatives seemed fairly involved.
Decision
We’ve downgraded to regular pip-compile and instead are doing the extra legwork in the Makefile instead. The
input files are indentical, so we do not need to pin sub-dependencies, and we still get automatic hash generation for all
packages.
Consequences
There should be no downsides to switching away from pip-compile-multi in this context. If Dependabot still does not
manage to parse our multiple requirements files, we should look to renaming them in case that tips the balance (as has
been suggested by a colleague)
1.21.8 8. Move Demos To GCP
Date: 2022-07-14
Status
Accepted
Context
Previously, demos for Bedrock were run on Heroku. This worked fine, but Heroku’s recent security incident there
meant our integration had to be disabled, prompting discussion of self-managed demo instances.
In addition, while it was possible to demo Bedrock in Pocket Mode on Heroku, by amending the settings via the
Heroku web UI, the domains set up (www-demoX.allizom.org) were originally set up for Mozorg, and as such may
be confusing for colleagues reviewing Pocket changes. Flipping and un-flipping settings in Heroku to enable Mozorg
Mode or Pocket Mode was also extra legwork that we ideally would do without, too.
Decision
We have implemented a new, self-managed, approach to running demos, using a handful of Google Cloud Platform
services. Cloud Build and Cloud Run are the most significant ones.
Cloud Build has triggers which monitor pushes to specific branches, then builds a Bedrock container from the branch,
using the appropriate env vars for Pocket or Mozorg use, including the SITE_MODE env var that specifies the mode
Bedrock runs in.
Cloud Run then deploys the built container as a ‘serverless’ webapp. By default, supervisord runs in the container, so
it updates DB and L10N files automatically.
1.21. Architectural Decision Records                                                                                119
bedrock Documentation, Release 1.0
This process is triggered by a simple push to a specific target branch. e.g. pushing code to mozorg-demo-2 will result
in the relevant code being deployed in Mozorg mode to www-demo2.allizom.org, while pushing to pocket-demo-4 will
deploy it to www-demo4.tekcopteg.com in Pocket mode.
Environment variables can also be configured by developers, via two dedicated env files in the Bedrock codebase,
which are only used for demo services. Clashes are unlikely, and can still be managed with common sense.
Consequences
Upsides:
It is now easier to stand up Pocket demos in addition to existing Mozorg demos, plus we have full control over the
infrastructure our demos are run on.
We will no longer need to use Heroku for demos. In the future, we may also be able to support ad-hoc ‘review apps’,
which we have also used Heroku for in the past.
Downsides:
    1) If a new secret value is required on a demo instance, and so that value cannot go into the demo env vars file
       because our codebase is public, some SRE-like devops is needed to add that secret value to GCP’s Secret Manager
       Service. This can be quick, but requires understanding how that side fits together, plus access, so may need a
       backender to add them.
    2) At the moment, only the MEAO Backend team have GCP access, which is handy to monitor whether a demo has
       successfull be pushed out, or to amend secrets, etc. Both of these issues can be addressed without a lot of work.
1.21.9 9. Manage Contentful schema state via migrations
Date: 2022-09-09
Status
Accepted
Context
Our chosen CMS Contentful is powerful and can be configured via its UI quite easily. However, wanted to bring this
under control using migrations so that changes are explicit, reviewable, repeatable and stored. This would be a key
part of moving to a “CMS-as-Code” approach to using Contentful, where content-type changes and data migrations
(outside of regular content entry) are managed via code.
Decision
We wanted to have as close as possible to the experience provided by the excellent Django Migrations framework,
where we would:
     • be able to script migrations, rather than resort to “clickops”
     • be able to apply them individually or en masse
     • be able to store the state of which migrations have/have not been applied in a central datastore (and ideally
       Contentful)
120                                                                                            Chapter 1. Contents
                                                                             bedrock Documentation, Release 1.0
We experimented with hand-cutting our own framework, which was looking viable, but then we came across
https://github.com/jungvonmatt/contentful-migrations which does all of the above. We’ve evaluated it and it seems
fit for purpose, even if it has some gaps, so we’ve adopted it as our current way to manage and apply migrations to our
Contentful setup.
Consequences
We’ve gained a tool that enables code-based changes to Contentful, which helps in two ways:
    1) It enables and eases the initial work to migrate from Legacy Compose to new Compose (these are both ways of
        structuring pages in Contentful)
    2) It lays tracks for moving to CMS-as-Code
1.21.10 10. Move CI to Github Actions for Unit and Integration tests
Date: 2023-04-06
Status
Accepted
Context
Prior to this work, Bedrock’s CI/CD pipeline involved Github, Gitlab and CircleCI. We were mirroring from Github to
Gitlab to benefit from Gitlab’s CI tooling for our functional integration tests, including private (i.e. Mozilla-managed)
runners.
Additionally, we were using a third party (CircleCI) to run our Python and JS unit tests.
Since then, two things have changed:
     1. Github Actions (GHA) have arrived
     2. We are now able to use private runners with GHA
Decision
We will move our CI/CD pipeline from being a combination of Github + Gitlab + CircleCI to just Github, using GHA.
This will mean:
     1. The mirroring to Gitlab will no longer be necessary.
     2. Unit tests move from CircleCI to GHA. They will continue to be run on every PR raised against mozilla/
        bedrock.
     3. Functional/integration tests move from Gitlab to GHA. They will still be triggered by a successful deployment
        to dev/test/stage/prod.
This work will be carried out in parallel with changes to how our deployment pipeline works, as that side is also being
moved out of Gitlab and into GHA + GCP. When a deployment succeeds, a GHA in the deployment repo will trigger
a GHA in mozilla/bedrock, which will then run the functional integration tests.
1.21. Architectural Decision Records                                                                                 121
bedrock Documentation, Release 1.0
Consequences
Pros
    • We’re no longer mirroring from Github to Gitlab, which will make understanding the deployment pipeline easier
      for new (and current) developers
    • We will no longer have Gitlab in our pipeline, removing a potential point of failure that could block releases
    • We can still use private runners for our functional integration tests and more (just via GHA instead of Gitlab),
      giving us control over security and machine resource spec
Cons
    • There’s a risk that there will still be new race conditions or CI kick-off failures if the webhook from the deployment
      repo to mozilla/bedrock fails.
    • We will not all get visibility of a failed webhook ping from the deployment repo’s GHA, because that’s locked
      down to be private. We can mitigate this risk with a sensible pattern of Slack notifications (e.g. Start, Success,
      Failure), so a missing notification will itself be a significant thing.
1.21.11 11. Use StatsD for metrics collection
Date: 2023-05-19
Status
Accepted
Context
We need to implement a metrics collection solution to gain insights into the performance and behavior of bedrock.
Metrics play a crucial role in understanding system health, identifying bottlenecks, and making informed decisions for
optimization and troubleshooting.
Decision
StatsD is a proven open-source solution that provides a lightweight and scalable approach to capturing, aggregating,
and visualizing application metrics. It offers numerous benefits that align with bedrock’s needs:
   1. Simplicity and Ease of Integration: StatsD is easy to install and integrate into our existing Python codebase.
      It provides a simple API that allows us to instrument our code and send metrics with minimal effort.
   2. Aggregation and Sampling: StatsD supports various aggregation methods, such as sum, average, maximum,
      and minimum, which can be applied to collected metrics. Additionally, it provides built-in support for sampling,
      allowing us to reduce the volume of metrics collected while still maintaining statistical significance.
   3. Scalability: StatsD is designed to handle high volumes of metrics and can easily scale horizontally to accommo-
      date increasing demands. It relies on a fire-and-forget mechanism, where the metrics are sent asynchronously,
      ensuring minimal impact on the performance of our application.
122                                                                                                  Chapter 1. Contents
                                                                             bedrock Documentation, Release 1.0
   4. Integration with Monitoring and Visualization Tools: At Mozilla we already have a stack available and con-
      figured by SRE that uses StatsD along with Telegraf to send metrics to Grafana for visualization and monitoring.
      This integration will enable us to analyze and visualize our metrics, create dashboards, and set up alerts for
      critical system thresholds.
Overview of how StatsD, Telegraf, and Grafana work together.
Here’s an overview of how these tools fit into the workflow:
    • StatsD: StatsD is responsible for collecting and aggregating metrics data within the application. It provides a
      simple API that allows us to instrument our code and send metrics to a StatsD server. StatsD operates over UDP
      and uses a lightweight protocol for sending metrics.
    • Telegraf: Telegraf is an agent-based data collection tool that can receive metrics from various sources, includ-
      ing StatsD. Telegraf acts as an intermediary between the data source (StatsD) and the data visualization tool
      (Grafana). It can collect, process, and forward metrics data to different destinations.
    • Grafana: Grafana is a popular open-source data visualization and monitoring tool. It provides a rich set of
      features for creating dashboards, visualizing metrics, and setting up alerts. Grafana can connect to Telegraf to
      retrieve metrics data and display it in a user-friendly and customizable manner.
Consequences
   1. Metrics Design and Instrumentation: Proper metrics design and instrumentation are crucial to deriving mean-
      ingful insights. We need to invest time and effort in identifying the key metrics to capture and strategically
      instrument our codebase to provide actionable data for analysis.
   2. Operational Overhead: Introducing a new tool requires additional operational effort for monitoring, maintain-
      ing, and scaling the StatsD infrastructure. However, since this infrastructure is in use currently by other projects
      within Mozilla, this overhead is already being assumed and is spread out across projects.
   3. Integration Effort: While integrating StatsD into bedrock is relatively straightforward, we will need to allocate
      development time to instrument our codebase and ensure that metrics are captured at relevant points within the
      application.
Considerations and best practices for metrics design
    • Identify Key Metrics: Identify the key aspects of our website that we want to monitor and measure. These could
      include response times, error rates, database query performance, and cache hit ratios.
    • Granularity and Context: Determine the appropriate level of granularity for our metrics. We can choose
      to measure metrics at the application level, specific Django views, individual API endpoints, or even down to
      specific functions or code blocks within bedrock.
    • Define Consistent Metric Names: Choose meaningful and consistent names for our metrics. This helps in easily
      understanding and interpreting the collected data.
    • Timing Metrics: Use timing metrics to measure the duration of specific operations. This can include measuring
      the time taken to render a template, execute a database query, or process a request. StatsD provides a timing metric
      type that captures the duration and calculates statistics such as average, maximum, and minimum durations.
    • Counting Metrics: Use counting metrics to track occurrences of specific events. This can include counting the
      number of requests received or the number of errors encountered. StatsD supports counting metric types that
      increments a value each time an event occurs.
1.21. Architectural Decision Records                                                                                  123
bedrock Documentation, Release 1.0
     • Sampling: Consider implementing sampling to reduce the number of metrics collected while still maintaining
        statistical significance. We can selectively sample a subset of requests or events to ensure a representative sample
        of data for analysis if a particular metric is of high volume.
     • Re-evaluate often: Continuously evaluate our metrics and refine them based on changing requirements and
        insights gained from analysis.
1.22 Browser Support
We seek to provide usable experiences of our most important web content to all user agents. But newer browsers are
far more capable than older browsers, and the capabilities they provide are valuable to developers and site visitors. We
will take advantage of modern browser capabilities. Older browsers will have a different experience of the website than
newer browsers. We will strike this balance by generally adhering to the core principles of Progressive Enhancement:
     • Basic content should be accessible to all web browsers
     • Basic functionality should be accessible to all web browsers
     • Sparse, semantic markup contains all content
     • Enhanced layout is provided by externally linked CSS
     • Enhanced behavior is provided by unobtrusive, externally linked JavaScript
     • End-user web browser preferences are respected
Some website experiences may require us to deviate from these principles – imagine a marketing campaign page
built under timeline pressure to deliver novel functionality to a particular locale for a short while – but those will be
exceptions and rare.
1.22.1 Browser Support Matrix
Last updated: Updated July 19, 2023
Firefox
It is important for website visitors to be able to download Firefox on a very broad range of desktop operating systems.
As such, we aim to deliver enhanced support to user agents in our browser support matrix below.
Enhanced support:
        Windows 11 and above
                 • All evergreen browsers
                      – Firefox
                      – Firefox ESR
                      – Chrome
                      – Edge
                      – Brave
                      – Opera
        Windows 10
                 • All evergreen browsers
124                                                                                                Chapter 1. Contents
                                                                          bedrock Documentation, Release 1.0
      macOS 10.15 and above
               • All evergreen browsers
               • Safari
      Linux
               • All evergreen browsers
Degraded support:
Website visitors on slightly older browsers fall under degraded support, which means that the website should be fully
readable and accessible, but they may not get enhanced CSS layout or JS features.
      Windows 10
               • Internet Explorer 11
      Windows 8.1 and below
               • Firefox 115
               • Chrome 109
               • Internet Explorer 10
      macOS 10.14 and below
               • Firefox 115
               • Chrome 114
               • Safari 12.1
Note: As of Firefox 116 (released August 1st 2023), support for Firefox has been ended on Windows 8.1 and below,
as well as on macOS 10.14 and below. Website visitors on these outdated operating systems now fall under degraded
support, and we offer them to download Firefox ESR instead.
Basic support:
Website visitors on very old versions of Internet Explorer will get only a very basic universal CSS style sheet, and a
basic no-JS experience.
      Windows 7
               • Internet Explorer 9
               • Internet Explorer 8
Unsupported:
Even older versions of Internet Explorer are now unsupported.
      Windows XP / Vista
               • Internet Explorer 7
               • Internet Explorer 6
Note: Firefox ended support for Windows XP and Vista in 2017 with Firefox 53. Since then, we have continued to
serve those users Firefox ESR 52 instead. However, since then support for downloading has been discontinued. The
SSL certificates on download.mozilla.org no longer support TLS 1.0.
1.22. Browser Support                                                                                             125
bedrock Documentation, Release 1.0
Privacy & security products
Browser support for our privacy and security products (such as VPN, Relay, Monitor etc) is thankfully a simpler story.
Since all these product use a Firefox account for authentication, we can simply follow the Firefox Ecosystem Platform
browser support documentation.
The most notable thing here for bedrock is that Internet Explorer 11 does not need to be supported.
1.22.2 Delivering basic support
On IE browsers that support conditional comments (IE9 and below), basic support consists of no page-specific CSS or
JS. Instead, we deliver well formed semantic HTML, and a universal CSS stylesheet that gets applied to all pages. We
do not serve these older browsers any JS, with the exception of the following scripts:
     • Google Analytics / GTM snippet.
     • HTML5shiv for parsing modern HTML semantic elements.
     • Stub Attribution script (IE8 / IE9).
Conditional comments should instead be used to handle content specific to IE. To hide non-relevant content from IE
users who see the universal stylesheet, a hide-from-legacy-ie class name can also be applied directly to HTML:
<p class="hide-from-legacy-ie">See what Firefox has blocked for you</p>
1.22.3 Delivering degraded support
On other legacy browsers where conditional comments are not supported, developers should instead rely on feature
detection to deliver a degraded experience where appropriate.
Note: The following feature detection helpers will return true for all browsers that get enhanced support, but will also
return true for IE11 currently, even though that has now moved to degraded support. The reason for this is that whilst
many of our newer products don’t support IE at all (e.g. Mozilla VPN, Firefox Monitor, Firefox Relay), we do still
need to provide support so that IE users can easily download Firefox. We can decide to update the feature detect in the
future, at a time when we think makes sense.
Feature detection using CSS
For CSS, enhanced experiences can be delivered using feature queries, whilst allowing older browsers to degrade
gracefully using simpler layouts when needed.
Additionally, there is also a universal CSS class hook available that gets delivered via a site-wide JS feature detection
snippet:
.is-modern-browser {
      /* Styles will only be applied to browsers that get enhanced support. */
}
126                                                                                             Chapter 1. Contents
                                                                           bedrock Documentation, Release 1.0
Feature detection using JavaScript
For JS, enhanced support can be delivered using a helper that leverages the same feature detection snippet:
(function() {
     'use strict';
     function onLoad() {
           // Code that will only be run on browsers that get enhanced support.
     }
     window.Mozilla.run(onLoad);
})();
The site.isModernBrowser global property can also be used within conditionals like so:
if (window.site.isModernBrowser) {
     // Code that will only be run on browsers that get enhanced support.
}
1.22.4 Exceptions (Updated 2019-06-11)
Some pages of the website provide critical functionality to older browsers. In particular, the Firefox desktop download
funnel enables users on older browsers to get a modern browser. To the extent possible, we try to deliver enhanced
experiences to all user agents on these pages.
The following pages get enhanced experiences for a longer list of user agents:
    • /firefox/
    • /firefox/new/
    • /firefox/download/thanks/
Note: An enhanced experience can be defined as a step above basic support. This can be achieved by delivering extra
page-specific CSS to legacy browsers, or allowing them to degrade gracefully. It does not mean everything needs to
look the same in every browser.
1.22. Browser Support                                                                                              127
