There are two env files, both ignored by git, both created for you by node scripts/init-env.mjs .
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.
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.
Server secrets that never reach the client:
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.
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.