Mobile — Distribution
The mobile app is built and distributed through Expo Application Services (EAS). This page covers how the three EAS channels (development, preview, production) are configured, how TestFlight distribution works, and the iOS-specific bits of the bundle.
EAS channels
Section titled “EAS channels”The app has three channels configured in eas.json, each with different trade-offs:
| Channel | Distribution | Used for |
|---|---|---|
| Development | Internal, device only — simulator builds disabled | Running the dev client on a physical iPhone for debugging |
| Preview | Internal, no auto-submit | Sharing a pre-production build with stakeholders before cutting a TestFlight release |
| Production | External via TestFlight / App Store | Public releases through Apple’s review pipeline |
All three channels share the same Cognito endpoints and AWS backend — the difference is how the build is distributed, not which backend it talks to.
Development builds
Section titled “Development builds”Development builds run the Expo dev client, so they support hot reload, remote debugging, and live updates without a full rebuild for every change. Development builds are not simulator builds — eas build --profile development --platform ios produces an .ipa that installs on a registered development device through Apple’s developer provisioning profile.
The reason simulator builds are disabled in this profile: the push-notification path needs a real device (simulators don’t have push tokens), and most of what’s worth debugging on the mobile app involves push registration, biometric unlock, or the OAuth WebBrowser, all of which either don’t work or behave differently on a simulator.
For simulator workflows during normal development, use npx expo start against a local Metro bundler — that does not need EAS.
Preview builds
Section titled “Preview builds”Preview builds are internal-distribution .ipa files shared directly with reviewers through Apple Business Manager or ad-hoc provisioning. They’re the right tool when you want someone to try a pre-release on their real device without going through the TestFlight review delay.
Preview is distinct from development in that it’s a release build: optimizations on, source maps stripped, production Metro config. It behaves like the production app except it’s not in TestFlight.
Production builds
Section titled “Production builds”Production builds are the only channel that submits to the App Store and TestFlight. The EAS build profile has autoIncrement: true (the build number ticks every build), and the channel is production. Submission metadata (the Apple account, ASC app id, team id) lives in the EAS config.
When a production build finishes, eas submit --profile production --platform ios uploads the .ipa to App Store Connect. From there, Apple runs its review, TestFlight receives the build, and external testers can install it. Once it’s approved for the App Store, the same build graduates to the public release.
TestFlight setup
Section titled “TestFlight setup”TestFlight for this app runs on an Apple Individual developer account — not an Apple Business or Organization account. That has a few practical implications:
- Single owner. The team id is tied to a single Apple ID; only that account can manage TestFlight testers and promote builds.
- No Apple Business Manager. Builds are distributed through TestFlight’s built-in invite flow (via email or public link), not through a managed-device channel.
- Apple review is still required. Even internal TestFlight distribution requires the build to pass Apple’s automated review. Real app review is only triggered when the build is promoted to public TestFlight or submitted for App Store release.
The production EAS profile carries:
- The Apple ID that owns the developer account
- The App Store Connect app id (
ascAppId) - The Apple team id
iOS bundle configuration
Section titled “iOS bundle configuration”The iOS bundle is configured through app.json:
- Bundle identifier — the reverse-DNS identifier registered with Apple for this app
- URL scheme — used for deep links into the app (for example, the OAuth redirect back from Google)
- Expo SDK — pinned to a specific SDK version; the app is currently on Expo SDK 54
- New architecture — React Native’s new architecture (Fabric + TurboModules) is enabled
The scheme is load-bearing for OAuth: the redirect URL registered with Cognito includes the scheme, and Cognito’s Google provider is configured to redirect back to thinkwork://auth/callback on native and https://<admin-url>/auth/callback on web.
Node and Expo pinning
Section titled “Node and Expo pinning”If you upgrade Expo SDK or React Native, update eas.json’s Node pin at the same time. Don’t rely on “latest” in production builds — Node point releases have changed Metro bundler behavior in subtle ways that only show up as runtime errors after a release.
Release checklist
Section titled “Release checklist”A rough checklist for cutting a production build:
- Merge your changes to
main pnpm installand run a local dev build to sanity-check- Bump the version in
app.json(if this is a user-visible release — not every build needs a version bump) eas build --profile production --platform ios- Wait for the build (usually 15–25 minutes)
eas submit --profile production --platform iosto upload to App Store Connect- Wait for Apple’s automated review (usually 10–30 minutes for TestFlight)
- Once TestFlight shows the build as “Ready to Test”, announce to testers
If the build fails Apple review, the failure reason is in App Store Connect. Common causes: missing usage descriptions for permissions, unexpected API usage, metadata rejections. None of these are blocking — fix the cause, bump the build number, resubmit.
Related pages
Section titled “Related pages”- Mobile Overview — the mobile app at a glance
- Mobile — Authentication — why the OAuth scheme and bundle id must match
- Deploy Configuration — the backend side of the Cognito and OAuth setup