Skip to content

Pre-flight Google Play subscription validation: catch problems before Play rejects

Google Play subscription reviews have brutal feedback loops. Submit a new subscription with a missing German localization or an offer without an eligibility rule, and you’ll get a rejection notice hours or days later, with a “please fix and resubmit” wall of text that doesn’t quite tell you which field.

This is entirely avoidable. Every rejection reason maps to something you can check locally, in seconds, with gplay.

From reviewing hundreds of rejection cases, the recurring pattern:

  1. Subscription created but never activated. status: PENDING — Google won’t display it.
  2. Base plan created but no price set in one or more countries. Google won’t publish a base plan that’s missing prices in Play’s list of supported markets.
  3. Free-trial offer with no eligibility rule. Google requires an eligibility rule (usually “new subscribers only”) or rejects the offer as promo-abuse-prone.
  4. Offer name not localized for locales where the app is listed. Google flags this even for locales where you’d say “who cares, they read English fine.”
  5. Introductory-price offer where the intro period equals the full billing period. Fails Google’s promo-value check.
  6. Auto-renewing base plan without any offer (fine) alongside a legacy prepaid offer of the same product (not fine).
  7. Product ID contains a character Google doesn’t accept in the current API version (mostly caught, but happens).
  8. Subscription referenced by a paywall on your site that doesn’t exist. Not technically rejected by Play but breaks store link discoverability.

You want to catch every one of these before the submit button.

Terminal window
gplay subscriptions validate \
--package com.example.app \
--product-id pro

Runs every check above against the current state in Play Console and prints a report:

Validating subscription 'pro' for com.example.app...
✓ Subscription status: ACTIVE
✓ 2 base plans: monthly (P1M), yearly (P1Y)
Base plan: monthly
✓ Auto-renewing
✓ Billing period valid: P1M
✓ Prices set in 175 regions
⚠ Price missing in 2 regions: TR, AR
✓ 1 offer: monthly-trial-7d
Offer: monthly-trial-7d
✓ Phases valid: FREE_TRIAL P7D 0
✗ Eligibility rule missing — Google requires this for FREE_TRIAL offers
⚠ Offer name localized in 8/12 supported locales
Missing: es-419, pt-BR, ja-JP, ko
Base plan: yearly
✓ Auto-renewing
✓ Billing period valid: P1Y
✓ Prices set in 175 regions
✓ No offers
Summary: 2 errors, 3 warnings.
Would likely be rejected on submission.

Fix the errors (missing eligibility rule, missing prices), address the warnings, re-run. Repeat until it says “OK to submit.”

By default validate runs the required checks (things Google definitely rejects). Add --strict to also flag ergonomics issues that don’t necessarily fail review but hurt conversion:

Terminal window
gplay subscriptions validate --strict \
--package com.example.app \
--product-id pro

--strict adds:

  • Offer names shorter than 15 characters (“Trial” instead of “Try free for 7 days”).
  • Base plans with no renewal_type set (defaults to auto-renewing but explicit is better).
  • Prices where the yearly is not at least 15% cheaper than 12x monthly (weird pricing signal).
  • Missing tag on offers (SPECIAL_INTRO, LEGACY_INTRO) that RevenueCat and other billing wrappers use.

Not blocking, but worth reviewing.

Terminal window
gplay subscriptions list --package com.example.app \
| jq -r '.[].productId' \
| xargs -I{} gplay subscriptions validate --package com.example.app --product-id {}

Prints a report per subscription. Use this before any batch price change or when auditing an app you inherited.

Fail the pipeline if any subscription has errors:

- name: Validate subscriptions before promoting
env:
GPLAY_SERVICE_ACCOUNT: ${{ secrets.PLAY_SA_JSON_PATH }}
run: |
gplay subscriptions list --package com.example.app \
| jq -r '.[].productId' \
| while read PRODUCT; do
RESULT=$(gplay subscriptions validate \
--package com.example.app \
--product-id "$PRODUCT" \
--output json)
ERRORS=$(echo "$RESULT" | jq '.errors | length')
if [ "$ERRORS" -gt 0 ]; then
echo "❌ $PRODUCT has $ERRORS errors:"
echo "$RESULT" | jq -r '.errors[] | " - \(.field): \(.message)"'
exit 1
fi
done

Runs every time you’re about to promote a build, in seconds.

Missing eligibility rule for a free trial:

Terminal window
gplay offers update \
--package com.example.app \
--subscription-id pro \
--base-plan-id monthly \
--offer-id monthly-trial-7d \
--eligibility DEVELOPER_ELIGIBILITY_NEW_SUBSCRIBERS_ONLY

Missing prices in specific regions:

Terminal window
gplay baseplans prices set \
--package com.example.app \
--subscription-id pro \
--base-plan-id monthly \
--region TR \
--price-micros 149000000 # ₺149

Or expand from an anchor:

Terminal window
gplay baseplans prices convert \
--package com.example.app \
--subscription-id pro \
--base-plan-id monthly \
--from-region US \
--only-missing

Missing offer name localizations:

Terminal window
gplay offers locales set \
--package com.example.app \
--subscription-id pro \
--base-plan-id monthly \
--offer-id monthly-trial-7d \
--locales es-419:"Prueba gratis 7 días",pt-BR:"7 dias grátis",ja-JP:"7日間無料",ko:"7일 무료"

Subscription still in PENDING:

Terminal window
gplay subscriptions activate --package com.example.app --product-id pro

Give your agent the validate output and let it fix in a loop:

Run gplay subscriptions validate for every subscription on com.example.app. For each error, fix it. For each warning, ask me before touching. When everything’s green, print the final validate output.

Chains: subscriptions list → per-subscription validate → per-error remediation command → re-validate → summary.

Works with all 12 supported agents. The IAP setup skill teaches the fix mappings so agents don’t need to guess:

Terminal window
npx skills add tamtom/gplay-cli-skills

To be honest about the tool’s limits:

  • Google’s discretionary review. Play reviewers occasionally reject on judgment calls (e.g. “your feature description doesn’t match the subscription value”). No local tool catches this.
  • Policy changes since the check was written. Google updates review policies quarterly-ish; validate ships known-good checks, not future ones.
  • Cross-store consistency. If your App Store subscription has different phases than your Play one, validate won’t flag it (that’s what RevenueCat MCP is for).
Terminal window
brew install tamtom/tap/gplay
gplay setup --auto
gplay subscriptions validate --package com.example.app --product-id <your-sub-id>

Full subscription reference at /reference/subscriptions/, offers at /reference/offers/. Run it before every submission — it takes about a second and catches every rejection reason we’ve seen.