Updates That
Rise Up On
Their Own.
One policy decides, on every launch, whether a user needs an OTA update, a new store build, or nothing. Upwell drives it end to end.
{
"android": {
"latest": { "build": 340 },
"minimum": { "build": 300 },
"ota": {
"latestRuntimeVersion":
"3.4.0",
"strandedRequireAfterDays": 14
}
}
}platform android build 330 runtime 3.3.0 installSource play ota no update for 3.3.0 // still on old JS, // silently. until now.
newer: policy latest 340 > installed build 330 staleness: 16 days (Play) stranded: yes, runtime 3.3.0 != latest OTA runtime 3.4.0 flow: playImmediate → store.required stranded for 16 days (strandedRequireAfterDays 14)
One policy. Every kind of update.
A JSON file you host anywhere. Change it, and every installed app changes how it updates, with no release.
Read the policy referenceBelow your minimum
Required update. Play immediate flow on Android, a blocking screen and the App Store sheet on iOS.
OTA update for this runtime
Downloaded in the background, applied on next launch, after a prompt, or right away.
Stranded on an old runtime
Detected. Recommended to update from the store, then required after N days.
Newer build in the store
Play flexible update with progress and restart-to-install. The App Store sheet on iOS.
Built for the update you didn't see coming.
A pure decision engine, real store flows, and a UI you can drop in or replace.
runtime check
→ stranded: recommend, then require
Stranded installs, found.
After a native change, old binaries stop getting OTA updates and nothing tells them. Upwell notices and moves them to the store. How it works
Real store flows.
Its own Play Core integration (progress, restart-to-install, resumed immediate updates) and the in-app App Store sheet. Falls back to a store link when a flow can't run.
What's new
Transparent OTA.
Describe every OTA release in releases.json. Users get an "Updated" sheet and a "What's new" history. Transparency tooling, not a compliance guarantee.
Simulate every state.
simulate('stranded') in a dev build or Expo Go. On Android, Play's FakeAppUpdateManager runs the real Kotlin path. No internal app sharing, no TestFlight.
Integration
A few lines. Then never think about it.
Wrap your app, render the UI kit once, and Upwell checks on launch and every foreground. Prefer your own design? useAppUpdate() gives you the decision and the actions.
// App.tsx
<UpwellProvider
policy="https://cdn.example.com/policy.json"
ota={expoUpdatesAdapter()}
>
<RootNavigator />
<UpwellUI />
</UpwellProvider>