An update installs over the existing app — and keeps its data — when three things match the previous build (package name, signing key, and the app being the same app) and one thing is higher: the version code. Change the package name or the key and Android sees a different app, which installs alongside the old one with empty storage; reuse a version code and Play refuses the upload while a phone refuses the install. Everything else about updating an HTML app is just rebuilding with new files.
The four identifiers
| What it is | Rule | Example | |
|---|---|---|---|
| Package name | The app's identity to Android and Play | Never changes | com.janedoe.recipes |
| Signing key | Proves updates come from you | Never changes | the .jks file |
| versionCode | An integer Android compares | Strictly higher every release | 1, 2, 3 … or 20260921 |
| versionName | The string users see | Anything; convention is semver | 1.0, 1.1, 2.0 |
What happens on update
Android replaces the app's code and assets — your new HTML — and leaves the app's data directory alone. localStorage, IndexedDB, cookies and files the app wrote all survive. So a user's saved notes, high scores or settings carry across, without you doing anything. Two consequences worth designing for:
- Old data, new code. If version 2 expects a field that version 1 never saved, read it with a fallback. If you renamed a key, migrate: read the old key, write the new one, delete the old one, on first launch.
- A "what's new" moment. Store the version the app last ran as; on launch compare with the current one and show release notes if it changed. Three lines of JavaScript.
A versioning scheme that runs itself
For versionCode, use the date: 20260921 for 21 September 2026, or 2026092101 if you might ship twice in a day. It is always higher than the last one, it tells you when a build was made, and it never needs a lookup. For versionName, use major.minor: bump minor for changes, major for a redesign. The builder tracks the version code for you and refuses a lower one; the scheme just makes the number meaningful.
Sideloaded versus Play
Sideloaded (APK by link): the user downloads the new file and installs it over the old one. No automatic updates, so tell people — a note in the app that checks a version number in a small JSON on your site is a common, cheap pattern. Google Play: upload the new AAB with the higher version code, add release notes, roll out. Phones update automatically within a day or so. Play also enforces that the signing key matches; with Play App Signing, that is the upload key you registered.
When you cannot update
- Lost the keystore (own key, not on Play App Signing): the app cannot be updated. Publish under a new package name and ask users to migrate. This is why the backup advice is repeated everywhere.
- Lost the upload key but enrolled in Play App Signing: request an upload-key reset in Play Console. A few days, no data loss for users.
- Changed package name by mistake: it is a new app. Users will have two icons. Rebuild with the original name.
- Free-tier managed key, now want your own: for sideloaded apps, just rebuild with your key and tell users to uninstall the old one (data is lost). For Play, a key upgrade through Console if enrolled in Play App Signing.