Publish to Google Play
Upload a signed App Bundle to a Google Play track with a staged rollout and release notes.
Turn on Google Play publishing for a pipeline so a successful Android build uploads its signed App Bundle to the track you choose. The runner that built the app uploads it from its own host, so the bundle never passes through the daemon.
What you need
- Project developer, maintainer, instance admin, or instance owner access. Storing a shared instance secret needs instance admin or owner access.
- An app that already exists in Play Console, with one release uploaded by hand. Google Play does not let the API create an app or upload its first release. Deliver to testers on Google Play walks through that first upload.
- A pipeline that builds a release App Bundle and signs it with your upload key. See Add Android signing.
- A Google Cloud service account with a JSON key, invited in Play Console under Users and permissions with release permissions for the app.
- Outbound HTTPS from the runner host to
oauth2.googleapis.comandandroidpublisher.googleapis.com. The daemon host needs the same for credential checks and rollout controls.
1. Store the service account key as a secret
Store the whole JSON key file as a secret named PLAY_SERVICE_ACCOUNT. When
all your apps are in one Play Console developer account, store it once as an
instance secret: open Settings > Store credentials and, under
Google Play service account, select Add service account. New
pipelines then choose it on the Publishing tab without another upload. A
project or pipeline secret with the same name overrides it.
From the API, any scope works. A project secret is shared by all of the project’s pipelines:
jq -n --rawfile value play-service-account.json \
'{name: "PLAY_SERVICE_ACCOUNT", value: $value}' |
curl -X POST "$OORE_URL/v1/projects/$PROJECT_ID/secrets" \
-H "Authorization: Bearer $OORE_TOKEN" \
-H 'Content-Type: application/json' \
-d @-See Use secrets in a pipeline for scopes and rotation.
2. Configure publishing
- Open the pipeline and its Publishing tab.
- Under Google Play, turn on Upload to Google Play after each successful build.
- Under Credentials, choose the secret for Service account key.
- Under Release, choose the Track and the Rollout: Full rollout, Staged rollout with an Initial rollout (%), or Draft. Choose the Release notes source.
- Under Advanced, enter the app’s application ID in Expected package.
- Select Turn on Google Play publishing, or Save changes when publishing was already on.
The readiness list at the top of the section shows what is still missing,
such as release signing or an App Bundle build. While uploads are on and the
Android command is Oore’s default, Oore builds an App Bundle as well as the
APK. An Android command override must build the bundle itself, such as
flutter build appbundle --release.
For automation, send the same settings with the Update Google Play publish settings operation:
curl -X PUT "$OORE_URL/v1/pipelines/$PIPELINE_ID/publish/google-play" \
-H "Authorization: Bearer $OORE_TOKEN" \
-H 'Content-Type: application/json' \
-d '{
"enabled": true,
"service_account_secret": "PLAY_SERVICE_ACCOUNT",
"track": "production",
"rollout_percent": 5,
"release_notes": "changelog"
}'The response includes service_account_secret_present, which is false until
the named secret exists in a scope this pipeline can see.
| Field | Default | Meaning |
|---|---|---|
enabled | false | Upload after each successful build of this pipeline. |
service_account_secret | Name of the secret holding the service account JSON key. | |
track | internal, closed, open, production, or the name of a custom closed track. closed is Play’s alpha and open is beta. | |
package_name | AAB | Expected application id. When set, a bundle for a different package fails the upload. |
rollout_percent | 100 | Initial staged rollout. Below 100 creates an in-progress release for that share of users. The internal track does not support staged rollouts. |
draft | false | Create a draft release instead of rolling out. |
release_notes | none | none, changelog (the build changelog) or repo_files. |
release_notes_locale | en-US | Language for changelog release notes. |
release_notes_dir | For repo_files: a repository directory with one <locale>.txt file per language, such as release-notes/en-US.txt. | |
upload_mapping | true | Upload build/app/outputs/mapping/<variant>/mapping.txt when the build produced it. |
in_app_update_priority | In-app update priority, 0 to 5. |
Release notes longer than Play’s 500-character limit are shortened.
3. Test the credentials
On the pipeline’s Publishing tab, select Test credentials under Credentials to check the key without running a build. The daemon exchanges the key for a token, then opens a Play edit for the app and discards it, which needs the same Play Console access as an upload and changes nothing. A failure shows Google’s reason, such as a rejected key or a service account that is not invited for the app.
The check opens the app named in the Expected package field. When that is
empty, it uses the saved package_name, then the package of the pipeline’s
latest Google Play release. From the API, use the
Test Google Play credentials
operation:
curl -X POST "$OORE_URL/v1/pipelines/$PIPELINE_ID/publish/google-play/test-credentials" \
-H "Authorization: Bearer $OORE_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"service_account_secret": "PLAY_SERVICE_ACCOUNT", "package_name": "com.example.app"}'The response has ok, a message with what passed or the exact failure, and
the key’s service_account_email.
Oore keeps the pipeline’s latest check, and the Publishing tab shows it with
when it ran. It is returned as last_credentials_check with the Google Play
publish settings until the key behind its secret name changes. Rotating the
secret, deleting it, or adding a narrower secret with the same name clears it.
4. Run a build
After the build, signing and artifact steps succeed, the runner adds a
publish-play step that:
- Picks the release App Bundle under
build/app/outputs/bundleand reads its package name,versionCodeandversionName. - Exchanges the service account key for an access token.
- Reads the version codes Play already holds and stops if the bundle’s
versionCodeis not higher than the highest one on the target track, or was already uploaded. The error names both numbers. Setbuild_number_offsetand build with--build-number=$OORE_VERSION_CODEto start above the store; see the.oore.yamlreference. - Opens a Play edit, uploads the bundle and
mapping.txt, sets the track release with the configured status and rollout, and commits the edit.
If any earlier step fails, nothing is uploaded. If a Play call fails, the
publish-play step fails with the Play API’s error text, and the edit is
discarded so nothing is left half-staged in Play Console.
After fixing the cause, such as a missing Play Console permission, retry the upload to upload the stored bundle without building again.
Verify the result
- The build shows a succeeded
publish-playstep, and its log names the track and rollout. - Play Console shows the version on the chosen track at the chosen rollout.
- The audit log has a
store_upload_completedentry for the build.
Store health on Home
Home lists every app a pipeline publishes to Google Play: each package named in a pipeline’s Expected package, or reached by a passing Test credentials check, and each package Oore has released. A pipeline that sets neither and has not released yet does not name an app, so Home cannot list it; fill in Expected package to add it.
For each app, the daemon reads the user-perceived crash rate and ANR rate over the last 28 days from the Play Developer Reporting API, with the top crash and ANR clusters. Home shows them against Google Play’s bad-behavior thresholds of 1.09% and 0.47%. The numbers refresh every six hours with the service account of the pipeline that made the newest release, or, before the first release, the pipeline whose Google Play settings were saved last.
The service account needs View app information for the app in Play
Console, and the daemon host needs outbound HTTPS to
playdeveloperreporting.googleapis.com. Without them, Home shows why the
numbers could not be read. A failed read is retried after 15 minutes; after
fixing access, choose Refresh in Store health to read the store at once.
Troubleshooting
publish-play fails because the version code is not higher
The message names both numbers. Set Build number offset above the
highest version code on Google Play, and pass
--build-number=$OORE_VERSION_CODE to Flutter.
publish-play fails with a Play API error
Fix the cause, such as a missing Play Console permission, then retry the upload. The stored bundle is uploaded again without rebuilding.
Test credentials fails
The message gives Google’s reason. Invite the service account for this app in Play Console, wait a few minutes for Google to process the invitation, and test again.
Limits
- One App Bundle per publish. A pipeline that builds several release flavors fails with a list of the bundles it found.
Next step
Promote builds between environments to move a tested version from internal testing to production without rebuilding it.