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.
{
"$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:
- Android: Play's
clientVersionStalenessDays, the number of days since this device learned about the update, when Play reports it. - Otherwise:
latest.releasedAtfrom the policy, when the target is the policy'slatest. - 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
{
"schemaVersion": 1,
"android": { "minimum": { "version": "3.0.0", "build": 300 } },
"ios": { "appStoreId": "1234567890", "minimum": { "version": "3.0.0", "build": "1" } }
}{
"schemaVersion": 1,
"android": {
"latest": { "version": "3.4.0", "build": 340, "releasedAt": "2026-10-18" },
"recommendAfterDays": 7,
"requireAfterDays": 30
}
}{
"schemaVersion": 1,
"android": { "ota": { "applyMode": "prompt" } },
"ios": { "ota": { "applyMode": "prompt" } }
}More in the guides: forced updates, staged rollout, stranded installs.