Get started
App Store Submission Preflight

App Store Submission Preflight

Read-only preflight for Google Play, Amazon, Microsoft Store, itch.io or Apple: assets, signing, permissions, versions and blockers.
#Pesquisa
Avaliação
São necessárias mais avaliações
Vendas
0
Como usar
Baixar

How it works

App Store Submission Preflight is for the day before you press "submit". You point it at your repo (or describe the app) and name the store: Google Play, Amazon Appstore, Microsoft Store, itch.io or the Apple App Store. It reads your manifest, build files, icons, store assets and privacy policy, and hands back a report and an ordered runbook. It never builds, signs, uploads or submits anything, and it never asks for or prints a key, password or token.

Phase 1 — Intake. Mode (repo or described), target store, app type and packaging, first release or update, and the version currently live.

Phase 2 — Assets and sizes. The required assets for that store, with real image dimensions read from your files and a status for each.

Phase 3 — Signing and packaging. Is a release signing identity configured, is the key kept out of git and backed up, is the package ID final. Checks structure only, never key material.

Phase 4 — Permissions, privacy and data safety. Declared permissions versus what the code uses versus what your privacy policy says.

Phase 5 — Version and build number. Build number higher than the live one, version sources that agree. Summary-line counts are per finding; BLOCKERS include a debuggable release build, a tracked key or secret file, a template package ID on first publish and a mismatching privacy-policy URL. All questions are collected in one "Needs your answer" section, and with no code tree the use of each permission is UNSURE, not guessed.

Phase 6 — Runbook. A numbered submission order for your store, plus the typical rejection reasons to pre-empt.

