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.
The checklist that gets you rejected
Section titled “The checklist that gets you rejected”From reviewing hundreds of rejection cases, the recurring pattern:
- Subscription created but never activated.
status: PENDING— Google won’t display it. - 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.
- Free-trial offer with no eligibility rule. Google requires an eligibility rule (usually “new subscribers only”) or rejects the offer as promo-abuse-prone.
- 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.”
- Introductory-price offer where the intro period equals the full billing period. Fails Google’s promo-value check.
- Auto-renewing base plan without any offer (fine) alongside a legacy prepaid offer of the same product (not fine).
- Product ID contains a character Google doesn’t accept in the current API version (mostly caught, but happens).
- 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.
The command
Section titled “The command”gplay subscriptions validate \ --package com.example.app \ --product-id proRuns 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.”
What --strict adds
Section titled “What --strict adds”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:
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_typeset (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.
Batch validate every subscription
Section titled “Batch validate every subscription”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.
Pre-flight in CI
Section titled “Pre-flight in CI”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 doneRuns every time you’re about to promote a build, in seconds.
Fixing the common errors
Section titled “Fixing the common errors”Missing eligibility rule for a free trial:
gplay offers update \ --package com.example.app \ --subscription-id pro \ --base-plan-id monthly \ --offer-id monthly-trial-7d \ --eligibility DEVELOPER_ELIGIBILITY_NEW_SUBSCRIBERS_ONLYMissing prices in specific regions:
gplay baseplans prices set \ --package com.example.app \ --subscription-id pro \ --base-plan-id monthly \ --region TR \ --price-micros 149000000 # ₺149Or expand from an anchor:
gplay baseplans prices convert \ --package com.example.app \ --subscription-id pro \ --base-plan-id monthly \ --from-region US \ --only-missingMissing offer name localizations:
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:
gplay subscriptions activate --package com.example.app --product-id proAI-agent-driven fix loop
Section titled “AI-agent-driven fix loop”Give your agent the validate output and let it fix in a loop:
Run
gplay subscriptions validatefor every subscription oncom.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:
npx skills add tamtom/gplay-cli-skillsWhat validate does NOT check
Section titled “What validate does NOT check”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).
Getting started
Section titled “Getting started”brew install tamtom/tap/gplaygplay setup --autogplay 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.