Note
I just threw this together in a couple minutes, so it's not perfect. The implementation probably has a lot of flaws and could be improved.
But I think I got my point across, if not let me know and I can try to explain it better.
.env*:
EXPO_PUBLIC_API_URL=https://jsonplaceholder.typicode.com
EXPO_PUBLIC_FEATURE_FLAG=true
SENTRY_AUTH_TOKEN=123456
GOOGLE_SERVICES_JSON=./google-services.json
On app launch (or build) the env helper should parse the .env*
file and expose the public environment variables to the app via the env
helper.
Example Use Case:
import { env } from '@expo/env';
...
return (
<View>
...
{env.EXPO_PUBLIC_FEATURE_FLAG
? <Text>Feature is enabled</Text>
: <Text>Feature is disabled</Text>}
...
</View>
)
Example Invalid Use Case:
import { env } from '@expo/env';
...
return(
<View>
...
<Text>
My Sentry Auth Token is: {env.SENTRY_AUTH_TOKEN} ๐
</Text>
...
</View>
)
Possible Env Config:
import { env } from '@expo/env';
import { z } from 'zod';
env.config({
/** Backend API URL */
EXPO_PUBLIC_API_URL: z.string().url(),
/** Main app domain */
EXPO_PUBLIC_DOMAIN: z.string().url(),
/** Feature flag */
EXPO_PUBLIC_FEATURE_FLAG: z.boolean().optional(),
/** Google Services JSON */
GOOGLE_SERVICES_JSON: z.string(),
});
The env helper should only give access to environment variables prefixed with EXPO_PUBLIC_
, all other variables should throw an error if accessed.
-
You now know 100% that the environment variable you are trying to access exists, no more wondering why your app isn't working because you misspelled
process.env.EXPO_PUBLIC_APII_URL
๐. -
Typesafety, with the env helper you get types for all your environment variables, no more
process.env.EXPO_PUBLIC_API_URL as string
-
You can also add validation to the env helper, to make sure that the values of the environment variables are of the correct format
The way I currently handle this is by setting up my own env helper, which is a bit of a pain to maintain and setup for each project.
Warning
This example may not be valid, since I had to cut out a lot of the code to remove unnecessary parts and redact sensitive information.
import Constants from 'expo-constants';
import { z } from 'zod';
// Environment variables that are only available at build time.
const buildTimeEnvVars = z
.object({
/** The current app variant */
APP_VARIANT: z.enum(['development', 'beta', 'production']),
/** Google Services file for iOS */
GOOGLE_SERVICES_IOS: z.string(),
/** Google Services file for Android */
GOOGLE_SERVICES_ANDROID: z.string(),
})
.partial();
// Public environment variables that are included in the app bundle.
// These must be prefixed with `EXPO_PUBLIC_`
const runtimeEnvVars = createPrefixedSchema('EXPO_PUBLIC_', {
/** Backend API URL */
EXPO_PUBLIC_API_URL: z.string().url(),
/** Main app domain */
EXPO_PUBLIC_DOMAIN: z.string().url(),
/** Google OAuth Web Client ID */
EXPO_PUBLIC_WEB_CLIENT_ID: z.string(),
/** RevenueCat Public API Key for iOS */
EXPO_PUBLIC_PURCHASES_API_KEY_IOS: z.string(),
/** RevenueCat Public API Key for Android */
EXPO_PUBLIC_PURCHASES_API_KEY_ANDROID: z.string(),
});
// Merge the build and runtime environment variables into a single schema
// to provide autocompletion for all environment variables
const envVariables = buildTimeEnvVars.merge(runtimeEnvVars);
//* NOTE: This does not guarantee typesafety. All this does is provide autocompletion to `process.env`
declare global {
namespace NodeJS {
interface ProcessEnv extends z.infer<typeof envVariables> {}
}
}
// Extract the APP_VARIANT from the Expo config and validate it
const APP_VARIANT = z
.enum(['development', 'beta', 'production'])
.parse(Constants.expoConfig?.extra?.APP_VARIANT);
// Since we don't have access to the `process.env` object in expo, we have to manually map the environment variables so they get inlined by babel.
const processEnvMap = {
EXPO_PUBLIC_API_URL: process.env.EXPO_PUBLIC_API_URL,
EXPO_PUBLIC_DOMAIN: process.env.EXPO_PUBLIC_DOMAIN,
EXPO_PUBLIC_WEB_CLIENT_ID: process.env.EXPO_PUBLIC_WEB_CLIENT_ID,
EXPO_PUBLIC_PURCHASES_API_KEY_IOS:
process.env.EXPO_PUBLIC_PURCHASES_API_KEY_IOS,
EXPO_PUBLIC_PURCHASES_API_KEY_ANDROID:
process.env.EXPO_PUBLIC_PURCHASES_API_KEY_ANDROID,
} as const;
export const env = {
APP_VARIANT,
...runtimeEnvVars.parse(processEnvMap),
};