AppPage.net

Free · No SDK · Android, iOS & Flutter

Force update & remote config for your app

A small JSON file your app reads at launch. Make old versions update, put the app in maintenance mode or show a notice once, from a panel, without shipping a new release.

It lives on your free app website: yourapp.apppage.net/app-config.json

{
  "android": { "minVersion": "2.0.0", "latestVersion": "2.3.1",
               "storeUrl": "https://play.google.com/store/apps/details?id=…" },
  "ios":     { "minVersion": "2.0.0", "latestVersion": "2.3.0",
               "storeUrl": "https://apps.apple.com/app/id…" },
  "updateMessage": "Please update to keep using the app.",
  "maintenance": { "active": false, "message": null },
  "notice": { "id": "5f2c91ab03de", "title": "Dark mode is here",
              "message": "Turn it on in Settings.", "url": null },
  "status": { "state": "operational", "title": null, "url": "…/status" },
  "update": { "required": true, "available": true },
  "updated": "2026-10-11T09:30:00Z"
}

Set it up in three steps

  1. 1

    Create your app's site

    Sign up and find your app on Google Play or the App Store, or start a site for an app that isn't out yet.

  2. 2

    Turn on the config

    In the panel, click Add /app-config.json and fill in what you need: versions, maintenance, a notice.

  3. 3

    Read it at launch

    Add a few lines to your app (below). From then on, change the behaviour from the panel; apps see it within a minute.

What you can do with it

  • Force an update. Found a bad bug or changed your API? Raise the lowest supported version and older versions show an update screen with a button to the store.
  • Suggest an update. Below the latest version, the app can show a gentle “a new version is available” banner instead.
  • Maintenance mode. Moving servers or migrating the database? Switch it on and the app shows your message instead of failing requests.
  • A notice, once. A new feature, a sale or a survey: each new text gets a new id, so the app shows it only once.
  • The app's status. With the /status page on, the file also says whether there's an ongoing outage, so the app can explain instead of showing an error.

The request

Call https://yourapp.apppage.net/app-config.json?platform=android&version=2.3.1 (or platform=ios). With the platform and the app's version, the answer adds update.required (the version is below the lowest supported one) and update.available (below the latest), so the app needs no version comparison of its own. Versions are numbers with dots; a build number after a space, such as 2.3.1 (45), is ignored. Sites on their own domain answer at yourdomain.com/app-config.json.

Add it to your app

Android (Kotlin)
// At launch, off the main thread (uses org.json, built into Android).
suspend fun loadAppConfig(): JSONObject? = withContext(Dispatchers.IO) {
    runCatching {
        val url = URL("https://yourapp.apppage.net/app-config.json?platform=android&version=${BuildConfig.VERSION_NAME}")
        JSONObject(url.readText())
    }.getOrNull()
}

lifecycleScope.launch {
    val config = loadAppConfig() ?: return@launch   // offline: carry on
    val maintenance = config.getJSONObject("maintenance")
    when {
        config.optJSONObject("update")?.optBoolean("required") == true ->
            showUpdateScreen(config.getJSONObject("android").optString("storeUrl"), config.optString("updateMessage"))
        maintenance.optBoolean("active") -> showMaintenance(maintenance.optString("message"))
        config.optJSONObject("notice") != null -> showNoticeOnce(config.getJSONObject("notice"))  // remember its "id"
    }
}
iOS (Swift)
struct AppConfig: Decodable {
    struct Store: Decodable { let storeUrl: String? }
    struct Update: Decodable { let required: Bool; let available: Bool }
    struct Maintenance: Decodable { let active: Bool; let message: String? }
    struct Notice: Decodable { let id: String; let title: String?; let message: String?; let url: String? }
    let ios: Store
    let update: Update?
    let updateMessage: String?
    let maintenance: Maintenance
    let notice: Notice?
}

func loadAppConfig() async -> AppConfig? {
    let version = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? ""
    guard let url = URL(string: "https://yourapp.apppage.net/app-config.json?platform=ios&version=\(version)"),
          let (data, _) = try? await URLSession.shared.data(from: url) else { return nil }
    return try? JSONDecoder().decode(AppConfig.self, from: data)
}

// At launch:
if let config = await loadAppConfig() {
    if config.update?.required == true { showUpdateScreen(config.ios.storeUrl, config.updateMessage) }
    else if config.maintenance.active { showMaintenance(config.maintenance.message) }
    else if let notice = config.notice { showNoticeOnce(notice) }  // remember notice.id
}
Flutter (Dart)
// pubspec.yaml: http, package_info_plus
Future<Map<String, dynamic>?> loadAppConfig() async {
  final info = await PackageInfo.fromPlatform();
  final platform = Platform.isIOS ? 'ios' : 'android';
  final uri = Uri.parse('https://yourapp.apppage.net/app-config.json?platform=$platform&version=${info.version}');
  try {
    final res = await http.get(uri).timeout(const Duration(seconds: 5));
    return res.statusCode == 200 ? jsonDecode(res.body) as Map<String, dynamic> : null;
  } catch (_) {
    return null; // offline: carry on
  }
}

// At launch:
final config = await loadAppConfig();
if (config?['update']?['required'] == true) {
  showUpdateScreen(config![Platform.isIOS ? 'ios' : 'android']['storeUrl'], config['updateMessage']);
} else if (config?['maintenance']?['active'] == true) {
  showMaintenance(config!['maintenance']['message']);
}

Good to know

  • Offline is fine: if the request fails, let the app carry on. Only an answer from the server should block it.
  • Keep the update screen honest: always offer the store button, and use a forced update only when older versions can't work any more.
  • Caching: the file is cached for 60 seconds, so a change reaches every app within a minute.
  • Web apps can read it too: it allows requests from any origin.

Questions

Do I need Firebase Remote Config?

Not for this. Firebase is great for A/B tests and many keys; for “update, maintenance, notice” a plain JSON file is simpler, has no SDK and works the same on Android, iOS and Flutter.

Is it the same file for Android and iOS?

Yes, with separate versions and store links per platform.