Because store rules change often, every item is tagged STABLE (long-standing practice) or VERIFY TODAY (sizes, API levels, forms, fees: check the store's current docs). It will not state a changing number as current fact, and anything it cannot tell is marked UNSURE.

Sample output

Real, unedited output from running this Skill on a sample problem:

Mode R — Google Play — update (user says live versionCode is 7; test vs production track not stated) — BLOCKERS 7 / WARNINGS 4 / UNSURE 9 / OK 3

## Scope and evidence
- Mode R: project files at /tmp/qa2/notely were readable.
- Store: Google Play. App type: native Android (gradle). Release kind: update.
- Files read: app/src/main/AndroidManifest.xml, app/build.gradle (property names only), privacy-policy.md, release.jks (filename and git-tracked status only, never opened), ic_launcher.png (dimensions via `file`), git ls-files.
- Unavailable: any source code tree (manifest and gradle only), listing text, store console, the live published package ID, the store data-safety answers.
- User statement: "live version is 7".

## Stable practice vs verify today
STABLE:
- A square store icon, a feature/hero graphic and real screenshots are needed.
- Releases must be signed. Updates need the same signing identity and a higher build number.
- A release build must not be debuggable.
- Permissions need a feature behind them.
- The privacy policy must match behaviour and the listing's policy URL must match the policy.
- Signing keys must not be committed.

VERIFY TODAY (check the Play Console requirements, policy and "Data safety" help pages):
- Pixel sizes and counts for the store icon, feature graphic and screenshots.
- Target API level and its deadline.
- Package format (app bundle vs APK).
- Sensitive-permission declaration form for contacts.
- Data-safety form, content rating questionnaire and testing-track rules.
- Account type, verification and fees.

## Assets and sizes
| Asset | Needed? | Found in repo | Status | Tag |
|---|---|---|---|---|
| Launcher icon | Yes | app/src/main/res/mipmap-xxhdpi/ic_launcher.png, 192x192, square | FOUND-OK | STABLE (sizes VERIFY TODAY) |
| Store (high-res) icon | Yes | none; the 192px launcher icon is a different file with different rules | MISSING (usually 512x512, VERIFY TODAY) | STABLE / VERIFY TODAY |
| Feature graphic | Yes | none | MISSING (usually a wide banner of roughly 1024x500, from memory, may be stale, VERIFY TODAY) | STABLE / VERIFY TODAY |
| Screenshots | Yes | none | MISSING (count and device classes VERIFY TODAY) | STABLE / VERIFY TODAY |
| Short and full description | Yes | no listing text provided | UNSURE | STABLE |
| Content rating and data-disclosure answers | Yes | not provided | UNSURE | VERIFY TODAY |
| Privacy policy URL | Yes | see Permissions section: the two URLs in the manifest differ | BLOCKER (counted under Permissions) | STABLE |

No placeholder look checked beyond the icon, because there are no screenshots or listing text to inspect.

## Signing and packaging
| Check | Evidence | Answer | Tag |
|---|---|---|---|
| Release signing identity configured | build.gradle:7-13 `signingConfigs.release`, :16 `signingConfig signingConfigs.release` | YES (structure only) | STABLE |
| Signing key not committed | release.jks is git-tracked (git ls-files lists it) | NO, BLOCKER: untrack it, rotate the key if it is real, keep it outside the repo | STABLE |
| Secret-looking value in build file | A secret-looking value is present at app/build.gradle:10, do not commit it. Move it to a property or environment variable. | WARNING | STABLE |
| Key backed up in two places | cannot see | UNSURE (see Needs your answer) | STABLE |
| Same signing identity as the first release | cannot see | UNSURE | STABLE |
| Package ID final and not a template default | AndroidManifest.xml:3 and build.gradle:3 are `com.example.notely` (template-default family). This is an update, so it is a blocker only if it differs from the published ID. | UNSURE | STABLE |
| Package format matches the store | no bundle or APK output config found | UNSURE | VERIFY TODAY |
| Release is not debuggable, production endpoints | AndroidManifest.xml:8 `android:debuggable="true"` on a release build | BLOCKER (endpoints not checked, no code) | STABLE |
| Min and target platform levels set deliberately | no minSdk or targetSdk found in build.gradle | UNSURE | VERIFY TODAY |
| Developer account, identity verification, fee | cannot see | UNSURE | VERIFY TODAY |

By hand (a person must do these): confirm or create the Play developer account and identity verification; pay any fee; store the keystore and its password in two safe places; complete the console forms.

## Permissions, privacy and data safety
| Item | Declared | Used (evidence) | Disclosed | Finding | Tag |
|---|---|---|---|---|---|
| READ_CONTACTS | AndroidManifest.xml:4 `android.permission.READ_CONTACTS` | UNSURE, code not available. The user says this is a notes app, which suggests no contacts feature. | Policy says "We collect no data." (privacy-policy.md:2); contacts not mentioned | UNSURE. If code confirms no use, it becomes NO-EVIDENCE: remove it or write a justification. Sensitive permission, expect extra declaration and review. | STABLE (declaration VERIFY TODAY) |
| Ads SDK | not applicable | build.gradle:21 `com.google.android.gms:play-services-ads` | privacy-policy.md:2 "We collect no data." | WARNING: policy says no data is collected beside an ads SDK; update the policy and the data-safety form | STABLE (form VERIFY TODAY) |
| Analytics SDK | not applicable | build.gradle:22 `com.google.firebase:firebase-analytics` | privacy-policy.md:2 "We collect no data." | WARNING: same contradiction | STABLE (form VERIFY TODAY) |
| Policy contact method and deletion/retention | not applicable | not applicable | privacy-policy.md has no contact method and no retention statement | WARNING: add a contact method; add retention text if data is collected. Account sign-up not evidenced. | STABLE |
| Policy URL consistency | AndroidManifest.xml:9 `privacy_policy_url` = https://notely.app/privacy; :10 `store_listing_privacy_url` = https://notely.app/legal/privacy-policy | not applicable | the two URLs differ from each other, and the policy file states no URL; whether either returns 404 is unknown | BLOCKER: the store listing URL must match the policy you were given | STABLE |

## Version and build number
- AndroidManifest.xml has no version attributes. build.gradle:4 `versionCode 7`, :5 `versionName "1.0.7"`. No git tags (the repo has no commits).
- The user says the live version is 7. versionCode 7 is not strictly higher: BLOCKER, STABLE. Bump to at least 8.
- versionName `1.0.7` is not a template default: OK. Whether it follows the earlier scheme cannot be seen: UNSURE is not counted separately; confirm with the live listing.
- Release notes for the new version were not provided.

## Submission runbook
1. You: fix the blockers below (debuggable flag, untrack the keystore, bump versionCode, reconcile the policy URLs, create the missing assets, fix the policy text). Evidence: a clean `git ls-files` and a diff. STABLE.
2. You: make a production build from a clean checkout of the tagged commit. Evidence: a signed artifact with a higher versionCode. STABLE. Package format VERIFY TODAY.
3. You: smoke-test the release artifact on a real device (first launch, core note creation, offline start, reinstall over the live version). Evidence: a test note. STABLE.
4. Console: upload to an internal or test track first if one exists. VERIFY TODAY.
5. Console: complete the listing, privacy policy URL, data-safety form, content rating, audience, countries. Evidence: forms saved with answers matching the policy and SDKs. VERIFY TODAY.
6. Console: upload the store icon, feature graphic and screenshots from the real build. VERIFY TODAY.
7. Console: add release notes and reviewer notes; if READ_CONTACTS stays, explain why. STABLE.
8. Console: clear pre-launch warnings. VERIFY TODAY.
9. Console: submit and record the date, build number and tag.
10. After approval: check the live listing on a clean device and tag the repo.

Typical rejection reasons to pre-empt (typical, not predictions):
1. Debuggable release build (finding: AndroidManifest.xml:8). STABLE.
2. Sensitive permission without a feature (READ_CONTACTS). STABLE; declaration VERIFY TODAY.
3. Privacy policy contradicts SDK data collection (ads and analytics rows). STABLE.
4. Policy URL in the listing differs from the policy (manifest URLs). STABLE.
5. Data-safety form inconsistent with the SDKs. VERIFY TODAY.
6. Missing store assets (icon, feature graphic, screenshots). STABLE.
7. Build number not higher than the live one (versionCode 7). STABLE.
8. Signing mismatch with the live release (key tracked or replaced). STABLE.
9. Target API level too low. VERIFY TODAY (not set in build.gradle).
10. Placeholder package ID if it differs from the published one. STABLE.

## Blockers first
| File or place | What to change | Why |
|---|---|---|
| app/src/main/AndroidManifest.xml:8 | Remove `android:debuggable="true"` | A debuggable release build is a blocker |
| release.jks | Untrack it (`git rm --cached`), keep it outside the repo, rotate if it is a real key | A signing key in git |
| app/build.gradle:4 | Raise versionCode above 7 | The user says 7 is live |
| AndroidManifest.xml:9-10 and the listing | Make the listing's policy URL identical to the policy URL, and publish the policy there | Listing and policy URLs must match |
| Store listing | Add a store icon (usually 512x512, VERIFY TODAY) | Required asset missing |
| Store listing | Add a feature graphic | Required asset missing |
| Store listing | Add real screenshots | Required asset missing |
Then the warnings: secret-looking value at app/build.gradle:10; policy "no data" versus ads and analytics SDKs; policy has no contact method.

## Needs your answer
1. Does the live app use the package ID `com.example.notely`? (If not, it is a blocker.)
2. Does any code use contacts? If you can share the source, I will search it; otherwise the permission stays UNSURE.
3. What listing text will you submit (short and full description)?
4. What are your planned content rating and data-safety answers?
5. Does a release keystore exist, is it backed up in two places, and do you know where the password is stored?
6. Is the release signed with the same key as the live version?
7. Will you upload an app bundle or an APK?
8. What min and target SDK do you intend, and have you checked today's requirement?
9. Is your Play developer account active and identity-verified, with any fee paid?

## Limits of this check
Read-only: nothing was built, signed or submitted. No secret values were read or repeated. No store access, so the live version and the policy URLs are as you stated. This does not guarantee approval. Sizes, counts, API levels and policy forms are from memory and may be stale; check the Play Console pages.

Use cases

  • A last-day check before your first Google Play, Amazon, Microsoft Store or App Store submission
  • Catching permissions your code never uses before a reviewer asks about them
  • Spotting a privacy policy that contradicts the ads or analytics SDKs you shipped
  • Making sure the build number was actually bumped and the version files agree
  • Getting an ordered runbook with the rejection reasons to pre-empt for your specific store

FAQ

Does it submit or build my app? No. It is read-only. It reads files in your repo, never writes, builds, signs or uploads, and keeps nothing after the conversation.

Does it need my keystore or passwords? Never. It only checks that signing is configured and the key is not committed, and asks you yes or no whether it is backed up. If you paste a secret, it will not repeat it and will suggest rotating it.

Are the store sizes and rules current? Not guaranteed, and it says so. Long-standing practice is tagged STABLE; sizes, API levels, forms and fees are tagged VERIFY TODAY with the kind of store page to check. Anything it cannot tell is UNSURE.

Does it guarantee approval or give legal advice? No. It lowers the chance of avoidable rejections and compares your own documents with each other. It is not legal, privacy-law or content-rating advice.

Which stores? Google Play, Amazon Appstore, Microsoft Store, itch.io and the Apple App Store.

Can it work without my repo? Yes, in described mode it judges only what you tell it and marks the rest UNSURE.