---
title: "Google Play’s subscription with Add-ons: guide to multi-line subscriptions"
description: "Read on to explore what multi-line subscriptions are, discuss practical strategies for using them effectively, walk through the implementation details using the Play Billing Library directly."
language: "en"
publishedAt: "2025-12-04T00:17:43Z"
updatedAt: "2025-12-04T00:17:43Z"
authors:
  - name: "Jaewoong Eum"
    url: "https://www.revenuecat.com/blog/author/jaewoong-eum"
category: "Engineering"
categoryUrl: "https://www.revenuecat.com/blog/engineering"
readingTime: 14
canonical: "https://www.revenuecat.com/blog/engineering/subscription-add-ons"
---

# Google Play’s subscription with Add-ons: guide to multi-line subscriptions

Read on to explore what multi-line subscriptions are, discuss practical strategies for using them effectively, walk through the implementation details using the Play Billing Library directly.

## Table of contents

- [What are multi-line subscriptions?](#what-are-multi-line-subscriptions)
  - [How the bundle works](#how-the-bundle-works)
  - [Key constraints to understand](#key-constraints-to-understand)
- [Strategies for using multi-line subscriptions](#strategies-for-using-multi-line-subscriptions)
  - [The modular feature strategy](#the-modular-feature-strategy)
  - [The premium upgrade strategy](#the-premium-upgrade-strategy)
  - [The bundle and save strategy](#the-bundle-and-save-strategy)
  - [Managing complexity](#managing-complexity)
- [Implementing Subscription with Add-ons without RevenueCat](#implementing-subscription-with-add-ons-without-revenuecat)
  - [Setting up your products in Google Play Console](#setting-up-your-products-in-google-play-console)
  - [Querying product details](#querying-product-details)
  - [Launching the purchase flow with multiple items](#launching-the-purchase-flow-with-multiple-items)
  - [Processing the purchase](#processing-the-purchase)
  - [Modifying an existing Subscription with Add-ons](#modifying-an-existing-subscription-with-add-ons)
  - [Server-side verification and real-time notifications](#server-side-verification-and-real-time-notifications)
  - [Handling edge cases](#handling-edge-cases)
- [Simplifying Implementation with RevenueCat](#simplifying-implementation-with-revenuecat)
  - [Configuring Products in RevenueCat](#configuring-products-in-revenuecat)
  - [Simplified Purchase Flow](#simplified-purchase-flow)
  - [Cross-platform consistency](#cross-platform-consistency)
  - [Analytics and Insights](#analytics-and-insights)
  - [Webhooks and server integration](#webhooks-and-server-integration)
- [Wrapping up](#wrapping-up)

Subscription monetization on Android has evolved significantly over the years. With the introduction of base plans and offers in 2022, Google gave developers more flexibility in structuring their subscription products. Now, Google has taken another major step forward with **Subscription with Add-ons**, also known as multi-line subscriptions or multi-product checkout for subscriptions. This feature allows developers to bundle multiple subscription products together into a single purchase, creating a more streamlined experience for both users and developers.

Read on to explore what multi-line subscriptions are, discuss practical strategies for using them effectively, walk through the implementation details using the Play Billing Library directly, and finally examine how [RevenueCat](https://www.revenuecat.com/) can simplify the entire process.

## **What are multi-line subscriptions?**

At its core, Subscription with Add-ons is a feature that lets you bundle multiple subscription products together so they can be purchased, billed, and managed as a unified subscription. Rather than requiring users to make separate purchases for a base subscription and additional premium features, you can now offer everything in a single checkout flow.

Consider a music streaming application that offers a base ‘Premium’ subscription. Previously, if you wanted to offer additional features like ‘HiFi Audio’ or ‘Offline Downloads’ as separate paid add-ons, users would need to purchase and manage each subscription independently. This meant multiple transactions, multiple renewal dates, and multiple entries in their subscription management screen. With Subscription with Add-ons, users can select the base Premium plan along with any combination of add-ons and complete a single purchase. They see one combined price, go through one checkout flow, and have everything synchronized to a single renewal date.

The terminology can be a bit confusing at first. Google uses ‘Subscription with Add-ons’ as the official feature name, but you might also see it referred to as ‘multi-line subscriptions’ (referring to the multiple line items in a single purchase) or ‘multi-product checkout’ (emphasizing the checkout experience). These all refer to the same capability.

### **How the bundle works**

When a user purchases a Subscription with Add-ons, the first item in the product list becomes the **base item**, and all subsequent items are treated as **add-ons**. This distinction is important because the base item determines certain behaviors for the entire bundle. For example, when the base subscription is canceled, all associated add-ons are automatically canceled as well. The add-ons cannot exist independently of the base subscription.

Google Play handles the complexity of aligning billing cycles automatically. When a user adds a new add-on to an existing subscription, Google Play calculates a prorated charge to align the add-on’s renewal date with the base item. This means that after the initial prorated period, all items in the bundle renew together on the same date. Similarly, if a user removes an add-on, it continues to provide access until the end of its current billing period but will not renew.

### **Key constraints to understand**

Before diving into implementation, there are several important constraints that shape how you can use this feature.

1. All items in a Subscription with Add-ons must have the same billing period. You **cannot** combine an annual base subscription with monthly add-ons, or vice versa. If your base plan bills monthly, all add-ons must also bill monthly. This constraint exists because Google Play needs to synchronize renewal dates across all items.
1. This feature is only available for auto-renewing subscriptions. Prepaid subscriptions, which have a fixed duration and do not automatically renew, cannot be used as the base item or as add-ons.
1. There’s a maximum limit of 50 items in a single Subscription with Add-ons purchase. While most applications will never approach this limit, it is worth knowing if you are building a highly modular subscription system.
1. This feature isn’t available in all regions. As of the current documentation, India and South Korea don’t support Subscription with Add-ons. You will need to provide alternative purchase flows for users in these regions.
1. Finally, pausing and resuming subscriptions is not supported for subscriptions that have add-ons. If your application relies heavily on the pause feature, you will need to consider whether the benefits of add-ons outweigh this limitation.
## **Strategies for using multi-line subscriptions**

Understanding the mechanics is one thing, but knowing how to apply this feature effectively requires thinking through your monetization strategy. Let me share several approaches that can help you maximize the value of Subscription with Add-ons.

### **The modular feature strategy**

One of the most straightforward applications is offering a modular subscription where users can customize their plan by selecting only the features they need. Instead of creating multiple pre-defined tiers (Basic, Standard, Premium), you create a base subscription and a menu of add-ons that users can mix and match.

For example, a productivity application might offer a base subscription that includes core features like task management and basic collaboration. Add-ons could include advanced reporting, team management features, third-party integrations, or increased storage. Users who only need advanced reporting can add just that feature, while power users can add multiple add-ons. This approach can increase conversion rates because users feel they are paying only for what they actually need.

The key to making this strategy work is ensuring your base subscription provides genuine value on its own. If the base feels stripped down or incomplete, users may perceive the add-ons as nickel-and-diming rather than genuine flexibility.

### **The premium upgrade strategy**

Another approach uses add-ons as a path to gradually upgrade users over time. You start users on a base subscription and then offer add-ons as upsells based on their usage patterns or at strategic moments in their journey.

Consider a photo editing application where the base subscription includes standard editing tools. As users become more engaged, you can offer add-ons like professional presets, advanced retouching tools, or cloud storage for their edited photos. The advantage of using add-ons rather than a traditional tier upgrade is that users keep their existing features and simply add new capabilities. There is no perception of ‘losing’ their current plan.

This strategy works particularly well when combined with personalized recommendations. By analyzing user behavior, you can surface relevant add-ons at moments when users are most likely to see value in them.

### **The bundle and save strategy**

While Subscription with Add-ons allows individual selection of features, you can also use it to create attractive bundles. By setting prices strategically, you can make bundles of add-ons more appealing than purchasing items individually.

For instance, if your base subscription is $9.99/month and you have three add-ons each priced at $4.99/month, you might offer all three add-ons as a bundle for $11.99/month instead of $14.97/month if purchased separately. Users who want multiple features get a better deal, and you increase your average revenue per user. The bundled price still provides more revenue than users who might only purchase one add-on.

### **Managing complexity**

One risk with add-ons is creating too much complexity. If users face a bewildering array of options, they may experience decision paralysis and abandon the purchase entirely. Consider limiting the number of add-ons to a manageable set, typically three to five options. Provide clear descriptions of what each add-on includes and who it is best suited for. You might also consider offering recommended bundles or a ‘select all’ option for users who want the complete experience.

## **Implementing Subscription with Add-ons without RevenueCat**

Let us walk through how to implement Subscription with Add-ons using the Google Play Billing Library directly. This implementation requires Play Billing Library v5 or higher, though you should use the latest version (currently v8) to ensure access to all features and security updates.

### **Setting up your products in Google Play Console**

Before writing any code, you need to configure your subscription products in the Google Play Console. The good news is that existing subscription products can be offered as add-ons without any special configuration. You do not need to create separate ‘add-on’ type products. Any auto-renewing subscription can serve as either a base item or an add-on.

When creating your products, remember that all items you plan to bundle together must have matching billing periods. If you want to offer both monthly and annual options, you will need to create separate base plans for each billing period.

### **Querying product details**

The first step in your implementation is querying the available products from Google Play. You use the `queryProductDetailsAsync` method to fetch details for all the subscription products you want to offer.

```kotlin
class BillingManager(private val context: Context) {
    private lateinit var billingClient: BillingClient

    private val productIds = listOf(
        "premium_base_monthly",
        "hifi_addon_monthly",
        "offline_addon_monthly",
        "family_addon_monthly"
    )

    fun initialize() {
        billingClient = BillingClient.newBuilder(context)
            .setListener { billingResult, purchases ->
                handlePurchasesUpdated(billingResult, purchases)
            }
            .enablePendingPurchases()
            .build()

        billingClient.startConnection(object : BillingClientStateListener {
            override fun onBillingSetupFinished(billingResult: BillingResult) {
                if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
                    querySubscriptionProducts()
                }
            }

            override fun onBillingServiceDisconnected() {
\/\/ Implement retry logic here
            }
        })
    }

    private fun querySubscriptionProducts() {
        val productList = productIds.map { productId ->
            QueryProductDetailsParams.Product.newBuilder()
                .setProductId(productId)
                .setProductType(BillingClient.ProductType.SUBS)
                .build()
        }

        val params = QueryProductDetailsParams.newBuilder()
            .setProductList(productList)
            .build()

        billingClient.queryProductDetailsAsync(params) { billingResult, productDetailsList ->
            if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
\/\/ Store product details for later use when launching purchase flow
                handleProductDetails(productDetailsList)
            }
        }
    }
}
```

The query returns `ProductDetails` objects for each subscription product. These objects contain all the information you need to display pricing to users and to launch the purchase flow, including the available base plans, offers, and their associated offer tokens.

### **Launching the purchase flow with multiple items**

When a user selects their desired subscription configuration (base plus selected add-ons), you launch the billing flow with multiple `ProductDetailsParams` objects. The critical detail here is that the first item in the list becomes the base item, so you must ensure your base subscription is added first.

```kotlin
fun launchSubscriptionWithAddons(
    activity: Activity,
    baseProductDetails: ProductDetails,
    baseOfferToken: String,
    addonProductDetailsList: List<Pair<ProductDetails, String>>
) {
    val productDetailsParamsList = mutableListOf<BillingFlowParams.ProductDetailsParams>()

\/\/ Add the base subscription first - this is crucialval baseParams = BillingFlowParams.ProductDetailsParams.newBuilder()
        .setProductDetails(baseProductDetails)
        .setOfferToken(baseOfferToken)
        .build()
    productDetailsParamsList.add(baseParams)

\/\/ Add each selected add-onfor ((addonDetails, offerToken) in addonProductDetailsList) {
        val addonParams = BillingFlowParams.ProductDetailsParams.newBuilder()
            .setProductDetails(addonDetails)
            .setOfferToken(offerToken)
            .build()
        productDetailsParamsList.add(addonParams)
    }

    val billingFlowParams = BillingFlowParams.newBuilder()
        .setProductDetailsParamsList(productDetailsParamsList)
        .build()

    val billingResult = billingClient.launchBillingFlow(activity, billingFlowParams)

    if (billingResult.responseCode != BillingClient.BillingResponseCode.OK) {
\/\/ Handle error - perhaps show a message to the user
        handleBillingError(billingResult)
    }
}
```

Each item in the list must be unique. You cannot include two `ProductDetailsParams` objects with the same product ID. The offer token specifies which base plan or offer to use for that item, and you must provide a valid offer token obtained from the `ProductDetails.subscriptionOfferDetails()` method.

### **Processing the purchase**

After the user completes the purchase flow, your `PurchasesUpdatedListener` receives the result. Processing a Subscription with Add-ons is similar to processing a single subscription, with one key difference: the purchase grants entitlements for multiple items.

```kotlin
private fun handlePurchasesUpdated(
    billingResult: BillingResult,
    purchases: List<Purchase>?
) {
    when (billingResult.responseCode) {
        BillingClient.BillingResponseCode.OK -> {
            purchases?.forEach { purchase ->
                processPurchase(purchase)
            }
        }
        BillingClient.BillingResponseCode.USER_CANCELED -> {
\/\/ User canceled the purchase flow
        }
        else -> {
\/\/ Handle other error codes
        }
    }
}

private fun processPurchase(purchase: Purchase) {
\/\/ For Subscription with Add-ons, getProducts() returns all product IDsval purchasedProductIds = purchase.products

\/\/ Verify the purchase with your backend server
    verifyPurchaseWithBackend(purchase) { isValid ->
        if (isValid) {
\/\/ Grant entitlements for all purchased productsfor (productId in purchasedProductIds) {
                grantEntitlement(productId)
            }

\/\/ Acknowledge the purchase if not already acknowledgedif (!purchase.isAcknowledged) {
                acknowledgePurchase(purchase)
            }
        }
    }
}

private fun acknowledgePurchase(purchase: Purchase) {
    val params = AcknowledgePurchaseParams.newBuilder()
        .setPurchaseToken(purchase.purchaseToken)
        .build()

    billingClient.acknowledgePurchase(params) { billingResult ->
        if (billingResult.responseCode != BillingClient.BillingResponseCode.OK) {
\/\/ Handle acknowledgment failure
        }
    }
}
```

The `purchase.products` property returns a list of all product IDs included in the purchase. You should grant entitlements for each product and verify the purchase with your backend server before doing so.

### **Modifying an existing Subscription with Add-ons**

Users may want to add more add-ons to their existing subscription or remove some they no longer need. When modifying an existing Subscription with Add-ons, you need to include the current purchase token and specify a replacement mode.

```kotlin
fun modifySubscriptionAddons(
    activity: Activity,
    currentPurchaseToken: String,
    baseProductDetails: ProductDetails,
    baseOfferToken: String,
    newAddonsList: List<Pair<ProductDetails, String>>
) {
    val productDetailsParamsList = mutableListOf<BillingFlowParams.ProductDetailsParams>()

\/\/ Include the base subscription
    productDetailsParamsList.add(
        BillingFlowParams.ProductDetailsParams.newBuilder()
            .setProductDetails(baseProductDetails)
            .setOfferToken(baseOfferToken)
            .build()
    )

\/\/ Include all add-ons (both existing ones to keep and new ones to add)for ((addonDetails, offerToken) in newAddonsList) {
        productDetailsParamsList.add(
            BillingFlowParams.ProductDetailsParams.newBuilder()
                .setProductDetails(addonDetails)
                .setOfferToken(offerToken)
                .build()
        )
    }

\/\/ Configure the subscription updateval subscriptionUpdateParams = BillingFlowParams.SubscriptionUpdateParams.newBuilder()
        .setOldPurchaseToken(currentPurchaseToken)
        .setSubscriptionReplacementMode(
            BillingFlowParams.SubscriptionUpdateParams.ReplacementMode.CHARGE_PRORATED_PRICE
        )
        .build()

    val billingFlowParams = BillingFlowParams.newBuilder()
        .setProductDetailsParamsList(productDetailsParamsList)
        .setSubscriptionUpdateParams(subscriptionUpdateParams)
        .build()

    billingClient.launchBillingFlow(activity, billingFlowParams)
}
```

When modifying a subscription, you must include all items that should remain active after the modification. If you want to keep the base subscription and an existing add-on while adding a new add-on, all three must be in the product list. If you omit an existing add-on from the list, it will be removed.

The replacement mode determines how Google Play handles the billing transition. `CHARGE_PRORATED_PRICE` calculates a prorated charge for new items to align their renewal with the base subscription. Other modes like `CHARGE_FULL_PRICE` or `WITHOUT_PRORATION` are available depending on your use case.

### **Server-side verification and real-time notifications**

For production applications, you must verify purchases on your backend server and handle Real-Time Developer Notifications (RTDN) to keep your entitlement records synchronized with Google Play.

One important detail for Subscription with Add-ons: the `subscriptionId` field is not provided in RTDN messages for multi-item purchases because there are multiple subscriptions involved. Instead, you should use the `purchaseToken` from the notification to query the Google Play Developer API and retrieve the full list of entitled items.

```kotlin
\/\/ This is for your backend serverfun handleRealTimeNotification(notification: DeveloperNotification) {
    val purchaseToken = notification.subscriptionNotification.purchaseToken

\/\/ Query the Google Play Developer API for full purchase detailsval subscriptionPurchase = playDeveloperApi
        .purchases()
        .subscriptionsv2()
        .get(packageName, purchaseToken)
        .execute()

\/\/ The lineItems field contains all items in the subscriptionval lineItems = subscriptionPurchase.lineItems

    for (item in lineItems) {
        val productId = item.productId
        val expiryTime = item.expiryTime
        val autoRenewingPlan = item.autoRenewingPlan

\/\/ Update your entitlement database based on each item's status
        updateEntitlement(purchaseToken, productId, expiryTime, autoRenewingPlan)
    }
}
```

The `lineItems` list in the API response contains details for each subscription item, including its product ID, expiration time, and renewal status. This allows you to track entitlements for each component of the subscription bundle independently.

### **Handling edge cases**

Several edge cases require special attention when implementing Subscription with Add-ons.

Grace periods and account holds apply to the entire subscription bundle. If a renewal payment fails, all items enter the recovery period together, regardless of which specific items were involved in the failed renewal. The grace period duration is determined by the item with the minimum grace period setting among all active items.

Refunds and revocation, interestingly, can be handled at the item level. Using the Google Play Developer API, you can revoke individual items without affecting the entire subscription. This is useful if a user requests a refund for a specific add-on while wanting to keep their base subscription.

Price changes follow similar rules as single-item subscriptions, but with additional complexity when multiple items have pending price increases. All outstanding opt-in price increases must result in the same renewal time with the new price. If an item has a pending opt-in price increase that the user has not confirmed, new price increases for other items in the bundle may be ignored unless they align.

## **Simplifying Implementation with RevenueCat**

While implementing Subscription with Add-ons directly with the Play Billing Library gives you complete control, it also requires managing significant complexity, and time resources (this is a thing) as well. You need to handle billing client lifecycle, query caching, purchase verification, entitlement management, real-time notifications, and all the edge cases we discussed. This is where RevenueCat provides substantial value.

RevenueCat abstracts away the complexities of the Play Billing Library and provides a unified API for managing subscriptions across platforms. For Subscription with Add-ons specifically, RevenueCat handles the intricacies of multi-item purchases, entitlement tracking, and server-side verification automatically.

### **Configuring Products in RevenueCat**

In RevenueCat, you configure your subscription products in the dashboard by creating **Products** and organizing them into [Offerings](https://www.revenuecat.com/docs/offerings/overview). Each Google Play subscription product ID maps to a RevenueCat Product, and you can group related products into Offerings that represent your subscription tiers or bundles.

For Subscription with Add-ons, you would create separate products for your base subscription and each add-on. RevenueCat’s entitlement system then allows you to map these products to specific features in your app. When a user makes a purchase, RevenueCat automatically tracks which entitlements they have based on their active subscriptions.

### **Simplified Purchase Flow**

With RevenueCat, launching a purchase and handling the result is significantly simpler. The SDK manages the billing client connection, purchase verification, and entitlement updates.

```kotlin
class SubscriptionManager(private val context: Context) {

    fun initialize() {
        Purchases.configure(
            PurchasesConfiguration.Builder(context, "your_revenuecat_api_key")
                .build()
        )
    }

    suspend fun getAvailableOfferings(): Offerings {
        return Purchases.sharedInstance.awaitOfferings()
    }

    suspend fun purchaseSubscriptionWithAddons(
        activity: Activity,
        basePackage: Package,
        addonPackages: List<Package>
    ) {
\/\/ RevenueCat handles the complexity of bundling these into a single purchaseval purchaseParams = PurchaseParams.Builder(activity, basePackage)
            .build()

        try {
            val (transaction, customerInfo) = Purchases.sharedInstance
                .awaitPurchase(purchaseParams)

\/\/ CustomerInfo automatically reflects all active entitlements
            updateUIWithEntitlements(customerInfo)
        } catch (e: PurchasesException) {
            handlePurchaseError(e)
        }
    }

    fun checkEntitlements() {
        Purchases.sharedInstance.getCustomerInfoWith { customerInfo ->
\/\/ Check which entitlements are activeval hasPremium = customerInfo.entitlements["premium"]?.isActive == true
            val hasHiFi = customerInfo.entitlements["hifi"]?.isActive == true
            val hasOffline = customerInfo.entitlements["offline"]?.isActive == true

            updateFeatureAccess(hasPremium, hasHiFi, hasOffline)
        }
    }
}
```

That’s all! Sounds easy? RevenueCat’s `CustomerInfo` object provides a real-time view of the user’s active entitlements across all their purchases. You do not need to manually track which products map to which features or handle the complexity of multi-item purchases. The SDK and RevenueCat’s backend handle purchase verification, receipt validation, and entitlement calculation automatically.

### **Cross-platform consistency**

One of RevenueCat’s strongest advantages is providing a consistent API across Android, iOS, and other platforms. If your application is available on multiple platforms, RevenueCat ensures that users have a unified subscription experience. A user who subscribes on Android and later switches to iOS will have their entitlements recognized automatically.

### **Analytics and Insights**

RevenueCat’s dashboard provides detailed analytics about your subscription performance, including metrics specific to subscription bundles. You can track which add-on combinations are most popular, monitor conversion rates for different offerings, and identify opportunities to optimize your pricing strategy.

### **Webhooks and server integration**

For applications that need server-side awareness of subscription status, RevenueCat provides webhooks that notify your server of subscription events. These webhooks are simpler to work with than Google Play’s RTDN because RevenueCat normalizes the data and handles the complexity of multi-item purchases.For detailed implementation guidance, refer to the [RevenueCat documentation for Google Play products](https://www.revenuecat.com/docs/getting-started/entitlements/android-products) and the [subscription management guide](https://www.revenuecat.com/docs/subscription-guidance/managing-subscriptions).

## **Wrapping up**

Subscription with add-ons gives us values in how Android developers can structure their subscription offerings. By enabling multi-item purchases with synchronized billing, Google has opened new possibilities for flexible, user-friendly monetization strategies. Whether you choose to offer modular subscriptions where users customize their plan, use add-ons as an upgrade path for engaged users, or create attractive bundles that increase average revenue per user, this feature provides the tools to implement your vision.

Implementing this feature directly with the Play Billing Library requires careful attention to product configuration, purchase flow management, and edge case handling. The code examples in this article provide a foundation, but production implementations will need additional error handling, retry logic, and thorough testing.

For teams that want to move faster or need cross-platform support, RevenueCat offers a compelling alternative that abstracts away much of this complexity while providing additional benefits like unified analytics and simplified server integration.

As you plan your implementation, start by clearly defining your monetization strategy. Understand which features should be in your base subscription versus add-ons, ensure your billing periods align, and consider how you will communicate the value of each option to users. With thoughtful planning and solid implementation, Subscription with Add-ons can help you create a subscription experience that serves both your users and your business goals.For the complete official documentation on this feature, visit the [Android Developers guide for Subscription with Add-ons](https://developer.android.com/google/play/billing/subscription-with-addons), and [RevenueCat Offerings](https://www.revenuecat.com/docs/offerings/overview).

---

## Related posts

- [Understanding Google Play subscription proration: a developer’s guide](https://www.revenuecat.com/blog/engineering/google-proration)
- [Understanding the native SDK wrapper pattern in Kotlin Multiplatform](https://www.revenuecat.com/blog/engineering/kmp-wrapper-pattern)
- [Simplify in-app purchase unit testing with RevenueCat’s Test Store](https://www.revenuecat.com/blog/engineering/testing-test-store)
