Environment variables in Expo projects

  • .env is for environment specific variables (DEBUG=true), it's not for storing secrets (API keys..)
  • The process.env property returns an object containing the user environment. Check out all the environment variables that are set
console.log(process.env);
  • You can't process.env use values in app.json, but you can use them in app.config.js. So, for example, hooks.postPublish.config.authToken for Sentry was in app.json before (as recommended in the docs), but if you use app.config.js you can load that auth token in via a secured environment variable.
  • On Windows, we use set to define environment variables while on Linux we use export
  • In React Native, __DEV__ is a global variable with a boolean value that indicates whether you're running in development mode or not. iOS or Android simulators will have his value true
  • __DEV__ can not be set inside babel.config.js (Expo). You'll get
node_modules/expo/AppEntry.js: __DEV__ is not defined
Failed building JavaScript bundle.

Expo options:

  • babel-plugin-transform-inline-environment-variables will replace environment variables values inside your code with process.env.BLAH, but you need to pass an include array while configuring it in Babel in order for any variable to work. ref. Will NOT work with a .env file
  • How to load variable values from a file .env
  • How to reference those values in config file app.json, babel.config.js etc.

For production to be available in code, you need to pass it when starting expo. Start the app/server in production mode

# setting env vars
NODE_ENV=production expo start
# require dotenv when running the script
node -r dotenv/config dotenv-example.js

NODE_ENV and BABEL_ENV are both undefined by default\

Expo - Release channels

You can use release channels --release-channel to set environment specific variable configuration. To set a release channel, you pass it when you run the command.

expo publish --release-channel prod-v1

And you can reference it with Constants.manifest.releaseChannel

import { Constants } from 'expo'

console.log(Constants.manifest.releaseChannel)

dotenv

dotenv vs. release channels

  • Release channels use Constants.manifest.releaseChannel to change things inside the code, while dotenv uses process.env. Both need some value to be passed while running the command. Neither can be used for secrets..
  • Default values for releaseChannel are undefined in dev mode, and default in production
const getEnvVars = (env = Constants.manifest.releaseChannel) => {
  // Default values for `releaseChannel` are `undefined` in dev mode and `default` in production
  if (__DEV__) {
    return ENV.develop
  }

  // using `indexOf` will let you pick up dev, develop, development, dev-v1, dev-v2, dev-v3, and so on..
  // Returns `-1` if the value is not found.
  if (env.indexOf('dev') !== -1) return ENV.develop
  if (env.indexOf('staging') !== -1) return ENV.staging
  if (env.indexOf('prod') !== -1) return ENV.production

  // If you do not specify a channel, you will publish to the `default` channel.
  return ENV.develop
}

export default getEnvVars()

react-native-dotenv

react-native-dotenv if you don't wanna deal with any native code integration. It can be used with Expo as well

  • Create a .env file
  • npm i react-native-dotenv
  • Update babel.config.js to include the preset "module:react-native-dotenv"
  • may need 'module:metro-react-native-babel-preset' as well if an RN projet. Not sure about Expo as expo has 'babel-preset-expo'
module.exports = {
  presets: ['module:metro-react-native-babel-preset', 'module:react-native-dotenv'],
}
  • Now you can use it in JS files
import { API_KEY, ANOTHER_CONFIG } from 'react-native-dotenv'

ApiClient.init(API_KEY, ANOTHER_CONFIG)
  • Caveeat: ONLY supports development .env and production .env.production environment configs. Which kind of makes sense when you take into account that Xcode only has Release and Debug modes, and Android requires you to unistall the debug version in order to install the release version. ref

  • tl;dr: can't use sine i have staging as well

Secrets!

Not in the code, never. Don't commit to git. Nope.

Anything included in your code could be accessed in plain text by anyone inspecting the app bundle

The best place i could think of is as environment variables inside Github Actions or Bitbucket Pipelines. That way you can set them securely, manage who has access, and pass them to your build process.

Bitbucket has 3 different levels of environment variables

  • Team and individual account variables
  • Repository variables
  • Deployment variables

and they can be secured, meaning you don't actually see the value after the variable has been set. Not in the settings, and not in the logs for the pipeline.

Links

Please note that this site and the posts on it are, and will always be, a work in progress. If i waited for perfection, i’d never get anything done.