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,
checkAppVersionreportsforceUpdate: true) - the store link shown to a user who must/should update
- plus an informational-only
webAppVersionfield (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 — seeapps/backend/utils/semver.util.js. Gracefully returnsforceUpdate: false, updateAvailable: falsewhen 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
apps/backend/graphql/types/app-version.type.js— client-facingcheckAppVersionschema (AppPlatformenum,AppVersionStatustype)apps/backend/graphql/types/admin/app-version-admin.type.js— admin settings schemaapps/backend/graphql/resolvers/app-version.resolver.js/graphql/resolvers/admin/app-version-admin.resolver.jsapps/backend/managers/app-version.manager.js— one manager backs both the admin editor and the client check (same underlyingSystemSettingrow)apps/backend/data-access-services/admin/app-version-settings.access-service.js— singleton-row read/write, same pattern aspayout-settings.access-service.jsapps/backend/utils/semver.util.js— numeric dot-separated version comparator (nosemvernpm dependency exists in this codebase)
Frontend — apps/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.
Permission — MANAGE_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
}
}