# Rolling Back a Release

> Revert a problematic Build in Applivery App Distribution by rebuilding your stable version with a higher version identifier and moving the Publication tag to it.

Source: https://docs.applivery.com/en/app-distribution/distribute/rolling-back-a-release/  •  Last updated: 2026-07-30

**Key topics:** Rollback procedure, Version identifiers, Build tags, Forced updates, Applivery, Builds, Publications, Applivery SDK

---

**TL;DR:** Rebuild your last stable version with a higher version identifier, upload it, and move the ring's tag to the new Build. Remember to lower the SDK forced update threshold if you use one.

You shipped a release, something is wrong with it, and you need people back on the version that worked. The instinct is to put the previous Build back into circulation — but that is not enough on its own, because whether a device accepts an older version is decided by the operating system, not by Applivery.

The reliable way to revert a release is to **ship forward**: take the code of the last version that worked, rebuild it with a **higher** version identifier than the problematic Build, and distribute that. The code goes backwards; the version number goes forwards. To the device it is an ordinary update, which is exactly why it installs.

:::info
This approach works on every platform Applivery distributes to. Some platforms would also accept a straight downgrade, but the roll-forward is the one procedure that is safe everywhere — so it is the one worth standardizing on.
:::

## Why you cannot simply re-serve the old Build

On Android, the system package manager refuses to install a package whose `versionCode` is lower than the one already installed. The install fails with `INSTALL_FAILED_VERSION_DOWNGRADE`. Android's [manifest documentation](https://developer.android.com/guide/topics/manifest/manifest-element) states the rule plainly: each successive version must carry a higher number.

So if you re-tag the old Build, anyone who already installed the problematic version stays stuck on it. The people you most need to fix are precisely the ones the old Build cannot reach.

Rebuilding with a higher version identifier sidesteps this entirely.

## The version identifier on each platform

Applivery **reads** version information from the package you upload — it is not something you can set in the Dashboard or pass to the API. It has to be set at build time.

| Platform | What to increase | Format |
| --- | --- | --- |
| Android | `versionCode` in your build configuration | Integer — `201` |
| iOS / macOS | `CFBundleVersion` in the app's Info.plist | One to three period-separated integers — `2.0.1` |
| Windows (MSIX / APPX) | Version in `AppxManifest.xml` | Four-part version |
| Custom platforms | `packageVersion` at upload time | Manual |

:::warning
Apple's `CFBundleVersion` is **not** a plain integer like Android's `versionCode`. It is a string of up to three period-separated integers, so "add one" is not meaningful on its own. If the problematic Build is `2.0.0`, your rollback Build should be `2.0.1` or higher. See Apple's [CFBundleVersion reference](https://developer.apple.com/documentation/bundleresources/information-property-list/cfbundleversion).
:::

The user-facing version — `versionName` on Android, `CFBundleShortVersionString` on Apple — has no technical rules. Use it to make the situation legible: keeping `1.9` makes clear which code is running, while `2.0.1` makes clear it is newer than the release it replaces. Pick whichever your users will find less confusing.

## The procedure

**Identify the last known-good version**

Find the source of the version you are reverting to, and confirm the version identifier of the problematic Build so you know what you have to exceed.

**Rebuild it with a higher version identifier**

Same code, new version identifier — higher than the problematic Build. This happens in your project configuration or CI pipeline, not in Applivery.

**Upload the rebuilt Build**

Give it a `versionName` that makes its purpose obvious, such as `v1.9 (rollback)`. Wait for processing to finish before continuing.

**Remove the tag from the problematic Build**

Open the problematic Build and remove the affected Publication's tag. It stops being served immediately.

**Add the tag to the rollback Build**

The Publication now serves the rollback Build, and devices see it as an available update.

**Lower the forced update threshold, if you use one**

