Skip to content
All the code for It also serves as the v1 for
HTML JavaScript CSS Other
Branch: master
Clone or download

Latest commit

Fetching latest commit…
Cannot retrieve the latest commit at this time.


Type Name Latest commit message Commit time
Failed to load latest commit information.
static.json (used by

This repo hosts the code for, which is also used to power the v1 backend of "give ppe" features on

New volunteer?

Join the slack!

  • new dev? please look at issues and comment on something to grab it!
  • new data moderator? Join the slack and come to #data!
  • not either? The most useful contribution is identifying more drop off locations and plugging them into the form linked on the public website, so if you don't see an issue here that calls to you, please work on that! Advice on making calls is in #131

Current setup

  • The website reads from a google sheet, generates a json blob, which is used to generate static HTML.

Reading our data to build your own frontend

  • Our data file updates every five minutes and can be read from
  • If you read the json directly, you need to ignore entries without an 'x' in the first field. Otherwise, you may publish info hospitals asked to have taken down. Don't do it!
  • If this sounds like too much work, then please use our:

Embeddable widget of donation sites

  • We have produced an embeddable version of our map, data and filters, without the call to action that's at the top of This was designed for on March 22, but can be reused by anyone.
  • You can view it here:
  • To embed into your site, use this html snippet:
<iframe style="width: 100%; height: 800px; border: none;" src=""></iframe>
  • We also support state specific data views, hiding the map, and hiding the filters through query params:
?hide-list={true/false} (also hides filters)
?hide-search={true/false} (beta)

All boolean parameters default to false (unless they're in beta).

So, for state-specific pages you can now use something like: This will return just the filtered list of donations sites in California.

Beta features:

Since beta features are disabled by default, you can enable them via:


Current Countries

  • United States - us
  • France - fr
  • Canada - ca

Current Locales

  • English - en
  • French - fr

Adding Countries and Locales

We use a directory structure to view country-specific datasets.

For example, /us/give.html will filter the map to the United States and /fr/give.html will filter to France.

To view translated version of a country you can pass in a locale parameter. /us/give.html?locale=fr-FR will show the map of the United States in French and /fr/give.html?locale=en-US will show the map of France in English.

To add a new country, you need to set a few variables.

  1. Get the country code from
  2. Add the country code and a link to the donation form to donation-form-bounce.html. The form should include translations for all official languages in that country.
  3. Add the translated strings for all official languages in that country in i18n.js. As a starting point, it is OK to launch a new language using an international variant. e.g. you can launch Canada with en translations and fr translations, they do not need to be localized to en-CA and fr-CA.
  4. Update the list of languages and countries in countries.js and locales.js and ensure that they propagate correctly to the language and country dropdowns. In countries.js you should also add the name of the string for that country's administrative region. e.g. US = "State", CA "Province", FR = "Department", copy for who they should direct large donations to, and copy for who they should contact if there are no donation sites near them.

Directory structure

  • /public - The client-side code for the website. Currently has some symlinks to legacy file locations.
  • /functions - The cloud function used to generate data.json. Not needed for frontend work.


Basic architecture

Firebase is used to pull data from our moderated datastore and then generate a data.json. There is a production environment findthemasks and a dev environment findthemasks-dev.

The setup uses cloud functions to provide http endpoints, cloud-storage to keep the generated results, and the firebase realtime database (NOT firestore) to cache oauth tokens.

Adding an oauth token requires hitting the /authgoogleapi?sheetid=longstring on the cloud-function endpoint and granting an OAuth token for a user that has access to the sheet.

How to deploy

  • Install the firebase cli for your platform.
  • Do once
    • firebase login
    • firebase use --add findthemasks-dev
    • firebase use --add findthemasks
    • cd functions; npm install # Note you need node v8 or higher. Look a nvm
  • Switch deployment envrionments firebase use [findthemasks or findthemasks-dev]
  • Deploy the cloud function. cd functions; npm run deploy

How to set config variables.

Secrets and configs not checked into github are specified via cloud function configs.

To set a config:

firebase functions:config:set findthemasks.geocode_key="some_client_id"

In code, this can be retrieved via:


In get all configs:

firebase functions:config:get

The namespace can be anything. Add new configs to the findthemasks namespace.

How to locally develop

Firebase comes with a local emulation environment that lets you live develop against localhost. Since we are using firebase configs, first we have to snag the configs from the environment. Do that with:

firebase functions:config:get > .runtimeconfig.json

Next generate a new Firebase Admin SDK private key here:

And save it to service_key.json

Then start up the emulator. Note this will talk to the production firebase database (likely okay as the firebase database is just storing oauth tokens).

export GOOGLE_APPLICATION_CREDENTIALS=/path/to/service_key.json
firebase emulators:start --only functions

This will create localhost versions of everything. Cloud functions should be on http://localhost:5001 and console.log() messages will stream to the terminal.

There is also a "shell"

firebase functions:shell

that can be used, but running the emulator and hitting with a web browser is often easier in our simple case.

Scripts that edit the spreadsheet (Apps Script)

There are currently 2 scripts that automatically update the spreadsheet, and one that backs it up:

  • fillInGeocodes: fills in the lat/lng column based on the address in the "address" column. (Note that the "address" column is defined as the column that has "address" in row 2.) Uses Google Maps geocoding API. Currently runs once/minute.
  • createStandardAddress: fills in the "address" column based on the data in the "orig_address", "city", and "state" columns. Uses Google Maps geocoding API. Currently runs once/minute.
  • backupSheet: makes a timestamped copy of the sheet. Currently runs once every 2 hours.

There are a few important things to know about these scripts:

  • They are visible by navigating to tools > script editor from the Google Sheet.
  • They can be run by anyone who has edit permission to the Google Sheet.
  • Triggers (automation) can be set up by navigating to Edit > Edit current project's triggers.
  • The dev-owner of each sheet should set up a trigger for each of the 3 scripts. (US spreadsheet dev owner is @susanashlock's gmail).
  • The scripts are run using quota of the user the user that runs them.
  • Each Gmail user has a fixed amount of geocoding quota per day. This quota is somewhere around 100-500 calls per day. @susanashlock's account has 'special' quota. We're not sure exactly what it is, but is sufficient to support ~1000 calls per day.


You can’t perform that action at this time.