Skip to content

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.

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.

  • 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

A disclosure line under the button covers price, period, trial and renewal. This plain material3 version shows the idea:

@Composable
fun 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.

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")
Get started

⭐ 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.