Browse docs
Setup

Environment Setup

The two .env files, what each key does, and why every paid integration runs green without one.

Updated Aug 2026
On this page

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.

Stop reinventing the app shell. Start shipping.

React Native and Expo boilerplate built for Claude Code, Cursor and Copilot. Auth, payments, push and offline sync already wired, with a 37-row map telling your agent where each one lives.

Get it for $59 →