[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"project-7302":3},{"id":4,"name":5,"fullName":6,"owner":7,"repo":5,"description":8,"homepage":9,"htmlUrl":10,"language":11,"languages":10,"totalLinesOfCode":10,"stars":12,"forks":13,"watchers":14,"openIssues":15,"contributorsCount":16,"subscribersCount":16,"size":16,"stars1d":16,"stars7d":17,"stars30d":18,"stars90d":16,"forks30d":16,"starsTrendScore":19,"compositeScore":20,"rankGlobal":10,"rankLanguage":10,"license":21,"archived":22,"fork":22,"defaultBranch":23,"hasWiki":22,"hasPages":22,"topics":24,"createdAt":10,"pushedAt":10,"updatedAt":35,"readmeContent":36,"aiSummary":37,"trendingCount":16,"starSnapshotCount":16,"syncStatus":19,"lastSyncTime":38,"discoverSource":39},7302,"gradle-play-publisher","Triple-T\u002Fgradle-play-publisher","Triple-T","GPP is Android's unofficial release automation Gradle Plugin. It can do anything from building, uploading, and then promoting your App Bundle or APK to publishing app listings and other metadata.","",null,"Kotlin",4292,346,78,23,0,4,14,2,29.62,"MIT License",false,"master",[25,26,27,28,29,30,5,31,32,33,34],"android","android-development","apps","automation","deployment","gpp","gradle-plugin","mobile","play-store","publishing","2026-06-12 02:01:37","\u003Cp align=\"center\">\n    \u003Cimg alt=\"Logo\" src=\"assets\u002Flogo.svg\" width=\"25%\" \u002F>\n\u003C\u002Fp>\n\n\u003Ch1 align=\"center\">\n    Gradle Play Publisher\n\u003C\u002Fh1>\n\n\u003Cp align=\"center\">\n    \u003Ca href=\"https:\u002F\u002Fgithub.com\u002FTriple-T\u002Fgradle-play-publisher\u002Factions\">\n        \u003Cimg src=\"https:\u002F\u002Fgithub.com\u002FTriple-T\u002Fgradle-play-publisher\u002Fworkflows\u002FCI\u002FCD\u002Fbadge.svg\" \u002F>\n    \u003C\u002Fa>\n    \u003Ca href=\"https:\u002F\u002Fplugins.gradle.org\u002Fplugin\u002Fcom.github.triplet.play\">\n        \u003Cimg src=\"https:\u002F\u002Fimg.shields.io\u002Fmaven-metadata\u002Fv\u002Fhttps\u002Fplugins.gradle.org\u002Fm2\u002Fcom\u002Fgithub\u002Ftriplet\u002Fplay\u002Fcom.github.triplet.play.gradle.plugin\u002Fmaven-metadata.xml.svg?label=Gradle%20Plugins%20Portal\" \u002F>\n    \u003C\u002Fa>\n\u003C\u002Fp>\n\nGradle Play Publisher (GPP) is Android's unofficial release automation Gradle Plugin. It can do\nanything from building, uploading, and then promoting your App Bundle or APK to publishing app\nlistings and other metadata.\n\n## Project status: maintenance mode\n\nIssues are ignored, but pull requests are not. If you need to get something done, submit a PR!\n\n## Table of contents\n\n1. [Quickstart guide](#quickstart-guide)\n2. [Prerequisites](#prerequisites)\n    1. [Initial Play Store upload](#initial-play-store-upload)\n    2. [Signing configuration](#signing-configuration)\n    3. [Service Account](#service-account)\n3. [Basic setup](#basic-setup)\n    1. [Installation](#installation)\n    2. [Authenticating Gradle Play Publisher](#authenticating-gradle-play-publisher)\n4. [Task organization](#task-organization)\n5. [Managing artifacts](#managing-artifacts)\n    1. [Common configuration](#common-configuration)\n    2. [Publishing an App Bundle](#publishing-an-app-bundle)\n    3. [Publishing APKs](#publishing-apks)\n    4. [Uploading an Internal Sharing artifact](#uploading-an-internal-sharing-artifact)\n    5. [Promoting artifacts](#promoting-artifacts)\n    6. [Handling version conflicts](#handling-version-conflicts)\n6. [Managing Play Store metadata](#managing-play-store-metadata)\n    1. [Quickstart](#quickstart)\n    2. [Directory structure](#directory-structure)\n    3. [Publishing listings](#publishing-listings)\n    4. [Publishing in-app products](#publishing-in-app-products)\n    5. [Publishing in-app subscriptions](#publishing-in-app-subscriptions)\n7. [Working with product flavors](#working-with-product-flavors)\n    1. [Disabling publishing](#disabling-publishing)\n    2. [Combining artifacts into a single release](#combining-artifacts-into-a-single-release)\n    3. [Using multiple Service Accounts](#using-multiple-service-accounts)\n8. [Advanced topics](#advanced-topics)\n    1. [Using CLI options](#using-cli-options)\n    2. [Using HTTPS proxies](#using-https-proxies)\n\n## Quickstart guide\n\n1. Upload the first version of your APK or App Bundle using the\n   [Google Play Console](https:\u002F\u002Fplay.google.com\u002Fapps\u002Fpublish)\n2. [Create a Google Play Service Account](#service-account)\n3. [Sign your release builds](https:\u002F\u002Fdeveloper.android.com\u002Fstudio\u002Fpublish\u002Fapp-signing#gradle-sign)\n   with a valid `signingConfig`\n4. [Add and apply the plugin](#installation)\n5. [Authenticate GPP](#authenticating-gradle-play-publisher)\n\n## Prerequisites\n\n### Initial Play Store upload\n\nThe first APK or App Bundle needs to be uploaded via the Google Play Console because registering the\napp with the Play Store cannot be done using the Play Developer API. For all subsequent uploads and\nchanges, GPP may be used.\n\n### Signing configuration\n\nTo successfully upload apps to the Play Store, they must be signed with your developer key. Make\nsure you have\n[a valid signing configuration](https:\u002F\u002Fdeveloper.android.com\u002Fstudio\u002Fpublish\u002Fapp-signing#gradle-sign).\n\n### Service Account\n\nTo use GPP, you must create a service account with access to the Play Developer API:\n\n1. If you don't already have one, create a GCP project for your app(s)\n2. Enable the\n   [AndroidPublisher API](https:\u002F\u002Fconsole.cloud.google.com\u002Fapis\u002Flibrary\u002Fandroidpublisher.googleapis.com)\n   for that GCP project\n3. Create a\n   [service account and key](https:\u002F\u002Fconsole.cloud.google.com\u002Fapis\u002Fcredentials\u002Fserviceaccountkey)\n    1. Make sure you're in the GCP project you used above (check the `project` query param in the\n       URL)\n    2. Select `New service account`\n    3. Give it a name and the Project Owner role (don't worry, we'll remove this later)\n    4. After creating the service account, find it in the list of all service accounts and use the 3\n       dots menu to `Manage keys`\n    5. From there, create a new key using the `Add key` menu (leave JSON selected)\n4. Move the downloaded JSON credentials into your project and\n   [tell GPP about it](#authenticating-gradle-play-publisher)\n5. Give your service account\n   [permissions to publish apps](https:\u002F\u002Fplay.google.com\u002Fconsole\u002Fdevelopers\u002Fusers-and-permissions)\n   on your behalf\n    1. Click `Invite new user`\n    2. Copy\u002Fpaste the service account email (you can find it in the JSON credentials)\n    3. Don't touch the roles\n    4. Specify which apps the service account should have access to. In this example, GPP has full\n       access to testing tracks and app listings, but will be unable to make production releases:\n       \u003Cimg alt=\"Minimum Service Account permissions\" src=\"assets\u002Fmin-perms.png\" width=\"66%\" \u002F>\n6. Run `.\u002Fgradlew bootstrapListing` or some other GPP task to validate your setup\n7. Now that you've successfully created the connection between GCP and Google Play, you can remove\n   the Project Owner permissions\n    1. Go to your [IAM settings](https:\u002F\u002Fconsole.cloud.google.com\u002Fiam-admin\u002Fiam)\n    2. Search for the service account you created\n    3. Click the edit icon (found at the end of the row)\n    4. In the permission selection panel that opens, click the trash icon to remove the owner role\n    5. Click save\n\n## Basic setup\n\n### Installation\n\nApply the plugin to each individual `com.android.application` module where you want to use GPP\nthrough the `plugins {}` DSL:\n\n\u003Cdetails open>\u003Csummary>Kotlin\u003C\u002Fsummary>\n\n```kt\nplugins {\n    id(\"com.android.application\")\n    id(\"com.github.triplet.play\") version \"4.0.0\"\n}\n```\n\n\u003C\u002Fdetails>\n\n\u003Cdetails>\u003Csummary>Groovy\u003C\u002Fsummary>\n\n```groovy\nplugins {\n    id 'com.android.application'\n    id 'com.github.triplet.play' version '4.0.0'\n}\n```\n\n\u003C\u002Fdetails>\n\n#### Snapshot builds\n\nIf you're prepared to cut yourself on the bleeding edge of GPP development, snapshot builds are\navailable from\n[Sonatype's `snapshots` repository](https:\u002F\u002Foss.sonatype.org\u002Fcontent\u002Frepositories\u002Fsnapshots\u002Fcom\u002Fgithub\u002Ftriplet\u002Fgradle\u002Fplay-publisher\u002F):\n\n\u003Cdetails open>\u003Csummary>Kotlin\u003C\u002Fsummary>\n\n```kt\nbuildscript {\n    repositories {\n        \u002F\u002F ...\n        maven(\"https:\u002F\u002Foss.sonatype.org\u002Fcontent\u002Frepositories\u002Fsnapshots\")\n    }\n\n    dependencies {\n        \u002F\u002F ...\n        classpath(\"com.github.triplet.gradle:play-publisher:5.0.0-SNAPSHOT\")\n    }\n}\n```\n\n\u003C\u002Fdetails>\n\n\u003Cdetails>\u003Csummary>Groovy\u003C\u002Fsummary>\n\n```groovy\nbuildscript {\n    repositories {\n        \u002F\u002F ...\n        maven { url 'https:\u002F\u002Foss.sonatype.org\u002Fcontent\u002Frepositories\u002Fsnapshots' }\n    }\n\n    dependencies {\n        \u002F\u002F ...\n        classpath 'com.github.triplet.gradle:play-publisher:5.0.0-SNAPSHOT'\n    }\n}\n```\n\n\u003C\u002Fdetails>\n\n### Authenticating Gradle Play Publisher\n\nAfter you've gone through the [Service Account setup](#service-account), you should have a JSON file\nwith your private key. Add a `play` block alongside your `android` one with the file's location:\n\n```kt\nandroid { ... }\n\nplay {\n    serviceAccountCredentials.set(file(\"your-key.json\"))\n}\n```\n\n> Note: If you commit unencrypted Service Account keys to source, you run the risk of letting anyone\n> access your Google Play account. To circumvent this issue, put the contents of your JSON file in\n> the `ANDROID_PUBLISHER_CREDENTIALS` environment variable and don't specify the\n> `serviceAccountCredentials` property.\n\n#### Application Default Credentials\n\nAlternatively, you can use [Application Default Credentials](https:\u002F\u002Fcloud.google.com\u002Fdocs\u002Fauthentication\u002Fprovide-credentials-adc)\n(and optionally [Service Account impersonation](https:\u002F\u002Fcloud.google.com\u002Fdocs\u002Fauthentication\u002Fuse-service-account-impersonation))\ninstead of specifying a JSON private key file or environment variable:\n\n```kt\nandroid { ... }\n\nplay {\n    useApplicationDefaultCredentials = true\n    impersonateServiceAccount = \"account@your-project.iam.gserviceaccount.com\" \u002F\u002F Optional\n}\n```\n\n> Note: Currently, [Service Account impersonation](https:\u002F\u002Fcloud.google.com\u002Fdocs\u002Fauthentication\u002Fuse-service-account-impersonation)\nis only supported when using [Application Default Credentials](https:\u002F\u002Fcloud.google.com\u002Fdocs\u002Fauthentication\u002Fprovide-credentials-adc).\n\n## Task organization\n\nGPP follows the Android Gradle Plugin's (AGP) naming convention: `[action][Variant][Thing]`. For\nexample, `publishPaidReleaseBundle` will be generated if you have a `paid` product flavor.\n\nLifecycle tasks to publish multiple product flavors at once are also available. For example,\n`publishBundle` publishes all variants.\n\nTo find available tasks, run `.\u002Fgradlew tasks --group publishing` and use\n`.\u002Fgradlew help --task [task]` where `task` is something like `publishBundle` to get more detailed\ndocumentation for a specific task.\n\n## Managing artifacts\n\nGPP supports uploading both the App Bundle and APK. Once uploaded, GPP also supports promoting those\nartifacts to different tracks.\n\n### Common configuration\n\nSeveral options are available to customize how your artifacts are published:\n\n* `track` is the target stage for an artifact, i.e. `internal`\u002F`alpha`\u002F`beta`\u002F`production` or any\n  custom track\n    * Defaults to `internal`\n* `releaseStatus` is the type of release, i.e. `ReleaseStatus.[COMPLETED\u002FDRAFT\u002FHALTED\u002FIN_PROGRESS]`\n    * Defaults to `ReleaseStatus.COMPLETED`\n* `userFraction` is the percentage of users who will receive a staged release\n    * Defaults to `0.1` aka 10%\n    * **Note:** the `userFraction` is only applicable where `releaseStatus=[IN_PROGRESS\u002FHALTED]`\n* `updatePriority` sets the update priority for a new release. See\n  [Google's documentation](https:\u002F\u002Fdeveloper.android.com\u002Fguide\u002Fplaycore\u002Fin-app-updates) on consuming\n  this value.\n    * Defaults to the API value\n\nExample configuration:\n\n```kt\nimport com.github.triplet.gradle.androidpublisher.ReleaseStatus\n\nplay {\n    \u002F\u002F Overrides defaults\n    track.set(\"production\")\n    userFraction.set(0.5)\n    updatePriority.set(2)\n    releaseStatus.set(ReleaseStatus.IN_PROGRESS)\n\n    \u002F\u002F ...\n}\n```\n\n#### Uploading release notes\n\nWhile GPP can automatically build and find your artifact, you'll need to tell the plugin where to\nfind your release notes.\n\nAdd a file under `src\u002F[sourceSet]\u002Fplay\u002Frelease-notes\u002F[language]\u002F[track].txt` where `sourceSet`\nis a [full variant name](https:\u002F\u002Fdeveloper.android.com\u002Fstudio\u002Fbuild\u002Fbuild-variants#sourceset-build),\n`language` is one of the\n[Play Store supported codes](https:\u002F\u002Fsupport.google.com\u002Fgoogleplay\u002Fandroid-developer\u002Fanswer\u002F3125566),\nand `track` is the channel you want these release notes to apply to (or `default` if unspecified).\n\nAs an example, let's assume you have these two different release notes:\n\n```\nsrc\u002Fmain\u002Fplay\u002Frelease-notes\u002Fen-US\u002Fdefault.txt\n...\u002Fbeta.txt\n```\n\nWhen you publish to the beta channel, the `beta.txt` release notes will be uploaded. For any other\nchannel, `default.txt` will be uploaded.\n\nIf no release notes are found, GPP will try to copy release notes from the previous release.\n\n> Note: the Play Store limits your release notes to a maximum of 500 characters.\n\n#### Uploading developer facing release names\n\nThe Play Console supports customizing release names. These aren't visible to users, but may be\nuseful for internal processes. Similar to release notes, release names may be specified by placing\na `[track].txt` file in the `release-names` directory under your `play` folder. For example, here's\na custom release name for the alpha track in the `play\u002Frelease-names\u002Falpha.txt` file:\n\n```\nMy custom release name\n```\n\nIf it makes more sense to specify the release name in your build script, the `releaseName` property\nis available:\n\n```kt\nplay {\n    \u002F\u002F ...\n    releaseName.set(\"My custom release name\")\n}\n```\n\n> Note: the `play.releaseName` property takes precedence over the resource files.\n\nThere is also a `--release-name` CLI option for quick access. For example,\n`.\u002Fgradlew publishBundle --release-name \"Hello World!\"`.\n\n> Note: the Play Store limits your release names to a maximum of 50 characters.\n\n#### Uploading a pre-existing artifact\n\nBy default, GPP will build your artifact from source. In advanced use cases, this might not be the\ndesired behavior. For example, if you need to inject translations into your APK or App Bundle after\nbuilding it but before publishing it. Or perhaps you simply already have an artifact you wish to\npublish. GPP supports this class of use cases by letting you specify a directory in which\npublishable artifacts may be found:\n\n```kt\nplay {\n    \u002F\u002F ...\n    artifactDir.set(file(\"path\u002Fto\u002Fapk-or-app-bundle\u002Fdir\"))\n}\n```\n\nFor quick access, you can also use the `--artifact-dir` CLI option:\n\n```sh\n.\u002Fgradlew publishBundle --artifact-dir path\u002Fto\u002Fapp-bundle\u002Fdir\n```\n\n> Note: all artifacts in the specified directory will be published.\n\n##### Uploading mapping files\n\n> Note: mapping files aren't applicable to App Bundles since the mapping file is contained within\n> the bundle.\n\nBy default, GPP will look for a file called `mapping.txt` in your artifact directory. If you need\nmore granularity, you can prefix `mapping.txt` with your APK file name. For example:\n\n```\nartifact-dir\u002F\n├── mapping.txt\n├── my-first-app.apk\n├── my-second-app.apk\n└── my-second-app.mapping.txt\n```\n\n`my-second-app.apk` will use `my-second-app.mapping.txt` and `my-first-app.apk` will use the\ndefault `mapping.txt` because no specific mapping file was specified.\n\n#### Retaining artifacts\n\nGPP supports keeping around old artifacts such as OBB files or WearOS APKs:\n\n```kt\nplay {\n    \u002F\u002F ...\n    retain {\n        artifacts.set(listOf(123)) \u002F\u002F Old APK version code\n        mainObb.set(123) \u002F\u002F Old main OBB version code\n        patchObb.set(123) \u002F\u002F Old patch OBB version code\n    }\n}\n```\n\n### Publishing an App Bundle\n\nRun `.\u002Fgradlew publishBundle`.\n\n#### Defaulting to the App Bundle\n\nYou'll notice that if you run `.\u002Fgradlew publish`, it uploads an APK by default. To change this,\ndefault to the App Bundle:\n\n```kt\nplay {\n    \u002F\u002F ...\n    defaultToAppBundles.set(true)\n}\n```\n\n### Publishing APKs\n\nRun `.\u002Fgradlew publishApk`. Splits will be uploaded if available.\n\n### Uploading an Internal Sharing artifact\n\nRun `.\u002Fgradlew uploadReleasePrivateBundle` for App Bundles and `.\u002Fgradlew uploadReleasePrivateApk`\nfor APKs. To upload an existing artifact, read about\n[how to do so](#uploading-a-pre-existing-artifact).\n\n#### Retrieving the download URL\n\nAfter running an Internal Sharing task, the output of the API response will be stored in the\nfollowing directory: `build\u002Foutputs\u002Finternal-sharing\u002F[bundle\u002Fapk]\u002F[variant]\u002F`. Each file will be\nnamed `[apk\u002Faab name].json`.\n\nFor example, here are the contents\nof `app\u002Fbuild\u002Foutputs\u002Finternal-sharing\u002Fbundle\u002Frelease\u002Fapp-release.json`:\n\n```json\n{\n    \"certificateFingerprint\": \"...\",\n    \"downloadUrl\": \"...\",\n    \"sha256\": \"...\"\n}\n```\n\n#### Installing Internal Sharing artifacts\n\nTo accelerate development, GPP supports uploading and then immediately installing Internal Sharing\nartifacts. This is similar to the AGP's `install[Variant]` task.\n\nRun `.\u002Fgradlew installReleasePrivateArtifact` to install an artifact built on-the-fly and\n`.\u002Fgradlew uploadReleasePrivateBundle --artifact-dir path\u002Fto\u002Fartifact installReleasePrivateArtifact`\nto install an existing artifact.\n\n### Promoting artifacts\n\nExisting releases can be promoted and\u002For updated to the [configured track](#common-configuration)\nwith `.\u002Fgradlew promoteArtifact`.\n\nBy default, the track *from* which to promote a release is determined by the most unstable channel\nthat contains a release. Example: if the alpha channel has no releases, but the beta and prod\nchannels do, the beta channel will be picked. To configure this manually, use the `fromTrack`\nproperty:\n\n```kt\nplay {\n    \u002F\u002F ...\n    fromTrack.set(\"alpha\")\n}\n```\n\nSimilarly, the track *to* which to promote a release defaults to the `promoteTrack` property. If\nunspecified, the resolved `fromTrack` property will be used instead and an in-place update will be\nperformed. Example configuration:\n\n```kt\nplay {\n    \u002F\u002F ...\n    promoteTrack.set(\"beta\")\n}\n```\n\nIf you need to execute a one-time promotion, you can use the CLI args. For example, this is how you\nwould promote an artifact from the alpha ➡️ beta track with only 25% of users getting the release:\n\n```sh\n.\u002Fgradlew promoteArtifact \\\n  --from-track alpha --promote-track beta \\\n  --release-status inProgress --user-fraction .25\n```\n\nIf you only need to update the rollout percentage of an existing in-progress release, you can do so\nwith the `update` flag. For example, this is how you would increase the production track rollout\npercentage to 50% of users.\n\n```sh\n.\u002Fgradlew promoteArtifact --update production --user-fraction .5\n```\n\n#### Finishing a rollout\n\nIf you have an ongoing `inProgress` release and would like to perform a full rollout, simply change\nthe release status to `completed`. A user fraction of `1.0` is invalid and will be rejected.\n\n### Handling version conflicts\n\nIf an artifact already exists with a version code greater than or equal to the one you're trying to\nupload, an error will be thrown when attempting to publish the new artifact. You have two options:\n\n* Ignore the error and continue (`ResolutionStrategy.IGNORE`)\n* Automatically pick the correct version code so you don't have to manually update it\n  (`ResolutionStrategy.AUTO`)\n\nExample configuration:\n\n```kt\nimport com.github.triplet.gradle.androidpublisher.ResolutionStrategy\n\nplay {\n    \u002F\u002F ...\n    resolutionStrategy.set(ResolutionStrategy.IGNORE)\n}\n```\n\n#### Post-processing outputs sanitized by auto resolution\n\nFor example, you could update your app's version name based on the new version code:\n\n```kt\nimport com.github.triplet.gradle.androidpublisher.ResolutionStrategy\n\nplay {\n    \u002F\u002F ...\n    resolutionStrategy.set(ResolutionStrategy.AUTO)\n}\n\nandroidComponents {\n    onVariants { variant ->\n        for (output in variant.outputs) {\n            val processedVersionCode = output.versionCode.map { playVersionCode ->\n                \u002F\u002F Do something to the version code...\n                \u002F\u002F In this example, version names will look like `myCustomVersionName.123`\n                \"myCustomVersionName.$playVersionCode\"\n            }\n\n            output.versionName.set(processedVersionCode)\n        }\n    }\n}\n```\n\n## Managing Play Store metadata\n\nGPP supports uploading any metadata you might want to change with each release, from screenshots and\ndescriptions to in-app purchases and subscriptions.\n\n### Quickstart\n\nGPP includes a bootstrap task that pulls down your existing listing and initializes everything for\nyou. To use it, run `.\u002Fgradlew bootstrapListing`.\n\n> Note: if you have a pre-existing `play` folder, it will be reset.\n\n### Directory structure\n\nGPP follows the Android Gradle Plugin's source set\n[guidelines and priorities](https:\u002F\u002Fdeveloper.android.com\u002Fstudio\u002Fbuild\u002Fbuild-variants#sourceset-build).\n`src\u002F[sourceSet]\u002Fplay` is the base directory for Play Store metadata. Since `main` is the most\ncommon source set, it will be assumed in all following examples.\n\nIn addition to merging metadata across variants, GPP merges translations. That is, if a resources is\nprovided in a default language such as `en-US` but not in `fr-FR`, the resource will be copied over\nwhen uploading French metadata.\n\n### Publishing listings\n\nRun `.\u002Fgradlew publishListing`.\n\n#### Uploading global app metadata\n\nBase directory: `play`\n\n File                   | Description\n------------------------|--------------------------------------------------------------------------------------------------\n `contact-email.txt`    | Developer email\n `contact-phone.txt`    | Developer phone\n `contact-website.txt`  | Developer website\n `default-language.txt` | The default language for both your Play Store listing and translation merging as described above\n\n#### Uploading text based listings\n\nBase directory: `play\u002Flistings\u002F[language]` where `language` is one of the\n[Play Store supported codes](https:\u002F\u002Fsupport.google.com\u002Fgoogleplay\u002Fandroid-developer\u002Fanswer\u002F3125566)\n\n File                    | Description           | Character limit\n-------------------------|-----------------------|-----------------\n `title.txt`             | App title             | 30\n `short-description.txt` | Tagline               | 80\n `full-description.txt`  | Full description      | 4000\n `video-url.txt`         | Youtube product video | N\u002FA\n\n#### Uploading graphic based listings\n\nDirectory: `play\u002Flistings\u002F[language]\u002Fgraphics` where `language` is defined as in the previous\nsection\n\nImage files are organized a bit differently than in previous sections. Instead of the file name, the\nparent directory's name is used as the media type. This is because multiple images may be provided\nfor the same media type. While file names are arbitrary, they will be uploaded in alphabetical order\nand presented on the Play Store as such. Therefore, we recommend using a number as the file name\n(`1.png` for example). Both PNG and JPEG images are supported.\n\n Directory                  | Max # of images | Image dimension constraints (px)\n----------------------------|-----------------|----------------------------------\n `icon`                     | 1               | 512x512\n `feature-graphic`          | 1               | 1024x500\n `phone-screenshots`        | 8               | [320..3840]x[320..3840]\n `tablet-screenshots`       | 8               | [320..3840]x[320..3840]\n `large-tablet-screenshots` | 8               | [320..3840]x[320..3840]\n `tv-banner`                | 1               | 1280x720\n `tv-screenshots`           | 8               | [320..3840]x[320..3840]\n `wear-screenshots`         | 8               | [320..3840]x[320..3840]\n\n### Publishing in-app products\n\nRun `.\u002Fgradlew publishProducts`.\n\nManually setting up in-app purchase files is not recommended. [Bootstrap them instead](#quickstart)\nwith `.\u002Fgradlew bootstrapListing --products`.\n\n### Publishing in-app subscriptions\n\nRun `.\u002Fgradlew publishSubscriptions`.\n\nManually setting up in-app subscriptions files is not recommended. [Bootstrap them instead](#quickstart)\nwith `.\u002Fgradlew bootstrapListing --subscriptions`.\n\nEach subscription file must have an associated metadata file (`subscriptions\u002F\u003Csubscription product id>.metadata.json`)\nthat contains JSON of the form `{\"regionsVersion\": ...}`. The `regionsVersion` is described\n[here](https:\u002F\u002Fdevelopers.google.com\u002Fandroid-publisher\u002Fapi-ref\u002Frest\u002Fv3\u002FRegionsVersion). The Google Play Developer API does\nnot have a way to get the latest regions version, so unfortunately bootstrapping uses a hardcoded value that you may need to fix.\n\n## Working with product flavors\n\nWhen working with product flavors, granular configuration is key. GPP provides varying levels of\ngranularity to best support your needs, all through the `playConfigs` block:\n\n\u003Cdetails open>\u003Csummary>Kotlin\u003C\u002Fsummary>\n\n```kt\nplay {\n    \u002F\u002F In a simple app, this play block is all you'll need. However, in an app with product flavors,\n    \u002F\u002F the play block becomes a place to store default configurations. Anything configured in here\n    \u002F\u002F will apply to all product flavors, that is, unless an override is supplied in the playConfigs\n    \u002F\u002F block.\n}\n\nandroid {\n    \u002F\u002F Suppose we have the following flavors\n    flavorDimensions(\"customer\", \"type\")\n    productFlavors {\n        register(\"firstCustomer\") { setDimension(\"customer\") }\n        register(\"secondCustomer\") { setDimension(\"customer\") }\n\n        register(\"demo\") { setDimension(\"type\") }\n        register(\"full\") { setDimension(\"type\") }\n    }\n\n    playConfigs {\n        \u002F\u002F Now, we can configure GPP however precisely is required.\n\n        \u002F\u002F Configuration overrides occur in a cascading manner from most to least specific. That is,\n        \u002F\u002F a property configured in a build type + flavor combo overrides that same property\n        \u002F\u002F configured in a flavor combo, which overrides a build type combo, which in turn overrides\n        \u002F\u002F the play block. Properties not configured are inherited.\n        register(\"firstCustomerFullRelease\") { ... } \u002F\u002F Build type + flavor\n        register(\"firstCustomer\") { ... } \u002F\u002F Flavor\n        register(\"release\") { ... } \u002F\u002F Build type\n    }\n}\n```\n\n\u003C\u002Fdetails>\n\n\u003Cdetails>\u003Csummary>Groovy\u003C\u002Fsummary>\n\n```groovy\nplay {\n    \u002F\u002F In a simple app, this play block is all you'll need. However, in an app with product flavors,\n    \u002F\u002F the play block becomes a place to store default configurations. Anything configured in here\n    \u002F\u002F will apply to all product flavors, that is, unless an override is supplied in the playConfigs\n    \u002F\u002F block.\n}\n\nandroid {\n    \u002F\u002F Suppose we have the following flavors\n    flavorDimensions 'customer', 'type'\n    productFlavors {\n        firstCustomer { dimension 'customer' }\n        secondCustomer { dimension 'customer' }\n\n        demo { dimension 'type' }\n        full { dimension 'type' }\n    }\n\n    playConfigs {\n        \u002F\u002F Now, we can configure GPP however precisely is required.\n\n        \u002F\u002F Configuration overrides occur in a cascading manner from most to least specific. That is,\n        \u002F\u002F a property configured in a build type + flavor combo overrides that same property\n        \u002F\u002F configured in a flavor combo, which overrides a build type combo, which in turn overrides\n        \u002F\u002F the play block. Properties not configured are inherited.\n        firstCustomerFullRelease { ... } \u002F\u002F Build type + flavor\n        firstCustomer { ... } \u002F\u002F Flavor\n        release { ... } \u002F\u002F Build type\n    }\n}\n```\n\n\u003C\u002Fdetails>\n\n### Disabling publishing\n\nSometimes, you may not want to publish all variants of your app. Or maybe you don't want publishing\nenabled on CI or local dev machines. Whatever the case may be, GPP can be disabled with the\n`enabled` property:\n\n\u003Cdetails open>\u003Csummary>Kotlin\u003C\u002Fsummary>\n\n```kt\nandroid {\n    \u002F\u002F ...\n\n    playConfigs {\n        register(\"myCustomVariantOrProductFlavor\") {\n            enabled.set(true)\n        }\n\n        \u002F\u002F ...\n    }\n}\n\nplay {\n    enabled.set(false) \u002F\u002F This disables GPP by default. It could be the other way around.\n    \u002F\u002F ...\n}\n```\n\n\u003C\u002Fdetails>\n\n\u003Cdetails>\u003Csummary>Groovy\u003C\u002Fsummary>\n\n```groovy\nandroid {\n    \u002F\u002F ...\n\n    playConfigs {\n        myCustomVariantOrProductFlavor {\n            enabled.set(true)\n        }\n\n        \u002F\u002F ...\n    }\n}\n\nplay {\n    enabled.set(false) \u002F\u002F This disables GPP by default. It could be the other way around.\n    \u002F\u002F ...\n}\n```\n\n\u003C\u002Fdetails>\n\n### Combining artifacts into a single release\n\nBy default, GPP assumes every product flavor consists of a separate, independent app. To tell GPP\nthis isn't the case, you must use the `commit` property:\n\n\u003Cdetails open>\u003Csummary>Kotlin\u003C\u002Fsummary>\n\n```kt\n\u002F\u002F Don't commit any changes by default\nplay.commit.set(false)\n\nandroid {\n    \u002F\u002F ...\n\n    playConfigs {\n        register(\"someFlavorThatWillCommit\") {\n            \u002F\u002F Pick a flavor that will commit the changes prepared by all flavors\n            commit.set(true)\n        }\n    }\n}\n\nafterEvaluate {\n    \u002F\u002F Now make sure the tasks execute in the right order\n    val intermediateTasks = listOf(\n            \"publishSomeFlavor2Release[Apk\u002FBundle]\",\n            \"publishSomeFlavor3Release[Apk\u002FBundle]\",\n            ...\n    )\n\n    \u002F\u002F The commit flavor task must run last so the other tasks can build up changes to commit\n    tasks.named(\"someFlavorThatWillCommitRelease[Apk\u002FBundle]\").configure {\n        mustRunAfter(intermediateTasks)\n    }\n}\n```\n\n\u003C\u002Fdetails>\n\n\u003Cdetails>\u003Csummary>Groovy\u003C\u002Fsummary>\n\n```groovy\n\u002F\u002F Don't commit any changes by default\nplay.commit.set(false)\n\nandroid {\n    \u002F\u002F ...\n\n    playConfigs {\n        someFlavorThatWillCommit {\n            \u002F\u002F Pick a flavor that will commit the changes prepared by all flavors\n            commit.set(true)\n        }\n    }\n}\n\nafterEvaluate {\n    \u002F\u002F Now make sure the tasks execute in the right order\n    def intermediateTasks = [\n            \"publishSomeFlavor2Release[Apk\u002FBundle]\",\n            \"publishSomeFlavor3Release[Apk\u002FBundle]\",\n            ...\n    ]\n\n    \u002F\u002F The commit flavor task must run last so the other tasks can build up changes to commit\n    tasks.named(\"someFlavorThatWillCommitRelease[Apk\u002FBundle]\").configure {\n        mustRunAfter(intermediateTasks)\n    }\n}\n```\n\n\u003C\u002Fdetails>\n\n### Using multiple Service Accounts\n\nIf you need to publish each build flavor to a separate Play Store account, simply provide separate\ncredentials per product flavor.\n\n\u003Cdetails open>\u003Csummary>Kotlin\u003C\u002Fsummary>\n\n```kt\nandroid {\n    \u002F\u002F ...\n\n    playConfigs {\n        register(\"firstCustomer\") {\n            serviceAccountCredentials.set(file(\"customer-one-key.json\"))\n        }\n\n        register(\"secondCustomer\") {\n            serviceAccountCredentials.set(file(\"customer-two-key.json\"))\n        }\n    }\n}\n```\n\n\u003C\u002Fdetails>\n\n\u003Cdetails>\u003Csummary>Groovy\u003C\u002Fsummary>\n\n```groovy\nandroid {\n    \u002F\u002F ...\n\n    playConfigs {\n        firstCustomer {\n            serviceAccountCredentials.set(file('customer-one-key.json'))\n        }\n\n        secondCustomer {\n            serviceAccountCredentials.set(file('customer-two-key.json'))\n        }\n    }\n}\n```\n\n\u003C\u002Fdetails>\n\n## Advanced topics\n\n### Using CLI options\n\nAll configuration options available in the `play` block are also available as CLI options so you\ndon't have to update your build file when making one-time changes. For example, to configure\n`play.track` on demand, use the `--track` option. `camelCase` options are converted to\n`kebab-case` ones.\n\nTo get a list of options and their quick documentation, use `.\u002Fgradlew help --task [task]` where\n`task` is something like `publishBundle`.\n\n### Using HTTPS proxies\n\nIf you need to use GPP behind an HTTPS-proxy, but it fails with an `SSLHandshakeException`, you can\nprovide your own truststore via the `javax.net.ssl.trustStore` property in your project's\n`gradle.properties`:\n\n```properties\nsystemProp.javax.net.ssl.trustStore=\u002Fpath\u002Fto\u002Fyour\u002Ftruststore.ks\nsystemProp.javax.net.ssl.trustStorePassword=YourTruststorePassword\n```\n\nGPP will automatically pick it up and use your proxy.\n","Gradle Play Publisher (GPP) 是一个非官方的 Android 发布自动化 Gradle 插件，能够帮助开发者完成从构建、上传到推广 App Bundle 或 APK 的全过程，并支持发布应用列表和其他元数据。该项目采用 Kotlin 语言编写，具备强大的自动化部署能力，简化了向 Google Play 商店提交应用的流程。适用于需要频繁更新或希望实现持续集成\u002F持续部署（CI\u002FCD）的 Android 应用开发团队。通过 GPP，可以显著提高发布效率并减少人为错误。","2026-06-11 03:11:40","top_language"]