Google Play subscription paywall requirements
Short answer: Google Play wants the subscription’s terms to be clear where the user decides: the cost, how often they are billed, that it renews automatically, the details of any free trial or intro offer, and how to cancel. Put them next to the purchase button, with no extra taps needed to see them.
What the policy asks for
Section titled “What the policy asks for”According to Google’s Play Console Help page on subscriptions, your app must clearly disclose:
- The offer terms: what the subscription includes.
- The cost.
- The billing frequency, such as per month or per year.
- That it auto-renews.
- Whether a subscription is needed to use the app: what works without one.
These belong in promotions, splash screens and plan-selection screens, without the user taking extra steps to find them.
For free trials and intro offers, before the user signs up:
- How long the trial or intro price lasts and what it includes
- When and how it converts to a paid subscription
- The price after the trial
- How to cancel before it converts
And users need an easy way to manage or cancel the subscription, for example a link to the Google Play subscriptions page.
Patterns Google lists as violations
Section titled “Patterns Google lists as violations”- Monthly plans that don’t say they renew automatically
- Annual plans shown mainly as a monthly price
- Terms that are only partly translated
- Hiding what is available without a subscription
- Calling an auto-renewing plan a “Free Trial” product
- Multi-step flows that lead to accidental subscriptions
- No clear statement of when a trial becomes paid
- No way to cancel
In Compose
Section titled “In Compose”A disclosure line under the button covers price, period, trial and renewal. This plain material3 version shows the idea:
@Composablefun SimplePaywall( plans: List<Plan>, onPurchase: (Plan) -> Unit, onRestore: () -> Unit, termsUrl: String, privacyUrl: String,) { var selected by rememberSaveable { mutableStateOf(plans.first().id) } val plan = plans.first { it.id == selected } val uri = LocalUriHandler.current
Column(Modifier.padding(24.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) { Text("Go Pro", style = MaterialTheme.typography.headlineLarge)
// Plans: a radio group, so TalkBack announces "1 of 2, selected". Column(Modifier.selectableGroup(), verticalArrangement = Arrangement.spacedBy(8.dp)) { plans.forEach { p -> OutlinedCard( Modifier .fillMaxWidth() .selectable(selected = p.id == selected, role = Role.RadioButton) { selected = p.id }, ) { Row(Modifier.padding(16.dp), verticalAlignment = Alignment.CenterVertically) { RadioButton(selected = p.id == selected, onClick = null) Text(p.title, Modifier.padding(start = 12.dp).weight(1f)) Text("${p.price} ${p.period}") } } } }
Button(onClick = { onPurchase(plan) }, Modifier.fillMaxWidth()) { Text(if (plan.trial != null) "Start free trial" else "Continue") }
// Google Play: price, period, trial terms and how to cancel, next to the button. val terms = plan.trial?.let { "$it, then " }.orEmpty() Text( "$terms${plan.price} ${plan.period}. Renews automatically. Cancel anytime in Google Play.", style = MaterialTheme.typography.bodySmall, textAlign = TextAlign.Center, modifier = Modifier.fillMaxWidth(), ) Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center) { TextButton(onClick = onRestore) { Text("Restore") } TextButton(onClick = { uri.openUri(termsUrl) }) { Text("Terms") } TextButton(onClick = { uri.openUri(privacyUrl) }) { Text("Privacy") } } Spacer(Modifier.padding(4.dp)) }}M3 Expressive Paywall builds the same line from each PaywallPlan and puts it in a shared footer
on every design, together with Restore, Terms and Privacy links:
- trial plan: “7-day free trial, then $29.99 per year. Renews automatically until canceled. Cancel anytime in Google Play.”
- one-time plan: “$79.99 one-time payment. No subscription, no renewals.”
What the library does not do for you:
- It can’t know what your app offers without a subscription. Say it in your copy, or use the Free vs Pro table.
- It doesn’t add a “Manage subscription” link inside your app. Add one in your settings screen
that opens
https://play.google.com/store/account/subscriptions. - Your trial text must match the trial configured in Play Console.
Related
Section titled “Related”- How to build a paywall in Jetpack Compose
- Free trial paywall in Jetpack Compose
- PaywallPlan and the disclosure text
FAQ
Does Google Play require a Restore button?
Google Play's subscription policy page does not name a Restore button. It is still good practice, because users who reinstall or switch phones look for it, and Apple's App Store review does expect a restore option.
Can I show the yearly plan as a monthly price?
Not as the main price. Google lists annual plans shown mainly as a monthly price as a violation. Show the full yearly price and add the monthly equivalent as secondary text.
Do I need to explain the free trial?
Yes. Before the trial starts, users must see how long it lasts, what it includes, when it converts to a paid subscription, the price after it, and how to cancel.
Try it in your app
Add the library (JitPack, minSdk 26), then follow Getting started.
implementation("com.github.sanikantv:m3-expressive-paywall:v1.0.0")⭐ If this guide helped, star M3 Expressive Paywall on GitHub. It helps other Compose developers find it.
Built by Sanikant. Set in Google Sans Flex (SIL Open Font License). Not affiliated with Google.