ReadyNative

Environment variables

Every env var the template and its modules read, which ones ship in the app, and where secrets go on EAS.

Every key lives in .env at the repo root (gitignored). bun run setup generates .env.example with exactly the keys your selection needs, each with a # docs: link to the dashboard that issues it; copy it to .env and fill in what you use. bun run doctor flags the required ones that are still empty.

There are two kinds, and the prefix decides which:

  • EXPO_PUBLIC_* keys are public. Metro inlines them into the JavaScript bundle, so anyone with the app can read them. Only publishable / anon / SDK keys belong here. They're typed in src/lib/env.ts as optional: a missing key makes its module show a "Configure X" state instead of crashing. Restart with bun run start -- -c after changing one.
  • Keys without the prefix are server-only. They're read with process.env.X inside src/app/api/** and src/server/** (API routes, Pro with backend=api-routes) and never reach the bundle. Never rename one to EXPO_PUBLIC_. Build-time keys (APP_VARIANT, SENTRY_AUTH_TOKEN, …) work the same way: app.config.ts and config plugins read them while EAS builds, and the app never sees them.

Template

Present in every tree, whatever you picked. The Free tier has no eas.json, so the per-profile values don't apply there.

VariableKindDefaultWhat it does
EXPO_PUBLIC_APP_VARIANTpublicdevdev / preview / prod at runtime; set per profile in eas.json
EXPO_PUBLIC_API_URLpublic-Base URL for relative requests (data clients, API routes, Stripe, Better Auth). Use https on a device
APP_VARIANTbuild timedevPicks the name and bundle-id suffix in app.config.ts; set per profile in eas.json
EAS_PROJECT_IDbuild time-Overrides app.easProjectId; feeds extra.eas.projectId and updates.url

EXPO_PUBLIC_API_URL on a physical iPhone must be https://, a LAN IP, localhost or *.local: App Transport Security blocks plain http:// to anything else, and src/lib/env.ts warns in dev when it sees one.

Modules

Only the modules you selected add keys. Every row below is a Pro module (auth, payments, analytics, crash, backend) except data/apollo, which Starter can pick too; the backend/api-routes rows, src/server/webhooks included, exist only in a Pro tree set up with backend=api-routes. Required means the module stays in its "Configure" state (and doctor fails) until the key is set.

VariableModuleKindRequiredWhat it is
EXPO_PUBLIC_SUPABASE_URLauth/supabasepublicyesProject URL (Supabase → Project settings → API)
EXPO_PUBLIC_SUPABASE_ANON_KEYauth/supabasepublicyesAnon / publishable key
EXPO_PUBLIC_CLERK_PUBLISHABLE_KEYauth/clerkpublicyesClerk publishable key
CLERK_SECRET_KEYauth/clerkserverfor API routesClerk secret key (sk_…); lets API routes verify the caller's session token
CLERK_JWT_KEYauth/clerkservernoJWT public key (PEM), verifies tokens networkless instead of CLERK_SECRET_KEY
BETTER_AUTH_SECRETauth/better-authserveryesSession signing secret, openssl rand -base64 32
BETTER_AUTH_URLauth/better-authserveryesCallback base URL, same as EXPO_PUBLIC_API_URL
DATABASE_URLauth/better-authservernoPostgres connection string for persistent users and sessions
APPLE_CLIENT_IDauth/better-authservernoApple Services ID for Sign in with Apple
APPLE_CLIENT_SECRETauth/better-authservernoApple client secret (JWT)
GOOGLE_CLIENT_IDauth/better-authservernoGoogle OAuth client id
GOOGLE_CLIENT_SECRETauth/better-authservernoGoogle OAuth client secret
EXPO_PUBLIC_REVENUECAT_IOS_KEYpayments/revenuecatpublicyesRevenueCat public SDK key for iOS (appl_…)
EXPO_PUBLIC_REVENUECAT_ANDROID_KEYpayments/revenuecatpublicyesRevenueCat public SDK key for Android (goog_…)
EXPO_PUBLIC_ADAPTY_PUBLIC_KEYpayments/adaptypublicyesAdapty public SDK key
EXPO_PUBLIC_ADAPTY_PLACEMENT_IDpayments/adaptypublicno (default)Placement whose paywall is shown
EXPO_PUBLIC_STRIPE_PUBLISHABLE_KEYpayments/stripepublicyesStripe publishable key (pk_…)
STRIPE_SECRET_KEYpayments/stripeserveryesStripe secret key (sk_…)
STRIPE_WEBHOOK_SECRETpayments/stripeserveryesWebhook signing secret (whsec_…)
STRIPE_PRICE_IDpayments/stripeserveryesRecurring price id (price_…)
STRIPE_ENTITLEMENTpayments/stripeserverno (pro)Entitlement an active or trialing subscription grants
STRIPE_ALLOW_ANONYMOUSpayments/stripeserverno (false)true lets checkout run with auth none, trusting client ids - demos only
EXPO_PUBLIC_POSTHOG_KEYanalytics/posthogpublicyesPostHog project API key
EXPO_PUBLIC_POSTHOG_HOSTanalytics/posthogpublicno (US cloud)https://eu.i.posthog.com for the EU cloud
EXPO_PUBLIC_AMPLITUDE_KEYanalytics/amplitudepublicyesAmplitude API key
EXPO_PUBLIC_SENTRY_DSNcrash/sentrypublicyesSentry DSN
SENTRY_ORGcrash/sentrybuild timenoOrg slug, for source map uploads on native builds
SENTRY_PROJECTcrash/sentrybuild timenoProject slug, same purpose
SENTRY_AUTH_TOKENcrash/sentrybuild timenoAuth token with project:releases + org:read; without it, stack traces stay minified
EXPO_PUBLIC_GRAPHQL_URLdata/apollopublicnoGraphQL endpoint
WEBHOOK_SECRETbackend/api-routesservernoShared secret for incoming webhooks under src/server/webhooks
CLERK_WEBHOOK_SIGNING_SECRETbackend/api-routesservernoSigning secret of the Clerk user.deleted webhook (/api/webhooks/clerk)
POSTHOG_PERSONAL_API_KEYbackend/api-routesservernoLets account deletion erase the user in PostHog
POSTHOG_PROJECT_IDbackend/api-routesservernoPostHog project id, same purpose
POSTHOG_API_HOSTbackend/api-routesservernoPostHog API host, same purpose
AMPLITUDE_API_KEYbackend/api-routesservernoLets account deletion erase the user in Amplitude
AMPLITUDE_SECRET_KEYbackend/api-routesservernoAmplitude secret key, same purpose
AMPLITUDE_REGIONbackend/api-routesservernoAmplitude region, same purpose
REVENUECAT_SECRET_KEYbackend/api-routesservernoLets account deletion erase the customer in RevenueCat

