In Part 1 of this series, you set up your first RevenueCat offering and displayed a basic paywall. In this tutorial, you'll go further—learning how to dynamically customize paywall experiences using offering metadata, implement hard paywall and freemium monetization strategies, install the RevenueCatUI package correctly across platforms, integrate Apple Search Ads attribution, and ultimately combine all of these capabilities into a unified, data-driven paywall system.

By the end of this guide, you will be able to:

  • Drive real-time paywall UI changes without shipping a new app release
  • Gate content with a hard paywall or progressively unlock features with a freemium model
  • Install RevenueCatUI on every supported platform
  • Attribute subscription revenue accurately back to Apple Search Ads campaigns
  • Compose metadata, entitlements, and attribution signals into a single coherent paywall strategy

Prerequisites

  • A RevenueCat project with at least one offering and one entitlement configured
  • The base RevenueCat SDK already installed (see Part 1)
  • Xcode 15+ (iOS) or Android Studio Hedgehog+ (Android)
  • Familiarity with your platform's package manager (SPM, Gradle, npm, pub, etc.)

1. Leveraging Offering Metadata for Dynamic Paywall Experiences

One of the most powerful—and underused—RevenueCat features is Offering Metadata: a free-form key-value store attached to any offering that your app can read at runtime. Because metadata lives on the RevenueCat dashboard, you can change paywall copy, colors, layouts, and even entire UI variants without touching your codebase or waiting for App Store review.

According to the RevenueCat documentation, "Offering Metadata can be utilized to create more flexible paywall experiences, target specific users with particular offerings, or make updates on the fly."

1.1 Dynamic Copy and Button Color Changes

The simplest metadata use case is A/B testing visual elements. Suppose you want to test whether a green "Subscribe Now" button outperforms the default blue one. Instead of shipping two app builds, you:

  1. Open your offering in the RevenueCat dashboard.
  2. Add metadata keys such as cta_button_color and headline_text.
  3. Read those keys at runtime and apply them to your paywall view.

The RevenueCat documentation illustrates this pattern with a concrete before/after example: the control paywall uses default values, while the variant reads different copy and button colors from metadata—all without a new app release. Source

Here is a Swift example that reads metadata from the current offering:

import RevenueCat

Purchases.shared.getOfferings { offerings, error in
    guard let offering = offerings?.current else { return }

    // Read metadata keys with safe fallbacks
    let headline = offering.getMetadataValue(for: "headline_text", default: "Unlock Premium")
    let ctaColor = offering.getMetadataValue(for: "cta_button_color", default: "#0055FF")

    // Apply to your SwiftUI or UIKit paywall view
    paywallViewModel.headline = headline
    paywallViewModel.ctaColor = Color(hex: ctaColor)
}

