Two questions decide what a Google Play subscriber actually pays, and they are answered in two different places. The base plan answers "which billing period, at which price, auto-renewing or prepaid?" The offer answers "who qualifies for a cheaper start, and for how long?" Model subscription offers wrong and every marketing request for a free trial turns into a brand-new subscription product — which fragments your catalog, splits your reporting across products that can never be deleted once someone has bought them, and leaves the client with purchase logic nobody dares to touch.
This tutorial follows the order Play Console and the Billing Library actually impose: create the product, configure base plans, add offers with eligibility, read the offer tokens in the client, handle plan changes with the right replacement mode, then verify entitlement on your server. Three things trip teams up again and again along the way — the offer token the client must pass explicitly, the proration mode chosen on an upgrade, and the server check that decides whether a free trial is real. The last sections cover the traps that only appear after launch: regional pricing gaps, offers that can never be shown, and a price change that quietly becomes a second base plan.
1. The three-layer model: product, base plan, offer
A Google Play subscription is built from three layers, and their lifetimes are completely different. That is what decides whether today's decision can still be changed in two years.
- Product — the catalogue entry, identified by a product ID. The ID cannot be changed after creation, and a product that anyone has bought cannot be deleted, only deactivated. Name it on day one for what it is, not for the promotion running this quarter: "first_month_half_price" is a permanent ID for a temporary idea.
- Base plan — the layer that actually sets the economics: billing period (weekly, monthly, 3 months, 6 months, yearly), renewal type (auto-renewing or prepaid), price per country, plus grace period and account hold behaviour. One product can carry several base plans, which is exactly how you sell monthly and yearly side by side without a second product.
- Offer — a price treatment attached to one base plan: a free trial, an introductory price, a win-back discount. Each offer carries an eligibility condition, and only eligible users ever see it.
There is one technical consequence worth memorising: the offerToken the client uses to purchase is the credential for a specific "base plan + offer" pair. As soon as a product has more than one base plan, or any offer at all, the token must be passed explicitly — there is no default. Section 5 is where that rule stops being trivia and starts explaining real crashes.
2. Step 1 — create the subscription product
In Play Console, go to Monetise with Play → Products → Subscriptions and create a subscription. This step does four things, and each one is either irreversible or expensive later.
- Product ID — lowercase letters, digits and underscores, unique inside the app, immutable, and never reusable even after deletion. Renaming means creating a new product, which means a new ID and a new purchase path.
- Name — for you to recognise the row in the console. What the buyer sees on the purchase sheet is generated by Play from the base plan and the offer.
- Description and benefits — what the subscription includes. This text surfaces in some store screens, and it is what support will quote back to you.
- Save — you have saved a draft. The product only becomes purchasable once at least one base plan exists and is active.
The most common structural mistake is creating separate products for monthly and yearly. You then maintain two purchase flows, two sets of states, two cancellation paths — while Play was designed to hold both as base plans under one product. Moving later is painful, so get it right the first time.
3. Step 2 — configure the base plans
Base plans are where the subscription's economics live. Every product needs at least one; most need two (monthly and yearly).
Choose the renewal type first. An auto-renewing base plan charges again at the end of each period, which is what most content and utility subscriptions need. A prepaid base plan sells one fixed period up front and requires the user to top up again when it ends — a good fit for low-price markets and for users who do not want a recurring charge. The two do not mix: a prepaid plan has no renewal cycle, no grace period concept, and cannot carry a free trial. If trials are part of your growth plan, the primary plan has to be auto-renewing.
Then set the period and the prices. The period sets the billing rhythm; prices are entered per country and region. This is the single most reliable source of support tickets: Play lets you price only some countries, and a country without a price is simply not purchasable. Buyers there see "item not available in your country", which looks like a bug to them and looks like nothing at all in your console. Before launch, fill in every target market and check that the displayed price satisfies local tax representation rules.
Finally the lifecycle settings — these decide what happens when a charge fails, not when it succeeds:
- Grace period — a window after a failed charge in which the user keeps entitlement, buying time to fix the payment method.
- Account hold — once the grace period ends unpaid, entitlement is suspended but the subscription record is kept, so paying later restores it.
- Resubscribe — lets a user who cancelled come back on the same plan instead of walking the purchase flow again.
Defaults here are not automatically right. Utility apps usually want a grace period and account hold; content apps may prefer a faster downgrade. Whichever you choose, the server-side mapping must match, because the subscription state API distinguishes grace period from account hold — collapsing both into "valid" throws that information away.
4. Step 3 — add offers and eligibility
Offers are the conversion lever, and the layer most often misconfigured. Every offer carries three things: an offer ID, one or more phases, and an eligibility condition.
Phases decide how the offer unfolds in time. A phase is either a free trial (price zero, a number of days) or a discounted price (a number of periods at a given amount). Phases can be stacked, for example "7 days free, then 50% off the first month" — but phase order is the order the user actually pays in, so reversing them means charging first and giving away later, which invites both support tickets and review questions.
Eligibility is the switch that decides who ever sees the offer. Play gives you three conditions:
- New subscriber — has never subscribed to this product. The usual condition for a free trial.
- Upgrade — currently holds another subscription that qualifies as an upgrade target.
- Win-back — subscribed before, then cancelled or expired. Far more precise than a blanket coupon, and it does not contaminate the price new users see.
Offer tags are not decoration. They travel with the purchase and are the only clean way to separate trial conversions from full-price purchases later in your own reporting and server logic. Skip them and, three months in, you cannot answer whether trials retained at all.
An offer only affects new purchases. Editing or deactivating one does not change billing for subscribers already inside it. Conversely, to give existing subscribers a new price you need the price change flow — not another offer.
Two boundaries are worth writing down. Each base plan can only carry a limited number of offers, so promotions should be reused rather than created endlessly. And when a base plan itself is inactive, every offer beneath it is unsellable: when an offer "does not show up", check the base plan state first, eligibility second, and client code last.
5. Step 4 — read the right offer token in the client
Product details return a list, not a single price. Querying a subscription yields subscriptionOfferDetails, where each entry is one "base plan + offer" combination carrying a basePlanId, an offerId (which may be empty, meaning the base plan's own full-price path) and an offerToken.
Purchasing means handing one entry from that list to the billing flow explicitly: build the ProductDetailsParams for the purchase and set the selected offerToken on it. The rule is simple, and the cost of breaking it is immediate:
- One base plan and no offers — the token may be omitted, though passing it explicitly is safer across library versions.
- Several base plans, or any offer — omitting the token fails the purchase. This is the root cause behind the classic "monthly works, yearly throws an error".
- The price shown in the UI must come from the entry you selected, never from the first element of the list. Displaying $4.99 while charging $49.99 is the most expensive bug in this article.
Write the selection rule out rather than trusting list order: find the base plan by the basePlanId you recognise, then pick the highest-priority offer inside it that the returned list actually contains, and fall back to the base plan's full-price entry when no offer is available. Eligibility is Play's job — the list you receive is already "what this user can buy right now", so do not re-implement eligibility rules locally and guess.
While testing, use a licence test account and remember that eligibility applies to testers too: an account that has already subscribed will not see a new-subscriber offer. That is expected behaviour, not broken code. To verify a trial renders, a fresh account is the fastest path.
6. Step 5 — upgrades, downgrades and replacement modes
Moving a user from monthly to yearly, or from a basic tier to a premium one, is a replacement, not a second purchase. How that replacement behaves is decided by the replacement mode, and choosing wrong means either double charging the user or leaving money on the table for a full period.
- With time proration — takes effect immediately and credits the remaining value of the old subscription against the new one, so the user pays the difference now.
- Charge prorated price — takes effect immediately and charges the prorated amount at once; commonly used for downgrades so the user does not consume first and settle later.
- Charge full price — takes effect immediately at the new plan's full price with no credit for the remainder; reasonable only when the amount at stake is smaller than the cost of prorating it.
- Without proration — takes effect immediately, the new subscription bills from the next cycle, and the remainder of the old period simply runs out. Kindest to the user, least profitable for you, and a sane default for downgrades.
In Billing Library 8 the entry point is unified as setSubscriptionReplacementMode; the older method names from earlier library versions are deprecated. If your code still calls them, the compile error is a gift — cheaper than discovering mid-rollout that the semantics differ.
Prepaid base plans do not participate in replacement: they have no renewal cycle and expire on their own schedule. Any "switch monthly to yearly" prompt must exclude prepaid users, or you will show them a button that can never complete.
One ordering rule: a replacement request either fails or takes effect, never partially. So do not grant local entitlement on the client before sending it — wait until the server holds the new purchase credential, then unlock.
7. Step 6 — verify and acknowledge on your server
What the client returns is only "the user says they bought it". Entitlement is decided server-side: call the subscription lookup (purchases.subscriptionsv2) with the purchase token, take the authoritative state and expiry, and write that into your own entitlement table.
The state field tells you which segment the user is in — active, in grace period, on hold, paused, cancelled, expired, pending. Those map directly onto entitlement branches: grace period usually keeps access, account hold usually narrows it, pause should stop service while keeping data. Hard-coding the mapping into "valid or invalid" throws away the very distinctions Play provides.
Then two actions that are easy to forget. Acknowledge the purchase within three days or it is refunded automatically; doing it from the server is more reliable than trusting the client. And wire real-time developer notifications so renewals, cancellations, grace period entries, recoveries and price change confirmations reach you. A notification is a signal to go and check, not the final state: fetch the complete subscription afterwards, deduplicate on the message ID, and process idempotently.
Get those three layers right and the loop closes: the client initiates, the server adjudicates, notifications keep it timely.
8. The traps that only appear after launch
These rarely show up in testing and arrive all at once with real traffic.
- Regional pricing gaps — a new market is added and the price table is not filled in, so buyers there see "not available". Walking the purchase sheet once with an account in that region beats trusting a console table.
- Changing price the wrong way — editing an existing base plan starts the price change flow, where existing subscribers must confirm before the new price applies. If the goal is "old users keep the old price, new users pay the new one", the usual answer is a new base plan and a change in what you sell, not an edit. Decide which outcome you want before touching the price.
- Deactivating an offer with live subscribers — deactivation blocks new purchases only; existing subscribers keep the offer until the period ends. Not a bug, but a support script you should have ready.
- Turning every promotion into a new product — you end up with undeletable products, fragmented reporting, and client logic nobody will refactor.
- Forgetting offer tags — without them you cannot separate trial conversions from direct purchases, so you cannot say which promotion actually retained.
- Visible to testers, invisible to users — eligibility applies to test accounts as well. Verifying with a clean account is the fastest debugging path you have.
9. Where KappS fits in
Subscription configuration is a product decision, but it runs on top of accounts that have to stay compliant for years: payments profile, tax information, developer verification status. If any of those breaks, the revenue chain breaks at the worst possible moment. KappS prepares and submits compliant material and keeps accounts and publishing state healthy, so those preconditions stop being a variable.
- Account and payments consistency — developer identity, payments profile and the actual payee aligned, so settlement does not stall on mismatched details.
- Tax and regional configuration review — W-8BEN/W-9, withholding rates and target markets checked before launch, not after a payout lands short.
- A second pair of eyes on configuration — base plans, offer eligibility and client-side token handling, especially across multi-product, multi-region, multi-language portfolios.
We do not offer anything that works around platform rules: prices and trials have to be real, eligibility has to follow Play's definitions, and every purchase has to survive server-side verification. Those are both the rules and the only version of this that keeps working.
Sources (Google documentation): Play Console Help — create and configure subscriptions, base plans, offers and eligibility, prepaid plans; Android Developers — Play Billing integration, subscription replacement modes, server-side verification, real-time developer notifications.
