Upwell
v0.1 · React Native · Expo

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.

policy.json
{
  "android": {
    "latest":  { "build": 340 },
    "minimum": { "build": 300 },
    "ota": {
      "latestRuntimeVersion":
        "3.4.0",
      "strandedRequireAfterDays": 14
    }
  }
}
device
platform        android
build           330
runtime         3.3.0
installSource   play
ota             no update
                for 3.3.0

// still on old JS,
// silently. until now.
explain()
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)
Expo
React Native
expo-updates
EAS Update
Google Play
App Store
TestFlight
Bare RN

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 reference
01

Below your minimum

Required update. Play immediate flow on Android, a blocking screen and the App Store sheet on iOS.

02

OTA update for this runtime

Downloaded in the background, applied on next launch, after a prompt, or right away.

03

Stranded on an old runtime

Detected. Recommended to update from the store, then required after N days.

04

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

installed runtime3.3.0latest OTA runtime3.4.0OTA updates receivednone since 3.4.0

→ 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

playFlexible
playImmediate
appStoreSheet
storeLink

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

fixFixed a crash at checkout on iOS 27.
contentUpdated the Diwali offers banner.
fixFaster cart loading on slow networks.

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.

upToDatestore.recommendedstore.downloadingstore.readyToInstallstore.requiredstrandedota.downloadingota.ready

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>

Ship the binary. Upwell does the rest.