Tip: Always provide sensible default values. If a metadata key is missing (e.g., the offering hasn't been updated yet), your paywall should still render correctly.

1.2 Device Language Detection for Localized Descriptions

Metadata also supports localization workflows that go beyond the standard App Store localization pipeline. By storing locale-specific copy directly in metadata keys (e.g., description_en, description_fr, description_de), you can serve translated paywall descriptions to users based on their device language—again, without an app update. Source

let deviceLanguage = Locale.current.languageCode ?? "en"
let descriptionKey = "description_\(deviceLanguage)"

let localizedDescription = offering.getMetadataValue(
    for: descriptionKey,
    default: offering.getMetadataValue(for: "description_en", default: "")
)

The fallback chain ensures that unsupported locales gracefully degrade to English copy.

Users who arrive at your app via a deeplink (e.g., from a marketing email or a social campaign) often have higher purchase intent than organic users. You can serve them a dedicated, high-conversion paywall layout by:

  1. Creating a separate offering in the RevenueCat dashboard (e.g., deeplink_offer) with its own metadata key layout_variant: fullscreen_promo.
  2. Detecting the deeplink in your app and fetching the corresponding offering by identifier.
  3. Rendering a different paywall component based on the layout_variant metadata value.

Source

// Android Kotlin example
Purchases.sharedInstance.getOfferingsWith(
    onError = { error -> /* handle */ },
    onSuccess = { offerings ->
        val offering = if (isDeeplinkLaunch) {
            offerings["deeplink_offer"] ?: offerings.current
        } else {
            offerings.current
        }

        val layoutVariant = offering?.metadata?.get("layout_variant") as? String ?: "default"
        showPaywall(offering, layoutVariant)
    }
)

1.4 On-the-Fly Targeting Without an App Release

Because offerings and their metadata are fetched fresh from RevenueCat's servers each session, you can push changes instantly. Combine this with RevenueCat's Targeting feature to show a specific offering—with its own metadata—to a precise audience segment. The result is a fully server-driven paywall that can be updated, tested, and rolled back in minutes. Source


2. Hard Paywall Strategy: Configuration and Display

A hard paywall gates all meaningful content behind a subscription prompt. Users cannot access core functionality until they subscribe. This is the most direct monetization model, and RevenueCat provides a dedicated guide for implementing it correctly. Source

2.1 When to Use a Hard Paywall

According to the RevenueCat documentation, a hard paywall works best when: Source

  • Your app provides high-value content or functionality
  • Your app operates with high unit economics (e.g., AI-driven features with significant per-user costs)
  • You have a clear value proposition
  • Your target audience has a high willingness to pay

2.2 Entitlement Configuration

The RevenueCat documentation recommends a single-tier access level for hard paywalls: one entitlement that grants access to all features of the application. Source

To create this entitlement:

  1. Go to your project in the RevenueCat dashboard.
  2. Navigate to Entitlements and click + New Entitlement.
  3. Name it something descriptive, such as pro_access or full_access.
  4. Attach all of your subscription products to this entitlement.

2.3 Gating Content in Code

With the entitlement in place, gate your app's root navigation on the entitlement check:

// iOS Swift – gate at app entry point
Purchases.shared.getCustomerInfo { customerInfo, error in
    if customerInfo?.entitlements["pro_access"]?.isActive == true {
        // Show the main app
        navigator.showHome()
    } else {
        // Show the hard paywall immediately
        navigator.showPaywall()
    }
}
// Android Kotlin – gate at MainActivity
Purchases.sharedInstance.getCustomerInfoWith(
    onError = { error -> showPaywall() },
    onSuccess = { customerInfo ->
        if (customerInfo.entitlements["pro_access"]?.isActive == true) {
            showHome()
        } else {
            showPaywall()
        }
    }
)

Important: Always call getCustomerInfo on app launch rather than relying on a cached value, so that subscription status is always current.

2.4 Template Variations

The implementation details for presenting the paywall UI will vary depending on which RevenueCat paywall template you are using. Refer to the RevenueCat paywall documentation for template-specific configuration guidance.


3. Freemium Strategy: Managing Entitlements and Upgrade Prompts

The freemium model gives users meaningful value for free, then converts them to paid subscribers when they encounter a premium feature or usage limit. RevenueCat's entitlement system is well-suited to this model because it lets you check access levels in a single API call without maintaining lists of product identifiers in your code. Source

3.1 When to Use Freemium

According to RevenueCat, a freemium strategy works well when: Source

  • There is a clear and compelling upgrade path from free to paid (e.g., feature gating, usage limits)
  • The app has low marginal costs per user
  • Your strategy is to leverage freemium users as growth drivers through sharing, referrals, or collaboration
  • The app faces competitive pressure where users expect to try the core experience before subscribing

3.2 Defining Free and Premium Features

Before writing a single line of code, define your access tiers clearly: Source

Tier Examples
Free Core functionality, basic features that showcase value, features that encourage engagement
Premium Advanced functionality, enhanced user experience, exclusive content or capabilities

Document this split in a product spec before configuring entitlements—changing the boundary later is much harder once users have expectations.

3.3 Entitlement Configuration for Freemium

Unlike the hard paywall's single entitlement, a freemium app typically needs at least two entitlements: one implicitly granted to all users (free tier) and one unlocked by purchase (premium tier). Source

RevenueCat entitlements are scoped to a project and are typically unlocked after a user purchases a product. Source For free-tier features, you simply don't check an entitlement—or you check for the absence of the premium entitlement.

3.4 Displaying Contextual Upgrade Prompts

The key to freemium conversion is showing the upgrade prompt at moments of high user intent—when a user tries to use a premium feature. This is called a contextual upgrade prompt.

// iOS Swift – check entitlement before unlocking a premium feature
func userTappedExportButton() {
    Purchases.shared.getCustomerInfo { customerInfo, error in
        if customerInfo?.entitlements["premium"]?.isActive == true {
            // User is subscribed – proceed
            exportDocument()
        } else {
            // User is on free tier – show upgrade prompt
            presentUpgradePaywall(context: "export_feature")
        }
    }
}
// Flutter Dart – contextual paywall for a premium feature
Future<void> onAdvancedFilterTapped() async {
  try {
    CustomerInfo customerInfo = await Purchases.getCustomerInfo();
    if (customerInfo.entitlements.active.containsKey('premium')) {
      openAdvancedFilters();
    } else {
      // Show paywall with context-specific headline via metadata
      showPaywall(context: 'advanced_filters');
    }
  } on PlatformException catch (e) {
    // handle error
  }
}

3.5 Progressive Premium Feature Unlocking

For apps with multiple premium tiers, you can check specific entitlements to unlock features progressively:

// React Native
const { customerInfo } = await Purchases.getCustomerInfo();
const entitlements = customerInfo.entitlements.active;

if (entitlements['enterprise']) {
  showEnterpriseFeatures();
} else if (entitlements['pro']) {
  showProFeatures();
} else {
  showFreeFeatures();
}

This approach scales cleanly as you add new subscription tiers without changing your product identifier logic.


4. Installing and Configuring the RevenueCatUI Package

RevenueCat's pre-built paywall templates are delivered via the RevenueCatUI package, which is bundled as part of the core RevenueCat SDK. You need to install it separately from the base Purchases SDK. Source

4.1 Supported SDK Versions

Before installing, verify that your current SDK version meets the minimum recommended version for RevenueCatUI support: Source

SDK Minimum Recommended Version
purchases-ios 5.16.0 and up
purchases-android 8.12.2 and up
react-native-purchases 8.6.1 and up
purchases-flutter 8.5.0 and up
purchases-kmp 1.5.1+13.18.1 and up
purchases-capacitor 10.3.1 and up
purchases-unity-ui 8.4.0 and up
purchases-js 0.19.0 and up

4.2 Supported Platforms

RevenueCatUI currently supports the following platforms: Source

  • ✅ iOS 15.0 and higher
  • ✅ Android 7.0 (API level 24) and higher
  • ✅ Mac Catalyst 15.0 and higher
  • ✅ macOS 12.0 and higher
  • ✅ Web (modern browsers)
  • ✅ visionOS
  • ❌ watchOS (not yet supported)
  • ❌ tvOS (not yet supported)

4.3 Native iOS Installation (Swift Package Manager)

If RevenueCat is already in your project:

  1. Open your project settings in Xcode and select Package Dependencies.
  2. Find the existing RevenueCat package entry.
  3. Click the + button under the package's product list and add RevenueCatUI as an additional product.

If RevenueCat is not yet in your project:

  1. In Xcode, go to File → Add Package Dependencies.
  2. Enter the RevenueCat package URL: https://github.com/RevenueCat/purchases-ios
  3. Choose your version rule (minimum 5.16.0).
  4. Select both RevenueCat and RevenueCatUI products and add them to your target.