Each module page under Features has the dashboard steps for its keys. The two example apps carry their own .env.example with the subset they use.

On EAS

Your .env is gitignored, so EAS never uploads it: a cloud build, eas update and an EAS Hosting deploy all run without it. A key that works locally but is missing in a TestFlight build almost always was never set on EAS.

Set each key in the EAS environment it's used in (development, preview, production). A build picks the environment from its profile: build.<profile>.environment in eas.json if you add one, otherwise production for store builds (the shipped production profile), development for dev-client builds (development) and preview for the rest:

bunx eas-cli env:set --environment production --name EXPO_PUBLIC_SUPABASE_URL --value https://xyz.supabase.co --visibility plaintext
bunx eas-cli env:set --environment production --name STRIPE_SECRET_KEY --value sk_live_xxx --visibility sensitive
bunx eas-cli env:list --environment production

env:set creates or updates a variable (the older env:create still works but is deprecated). --visibility takes three values:

  • plaintext - visible on expo.dev, in EAS CLI and in logs. Fine for EXPO_PUBLIC_* keys: they end up in the bundle anyway.
  • sensitive - masked in build and workflow logs, still readable in EAS CLI and on the dashboard behind a toggle. Use it for server keys your API routes read (STRIPE_*, BETTER_AUTH_*, CLERK_SECRET_KEY, WEBHOOK_SECRET): EAS Hosting can't deploy secret variables, only plaintext and sensitive ones. Also use it for SENTRY_AUTH_TOKEN if you upload source maps after eas update from your machine.
  • secret - never readable outside EAS servers, not even by EAS CLI. Right for keys only an EAS build job needs (SENTRY_AUTH_TOKEN for native builds, an NPM_TOKEN). A secret isn't available while EAS CLI resolves app.config.ts on your machine, can't be pulled, and isn't used by eas update.

bunx eas-cli env:pull --environment development writes that environment's variables to .env.local (pass --path for another file); secret ones appear only as a commented-out ***** line. Expo loads .env.local on top of .env, so a pulled value wins over the one in .env. bun run doctor reads only .env, though, so it still lists pulled keys as missing; pull with --path .env (it asks before overwriting) if you want doctor to see them.

eas update needs --environment on SDK 55 and later - see Your first update. APP_VARIANT and EXPO_PUBLIC_APP_VARIANT are already set per profile in eas.json, so don't create them.

Your tree ships no CI workflows. If you add your own (see Expo's GitHub Actions guide), EAS needs an EXPO_TOKEN secret (expo.dev → Account settings → Access tokens); set EAS_PROJECT_ID there too if you'd rather not commit the id.

On this page

Get ReadyNative