storebound/skills/play-first-release/SKILL.md

132 lines
12 KiB
Markdown

---
name: play-first-release
description: Step-by-step first (and later) Google Play release of a mobile app or game - Play Console app creation, service account + API access, signing, AAB upload to internal testing, store listing, graphics, data safety, App content declarations, production draft and sending for review. Use when the user wants to publish, release, upload or set up an Android app or game on Google Play, push a Play listing or AAB, or hits Play Developer API errors (edits, "outside of this Edit", draft app releases).
---
# Google Play: first release
The CLI is `storebound` (on PATH while the plugin is enabled; otherwise `bunx storebound` in a project that has it,
or `bun <toolchain>/cli/index.ts`; agents can also use the `storebound mcp` tools).
It reads `storebound.config.ts` in the project (create with `storebound init`) and credentials from env or
`.storebound/credentials.json` (gitignored). Every write command has `--dry`. Run `storebound check` first.
**Do it, don't ask** (first rule in AGENTS.md / `docs/agents-core.md`): you run every step below yourself, API steps
with storebound and UI steps in Play Console with your browser tool, and verify each. The owner only presses the
public or irreversible buttons (Send changes for review, production release or rollout, agreements, deleting
products) and answers the compliance facts no file proves, all in one batch at the start.
## Steps (API = storebound, UI = Play Console, driven by you with your browser tool)
1. **UI · Create the app.** `storebound console plan play-app-record` (then drive the form with your browser tool,
or the scripted `storebound console fill play-app-record`). The form is
`/console/u/0/developers/<developerId>/create-new-app`: App name (max 30), App package name (`play.packageName`,
max 150; press the read-only "Check availability": "Package name available"; fixed for good), default language,
App or Game and Free or Paid (material radios: click the inner `[role=radio]`, then no "Select …" hint may be
left), and the declarations, which the owner ticks before pressing Create app. Free can never become Paid.
`storebound console verify play-app-record` checks the form first. Afterwards the app lives at
`/console/u/0/developers/<developerId>/app/<appId>/app-dashboard`: run `storebound console verify play-app-record
--url "<that URL>"` (dry diff of `play.appId`), then the same with `--write` to save it (the App content forms
deep-link from it). Missed the redirect: `storebound play app-id` (the app list shows 10 per page and a new app is
not on page 1; search the package name, pipe the captures in).
**Owner step after every Create app:** a service account's app-level permissions do NOT cover apps created later.
Granting them is the owner's (a permission grant; agents in auto mode are blocked from it too): give the exact
path in the same message as the Create app button, Users and permissions > the service account > Add app > the new
app > Admin (or at least: view app information, manage store presence, release to testing tracks, manage testing
tracks, manage production releases, manage policy declarations, manage orders and subscriptions) > Apply > Save
changes. Then run `storebound apps check --store play` yourself.
2. **UI · API access.** Google Cloud project (any) > enable "Google Play Android Developer API" > create a service
account > JSON key (keep it outside the repo). Play Console > Users and permissions > Invite new users > the
service account email > app permissions: view app information, manage store presence, release to testing tracks,
manage testing tracks, release to production (only if you want API production drafts), manage orders (for purchase
checks). Put the key path in `PLAY_SERVICE_ACCOUNT`. Permissions can take a few minutes to apply.
3. **Local · Signing.** Generate an upload key (`keytool -genkeypair -v -storetype PKCS12 -keystore upload.p12
-alias upload -keyalg RSA -keysize 4096 -validity 10950`), keep keystore + passwords out of git and back them up
(encrypted, two places). Play App Signing holds the real app key; a lost upload key can be reset via the Console.
Check the AAB signer before uploading: `keytool -printcert -jarfile app-release.aab`. Put the keystore path and
passwords in `ANDROID_KEYSTORE`, `ANDROID_KEYSTORE_PASSWORD`, `ANDROID_KEY_ALIAS`, `ANDROID_KEY_PASSWORD` (env or
`.storebound/credentials.json`) and `storebound play build` signs with it (`build-and-sign` skill).
4. **Local · Version.** versionName (e.g. 1.0.0) and a versionCode that always increases
(`storebound play next-version-code` prints the next free one; use exactly that code in later steps).
`storebound play build --version-name 1.0.0 --dry`, then without `--dry`: Gradle `bundleRelease` (Flutter:
`flutter build appbundle`) with the next free versionCode, AAB in `build/android/`. Capacitor, React Native,
Flutter and Unity exports are found automatically.
5. **UI or API · First upload.** The very first AAB may need the Console (Testing > Internal testing > Create new
release) so Play links the package name; after that: `storebound play upload --track internal --dry`, then without
`--dry` (asks first; `play release app-release.aab` does the same with more track options).
Add testers (internal testing list) and **license testers** (Settings > License testing) so test purchases are free.
6. **API · Listing text.** Fill `store/listing.md` (one section per language), then `storebound play listing`.
It also sets contact email + website (website = the domain serving app-ads.txt). Run `--dry` first and show the
owner the plan (details and per language old -> new per field; draft languages skipped until `listing reviewed`,
`--include-drafts` only on the owner's word); then run it yourself with `--yes`: it PUTs only what changes.
7. **API · Graphics.** Render (`store-screenshots` skill), `storebound shots jpeg`, then
`storebound play images > .storebound/play-images.log 2>&1 &` and follow the log (it is slow: one edit per language).
Required: the 512 x 512 icon, the 1024 x 500 feature graphic and at least 2 phone screenshots (Play Console Help
answer 9866151). 7" / 10" tablet screenshots are optional (no tablet featuring without them): a phone-only project
gets an info line, not an error; `play.tablets: true` renders and requires them, `false` never asks. Tablet
screenshots already on Play stay unless the owner runs `--prune`.
8. **API · In-app products.** `store-iap-setup` skill (`storebound play products`). Needs a build with the BILLING
permission on any track first.
9. **API · Data safety.** Answers in `dataSafety` of the config, `storebound play data-safety`, then check the summary
in Policy and programs > App content > Data safety.
10. **UI · App content.** `storebound console plan play-app-content --json` lists every card of
`/app-content/overview` in order (Privacy policy, Ads, Sign in details, Content ratings, Target audience and
content, Data safety, Advertising ID, Government apps, Financial features, Health apps), each card's page and
start button (aria-label "Start <Name> declaration"), its own plan (`console plan play-privacy-policy`,
`play-ads`, `play-sign-in-details`, `play-content-rating`, `play-target-audience`, `play-advertising-id`,
`play-government-apps`, `play-financial-features`, `play-health-apps`) and ONE owner batch (`ownerBatch`,
printed first): the open answers (write them into the config: `console.contentRating.details`,
`console.appContent`, `social`, `products[].randomRewards`; rerun the plan) and, before you start, the IARC Terms
of Use box on the questionnaire's page 1 (an agreement; page 1's Next does nothing until it is ticked). Then
`console plan play-app-content --all` walks every card in order without a stop: you press each card's **Save**
(a declaration save: Google gets nothing before "Send changes for review", the owner's, verified 2026-10-13).
Data safety goes through the API (step 9). Behind a sign-in: you type the demo account from
`.storebound/credentials.json` (`reviewDemoAccount`, never printed; the owner only when it is not there;
`review-access`); with accounts: the
delete-account URL (`account-deletion`); subscriptions: `storebound play subscriptions` (`store-subscriptions`).
11. **UI · Store settings (App category).** `play listing` cannot set it (the Play API has no category field), so a
new app shows "Not selected": set `play.category` (Play's name, e.g. "Strategy"; `storebound check` warns
without it), then `storebound console plan play-store-settings`: /store-settings > the FIRST Edit > App or game >
the Category dropdown's `[role=option]` with exactly that name > Save. Contact details come from `play listing`.
12. **API + UI · Production.** `storebound play release --version-code N --production draft`, then in the Console
(you, with your browser tool):
a) `storebound console plan play-production-countries`: tracks/production > Countries / regions tab > Edit
countries / regions > the mat-checkbox "Select all rows" (or `play.countries`) > Save; a "Go to Publishing
overview?" dialog follows. `edits.countryAvailability` is read-only in the API; `storebound validate --play`
reports "production has 0 countries".
b) `storebound console plan play-production-release`: Releases tab > Edit release > Next (a missing
deobfuscation file is only a warning) > Save.
c) Publishing overview > "Submit N changes for review" > dialog "Send N changes for review?" > **Send changes for
review**: the owner's final button, and it needs a real click (a JS `.click()` did nothing on it).
While a review is already running, "Submit N changes for review" opens **"Do you want to restart your
review?"** with **Restart review** instead: that restarts the running review with the new changes. It is the
owner's button too (never the agent's); Cancel leaves the running review alone.
Managed publishing is off by default: the app goes live automatically once Google approves it. If the owner wants
to pick the moment, they turn on Publishing overview > Managed publishing first; record it in
`play.managedPublishing` (`play release` and the plan warn while it is off).
13. **Verify.** `storebound validate --play` (no blockers), `storebound play status` (tracks, listings, products), Console dashboard has no open tasks,
Publishing overview shows "In review".
## Gotchas
- Updates after the first release: staged rollout with `release-management` (`play rollout`).
- **Draft app**: until the first release is reviewed, the API only accepts `status: draft` ("Only releases with
status draft may be created on draft app"). Create the production draft via API, finish in the Console (step 12).
- **"A change was made to the application outside of this Edit"**: any Console save or other edit commit invalidates
an open API edit. `storebound` redoes the edit automatically (3 retries) and uses one edit per language for images, but
do not save things in the Console while uploads run.
- Uploads (AAB, 100+ images) take minutes: run them in the background with a log file and watch progress lines.
- **Personal developer accounts** created after 2023-11-13 need a closed test with at least 12 opted-in testers for
14 days in a row before they can apply for production (Play Console Help answer/14151465); **organization accounts**
skip that rule. Set `play.accountType` and `play.accountCreated` so `storebound scan` reminds you.
- **16 KB page sizes**: apps with native code (Unity, Flutter, React Native, NDK) must support 16 KB pages; from
2027-02-01 Play blocks updates that don't. `storebound scan` flags old Unity / React Native / NDK / AGP versions and
lists prebuilt .so files; check the built app with `zipalign -v -c -P 16 4 app.apk`.
- Phone screenshots: 2-8, sides 320-3840 px, long side at most 2x the short side (1080 x 1920 fine, 1080 x 2400 not).
Feature graphic 1024 x 500 required. PNG without alpha, or JPEG (q92 is 4x smaller and uploads faster).
- Play listing limits: title 30, short 80, full 4000, release notes 500 (checked locally before upload).
- The legacy `inappproducts` API returns 403 "Please migrate to the new publishing API": use
`monetization.onetimeproducts` (what `storebound play products` does).
- Wrong path casing on the new product endpoints returns an HTML 404, not JSON (`oneTimeProducts` camelCase for list
and batchUpdateStates).
- Website in the listing must be the exact domain whose `/app-ads.txt` lists your ad networks.