Published on

Expo Release Cookbooks: TestFlight, Play Internal, and Git Tag Production

Authors
  • avatar
    Name
    Motions Technologies
    Twitter

Expo Release Cookbooks: TestFlight, Play Internal, and Git Tag Production

Shipping Expo apps is less about eas build once and more about a repeatable path from commit → testers → store. Across customer, driver, and multi-app fleets, we standardized on a two-track model that keeps "what is in 1.2.0?" answerable from git.

Two paths, deliberately separate

PathTriggerDestination
BetaPush to mainTestFlight / Play internal testers
ProductionGit tag v*App Store / Play review → phased release

main ships continuously to testers. Only a tag cuts a release candidate. That separation prevents "we merged Friday and accidentally submitted to the store."

iOS / TestFlight one-time setup

Per app you need:

  • Apple Developer Program membership
  • App Store Connect app record (numeric ASC App ID)
  • App Store Connect API key (.p8) stored securely for CI
  • EAS project linked (eas login, eas init as needed)

Store secrets in EAS env / GitHub secrets, not in the repo. Classic footgun: putting EXPO_PUBLIC_* only in eas.json env blocks — those apply to builds, not necessarily to eas update. Prefer EAS environment variables so builds and OTA updates see the same API URLs and Sentry DSNs.

Android / Play symmetry

Treat Play internal testing as TestFlight's cousin. While org verification (for example DUNS) blocks production Play access, keep an EAS internal APK / preview workflow so Android QA does not stall.

Workflows we keep in each mobile repo:

  • eas-testflight.yml — iOS beta on main
  • eas-play-internal.yml or preview APK — Android beta
  • release.yml — tag v* production path

Versioning discipline

  • App version / build numbers bump in CI from the tag or a single source of truth
  • Changelog comes from git history between tags
  • Never hand-bump store build numbers in a random local commit that never lands on main

Sentry and runtime config

Create the Sentry project first, then:

eas env:create --name EXPO_PUBLIC_SENTRY_DSN --value 'https://…' \
  --type string --visibility plaintext --scope project \
  --environment production --environment preview

Also provide SENTRY_AUTH_TOKEN (and org/project) to CI for source maps. Crash-free releases are a store requirement in practice even when Apple does not say so out loud.

Checklist before you tag

  1. Smoke the API environments the binary will call
  2. Verify push notifications / Sign in with Apple entitlements if used
  3. Confirm privacy nutrition labels and permission strings match actual APIs
  4. Run a TestFlight build that matches the tag commit
  5. Only then git tag vX.Y.Z && git push --tags

Multi-app fleets

Customer and driver apps (or admin/tenant/handyman variants in other products) should copy the cookbook, not invent new release philosophy. Same workflow names, same secret shapes, different bundle IDs and ASC/Play app records.

Closing

Expo makes builds easy; release engineering makes nights quiet. Separate beta from production with main vs tags, put runtime config in EAS environments, and keep iOS/Android workflows symmetric. Your future self — tagging a hotfix at 11pm — will thank you.