●DEADLINE — From today, August 31, new submissions to Google Play and updates to existing apps must target Android 16, API level 36 or higher●SCOPE — Existing apps are not removed. But anything below the requirement stops appearing for users on newer Android versions, so it does not vanish, it just stops reaching people●EXTENSION — If you cannot make it, an extension can be requested through November 1. Every year some developers reach the deadline without knowing that option existed●MIGRATION — What Rork generates sits on React Native and Expo, so raising targetSdkVersion always drags the Expo SDK and its native dependencies along with it●TESTING — The time really goes after the build passes. Background execution limits, the permission model, and foreground service type declarations all take effect at once●ORDER — A workable sequence: check whether an extension applies, plan the Expo SDK upgrade, then run device regression tests on a minimal build. Doing all three at once hides the cause●DEADLINE — From today, August 31, new submissions to Google Play and updates to existing apps must target Android 16, API level 36 or higher●SCOPE — Existing apps are not removed. But anything below the requirement stops appearing for users on newer Android versions, so it does not vanish, it just stops reaching people●EXTENSION — If you cannot make it, an extension can be requested through November 1. Every year some developers reach the deadline without knowing that option existed●MIGRATION — What Rork generates sits on React Native and Expo, so raising targetSdkVersion always drags the Expo SDK and its native dependencies along with it●TESTING — The time really goes after the build passes. Background execution limits, the permission model, and foreground service type declarations all take effect at once●ORDER — A workable sequence: check whether an extension applies, plan the Expo SDK upgrade, then run device regression tests on a minimal build. Doing all three at once hides the cause
After the Google Play deadline: which dormant Android apps to raise, and which to leave at 35
Apps you have stopped updating are governed by a different bar than apps you still ship. Here is how to inventory effective targetSdk across your builds and decide which apps to raise, hold, or retire.
The first thing I noticed in Play Console was not a number that was growing. It was a row that was not moving.
New installs on an app I had not touched in a long while had gone quiet compared with the previous month. Reviews were fine. Ranking had not slipped. The cause was something I had failed to do, not something that had gone wrong.
Any indie developer running more than two or three apps ends up with uneven update cadence. Some apps get touched monthly. Some go a full year untouched. Annual platform requirements arrive for both, equally.
The awkward part is that "this app is dormant, so the requirement does not apply" is only half true.
The bar for shipping and the bar for staying visible differ by one level
Google Play's target API level requirement is really two rules living under one name. Read them as a single rule and you will misjudge your apps.
Rule
Who it applies to
Level after 2026-08-31
What happens if you miss it
Submission rule
New apps, and updates to existing apps
API 36 (Android 16) or higher
You cannot publish. The upload will not move forward
Visibility rule
Every app already published
API 35 or higher
The listing stays, but new users on devices running a newer Android will not see it
That one-level gap is the whole point. If you have no intention of shipping again, you do not need 36. You do need 35.
So the decision is not binary. It is three-way: raise to 36 and keep shipping, hold at 35 and protect visibility only, or do neither and accept that new users stop finding the app.
Wear OS and Android Automotive OS apps are outside this requirement. If you own any, take them off the inventory list first so they stop consuming attention.
Start the inventory from the artifacts on disk, not from the console
Opening Play Console app by app falls apart somewhere past the third app. I switched to reading the build artifacts I already have locally.
The script below walks both AAB and APK files and prints package name, versionCode, and targetSdk as tab-separated rows.
#!/usr/bin/env bash# List the effective targetSdk of every build artifact under a directory.# Usage: ./target-sdk-inventory.sh ~/builds# Requires bundletool (for .aab) and aapt2 (for .apk) on PATH.set -euo pipefailROOT="${1:-.}"# If you keep bundletool as a jar, pass it in:# BUNDLETOOL="java -jar $HOME/tools/bundletool-all.jar" ./target-sdk-inventory.sh ~/buildsBUNDLETOOL="${BUNDLETOOL:-bundletool}"AAPT2="${AAPT2:-aapt2}"printf 'artifact\tpackage\tversionCode\ttargetSdk\n'# -print0 with read -r -d '' survives paths containing spaces.find "$ROOT" -type f \( -name '*.aab' -o -name '*.apk' \) -print0 |while IFS= read -r -d '' f; do pkg=""; vc=""; tsdk="" case "$f" in *.aab) # set -e is on, so guard the read with || true: one bad artifact must not # abort the whole inventory. manifest="$($BUNDLETOOL dump manifest --bundle="$f" 2>/dev/null || true)" if [ -z "$manifest" ]; then printf '%s\t-\t-\tREAD_FAILED\n' "$(basename "$f")" continue fi pkg=$(printf '%s' "$manifest" | sed -n 's/.*package="\([^"]*\)".*/\1/p' | head -1) vc=$(printf '%s' "$manifest" | sed -n 's/.*android:versionCode="\([^"]*\)".*/\1/p' | head -1) tsdk=$(printf '%s' "$manifest" | sed -n 's/.*android:targetSdkVersion="\([^"]*\)".*/\1/p' | head -1) ;; *.apk) badging="$($AAPT2 dump badging "$f" 2>/dev/null || true)" if [ -z "$badging" ]; then printf '%s\t-\t-\tREAD_FAILED\n' "$(basename "$f")" continue fi pkg=$(printf '%s' "$badging" | sed -n "s/^package: name='\([^']*\)'.*/\1/p") vc=$(printf '%s' "$badging" | sed -n "s/.*versionCode='\([^']*\)'.*/\1/p" | head -1) tsdk=$(printf '%s' "$badging" | sed -n "s/^targetSdkVersion:'\([^']*\)'.*/\1/p") ;; esac # An empty targetSdk means the manifest never declared one. Keep that # distinct from a read failure. printf '%s\t%s\t%s\t%s\n' "$(basename "$f")" "${pkg:--}" "${vc:--}" "${tsdk:-UNKNOWN}"done
Keeping UNKNOWN and READ_FAILED apart matters more than it looks. Collapse both into a blank cell and "this app never declared a target level" becomes visually identical to "my toolchain is not installed." The first is a real problem with the app. The second is a problem with my laptop. The one thing an inventory must never do is let a real problem hide inside an environment problem.
The same reasoning explains why set -euo pipefail sits at the top while the read commands are wrapped in || true. If a single unreadable artifact halts the run, you lose the count of how many you actually inspected. Decide deliberately where to stop and where to record and continue.
One detail worth knowing: when targetSdkVersion is absent from the manifest, Android treats minSdkVersion as the implied target. Omitting it does not opt you into the newest behavior. Long-dormant projects are exactly where that blank tends to show up.
✦
Thank you for reading this far.
Continue Reading
What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.
WHAT YOU'LL LEARN
✦You will be able to tell whether a dormant app is about to stop reaching new users, separately from whether you can still ship updates
✦You will be able to list the effective targetSdk of every app you own in one pass, straight from the build artifacts on disk
✦You will be able to record the decision itself so that next year's requirement does not send you back through the same investigation
Secure payment via Stripe · Cancel anytime
✦
Unlock This Article
Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.
Four questions that sort each row into raise, hold, or retire
Once the list exists, the work is per-row judgment. I sort with four questions.
Is this app still acquiring new installs? If yes, losing visibility hurts immediately. If no, the visibility rule is close to harmless.
How many dependencies does it have, and when did it last build? The cost of raising is driven by dependency count, not by how much code you wrote. A project frozen two years ago stalls on dependency updates long before you reach the target level line.
Is revenue tied to new installs or to existing users? Ad revenue keeps flowing while existing users open the app. One-time purchases and first-run conversions do not. "Fewer impressions in search" means different things in those two cases.
Do you plan to add anything in the next year? If you do, holding is only deferral.
Those four answers land in a table:
Choice
Fits when
Cost you pay now
Cost you defer
What to watch
Raise to 36 and keep shipping
New installs are real and you expect to touch the app this year
Dependency updates and device regression testing, scaling with dependency count
Nothing
Crash rate and ANR at each staged rollout step
Hold at 35
New installs are thin but existing users still generate revenue
Shipping one build, once
The entire move to 36, payable in full on the day you want to ship again
Crash rate among existing users, plus dependency security advisories
Accept the loss of visibility
New installs are near zero and existing usage is flat
Nothing
The option to come back, at a bar that will have moved up
Only the shape of the decline in existing users
The third row exists because it is an honest option. Choosing not to touch an app is different from never getting around to it. The first is a decision; the second is the absence of one. Since I started writing that row down explicitly, I have wasted far less time poking at apps out of guilt.
The counterintuitive part: doing nothing is not always the cheapest
When you pick "hold," the mental arithmetic says the cost is zero. In practice, if the app is not already at 35, holding requires shipping one build. And that single build is often the most expensive thing on the list.
Getting a two-year-old project to compile rarely ends at editing the target level. Build tool versions, where the signing key lives, dependencies that are no longer distributed — any one of those turns a one-line change into several days.
In the order I actually hit them, the first obstacle was not code. It was signing. If you cannot find where the upload key lives, a successful build still never reaches submission. The second was a dependency that had been pulled from distribution: the version pinned in the lockfile no longer resolves, and the error it throws mentions nothing about API levels, so the diagnosis costs more than the fix.
Both are avoidable if you check for them early. At the moment you decide to hold, verify two things only — where the signing key is, and whether every dependency still resolves. Doing that shortens the day you eventually raise by a visible margin. Separately from any production release decision, I now confirm once a year that each app can still be built at all.
There is one more distinction that is easy to get wrong. The visibility rule governs whether new users see the app; it has nothing to do with how existing users receive updates. If you choose to hold, you can assume nothing changes for people who already installed it. If you are still trying to acquire users, though, the asymmetry cuts the other way: by the time you notice the drop, it has already been happening for a while.
So over time I stopped framing this as "raise or hold." The real question is whether you keep each app in a state where a build can still be produced. The maintenance cost of that state outlasts any particular year's requirement.
Holding still leaves work on the table
Deciding to stop shipping is not the same as deciding to stop watching. I keep four things alive on the hold side.
Keep store listing content coherent. Descriptions and screenshots can be updated without submitting a binary. Stale instructions damage your rating faster than reduced visibility does.
Leave crash alerting on. Existing users are still there, and an OS update can break an app that you have not touched at all.
Keep receiving dependency advisories. Whether you act on them is a separate call, but knowing beats not knowing.
Record the environment in which the app last built. This one pays back the most.
Concretely, I keep a plain text file in the repository listing the JDK version, Android Gradle Plugin version, Gradle version, Node version, and the date the build last succeeded. Knowing what to restore removes most of the friction from day one of a restart.
If you raise, fix the order before you start
For apps moving to 36, deciding the order up front avoids rework. Mine runs like this.
Audit dependency readiness first. Before touching the target level, check whether your dependencies support the new API level. Skipping this produces the most annoying failure mode there is: a build that succeeds locally and stops at the store pre-check. I wrote up how I list effective values in the three places I had to fix to get targetSdkVersion 36 through on a Rork project.
Raise compileSdk and targetSdk in configuration, not in native files. In Expo or Rork-generated projects, editing android/app/build.gradle by hand gets erased on the next prebuild. Put it in app.json instead.
Keep device regression minimal. Launch, permission dialogs, foreground services, and returning from background. Those four first.
Ship through a staged rollout. Not shipping to everyone at once changes the shape of the risk more than any amount of extra testing.
Spelling out minSdkVersion here is deliberate. It stops you from quietly moving the floor while you raise the ceiling. Dropping old devices is an entirely separate decision from meeting a platform requirement, and mixing both into one commit makes "why did this device stop being supported?" unanswerable six months later.
What happens after the deadline, and in what order
Past the deadline, the changes arrive in sequence rather than all at once.
First, submissions that miss the bar stop going through. That is immediate. Second, apps already published stay listed — nothing disappears from the store. Third, for apps caught by the visibility rule, new users on devices running a newer Android stop seeing the app in search and browse. People who already installed it are unaffected.
Last comes the day you decide to ship again. The cost you deferred did not evaporate. You pay it in one go, at a bar that has moved up since.
Understanding that order keeps you from trying to raise everything on the final day. In years when an extension window exists, the deadline that matters is not the technical one — it is the day you have to switch from "we will make it" to "we will file." The decision deadline always lands before the work deadline.
So that next year is not the same investigation
Annual requirements are annual. That is precisely why the inventory should outlive the afternoon you built it.
Add decision columns to the script's output:
# Inventory ledger: a skeleton meant to be filled in by hand../target-sdk-inventory.sh ~/builds \ | awk -F'\t' ' NR==1 { print "artifact,package,versionCode,targetSdk,last_release,decision,reason"; next } { print $1 "," $2 "," $3 "," $4 ",,," } ' > inventory-2026-08.csv
Put raise, hold, or retire in decision, and one line in reason. Next year you run the same script and diff it against last year's ledger. Starting from "is last year's hold still a hold?" is a categorically faster place to begin than a blank page.
Leaving reason empty defeats the point. Numbers alone will not remind future you why the call went that way. A fragment like "zero new installs, 800 existing users, ads only" is enough to make the judgment reusable.
One thing to settle today
If you do only one thing, list your artifacts once and check whether the apps you intend to hold have reached 35. The conversation about 36 can wait. Any app below that line is the one quietly losing ground this year.
I stalled more than once staring at my own hold list. If this hands you the method rather than the answer, that is the part I wanted to pass along.
Share
Thank You for Reading
Rork Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.