Upwell

policy.json reference

Every field of the remote policy that controls Upwell, with defaults and examples.

The policy is a JSON file you host. Upwell fetches it on every check, validates it, and caches the last valid copy. Change it and you change how every installed app behaves, with no new release.

policy.json
{
  "$schema": "https://upwell.run/schemas/policy.schema.json",
  "schemaVersion": 1,
  "android": {
    "latest": { "version": "3.4.0", "build": 340, "releasedAt": "2026-10-18" },
    "minimum": { "version": "3.0.0", "build": 300 },
    "recommendAfterDays": 2,
    "requireAfterDays": null,
    "rollout": 100,
    "ota": {
      "latestRuntimeVersion": "3.4.0",
      "applyMode": "nextLaunch",
      "strandedRequireAfterDays": 14
    },
    "message": {
      "title": "Update available",
      "body": "This version fixes checkout on older devices.",
      "releaseNotesUrl": "https://example.com/changelog"
    }
  },
  "ios": {
    "appStoreId": "1234567890",
    "latest": { "version": "3.4.0", "build": "1", "releasedAt": "2026-10-18" },
    "minimum": { "version": "3.0.0", "build": "1" },
    "recommendAfterDays": 2,
    "ota": {
      "latestRuntimeVersion": "3.4.0",
      "applyMode": "prompt",
      "strandedRequireAfterDays": 14
    }
  }
}

Top level

Prop

Type

Platform fields

Every field is optional. A platform section with nothing in it is valid and does nothing.

Prop

Type

Version references

latest and minimum are version references:

Prop

Type

Comparisons use build numbers

Android compares versionCode only. iOS compares the version first, then CFBundleVersion, because many teams reset the build number for each version. Version names are shown to users, but never decide anything on Android. See Comparing versions.

OTA

Prop

Type

iOS only

Prop

Type

Staleness

"How old is the update?" decides recommendAfterDays, requireAfterDays and strandedRequireAfterDays. It comes from:

  1. Android: Play's clientVersionStalenessDays, the number of days since this device learned about the update, when Play reports it.
  2. Otherwise: latest.releasedAt from the policy, when the target is the policy's latest.
  3. iOS: the App Store's release date, when the lookup's version is the target.

Set releasedAt on iOS

iOS has no staleness or priority signal. Without latest.releasedAt, staleness is unknown, so recommendAfterDays above 0 never recommends and requireAfterDays never escalates.

Messages

message and requiredMessage let you write the copy per release without shipping a new build:

"message": {
  "title": "A faster checkout",
  "body": "Version 3.4 cuts checkout time in half.",
  "releaseNotesUrl": "https://example.com/changelog/3.4"
},
"requiredMessage": {
  "title": "Update required",
  "body": "This version can no longer place orders. Update to continue."
}

When a field is missing, the UI kit falls back to its strings.

Forward compatibility

Unknown keys are ignored with a warning, not rejected, so a policy written for a newer Upwell doesn't break older app versions. Warnings appear in state.policyWarnings. Invalid values (wrong types, minimum above latest) make the whole policy invalid, and Upwell keeps using the last valid copy. See Validation.

Examples

More in the guides: forced updates, staged rollout, stranded installs.

On this page