There are two env files, both ignored by git, both created for you by node scripts/init-env.mjs.
apps/mobile/.env, the app
Everything here starts with EXPO_PUBLIC_ and is baked into the JavaScript bundle when it builds. Treat these as public, not secret. Anyone can read them out of a shipped app.
EXPO_PUBLIC_API_URL=http://localhost:3000
EXPO_PUBLIC_AUTH_ENABLED=true
EXPO_PUBLIC_AUTH_PROVIDER=custom
EXPO_PUBLIC_PAYMENTS_PROVIDER=revenuecat
# Optional integrations, blank means the feature turns itself off:
EXPO_PUBLIC_POSTHOG_KEY=
EXPO_PUBLIC_SENTRY_DSN=
EXPO_PUBLIC_REVENUECAT_IOS_KEY=
EXPO_PUBLIC_GOOGLE_MAPS_KEY=The provider value is custom, not custom-jwt
apps/mobile/lib/auth.ts matches on custom. Any other value falls through to the same default, so a typo does not raise an error, it just quietly keeps the built-in provider. See Choosing an Auth Provider.
Env changes need a restart
EXPO_PUBLIC_* values are read when the bundler starts. After editing them, stop the dev server and start it again. Hot reload will not pick them up.
apps/api/.env, the backend
Server secrets that never reach the client:
PORT=3000
JWT_SECRET=<generated for you by init-env.mjs>
ACCESS_TOKEN_TTL=900
REFRESH_TOKEN_TTL=2592000
DATABASE_URL=file:./dev.db
ALLOWED_ORIGINS=http://localhost:8081,http://localhost:19006
RESEND_API_KEY=The only one you must care about on day one is JWT_SECRET.
Check your JWT_SECRET before you deploy anything
Open apps/api/.env. If the line still reads JWT_SECRET=replace-me-with-a-long-random-string-at-least-32-bytes, replace it now. That placeholder is in every copy of this repository, it is long enough to pass the server's only validation, and the server will start on it without a warning. With it, anyone can forge a session for any user.
The backend refuses to start on that value when NODE_ENV=production and warns loudly in development, but do not rely on the guard: fix the value.
node -e "console.log(require('crypto').randomBytes(48).toString('base64'))"The no-key rule
Every paid integration, Sentry, PostHog, RevenueCat, Stripe, Clerk, Resend, Google Maps, turns itself off when its key is missing. That is deliberate: the app runs green on a fresh clone, and you add services one at a time instead of collecting six accounts before you can see a screen.
The exception is Google Maps in a production build, which fails loudly on purpose so you cannot ship a blank map by accident.
Testing on a real device
Set EXPO_PUBLIC_API_URL=http://YOUR-COMPUTER-IP:3000 and restart the dev server so your phone can reach the backend. Find your address with ipconfig on Windows or ifconfig on macOS and Linux.