There are days when you look at your subscription, think about dropping to a cheaper plan, and stop. The question is simple: if I downgrade, will the app I'm building still be mine to work on?
Rork's FAQ (as of October 2026) says this about code export. On paid plans you own the generated code and can export it through the GitHub integration. Rork Pro exports a React Native / Expo project, and Rork Max exports a native SwiftUI Xcode project. The sync is two-way. All of that is stated plainly.
What I could not find in the FAQ is what happens to a project after you move to a lower plan. I won't build a plan on something that isn't written down. As an indie developer juggling several apps and subscriptions, I end up reviewing plans regularly, and I've learned to confirm I have somewhere to return to before touching a contract. So before any plan change, my routine is to clone the project fresh from GitHub, while the paid plan is still active, and confirm it can be rebuilt.
Exporting is only half of it. Whether the exported folder can be rebuilt outside Rork is a separate question.
"Exported" and "rebuildable" are different things
You can see on screen that the sync reached GitHub. Whether a git clone on another machine actually works, you only learn by trying. These are the gaps that tend to appear:
- The lock file is missing, or there are several, so dependency versions can't be reproduced
app.jsonhas no store identifiers (iOS bundleIdentifier, Android package)app.jsonpoints at an icon or splash image that isn't in the clone- A relative import points at a file that doesn't exist
- The code reads an
EXPO_PUBLIC_environment variable whose name is recorded nowhere
That last one is easy to miss. .env holds secrets, so it normally stays out of GitHub. But then the names of the values you'd need to recreate disappear along with it. That's why a .env.example containing names only is worth having.
A script that inspects the fresh clone
The script below does not install dependencies or run a build. It reads files and counts the five kinds of gaps above. It uses only the standard library, so any Python 3 will run it. Save it as rebuild_check.py.
#!/usr/bin/env python3
"""rebuild_check.py — statically check that a fresh clone can be rebuilt without Rork."""
import json, re, sys
from pathlib import Path
root = Path(sys.argv[1] if len(sys.argv) > 1 else ".").resolve()
problems, notes = [], []
# 1. package.json and lock files
pkg_path = root / "package.json"
if not pkg_path.exists():
sys.exit("No package.json found. Check that you pointed at the fresh clone.")
pkg = json.loads(pkg_path.read_text(encoding="utf-8"))
deps = {**pkg.get("dependencies", {}), **pkg.get("devDependencies", {})}
locks = [n for n in ("package-lock.json", "yarn.lock", "pnpm-lock.yaml", "bun.lock", "bun.lockb") if (root / n).exists()]
if not locks:
problems.append("No lock file (dependency versions can't be reproduced)")
elif len(locks) > 1:
problems.append(f"Multiple lock files: {', '.join(locks)}")
for need in ("expo", "react", "react-native"):
if need not in deps:
problems.append(f"{need} is missing from dependencies")
if "start" not in pkg.get("scripts", {}):
notes.append("No scripts.start (npx expo start works instead)")
# 2. Identifiers in app.json and the images it references
cfg_path = root / "app.json"
if cfg_path.exists():
cfg = json.loads(cfg_path.read_text(encoding="utf-8")).get("expo", {})
if not cfg.get("ios", {}).get("bundleIdentifier"):
problems.append("ios.bundleIdentifier is not set")
if not cfg.get("android", {}).get("package"):
problems.append("android.package is not set")
refs = [cfg.get("icon"), cfg.get("splash", {}).get("image"),
cfg.get("android", {}).get("adaptiveIcon", {}).get("foregroundImage")]
for ref in filter(None, refs):
if not (root / ref).exists():
problems.append(f"Image referenced by app.json is missing: {ref}")
else:
notes.append("No app.json (if you use app.config.*, check by hand)")
# 3. Relative import targets and environment-variable names
src = [p for p in root.rglob("*") if p.suffix in (".ts", ".tsx", ".js", ".jsx")
and not {"node_modules", ".git", "ios", "android"} & set(p.parts)]
imp = re.compile(r"""(?:from\s+|require\(\s*|import\(\s*)['"](\.{1,2}/[^'"]+)['"]""")
exts = ["", ".ts", ".tsx", ".js", ".jsx", ".json", "/index.ts", "/index.tsx", "/index.js"]
used_env = set()
for p in src:
text = p.read_text(encoding="utf-8", errors="ignore")
for m in imp.finditer(text):
base = (p.parent / m.group(1)).resolve()
if not any(Path(str(base) + e).is_file() for e in exts):
problems.append(f"{p.relative_to(root)}: import target not found: {m.group(1)}")
used_env |= set(re.findall(r"process\.env\.(EXPO_PUBLIC_[A-Z0-9_]+)", text))
listed = set()
for name in (".env.example", ".env.local.example"):
f = root / name
if f.exists():
listed |= set(re.findall(r"^([A-Z0-9_]+)=", f.read_text(encoding="utf-8"), re.M))
for name in sorted(used_env - listed):
problems.append(f"{name} is read by the code but its name is not in .env.example")
print(f"Checked: {root.name} / {len(src)} source files")
for n in notes:
print(" note:", n)
for p in problems:
print(" fix:", p)
print("Result:", "Everything needed to rebuild is present" if not problems else f"{len(problems)} item(s) to fix")
sys.exit(1 if problems else 0)Usage takes three lines. Clone from GitHub into a new folder and run the script there. The point is to run it on the fresh clone, not on the folder you've been working in. Your working folder may contain files that never synced, which defeats the check.
git clone <your GitHub repository URL> rebuild-test
cd rebuild-test
python3 ../rebuild_check.py .What was tested, and what wasn't
To be plain about it: I ran this script against two small sample folders I built myself. These are not results from a real Rork export. One sample has everything in place; the other has deliberate gaps.
The complete sample printed this:
Checked: good / 2 source files
Result: Everything needed to rebuild is presentThe broken sample (two lock files, no react or react-native, no android.package, a missing image, a missing import target, an unrecorded env-var name) produced seven findings:
fix: Multiple lock files: package-lock.json, yarn.lock
fix: react is missing from dependencies
fix: react-native is missing from dependencies
fix: android.package is not set
fix: Image referenced by app.json is missing: ./assets/icon.png
fix: app/index.tsx: import target not found: ../lib/util
fix: EXPO_PUBLIC_SUPABASE_KEY is read by the code but its name is not in .env.example
Result: 7 item(s) to fixSo what I confirmed is narrow: the script catches the gaps I planted. I did not confirm the following.
- Whether a real export uses
app.jsonorapp.config.ts. With the latter, the identifier and image checks are skipped and only a note appears - Alias imports such as
@/are not resolved, so projects that use them get a weaker import check - Whether
npm installsucceeds or the build passes. You only learn that by running them
That last point matters. A clean result from this script does not guarantee a rebuild. Treat it as a coarse net that catches the most common kinds of gaps first.
What to fix first
If findings appear, this order causes the least rework.
- Lock files: pick one package manager and delete the others
- Identifiers (bundleIdentifier / package): for an app you'll ship to a store these are hard to change later, so write down the values you started with
- Images and import targets: check whether they exist in the Rork project itself. If they don't, the sync may have missed them
- Environment variables: add the names to
.env.example. Leave the values out, since they're secrets
If step 3 suggests a sync gap, hold off on the plan change. Changing your subscription while the clone is incomplete could leave you without a place to go back to.
One thing to do today
On the day you consider a plan change, clone from GitHub into a fresh folder once. That alone tells you whether the integration is working.
Then save the script's output, so you can run the same check before and after the change. To also check that no key slipped into the exported code, A 40-Line Script to Check Whether Rork Wrote an API Key Into Your Code is a useful companion. If you want exclusions in place before you clone, see Write .gitignore Before You Run git init on an Exported Rork Project. For avoiding overwrites on re-export, the routine is in Reading only what Rork changed: the diff routine I run after every re-export.