"I pushed my app to TestFlight and realized it was still calling the development API in production" — if you've been building with Rork Max for a while, there's a good chance you've run into something like this. I once nearly shipped an app to the App Store where Stripe was still running in sandbox mode, all because I'd built with the wrong EAS profile.
EAS Build profiles are unavoidable when shipping native apps with Rork Max. But if you treat them as an afterthought, you're setting yourself up for a painful discovery right before launch. This article walks through the most common profile-switching problems, symptom by symptom, with concrete fixes you can apply today.
What makes EAS profile issues particularly tricky in Rork projects is that Rork's AI generates code and configuration automatically. That's a huge productivity win, but it means you might not have a deep understanding of how environment variables, native permissions, or signing credentials are wired together. When something breaks, it's not always obvious whether the issue is in the code Rork generated or in how you configured EAS. The breakdown below is organized around the symptoms you'll actually see, not the underlying technical concepts.
Understanding What Each EAS Build Profile Actually Does
Let's start by clarifying what each profile in eas.json is for — because they're surprisingly easy to mix up.
// eas.json — default config generated by Rork Max
{
"build": {
"development": {
"developmentClient": true,
"distribution": "internal"
},
"preview": {
"distribution": "internal"
},
"production": {
"distribution": "store"
}
}
}Here's how to think about each one:
- development: Includes Expo's development client, which lets you use the debugger and hot reload. This build type cannot be submitted to the App Store — Apple will reject it at upload.
- preview: Designed for TestFlight and internal distribution. Behaves close to production but can be distributed without App Store review.
- production: What you submit to the App Store or Google Play. Optimized, stripped of dev tooling, and the only type you should ever send for review.
The critical detail is developmentClient: true. When that's present, Expo's development client is bundled into your app, significantly inflating the binary size and making it ineligible for store submission. Even if your app looks identical across all three profiles, the internals are quite different.
Problem 1: Environment Variables Aren't Reflecting the Right Profile
"I built with EAS and the API endpoint is still pointing at dev" — this is the most common profile-related issue I hear about.
The problem is usually that eas.json doesn't have explicit env definitions per profile, so the build falls back to whatever's in your local .env file. The fix is to be explicit about each environment.
// eas.json — define environment variables per profile
{
"build": {
"development": {
"developmentClient": true,
"distribution": "internal",
"env": {
"APP_ENV": "development",
"EXPO_PUBLIC_API_URL": "https://dev-api.yourapp.com"
}
},
"preview": {
"distribution": "internal",
"env": {
"APP_ENV": "preview",
"EXPO_PUBLIC_API_URL": "https://staging-api.yourapp.com"
}
},
"production": {
"distribution": "store",
"env": {
"APP_ENV": "production",
"EXPO_PUBLIC_API_URL": "https://api.yourapp.com"
}
}
}
}Variables prefixed with EXPO_PUBLIC_ are accessible client-side, but they're statically embedded at build time — changing them after a build does nothing until you rebuild. To verify which profile a build was compiled with, temporarily render process.env.APP_ENV somewhere visible in your app UI. It's a sanity check that takes thirty seconds and saves a lot of confusion.
Problem 2: Production Build Is Using Stripe Test Keys
If users report that charges aren't going through after launch, check whether your production build is still using pk_test_ Stripe keys. This happens when API keys get hardcoded in Rork-generated source code, or when .env values bleed into the wrong build profile.
The right approach is to keep secrets out of eas.json and your source entirely. Use EAS Secrets instead.
# Register production Stripe keys as EAS Secrets
eas secret:create --scope project --name STRIPE_PUBLISHABLE_KEY --value pk_live_YOUR_KEY
eas secret:create --scope project --name STRIPE_SECRET_KEY --value sk_live_YOUR_KEYEAS Secrets are injected as environment variables at build time, so your team never handles production credentials locally. When reviewing Rork-generated code, always search for pk_test_ or sk_test_ before building for production. It's a quick grep that's worth running every time.
Problem 3: You Uploaded a Development Build to TestFlight
ERROR: Invalid Binary
The binary you uploaded was built for development use only.
This error means you sent a development profile build to App Store Connect. The fix: rebuild with the right profile.
# For TestFlight (internal distribution)
eas build --platform ios --profile preview
# For App Store submission
eas build --platform ios --profile productionAfter the build finishes, check your EAS dashboard to confirm distribution shows Internal or Store before submitting. It takes a minute, but it's faster than waiting through an unnecessary rejection cycle.
One way to prevent this: configure a separate app icon tint or add the suffix "(Dev)" to your app name in the development env, so you can instantly tell which build is installed on your device. When your home screen shows two separate app icons — one labeled "MyApp" and one labeled "MyApp (Dev)" — you'll never accidentally submit the wrong one.
Problem 4: Confusing EAS Build with EAS Update
This distinction trips up a lot of Rork developers who discover OTA updates and start relying on them heavily.
- EAS Build (
eas build) — generates a full native binary (IPA/APK). Required any time native code or configuration changes. - EAS Update (
eas update) — pushes a new JavaScript bundle over the air. Fast, but only captures JS-layer changes.
If Rork's AI added a native module — camera, push notifications, biometrics — or if you changed permissions in app.json, an EAS Update won't pick those up. You need a full rebuild. The typical symptom: your OTA update goes out, but the new feature simply doesn't appear for users on existing builds.
When in doubt: rebuild if anything changed in app.json, package.json, or any native configuration file.
Problem 5: Only Testing in Rork Companion, Not a Real Build
Rork Companion is great for rapid iteration, but it doesn't fully replicate what a standalone EAS build does on a user's device. Common areas that behave differently:
- Camera, microphone, location: Companion shares Expo's permissions. Your standalone app needs them declared in
app.json. - Push notifications: Companion uses its own push token, separate from your app's Expo token.
- Deep links: Companion uses Expo's URL scheme. Your app uses the custom scheme you configured.
- Native modules not bundled with Expo Go: These simply won't work in Companion.
Before any public release — TestFlight or store — do a final walkthrough on an actual preview build installed directly on a physical device. Rork Companion speeds up development significantly, but it's not a substitute for testing the artifact you're actually shipping.
Pre-Production Checklist
Before triggering a production build, run through these quickly:
- [ ]
EXPO_PUBLIC_API_URLin the production env points to your real backend, not a dev URL - [ ] No
pk_test_orsk_test_Stripe keys exist anywhere in source code - [ ]
app.jsonhas the correct production bundle ID and version number incremented - [ ] All required permissions (camera, location, notifications) are declared in
app.json - [ ] Signing credentials are configured for the production profile in EAS
- [ ] You've tested the preview build on a real device, not just in Rork Companion
It takes about two minutes to go through this list. It's saved me from more than one embarrassing post-launch fix.
Commands Worth Knowing
# Review build history — see which profile built what
eas build:list
# List all registered EAS Secrets
eas secret:list
# Confirm your EAS configuration is valid
eas build:configureeas build:list is underused. It shows your complete build history with profile names, so when a tester reports odd behavior you can quickly look up exactly what version they have installed.
The Setup That Makes Profile Mistakes Hard to Make
Most profile-switching errors happen because development and production builds look identical once installed. The most effective fix: give the development build a distinct bundle identifier.
// eas.json — separate bundle IDs for dev vs. production
{
"build": {
"development": {
"developmentClient": true,
"distribution": "internal",
"ios": {
"bundleIdentifier": "com.yourapp.dev"
},
"android": {
"applicationId": "com.yourapp.dev"
}
}
}
}With a separate bundle ID, development and production install as independent apps on your device. You can run both simultaneously, and there's no risk of confusing which is which. It's a small change that eliminates a whole category of mix-up.
If you take one thing from this article: add explicit env values to each profile in eas.json. That single change prevents the majority of profile-related issues that come up in Rork Max projects. Once that's in place, the pre-production checklist above becomes a quick confidence check rather than a source of anxiety.