Skip to content

Builds & Releases

The mobile app ships through EAS Build and EAS Submit, not through the Kubernetes cluster.

Why you cannot use Expo Go

The app depends on native modules that Expo Go does not include:

  • @livekit/react-native-webrtc — voice and video rooms
  • expo-notifications — push
  • @bugsnag/expo — crash reporting
  • react-native-purchases — in-app purchases

You need a development build: a custom binary containing the app's native modules, which then loads your JavaScript from Metro. Install it once and iterate on JS normally.

Build profiles

apps/mobile/eas.json defines three:

ProfileDistributionEnvironmentPoints at
developmentinternal, iOS simulatordevelopmenthttp://localhost:4000 / :4010
previewinternalstagingstaging-api.playpalz.gg / staging-socket.playpalz.gg
productionstoreproductionapi.playpalz.gg / socket.playpalz.gg

The environment variables baked into each profile determine what the binary talks to. production sets autoIncrement: true, so the build number rises automatically.

appVersionSource: "remote" means EAS owns the build number, not the repo — do not hand-edit it.

App identity

Value
Slugplaypalz
iOS bundle idcom.playpalzinc.playpalz
Android packagecom.playpalzinc.playpalz
Version1.0.0 (app.json)

app.config.ts overrides the display name and URL scheme per environment on top of app.json:

EXPO_PUBLIC_ENVNameScheme
developmentPlayPalz Devplaypalz-dev
stagingPlayPalz Stagingplaypalz-staging
productionPlayPalzplaypalz

Per-environment bundle identifiers are commented out

app.config.ts contains commented-out bundleIdentifier and package overrides. As it stands, all three environments share one identifier, so a dev, staging, and production build cannot coexist on one device — installing one replaces another. Uncommenting them (and registering the extra ids with Apple and Google) is what makes side-by-side installs possible.

Building

bash
npm i -g eas-cli
eas login

cd apps/mobile

eas build --profile development --platform ios
eas build --profile preview      --platform all
eas build --profile production   --platform all

A development build for the simulator (ios.simulator: true) produces a .app you drag onto a running simulator. Device builds need the device UDID registered.

Submitting

bash
eas submit --profile production --platform ios       # → App Store Connect / TestFlight
eas submit --profile production --platform android   # → Google Play

Config plugins

app.json declares the plugins that shape the native project:

PluginPurpose
expo-routerFile-based routing
expo-splash-screenSplash, background #160b2d
expo-videoBackground playback and picture-in-picture
expo-image-pickerPhoto library and camera
@config-plugins/react-native-webrtcWebRTC native setup
@livekit/react-native-expo-pluginLiveKit native setup

app.config.ts additionally injects the notification usage description on iOS and the NOTIFICATIONS, VIBRATE, and RECEIVE_BOOT_COMPLETED permissions on Android.

Changing a plugin requires a new build. Config plugins run at prebuild time and modify native projects; a Metro reload will not pick them up.

Native change checklist

You need a new development build whenever you:

  • Add or remove a native dependency
  • Change a config plugin or its options
  • Change the bundle identifier, app name, or scheme
  • Change permissions or Info.plist entries
  • Upgrade the Expo SDK

You do not need one for JavaScript, styling, or asset changes.

Crash reporting

Bugsnag is initialised in app/_layout.tsx, and only outside development:

ts
if (process?.env.EXPO_PUBLIC_ENV !== "development") {
  const Bugsnag = require("@bugsnag/expo").default;
  Bugsnag.start();
}

The lazy require is deliberate — Bugsnag pulls in @react-native-community/netinfo, whose native module is missing in development, and a static import would throw before the module finished evaluating.

package.json also wires eas-build-on-success to npx bugsnag-eas-build-on-success, which uploads source maps so production stack traces are readable.

The Bugsnag API key is committed

BUGSNAG_API_KEY appears in plaintext in apps/mobile/eas.json. Bugsnag notifier keys are client-side by design and are not a serious secret, but move it to an EAS secret rather than the repo if you would rather not have it in git history.

Release checklist

  1. pnpm check-types and pnpm lint pass.
  2. Backend changes the release depends on are deployed and migrated.
  3. version in app.json bumped for a user-visible release.
  4. eas build --profile production --platform all.
  5. Smoke-test the resulting build via TestFlight / internal track: login, feed, post with an image, DM, a voice room, a purchase.
  6. eas submit --profile production --platform all.
  7. Watch Bugsnag for a spike after rollout.

Local development builds

You can also build locally if you have the toolchains:

bash
cd apps/mobile
npx expo run:ios
npx expo run:android

Slower than EAS the first time and it requires a working Xcode / Android Studio setup, but it does not consume EAS build minutes and gives you native logs directly.

Internal documentation — PlayPalz platform