31 August 2026 · 5 min read

Play reads your manifest before your users do

Every permission line in an Android manifest is a promise Google Play reviews before a user sees the app, with less context than the user has. The account carries the risk.

An Android manifest is read twice before anyone uses the app. The device reads it at install, to know what the app may ask for. Google Play reads it before that, at review, to decide whether the app may be listed at all, and the reviewer has less context than the user will: no onboarding screen, no explanation, just a list of permissions and a policy that says which ones need justifying. When eight apps ship under one developer account, as mine do, a permission that looks unjustified in one app is a strike against the account that publishes all eight. The habit that keeps the account clean is boring and effective: a written reason for every permission, a screen where it is asked, and a path through the app when it is refused, kept in one table that the release process reads before the reviewer does.

The policy has been tightening for years

Play's permission policy has moved in one direction, and each move has the same shape: a permission group that used to be free to request becomes one that must be justified or replaced. The permissions policy restricts the SMS and call log groups to apps whose core purpose needs them, treats the inventory of installed apps as personal data so that broad package visibility is allowed only for apps like launchers, file managers and security tools, and requires high-risk permissions to be declared through a form in the console. The photo and video policy says that apps targeting Android 13 or later may request the broad media permissions only when the system photo picker is not sufficient, which for one-time or occasional access it always is, with full compliance mandatory for every developer from 28 May 2025 and non-compliant apps subject to removal after that date.

Google Play permission policy milestones, 2019 to 2025 A timeline with four points: 2019, SMS and call log permissions restricted to core-purpose apps; 2021, package visibility restricted and broad app inventory access declared; 2024, foreground service types required with declarations; 28 May 2025, photo and video permissions restricted with the photo picker required for occasional access. 2019 SMS and call log 2021 package visibility 2024 foreground service types 28 May 2025 photos and video Each step turns a free-to-request permission into one that needs a reason or a replacement.
Source: the Google Play permissions policy and photo and video permissions policy; the earlier dates are the years each restriction came into force, the last is the date the policy names.

The direction is the point. A permission that is acceptable today is a candidate for the next restriction, and an app whose manifest carries permissions it does not need is an app that will fail a future review for a reason nobody remembers adding. The photo picker rule caught a great many apps that had requested storage access years earlier for a feature since removed, and the fix in each case was not engineering but archaeology: finding out why the line was there.

The permission ledger

The archaeology is what the ledger prevents. It is a table with one row per manifest permission and five columns: the user-visible reason, in the words the app will show; the screen on which the permission is requested, which must be the screen where the feature that needs it is used; the fallback when the user refuses, which must be a working path through the app and not a dead end; the policy section the permission falls under; and the date and release in which it was added. A permission without a complete row does not ship. A row whose reason no longer describes a feature in the app is a permission to remove.

The permission ledger for a small habit tracker A table with rows for each permission a habit tracker might declare. Post notifications: reason, remind you at the time you chose; screen, the reminder settings; fallback, reminders shown in-app only; policy, user data and notifications. Exact alarms: reason, fire the reminder at the exact minute; screen, when a reminder time is set; fallback, inexact alarm within a window; policy, alarms and reminders. Internet: not declared, the app has no server. Read media images: not declared, no feature needs photos. Two rows are marked as candidates for removal because their reason no longer matches a feature. One row per line in the manifest PERMISSION REASON SHOWN SCREEN IF REFUSED Post notifications remind you at your time reminder settings in-app reminders Schedule exact alarm fire at the exact minute when a time is set inexact window Vibrate buzz with the reminder reminder settings silent reminder Internet no feature needs it: the app has no server remove Read media images reason points at a feature removed two releases ago remove The highlighted rows are the point: permissions with no living reason, found first.
Illustrative: a ledger for a habit tracker of the kind I ship; the rows are examples of the shape, not a specific app's manifest.

The three middle columns do work that the policy text asks for in different words. The reason column is what the permissions declaration form wants, written once and reused. The screen column enforces the rule that permissions are requested in context, at the moment the feature needs them and not at first launch, which is both policy and the single largest factor in whether users grant them. The fallback column is the one most teams skip and the one the photo policy makes explicit: an app must make a reasonable effort to work for a user who declines, and a feature that dead-ends on refusal is a feature that will be described in a review as coercive.

The review path, with the ledger feeding it

The ledger is not documentation for its own sake; it feeds the release process at the points where Play asks questions. The console's data safety section and the permissions declaration form are both filled in from the ledger's columns, so that the answers Play sees are the same answers the app shows users, which is the consistency a reviewer is checking for. A pull request that adds a manifest permission fails a check unless it also adds a ledger row, and a release checklist step diffs the manifest against the ledger so that a permission added by a dependency, which happens more often than people expect, is caught before upload rather than by the reviewer.

The review path from upload through policy review to publication, with the ledger feeding the declaration forms A flow: the manifest is diffed against the ledger in continuous integration; the bundle is uploaded; the console's declaration forms and data safety section are filled from the ledger; Play's policy review reads the manifest and the declarations; the app is published, or a policy issue is raised against the account. The ledger feeds the forms and the diff, and the account, not the app, is marked as where a strike lands. The ledger answers the reviewer before the reviewer asks Ledger one row each CI diff manifest vs ledger Declarations from the ledger Policy review manifest and forms live issue against the account A strike lands on the account behind all eight apps, so the ledger is kept per account. the diff catches permissions that dependencies add without anyone deciding to
Illustrative: the release path as I run it; the ledger is the single source for both the automated check and the console forms.

Two permissions that look free and are not

Two lines in particular deserve rows because they are so easily added without thought. The first is the internet permission, which nearly every template includes and which a device-only app does not need; its presence in an app that promises not to send data anywhere is a contradiction a reviewer can see in the manifest and a user can see in the store listing, and removing it is the cheapest credibility an offline app can buy. The second is the exact alarm permission, which reminder features reach for by habit and which Android 12 and later treat as a user-revocable grant; its row needs a fallback, an inexact alarm within a window, because a user who revokes it must still get their reminder, late by a few minutes rather than never.

The ledger also has to say who owns each row, because permissions arrive from libraries as often as from feature work. An analytics or advertising dependency can add a permission through its own manifest that merges into the app's, and the first the team hears of it is a reviewer's question. The diff in continuous integration is what catches that, and the row it demands is what forces the decision: keep the dependency and justify the permission, or drop the dependency. There is no third option, and pretending there is one is how manifests grow lines nobody can explain.

Why the account is the unit of risk

Google Play's enforcement is applied to developer accounts, and the practical meaning of that for a small studio is that every app is a liability for every other. A permissions problem in the least-used of eight apps is not contained to that app; repeated or serious issues escalate to the account, and an account under enforcement cannot publish updates to any of its apps. That is the asymmetry the ledger is built for. The cost of a row is ten minutes per permission. The cost of a missing one is, in the worst case, every release of every app stalled while an appeal is written about a line in a manifest that nobody remembers adding.

It also changes what "minimal permissions" means. For a single app, minimal is a nice principle. For an account with several, minimal is a control on shared risk, and the ledger is the mechanism: fewer rows means fewer things to justify, fewer surfaces for a future policy to catch, and fewer questions from a reviewer who has only the manifest to go on. The manifest is the first thing Play reads and the last thing most developers look at. Reading it first, with a ledger, is how the eight apps keep publishing.

AndroidGoogle PlayRelease Engineering
All writing

Written by Mohd Shayan

Get new posts by email

Occasional essays on engineering, AI, and building for the people technology leaves behind.

One email per new post. Unsubscribe any time.

Subscribe with RSS