Install to Claude Code
npx -y skills add https://github.com/tamtom/gplay-cli-skills --skill gplay-iap-setupDescription
gplay iap setup
SKILL.md
---
name: gplay-iap-setup
description: In-app products, subscriptions, base plans, and offers setup for Google Play monetization, including bulk-localizing subscription display names, descriptions, and benefits across all locales. Use when configuring in-app purchases or subscription products.
---
# In-App Purchase Setup for Google Play
Use this skill when you need to set up monetization for your Android app.
## Two APIs: Legacy vs New Monetization
Google Play has two APIs for one-time products:
| | Legacy (`gplay iap`) | New Monetization (`gplay onetimeproducts`) |
|---|---|---|
| API | `inappproducts` | `monetization.onetimeproducts` |
| Price format | `priceMicros`/`currency` | `units`/`nanos`/`currencyCode` |
| Structure | Flat `prices` map | `purchaseOptions` with `regionalPricingAndAvailabilityConfigs` |
| States | `active`/`inactive` | `DRAFT` → `ACTIVE` (requires explicit activation) |
| Regional pricing | `--auto-convert-prices` flag | `--regions-version` required |
**Prefer the new monetization API** (`gplay onetimeproducts`) for new products. It supports purchase options, better regional pricing control, and is the actively developed API.
**Use the legacy API** (`gplay iap`) only for managing existing legacy products.
**Never mix the two APIs for the same product.** A product created via `gplay iap create` cannot be managed via `gplay onetimeproducts` and vice versa.
## Critical: Product IDs Are Permanent
**Google Play permanently reserves product IDs after deletion.** If you create `premium_unlock` and later delete it, the ID `premium_unlock` can never be reused — not even with a different API. Choose product IDs carefully.
This means:
- Do NOT create a "test" product with a good ID and then delete it
- Do NOT create via the legacy API and then try to recreate via the new API
- If you burn an ID, you must choose a new one (e.g., `premium_unlock_v2`)
## One-Time Products (New Monetization API)
### List products
```bash
gplay onetimeproducts list --package com.example.app
```
### Create product
**`--regions-version` is required** — the `create` command uses PATCH with `allowMissing=true` internally:
```bash
gplay onetimeproducts create \
--package com.example.app \
--product-id premium_unlock \
--json @product.json \
--regions-version "2025/03"
```
### product.json (new monetization format)
```json
{
"productId": "premium_unlock",
"listings": [
{ "languageCode": "en-US", "title": "Premium Unlock", "description": "Unlock all premium features" },
{ "languageCode": "es-ES", "title": "Desbloqueo Premium", "description": "Desbloquea todas las funciones premium" }
],
"purchaseOptions": [
{
"buyOption": { "legacyCompatible": true },
"newRegionsConfig": {
"availability": "AVAILABLE",
"usdPrice": { "currencyCode": "USD", "units": "9", "nanos": 990000000 },
"eurPrice": { "currencyCode": "EUR", "units": "9", "nanos": 990000000 }
},
"regionalPricingAndAvailabilityConfigs": [
{ "regionCode": "US", "availability": "AVAILABLE", "price": { "currencyCode": "USD", "units": "9", "nanos": 990000000 } },
{ "regionCode": "GB", "availability": "AVAILABLE", "price": { "currencyCode": "GBP", "units": "7", "nanos": 990000000 } },
{ "regionCode": "IN", "availability": "AVAILABLE", "price": { "currencyCode": "INR", "units": "249", "nanos": 990000000 } }
]
}
]
}
```
### Activate the purchase option
New products start in **DRAFT** state. You must activate before users can purchase:
```bash
gplay purchase-options batch-update-states \
--package com.example.app \
--product-id premium_unlock \
--json '{"requests":[{"activatePurchaseOptionRequest":{"packageName":"com.example.app","productId":"premium_unlock","purchaseOptionId":"default"}}]}'
```
### Update product
```bash
gplay onetimeproducts patch \
--package com.example.app \
--product-id premium_unlock \
--json @product-updated.json \
--regions-version "2025/03" \
--update-mask "purchaseOptions"
```
### Get product
```bash
gplay onetimeproducts get --package com.example.app --product-id premium_unlock
```
### Delete product
```bash
gplay onetimeproducts delete \
--package com.example.app \
--product-id premium_unlock \
--confirm
```
### Batch operations
```bash
# Get multiple products
gplay onetimeproducts batch-get \
--package com.example.app \
--product-ids "premium_unlock,coins_100"
# Update multiple products (regionsVersion goes inside JSON)
gplay onetimeproducts batch-update \
--package com.example.app \
--json @products-batch.json
```
## Legacy In-App Products (IAP)
Use only for managing existing legacy products.
### List products
```bash
gplay iap list --package com.example.app
```
### Create product
`iap create` has no `--sku` flag — the SKU/productId lives in the JSON body:
```bash
gplay iap create \
--package com.example.app \
--json @product.json
```
### product.json (legacy format)
```json
{
"sku": "premium_upgrade",
"status": "active",
"purchaseType": "managedUser",
"defaultPrice": {
"priceMicros": "990000",
"currency": "USD"
},
"prices": {
"US": { "priceMicros": "990000", "currency": "USD" },
"GB": { "priceMicros": "799000", "currency": "GBP" }
},
"listings": {
"en-US": { "title": "Premium Upgrade", "description": "Unlock all premium features" },
"es-ES": { "title": "Actualización Premium", "description": "Desbloquea todas las funciones premium" }
}
}
```
### Update / Batch / Delete
```bash
# Update
gplay iap update --package com.example.app --sku premium_upgrade --json @product-updated.json
# Batch update
gplay iap batch-update --package com.example.app --json @products.json
# Batch get
gplay iap batch-get --package com.example.app --skus "premium,coins_100,coins_500"
# Delete (permanent — ID cannot be reused)
gplay iap delete --package com.example.app --sku premium_upgrade --confirm
```
## Subscriptions
### List subscriptions
```bash
gplay subscriptions list --package com.example.app
```
### Create subscription
```bash
gplay subscriptions create \
--package com.example.app \
--json @subscription.json
```
### subscription.json
Subscriptions use the `units`/`nanos`/`currencyCode` price format:
```json
{
"productId": "premium_monthly",
"basePlans": [
{
"basePlanId": "monthly",
"state": "ACTIVE",
"regionalConfigs": [
{
"regionCode": "US",
"newSubscriberAvailability": true,
"price": { "currencyCode": "USD", "units": "4", "nanos": 990000000 }
}
],
"autoRenewingBasePlanType": {
"billingPeriodDuration": "P1M"
}
},
{
"basePlanId": "yearly",
"state": "ACTIVE",
"regionalConfigs": [
{
"regionCode": "US",
"newSubscriberAvailability": true,
"price": { "currencyCode": "USD", "units": "49", "nanos": 990000000 }
}
],
"autoRenewingBasePlanType": {
"billingPeriodDuration": "P1Y"
}
}
],
"listings": [
{ "languageCode": "en-US", "title": "Premium Subscription", "description": "Get all premium features" }
]
}
```
### Bulk-localize subscriptions across locales
Subscription listings are an **array** of per-locale objects (not an object
keyed by locale). Each entry uses `languageCode`, `title`, `benefits` (array,
max 4), and `description`. One `subscriptions update` call sets every locale
atomically — use `--update-mask listings` so base plans and pricing are left
untouched.
**1. Discover the locales your app already ships** (cover at least these):
```bash
EDIT_ID=$(gplay edits create --package com.example.app | jq -r '.id')
gplay listings list --package com.example.app --edit "$EDIT_ID" --output table
```
**2. Build a listings-only JSON file** (`subscription-listings.json`):
```json
{
"listings": [
{ "languageCode": "en-US", "title": "Premium Monthly", "benefits": ["Unlimited access", "No ads"], "description": "Premium access to all features." },
{ "languageCode": "de-DE", "title": "Premium Monatlich", "benefits": ["Unbegrenzter Zugang", "Keine Werbung"], "description": "Premium-Zugang zu allen Funktionen." },
{ "languageCode": "es-ES", "title": "Premium Mensual", "benefits": ["Acceso ilimitado", "Sin anuncios"], "description": "Acceso premium a todas las funciones." },
{ "languageCode": "ja-JP", "title": "プレミアム月額", "benefits": ["無制限アクセス", "広告なし"], "description": "すべての機能にプレミアムアクセス。" }
]
}
```
**3. Apply to one subscription:**
```bash
gplay subscriptions update \
--package com.example.app \
--product-id premium_monthly \
--json @subscription-listings.json \
--update-mask listings
```
**4. Loop over every subscription in the app:**
```bash
PACKAGE="com.example.app"
gplay subscriptions list --package "$PACKAGE" --paginate \
| jq -r '.[].productId' \
| while read -r PRODUCT_ID; do
gplay subscriptions update \
--package "$PACKAGE" \
--product-id "$PRODUCT_ID" \
--json @subscription-listings.json \
--update-mask listings
done
```
Verify with `gplay subscriptions get --package com.example.app --product-id premium_monthly --pretty`
and confirm every `languageCode` appears in the `listings` array. Constraints:
title max 55 chars, description max 80 chars, benefits max 4 items. When the
user gives a single display name, reuse it for all locales; when they give
per-locale translations, use each locale's own text.
## Base Plans
Base plans define the billing period and price for subscriptions.
### Activate base plan
```bash
gplay baseplans activate \
--package com.example.app \
--product-id premium_monthly \
--base-plan-id monthly
```
### Deactivate base plan
```bash
gplay baseplans deactivate \
--package com.example.app \
--product-id premium_monthly \
--base-plan-id monthly
```
### Migrate prices
```bash
gplay baseplans migrate-prices \
--package com.example.app \
--product-id premium_monthly \
--base-plan-id monthly \
--json @migration.json
```
## Subscription Offers
Offers provide discounts, free trials, or introductory pricing.
### List offers
```bash
gplay offers list \
--package com.example.app \
--product-id premium_monthly \
--base-plan-id monthly
```
### Create offer
```bash
gplay offers create \
--package com.example.app \
--product-id premium_monthly \
--base-plan-id monthly \
--json @offer.json
```
### offer.json (Free trial)
```json
{
"offerId": "trial_7day",
"state": "ACTIVE",
"phases": [
{
"duration": "P7D",
"pricingType": "FREE_TRIAL"
}
],
"regionalConfigs": [
{
"regionCode": "US"
}
]
}
```
### offer.json (Introductory price)
```json
{
"offerId": "intro_50_off",
"state": "ACTIVE",
"phases": [
{
"duration": "P1M",
"pricingType": "SINGLE_PAYMENT",
"price": {
"priceMicros": "2490000",
"currency": "USD"
}
}
]
}
```
### Activate/Deactivate offer
```bash
# Activate
gplay offers activate \
--package com.example.app \
--product-id premium_monthly \
--base-plan-id monthly \
--offer-id trial_7day
# Deactivate
gplay offers deactivate \
--package com.example.app \
--product-id premium_monthly \
--base-plan-id monthly \
--offer-id trial_7day
```
## OTP Purchase Option Offers
Manage offers on one-time product purchase options:
```bash
# List offers
gplay otp-offers list --package com.example.app --product-id premium_unlock --purchase-option-id default
# Activate offer
gplay otp-offers activate --package com.example.app --product-id premium_unlock --purchase-option-id default --offer-id promo_50off
# Deactivate offer
gplay otp-offers deactivate --package com.example.app --product-id premium_unlock --purchase-option-id default --offer-id promo_50off
```
## Regional Pricing
### Convert prices
```bash
gplay pricing convert \
--package com.example.app \
--json @price-request.json
```
### price-request.json (ConvertRegionPricesRequest)
The body is a single base `price` as Money — `units` is the whole-currency
amount as a string, `nanos` is the fractional part (990000000 = .99):
```json
{
"price": {
"currencyCode": "USD",
"units": "9",
"nanos": 990000000
}
}
```
The response returns converted prices for all supported regions plus a
`regionVersion` you can pass as `--regions-version` to subscriptions, base
plans, offers, and one-time product commands.
## Common Monetization Patterns
### Pattern 1: New One-Time Product (recommended)
```bash
# 1. Create product
gplay onetimeproducts create \
--package com.example.app \
--product-id premium_unlock \
--json @premium.json \
--regions-version "2025/03"
# 2. Activate purchase option
gplay purchase-options batch-update-states \
--package com.example.app \
--product-id premium_unlock \
--json '{"requests":[{"activatePurchaseOptionRequest":{"packageName":"com.example.app","productId":"premium_unlock","purchaseOptionId":"default"}}]}'
# 3. Verify
gplay onetimeproducts get --package com.example.app --product-id premium_unlock
```
### Pattern 2: Subscription with Free Trial
```bash
# 1. Create subscription
gplay subscriptions create \
--package com.example.app \
--json @sub.json
# 2. Create free trial offer
gplay offers create \
--package com.example.app \
--product-id premium \
--base-plan-id monthly \
--json @trial.json
```
### Pattern 3: Multi-Tier Subscription
```json
{
"productId": "premium",
"basePlans": [
{
"basePlanId": "basic_monthly",
"regionalConfigs": [{ "regionCode": "US", "newSubscriberAvailability": true, "price": { "currencyCode": "USD", "units": "2", "nanos": 990000000 } }],
"autoRenewingBasePlanType": { "billingPeriodDuration": "P1M" }
},
{
"basePlanId": "premium_monthly",
"regionalConfigs": [{ "regionCode": "US", "newSubscriberAvailability": true, "price": { "currencyCode": "USD", "units": "4", "nanos": 990000000 } }],
"autoRenewingBasePlanType": { "billingPeriodDuration": "P1M" }
},
{
"basePlanId": "premium_yearly",
"regionalConfigs": [{ "regionCode": "US", "newSubscriberAvailability": true, "price": { "currencyCode": "USD", "units": "49", "nanos": 990000000 } }],
"autoRenewingBasePlanType": { "billingPeriodDuration": "P1Y" }
}
]
}
```
## Testing
### Use test purchases
In your app code, use test product IDs:
- `android.test.purchased`
- `android.test.canceled`
- `android.test.refunded`
- `android.test.item_unavailable`
### License testing
Add test accounts in Play Console:
Settings → License Testing → Add license testers
## Best Practices
1. **Use clear product IDs** - e.g., `premium_monthly`, not `prod_001`. IDs are permanent and cannot be reused after deletion.
2. **Prefer the new monetization API** - Use `gplay onetimeproducts` for new products, not `gplay iap`.
3. **Localize descriptions** - Provide listings for all supported languages.
4. **Set up regional pricing** - Use PPP pricing (see `gplay-ppp-pricing` skill) instead of same price everywhere.
5. **Activate after creation** - New OTP products start in DRAFT. Use `gplay purchase-options batch-update-states` to activate.
6. **Discover commands** - Run `gplay --help` to see all command groups. Purchase option management is under `gplay purchase-options`, not under `gplay onetimeproducts`.
7. **Test thoroughly** - Use test accounts and test product IDs.
8. **Monitor conversions** - Track which products/offers perform best.
9. **Update prices carefully** - Price changes affect existing subscribers.
## Billing Periods
- `P1W` - 1 week
- `P1M` - 1 month
- `P3M` - 3 months
- `P6M` - 6 months
- `P1Y` - 1 year
