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
RevenueCatUIon 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:
- Open your offering in the RevenueCat dashboard.
- Add metadata keys such as
cta_button_colorandheadline_text. - 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.
1.3 Completely Different Layouts for Deeplink Users
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:
- Creating a separate offering in the RevenueCat dashboard (e.g.,
deeplink_offer) with its own metadata keylayout_variant: fullscreen_promo. - Detecting the deeplink in your app and fetching the corresponding offering by identifier.
- Rendering a different paywall component based on the
layout_variantmetadata value.
// 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:
- Go to your project in the RevenueCat dashboard.
- Navigate to Entitlements and click + New Entitlement.
- Name it something descriptive, such as
pro_accessorfull_access. - 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
getCustomerInfoon 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:
- Open your project settings in Xcode and select Package Dependencies.
- Find the existing
RevenueCatpackage entry. - Click the + button under the package's product list and add
RevenueCatUIas an additional product.
If RevenueCat is not yet in your project:
- In Xcode, go to File → Add Package Dependencies.
- Enter the RevenueCat package URL:
https://github.com/RevenueCat/purchases-ios - Choose your version rule (minimum
5.16.0). - Select both
RevenueCatandRevenueCatUIproducts and add them to your target.