Skip to main content

App Version Control

Admins can set the minimum-supported and latest iOS/Android app versions from /system/app-versions, and every client (iOS, Android — web has no version-gating concept) can check at launch whether it must force-update or should show a soft "update available" nudge.

This was built on top of iosAppVersion/androidAppVersion/webAppVersion/minSupportedIosVersion/minSupportedAndroidVersion/iosStoreLink/androidStoreLink — columns that have existed on SystemSetting since the very first migration (20220111010310-create-config-app.js) but were never exposed anywhere in GraphQL or read by any manager until now. No new database columns were needed.

Implementation checklist

Admin settings (/system/app-versions, requires MANAGE_APP_VERSIONS)

  • View current thresholds for both platforms (adminGetAppVersionSettings)
  • Update one or more thresholds — only the fields provided are changed (adminUpdateAppVersionSettings), covering per platform (iOS/Android):
    • the latest available version (soft "update available" nudge, not enforced)
    • the minimum supported version (below this, checkAppVersion reports forceUpdate: true)
    • the store link shown to a user who must/should update
    • plus an informational-only webAppVersion field (the web app has no version-gating check today)

Client-facing launch check (unauthenticated)

  • checkAppVersion(platform: AppPlatform!, currentVersion: String!) — compares the calling app's reported version against the configured thresholds and returns {forceUpdate, updateAvailable, latestVersion, minSupportedVersion, storeLink}. Version comparison is numeric per dot-separated segment (2.9 < 2.10), not a plain string compare — see apps/backend/utils/semver.util.js. Gracefully returns forceUpdate: false, updateAvailable: false when no thresholds have been configured yet for that platform (fresh install), rather than throwing.

apps/ios is not wired up to call this yet — that's a separate, deliberately out-of-scope future task. The query exists end to end now specifically so it's ready when that work starts: packages/apollo-swift (the Swift codegen package for iOS) already reads its schema/operations from the exact same files as apollo-web (packages/graphql/schema/schema.web.graphqls + packages/graphql/operations/Web/**/*.graphql), so checkAppVersion — defined once in packages/graphql/operations/Web/AppVersion/CheckAppVersion.graphql — is already picked up by both. See CLAUDE.md for the standing rule this establishes: future queries/mutations meant for iOS/Android should be added to packages/graphql/operations/Web/ (the shared client schema) even before apps/ios itself is touched, so the backend contract is ready ahead of the actual mobile integration work.

Where this lives

Backend

Frontendapps/frontend-admin/src/app/system/app-versions/page.tsx + AppVersionsContent.tsx. Nav entry gated by the MANAGE_APP_VERSIONS permission (or super_admin), same pattern as the MANAGE_PAYOUTS-gated Payments section.

PermissionMANAGE_APP_VERSIONS, added to the real permission catalog in admin-user.manager.js#getAdminPermissions alongside MANAGE_PAYOUTS/MANAGE_PROMOTIONS.

GraphQL reference

# Admin (requires MANAGE_APP_VERSIONS)
query AppVersionSettings {
adminGetAppVersionSettings {
id iosAppVersion androidAppVersion webAppVersion
minSupportedIosVersion minSupportedAndroidVersion
iosStoreLink androidStoreLink
}
}

mutation UpdateAppVersionSettings($input: AppVersionSettingsInput!) {
adminUpdateAppVersionSettings(input: $input) { id iosAppVersion minSupportedIosVersion }
}

# Client (unauthenticated - iOS/Android launch check; not called from anywhere yet)
query CheckAppVersion($platform: AppPlatform!, $currentVersion: String!) {
checkAppVersion(platform: $platform, currentVersion: $currentVersion) {
forceUpdate updateAvailable latestVersion minSupportedVersion storeLink
}
}