Skip to content

Environment variables and secrets

Your functions read their settings from .env files, secrets from .secret.local, and the emulators’ addresses from variables Firenook sets. This page lists what Firenook reads, in which order, and what your code sees.

Put the files in your codebase’s folder, next to its package.json:

functions/.env
SHOP_NAME=Demo Shop
SUPPORT_EMAIL=help@example.com

Firenook reads these files, as the official CLI does. A later file wins over an earlier one for the same key:

File What it is for
.env Values for every project
.env.<project id>, such as .env.demo-shop Values for one project, by its id
.env.<alias>, such as .env.staging Values for one project, by its .firebaserc alias
.env.local Values for the emulators only, never deployed

The start says which files it loaded:

firenook functions: Loaded environment variables from .env, .env.demo-shop, .env.local.

When firebase.json gives the codebase a configDir, the .env files are read from that folder instead.

Which of .env.<project id> and .env.<alias> is read depends on how the project was chosen. With this .firebaserc:

.firebaserc
{
"projects": {
"default": "demo-shop",
"staging": "demo-shop-staging"
}
}
You start with Files read
--project staging, an alias .env, .env.staging, .env.local
No --project, after firebase use staging in this folder .env, .env.staging, .env.local
--project demo-shop, a project id .env, .env.demo-shop, .env.local
No --project, so the default alias .env, .env.demo-shop or .env.default, .env.local

A project named by its id has no alias. Having both files for one project, its id’s and its alias’s, stops the codebase loading, as on the official emulator. Here with .env.demo-shop-staging and .env.staging and --project staging:

firenook functions: Failed to load function definition from source: FirebaseError: Can't have both dotenv files with projectId (env.demo-shop-staging) and projectAlias (.env.staging) as extensions.

Keep one of the two. Without --project, .env.<project id> and .env.default count as such a pair.

The files use the same dialect as the official CLI: KEY=value lines, an optional export in front, # comments, double quotes with \n escapes that can span lines, and single quotes kept as written. A key starts with an uppercase letter or an underscore and holds only uppercase letters, digits and underscores. Keys that start with X_GOOGLE_, FIREBASE_ or EXT_, and names the runtime uses, such as PORT, K_SERVICE and GCLOUD_PROJECT, are refused, and the codebase does not load:

firenook functions: Failed to load function definition from source: FirebaseError: Failed to load environment variables from .env.
Validation failed
Failed to validate key FIREBASE_REGION: Key FIREBASE_REGION starts with a reserved prefix (X_GOOGLE_ FIREBASE_ EXT_)

Firenook watches the .env files and .secret.local, but a change does not reload your functions, as in the official CLI. Your functions keep the old values until the next reload. Press r in the terminal to reload, or use Reload in the console. Until you do, the codebase’s page in the console names the file that changed after the last load.

Declare a secret with defineSecret and bind it to the functions that use it:

functions/index.js
const {onRequest} = require("firebase-functions/https");
const {defineSecret} = require("firebase-functions/params");
const stripeKey = defineSecret("STRIPE_KEY");
exports.checkout = onRequest({secrets: [stripeKey]}, (request, response) => {
// stripeKey.value() is the value from .secret.local
});

Give it a value in .secret.local, in the codebase’s source folder (never in configDir):

functions/.secret.local
STRIPE_KEY=sk_test_123

Firenook never contacts Secret Manager. The official emulator tries your production secrets first when .secret.local lacks one; Firenook leaves a missing secret empty, and the SDK logs a warning when your code reads it:

No value found for secret parameter "STRIPE_KEY". A function can only access a secret if you include the secret in the function's dependency array.

firenook init writes a functions/.gitignore with *.local, which keeps .env.local and .secret.local out of Git.

Params declared with defineString, defineInt, defineBoolean, defineList and the like take their values from the same .env files. Call .value() in your function to read one:

