Skip to main content

Displaying Products

If you've configured Offerings in RevenueCat, you can control which products are shown to users without requiring an app update. Building paywalls that are dynamic and can react to different product configurations gives you maximum flexibility to make remote updates.


Before products and offerings can be fetched from RevenueCat, be sure to initialize the Purchases SDK by following our Quickstart guide.

Fetching Offerings

Offerings are fetched through the SDK based on their configuration in the RevenueCat dashboard.

The getOfferings method will fetch the Offerings from RevenueCat. These are pre-fetched in most cases on app launch, so the completion block to get offerings won't need to make a network request in most cases.

        Purchases.shared.getOfferings { (offerings, error) in
if let packages = offerings?.current?.availablePackages {
📘Offerings, products or available packages empty

If your offerings, products, or available packages are empty, it's due to some configuration issue in App Store Connect or the Play Console.

The most common reasons for this in App Store Connect are an out-of-date 'Paid Applications Agreement' or products not at least in the 'Ready To Submit' state. For Google Play, this usually occurs when the app is not published on a closed track and a valid test user added.

You can find more info about trouble shooting this issue in our Help Center.

You must choose one Offering that is the "Default Offering" - which can easily be accessed via the current property of the returned offerings for a given customer.

📘What's the difference between a current Offering and a default Offering?

The current Offering for a given customer may change based on the experiment they're enrolled in, any targeting rules they match, or the default Offering of your Project. Your Project's default Offering is the Offering that will be served as "current" when no other conditions apply for that customer.

To change the default Offering of your Project, navigate to the Offerings tab for that Project in the RevenueCat dashboard, and find the Offering you'd like to make default. Then, click on the icon in the Actions column of that Offering to reveal the available options, and click Make Default to make the change.


If you'd like to customize the Offering that's served based on an audience, or their location in your app, check out Targeting.

Offerings can be updated at any time, and the changes will go into effect for all users right away.

Fetching Offerings by Placement

Alternatively, if your app has multiple paywall locations and you want to control each location uniquely, you can do that with Placements and the getCurrentOffering(forPlacement: "string") method.

Purchases.shared.getOfferings { offerings, error in
if let offering = offerings?.currentOffering(forPlacement: "your-placement-identifier") {
// TODO: Show paywall
} else {
// TODO: Do nothing or continue on to next view

To learn more about creating Placements and serving unique Offerings through them, click here.

Custom Offering identifiers

It's also possible to access other Offerings besides the Current Offering directly by its identifier.

        Purchases.shared.getOfferings { (offerings, error) in
if let packages = offerings?.offering(identifier: "experiment_group")?.availablePackages {

Displaying Packages

Packages help abstract platform-specific products by grouping equivalent products across iOS, Android, and web. A package is made up of three parts: identifier, type, and underlying store product.

IdentifierThe package identifier (e.g.
TypeThe type of the package:
ProductThe underlying product that is mapped to this package which includes details about the price and duration.

Packages can be access in a few different ways:

  1. via the .availablePackages property on an Offering.
  2. via the duration convenience property on an Offering
  3. via the package identifier directly
        let packages = offerings.offering(identifier: "experiment_group")?.availablePackages
// --
let monthlyPackage = offerings.offering(identifier: "experiment_group")?.monthly
// --
let packageById = offerings.offering(identifier: "experiment_group")?.package(identifier: "<package_id>")

Getting the Product from the Package

Each Package includes an underlying product that includes more information about the price, duration, and other metadata. You can access the product via the storeProduct property (or rcBillingProduct property for RevenueCat Billing):

        Purchases.shared.getOfferings { (offerings, error) in
// Accessing the monthly product
if let product = offerings?.current?.monthly?.storeProduct {
// Display the product information (like price and introductory period)

Choosing which Offering to display

In practice, you may not want to display the default current Offering to every user and instead have a specific cohort that see a different Offering.

For example, displaying a higher priced Offering to users that came from paid acquisition to help recover ad costs, or a specific Offering designed to show iOS Subscription Offers when a user has cancelled their subscription.

This can be accomplished with custom Offering identifiers for each of these "cohorts".

        Purchases.shared.getOfferings { (offerings, error) in
var packages: [Package]?

if user.isPaidDownload {
packages = offerings?.offering(identifier: "paid_download_offer")?.availablePackages
} else if user.signedUpOver30DaysAgo {
packages = offerings?.offering(identifier: "long_term_offer")?.availablePackages
} else if user.recentlyChurned {
packages = offerings?.offering(identifier: "ios_subscription_offer")?.availablePackages

// Present your paywall

As of now, cohort logic needs to be managed outside of RevenueCat.

Best Practices

✅ Make paywalls dynamic by minimizing or eliminating any hardcoded strings❌ Make static paywalls hardcoded with specific product IDs
✅ Use default package types❌ Use custom package identifiers in place of a default option
✅ Allow for any number of product choices❌ Support only a fixed number of products
✅ Support for different free trial durations, or no free trial❌ Hardcode free trial text

Next Steps

  • Now that you've shown the correct products to users, time to make a purchase
  • Check out our sample apps for examples of how to display products.