See [Watch out for forced updates](#watch-out-for-forced-updates) below. Skipping this step can lock users out of the app entirely.

### Worked example

```text
Problem:
  Build v2.0  ·  versionCode 200  ·  live in production  ·  defect found

Rollback:
  Rebuild v1.9 source  ·  versionCode 201  (higher than 200)
  Upload, then move the production tag from Build 200 to Build 201
```

A device sitting on `200` receives `201`, treats it as an update, and installs it — but the code it ends up running is the stable 1.9 logic.

### From the API

Both tag changes use [PUT – Update a Build](https://docs.applivery.com/en/app-distribution/api/builds/update-build/).

:::warning
The `tags` field **replaces the entire array**. Send every tag the Build should keep, not just the one you are changing.
:::

```bash
# 1. Stop serving the problematic Build
curl 'https://api.applivery.io/v1/integrations/builds/BUILD_ID_PROBLEM' \
  -X PUT \
  -H 'Authorization: Bearer YOUR_APP_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{ "tags": [] }'

# 2. Point the ring at the rollback Build
curl 'https://api.applivery.io/v1/integrations/builds/BUILD_ID_ROLLBACK' \
  -X PUT \
  -H 'Authorization: Bearer YOUR_APP_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{ "tags": ["ring-production"] }'
```

The Workspace API equivalent is `PUT https://api.applivery.io/v1/organizations/ORG_ID/apps/APP_ID/builds/BUILD_ID` with a Service Account token.

If you use [progressive deployment](https://docs.applivery.com/en/app-distribution/distribute/progressive-deployment/), roll back one ring at a time, starting with the ring where the problem appeared.

## Apple devices behave differently depending on how the app was installed

Apple does not document what happens when you install an app whose `CFBundleVersion` is lower than the one already on the device, so we tested it ourselves. On **iOS 26.5**, the same app on the same device behaved in two opposite ways depending on the delivery path:

| How the app reaches the device | Installing an older Build |
| --- | --- |
| **App Distribution** — user installs from the Enterprise Store | The older Build installs normally |
| **Device Management** — Build assigned through MDM | The device keeps the newer version |

:::warning
When an older Build is assigned through Device Management, the device does **not** downgrade — and it does **not** report an error either. The command is accepted, nothing fails, and the newer version simply stays installed. If you are rolling back this way, the Dashboard will not tell you that nothing happened. Verify the installed version before assuming devices are fixed.
:::

The practical consequence:

-   If you distribute through the **Enterprise Store**, you can roll back by simply moving the tag back to the previous Build. No rebuild needed.
    
-   If you deploy through **Device Management**, moving the tag is not enough. You have to rebuild with a higher version identifier, exactly as described above.
    

:::info
This behavior is not documented by Apple, which means it is not guaranteed to stay the same in future iOS releases. Our test covered a supervised device enrolled through Apple Business. Treat the Enterprise Store shortcut as a convenience, not as something to build automation around — the roll-forward procedure is the one that keeps working regardless.
:::

## Watch out for forced updates

If your App embeds the [Applivery SDK](https://docs.applivery.com/en/app-distribution/sdk/) with forced updates enabled, there is a trap here worth knowing about.

Forced updates block app usage when the installed version falls below a **minimum version threshold** you configure in the Dashboard. If that threshold is still set to the problematic release, your rollback Build sits below it — and every user who installs it gets locked out of the app by the very fix you shipped.

**Lower the threshold as part of the rollback, not after it.**

One thing works in your favour: the SDK always updates to the **most recent Build available** for the App, matching only bundle ID or package name. It goes by recency, not by version number, so a freshly uploaded rollback Build is picked up as the update regardless of where its version sits.

:::info
The SDK does not respect Publication filters, groups, or audiences. If you need the rollback confined to one ring, be aware that SDK-driven updates do not honour that boundary.
:::

## After the rollback

-   **Keep the problematic Build.** Do not delete it — you will want it to reproduce the defect. Untagging is enough to take it out of circulation.
    
-   **Mind the version numbers going forward.** Your next real release has to exceed the rollback Build, not the problematic one. If the rollback was `201`, the fixed release starts at `202`.
    
-   **Let people know.** If the Publication notifies its audience, an explicit message speeds things up — users who postponed the previous update may otherwise ignore this one too.
    

:::tip
The cleanest way to avoid ever needing this is to catch the defect in a small ring first. See [Progressive Deployment with Tags](https://docs.applivery.com/en/app-distribution/distribute/progressive-deployment/).
:::