functions/index.js
const {onRequest} = require("firebase-functions/https");
const {defineString, defineInt} = require("firebase-functions/params");
const greeting = defineString("GREETING", {default: "hello"});
const pageSize = defineInt("PAGE_SIZE", {default: 20});
exports.settings = onRequest((req, res) => {
res.json({shop: process.env.SHOP_NAME, greeting: greeting.value(), pageSize: pageSize.value()});
});

Firenook never asks for a value. It resolves params as the official emulator does when no terminal is attached:

  • A value in an .env file wins.

  • Otherwise the param takes its default, in .value() and in a function’s options, such as region, memory or timeoutSeconds. The start names the params that took theirs:

    firenook functions: Params no env file sets take their default: GREETING, PAGE_SIZE.

    and the function answers with them:

    Terminal window
    $ curl http://127.0.0.1:5001/demo-shop/us-central1/settings
    {"shop":"Demo Shop","greeting":"hello","pageSize":20}
  • A param with no value and no default stops its codebase loading, with the official question and where to put the answer. Here defineString("API_URL") has neither:

    firenook functions: Failed to load function definition from source: FirebaseError: Question "Enter a string value for API_URL:" does not have a default and cannot be answered in non-interactive mode. Firenook never prompts: set the value in .env, .env.demo-shop or .env.local, or give the param a default.

    When it is the only codebase, the start stops there.

Two differences are deliberate. The official emulator writes the answers it took, defaults included, to .env.local; Firenook writes nothing and takes the default again at every load, so a changed default applies on the next reload. And a list param reaches your code as the JSON array the SDK’s .value() parses, as a deployed function gets it.

The console’s Environment tab, on a function’s page, lists every param with the file its value came from, marks the ones that took their default, and can write a value to .env.local for you. It also lists the secrets a function declares and can add a missing one to .secret.local.

Code that reads process.env while it loads, outside a function, sees the .env values too, as on the official emulator: a region taken from process.env.SHOP_REGION is the same at load and in the function. Defaults and secrets are not there yet; the function has them.

PROJECT_ID, GCLOUD_PROJECT, STORAGE_BUCKET and DATABASE_URL are built in: options that use them get the project’s values.

Firenook starts one Node worker per codebase. Its environment is yours, with your .env values over it and the emulator’s own variables over both. FIREBASE_TOKEN is removed, and GOOGLE_APPLICATION_CREDENTIALS and CLOUDSDK_CONFIG point at demo credentials in the run’s folder, so your code never holds your Google credentials, and a token it asks for comes from the emulators. A GOOGLE_APPLICATION_CREDENTIALS your own .env sets is kept, and requests made with it go to Google.

These are the variables Firenook sets, with the values a project on the default ports gets:

Variable Value
FUNCTIONS_EMULATOR true
FIRESTORE_EMULATOR_HOST 127.0.0.1:8080
FIREBASE_AUTH_EMULATOR_HOST 127.0.0.1:9099
FIREBASE_STORAGE_EMULATOR_HOST 127.0.0.1:9199
STORAGE_EMULATOR_HOST http://127.0.0.1:9199
PUBSUB_EMULATOR_HOST 127.0.0.1:8085
CLOUD_TASKS_EMULATOR_HOST 127.0.0.1:9499
CLOUD_EVENTARC_EMULATOR_HOST http://127.0.0.1:9299
FIREBASE_EMULATOR_HUB 127.0.0.1:4400
GCLOUD_PROJECT your project id, such as demo-shop
FIREBASE_CONFIG {"storageBucket":"demo-shop.appspot.com","databaseURL":"https://demo-shop.firebaseio.com","projectId":"demo-shop"}
TZ UTC
K_SERVICE, FUNCTION_TARGET the name of the function being called

The Admin SDK reads the *_EMULATOR_HOST variables, so initializeApp() with no arguments talks to the emulators. The ports follow your firebase.json.

Change a value and see it in a call: debugging and reload on save covers reloads, the debugger and the logs.