◉RORK LABJP
●GPT-6.1 SOL — Added to Rork's model menu (09-29) with Low, Medium and High effort; included on Pro and Max●SONNET 5.5 — Claude Sonnet 5.5 is selectable: strong at UI design and faster than Sonnet 5 (09-28)●SDK 58 — Beta since 09/15 with no stable date yet; running projects in Expo Go has required login since 09/03●WIDGET — A write-up traces an iOS widget that renders black only in production builds●OBSERVE — EAS Observe now records native crashes too (SDK 57.0.21 or later, 10/07)●NEW — My App Open Ad Only Showed When the App…●GPT-6.1 SOL — Added to Rork's model menu (09-29) with Low, Medium and High effort; included on Pro and Max●SONNET 5.5 — Claude Sonnet 5.5 is selectable: strong at UI design and faster than Sonnet 5 (09-28)●SDK 58 — Beta since 09/15 with no stable date yet; running projects in Expo Go has required login since 09/03●WIDGET — A write-up traces an iOS widget that renders black only in production builds●OBSERVE — EAS Observe now records native crashes too (SDK 57.0.21 or later, 10/07)●NEW — My App Open Ad Only Showed When the App…
Articles/App Dev
◇ App Dev/2026-06-25Advanced

Three Builds on One iPhone: Environment Separation for Rork (Expo) Apps

Split a Rork-generated Expo app into dev, staging, and production builds that live side by side on one device. A hands-on walkthrough of dynamic app.config.ts, eas.json profiles, and isolating notifications, analytics, and billing per environment.

Rork585Expo219EAS9environmentsapp variant

✦ Premium Article

One morning I tried a pre-release fix by installing a test build on my own iPhone, and the icon for the app I use every day quietly disappeared. The bundle ID was identical, so the test build overwrote my production install. Re-login, restoring purchases, re-granting notification permission. I lost maybe ten minutes, but the constraint that followed — "I can't casually test on my daily-driver phone" — kept shaving away at my development speed.

As an indie developer running several apps in parallel, that friction maps directly onto productivity. This article lays out an environment-separation setup that splits a Rork-generated Expo app into dev, staging, and production builds that coexist on the same device, based on the exact configuration I run across my own wallpaper apps at Dolice.

The setup at a glance

Before the concrete config, it helps to see that there are only three things to do.

  1. Make `app.config.ts` dynamic via `APP_VARIANT`, giving each environment its own bundle ID and app name
  2. Add development / preview / production profiles to `eas.json`, pinning `APP_VARIANT` in each
  3. Swap external service credentials — notifications, analytics, billing — to a separate set per environment

With those three in place, the same source code yields three distinct app identities that coexist on one device.

Why a single bundle ID eventually traps you

The code Rork first emits usually has just one bundle ID (bundleIdentifier on iOS, package on Android). That is fine for a prototype, but after launch it breaks in three predictable ways.

You can't keep a production app and a test app on the same device, because the OS identifies installs by bundle ID and overwrites matching ones. EAS Update (OTA) channels get crossed, so a JS bundle you meant for testing can reach production users. And your analytics, billing, and notification data get contaminated, mixing your debugging into real DAU and conversion numbers until they're unreadable.

The fix is simple in principle: split into dev (local development), staging (pre-release verification), and production, and give each its own bundle ID, app name, and service credentials.

✦

Thank you for reading this far.

Continue Reading

What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.

WHAT YOU'LL LEARN
✦Switching app.config.ts dynamically with APP_VARIANT to give each environment its own bundle ID and name
✦Organizing eas.json into development / preview / production profiles so all three coexist on one device
✦Isolating push tokens, analytics, and RevenueCat per environment so testing never pollutes production data
Secure payment via Stripe · Cancel anytime
✦

Unlock This Article

Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.

or
Unlock all articles with Membership →
Share

Thank You for Reading

Rork Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • ✦Copy-paste ready implementation code
  • ✦New advanced guides published daily
  • ✦$5/mo or $10 for lifetime access
View Membership →

Related Articles

◇ App Dev2026-09-18
The three lines I check when prebuild stops on the standard SDK 57 Swift AppDelegate error
Turning on ios.enableSceneSupport for iOS 27 can stop prebuild cold. The cause was not my SDK version but the shape of AppDelegate.swift. Here are the three lines to check, and why copying the SDK 58 migration steps by hand will break your build.
◇ App Dev2026-09-16
The Token I Deleted Came Back After a Restart — Detecting Failed SecureStore Deletes
On Android, getItemAsync can report null right after sign-out while the value is still sitting on disk. Here is how I rewrote a sign-out path that was verifying deletion by reading instead of by the delete result.
◇ App Dev2026-09-10
Your First EAS Workflow in a Rork Repo, and the Alert That Goes Missing Exactly When You Need It
Putting two files into .eas/workflows in a repo exported from Rork, and why a notification wired with needs stays silent on exactly the nights it fails — with the output of a small local checker I actually ran.