diff --git a/code_blocks/tools/paywalls/automatic_dismissal.swift b/code_blocks/tools/paywalls/automatic_dismissal.swift
new file mode 100644
index 00000000..9cd74b18
--- /dev/null
+++ b/code_blocks/tools/paywalls/automatic_dismissal.swift
@@ -0,0 +1,17 @@
+import SwiftUI
+import RevenueCat
+import RevenueCatUI
+
+struct App: View {
+ var body: some View {
+ ContentView()
+ .presentPaywallIfNeeded(requiredEntitlementIdentifier: "premium") { result in
+ switch result {
+ case .restored(let customerInfo):
+ print("Restored: \(customerInfo)")
+ default:
+ break
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/code_blocks/tools/paywalls/callbacks_swiftui.swift b/code_blocks/tools/paywalls/callbacks_swiftui.swift
new file mode 100644
index 00000000..790d6ddd
--- /dev/null
+++ b/code_blocks/tools/paywalls/callbacks_swiftui.swift
@@ -0,0 +1,32 @@
+import SwiftUI
+import RevenueCat
+import RevenueCatUI
+
+struct App: View {
+ var body: some View {
+ ContentView()
+ .presentPaywallIfNeeded(requiredEntitlementIdentifier: "premium") {
+ purchaseCompleted: { customerInfo in
+ unlockPremiumFeatures()
+ },
+ restoreCompleted: { customerInfo in
+ updateUIForRestoredPurchases()
+ },
+ dismiss: {
+ handlePaywallDismissal()
+ }
+ }
+ }
+
+ private func unlockPremiumFeatures() {
+ // Handle premium features unlock
+ }
+
+ private func updateUIForRestoredPurchases() {
+ // Update UI for restored purchases
+ }
+
+ private func handlePaywallDismissal() {
+ // Handle paywall dismissal
+ }
+}
\ No newline at end of file
diff --git a/code_blocks/tools/paywalls/embedded_flutter.dart b/code_blocks/tools/paywalls/embedded_flutter.dart
new file mode 100644
index 00000000..6faa95f6
--- /dev/null
+++ b/code_blocks/tools/paywalls/embedded_flutter.dart
@@ -0,0 +1,9 @@
+import 'package:flutter/material.dart';
+import 'package:purchases_ui_flutter/purchases_ui_flutter.dart';
+
+class MyWidget extends StatelessWidget {
+ @override
+ Widget build(BuildContext context) {
+ return PaywallView(offeringIdentifier: "your_offering");
+ }
+}
\ No newline at end of file
diff --git a/code_blocks/tools/paywalls/embedded_kmp.kt b/code_blocks/tools/paywalls/embedded_kmp.kt
new file mode 100644
index 00000000..c6c2e3c8
--- /dev/null
+++ b/code_blocks/tools/paywalls/embedded_kmp.kt
@@ -0,0 +1,13 @@
+import com.revenuecat.purchases.ui.revenuecatui.Paywall
+import com.revenuecat.purchases.ui.revenuecatui.PaywallOptions
+
+@Composable
+fun MyScreen() {
+ val options = remember {
+ PaywallOptions(dismissRequest = { TODO("Handle dismiss") }) {
+ shouldDisplayDismissButton = true
+ }
+ }
+
+ Paywall(options)
+}
\ No newline at end of file
diff --git a/code_blocks/tools/paywalls/embedded_react_native.tsx b/code_blocks/tools/paywalls/embedded_react_native.tsx
new file mode 100644
index 00000000..0496cdaf
--- /dev/null
+++ b/code_blocks/tools/paywalls/embedded_react_native.tsx
@@ -0,0 +1,8 @@
+import React from 'react';
+import RevenueCatUI from 'react-native-purchases-ui';
+
+function MyComponent() {
+ return (
+
+ );
+}
\ No newline at end of file
diff --git a/code_blocks/tools/paywalls/error_handling.js b/code_blocks/tools/paywalls/error_handling.js
new file mode 100644
index 00000000..2a1773e7
--- /dev/null
+++ b/code_blocks/tools/paywalls/error_handling.js
@@ -0,0 +1,8 @@
+try {
+ const result = await RevenueCatUI.presentPaywall();
+ if (result === 'ERROR') {
+ Alert.alert("Error loading paywall", "Please check your connection and try again.");
+ }
+} catch (error) {
+ console.error(error);
+}
\ No newline at end of file
diff --git a/code_blocks/tools/paywalls/fullscreen_android.kt b/code_blocks/tools/paywalls/fullscreen_android.kt
new file mode 100644
index 00000000..d39641c1
--- /dev/null
+++ b/code_blocks/tools/paywalls/fullscreen_android.kt
@@ -0,0 +1,8 @@
+import android.content.Context
+import com.revenuecat.purchases.ui.revenuecatui.PaywallActivityLauncher
+
+class MainActivity : AppCompatActivity() {
+ fun presentPaywall(context: Context) {
+ PaywallActivityLauncher.launch(context, offeringIdentifier = "your_offering")
+ }
+}
\ No newline at end of file
diff --git a/code_blocks/tools/paywalls/fullscreen_flutter.dart b/code_blocks/tools/paywalls/fullscreen_flutter.dart
new file mode 100644
index 00000000..07d6fce1
--- /dev/null
+++ b/code_blocks/tools/paywalls/fullscreen_flutter.dart
@@ -0,0 +1,19 @@
+import 'package:flutter/material.dart';
+import 'package:purchases_ui_flutter/purchases_ui_flutter.dart';
+
+class FullscreenPaywall extends StatelessWidget {
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ body: SafeArea(
+ child: PaywallView(
+ offering: offering, // Optional Offering object
+ onDismiss: () {
+ // Handle fullscreen paywall dismissal
+ // Navigate away or close the screen
+ },
+ ),
+ ),
+ );
+ }
+}
\ No newline at end of file
diff --git a/code_blocks/tools/paywalls/fullscreen_kmp.kt b/code_blocks/tools/paywalls/fullscreen_kmp.kt
new file mode 100644
index 00000000..83db7c01
--- /dev/null
+++ b/code_blocks/tools/paywalls/fullscreen_kmp.kt
@@ -0,0 +1,16 @@
+import com.revenuecat.purchases.ui.revenuecatui.Paywall
+import com.revenuecat.purchases.ui.revenuecatui.PaywallOptions
+
+@Composable
+fun FullscreenPaywall() {
+ val options = remember {
+ PaywallOptions(dismissRequest = {
+ // Handle fullscreen paywall dismissal
+ // Navigate away or close the screen
+ }) {
+ shouldDisplayDismissButton = false // Fullscreen without dismiss button
+ }
+ }
+
+ Paywall(options)
+}
\ No newline at end of file
diff --git a/code_blocks/tools/paywalls/fullscreen_react_native.tsx b/code_blocks/tools/paywalls/fullscreen_react_native.tsx
new file mode 100644
index 00000000..c7c0aa00
--- /dev/null
+++ b/code_blocks/tools/paywalls/fullscreen_react_native.tsx
@@ -0,0 +1,59 @@
+import React from 'react';
+import { View } from 'react-native';
+import RevenueCatUI, { PAYWALL_RESULT } from 'react-native-purchases-ui';
+
+// Method 1: Using presentPaywallIfNeeded with fullScreenCover presentation mode
+async function presentFullscreenPaywall() {
+ const paywallResult: PAYWALL_RESULT = await RevenueCatUI.presentPaywallIfNeeded({
+ requiredEntitlementIdentifier: "premium",
+ presentationMode: "fullScreenCover"
+ });
+
+ switch (paywallResult) {
+ case PAYWALL_RESULT.PURCHASED:
+ case PAYWALL_RESULT.RESTORED:
+ console.log('Access granted');
+ break;
+ case PAYWALL_RESULT.CANCELLED:
+ console.log('User cancelled');
+ break;
+ case PAYWALL_RESULT.ERROR:
+ console.log('Error occurred');
+ break;
+ }
+}
+
+// Method 2: Using Paywall component in fullscreen container
+function FullscreenPaywallComponent() {
+ return (
+
+ {
+ // Dismiss the paywall, i.e. remove the view, navigate to another screen, etc.
+ // Will be called when the close button is pressed (if enabled) or when a purchase succeeds.
+ }}
+ />
+
+ );
+}
+
+// Method 3: Using Paywall component with specific offering
+function FullscreenPaywallWithOffering() {
+ return (
+
+ {
+ // Optional listener. Called when a restore has been completed.
+ // This may be called even if no entitlements have been granted.
+ }}
+ onDismiss={() => {
+ // Dismiss the paywall, i.e. remove the view, navigate to another screen, etc.
+ // Will be called when the close button is pressed (if enabled) or when a purchase succeeds.
+ }}
+ />
+
+ );
+}
\ No newline at end of file
diff --git a/code_blocks/tools/paywalls/fullscreen_swiftui.swift b/code_blocks/tools/paywalls/fullscreen_swiftui.swift
new file mode 100644
index 00000000..68e6c02a
--- /dev/null
+++ b/code_blocks/tools/paywalls/fullscreen_swiftui.swift
@@ -0,0 +1,11 @@
+import SwiftUI
+import RevenueCat
+import RevenueCatUI
+
+struct App: View {
+ var body: some View {
+ ContentView()
+ .presentPaywallIfNeeded(requiredEntitlementIdentifier: "premium")
+ .presentationMode(.fullScreenCover)
+ }
+}
\ No newline at end of file
diff --git a/code_blocks/tools/paywalls/fullscreen_uikit.swift b/code_blocks/tools/paywalls/fullscreen_uikit.swift
new file mode 100644
index 00000000..74de0cce
--- /dev/null
+++ b/code_blocks/tools/paywalls/fullscreen_uikit.swift
@@ -0,0 +1,10 @@
+import UIKit
+import RevenueCat
+import RevenueCatUI
+
+class ViewController: UIViewController {
+ func presentPaywall() {
+ let paywallViewController = PaywallViewController(offeringIdentifier: "your_offering")
+ present(paywallViewController, animated: true)
+ }
+}
\ No newline at end of file
diff --git a/code_blocks/tools/paywalls/hard_paywall_flutter.dart b/code_blocks/tools/paywalls/hard_paywall_flutter.dart
new file mode 100644
index 00000000..6311651d
--- /dev/null
+++ b/code_blocks/tools/paywalls/hard_paywall_flutter.dart
@@ -0,0 +1,19 @@
+import 'package:flutter/material.dart';
+import 'package:purchases_ui_flutter/purchases_ui_flutter.dart';
+
+class HardPaywall extends StatelessWidget {
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ body: SafeArea(
+ child: PaywallView(
+ offering: offering, // Optional Offering object
+ onDismiss: () {
+ // Hard paywall - no dismissal allowed
+ // Only dismiss on successful purchase or restore
+ },
+ ),
+ ),
+ );
+ }
+}
\ No newline at end of file
diff --git a/code_blocks/tools/paywalls/hard_paywall_kmp.kt b/code_blocks/tools/paywalls/hard_paywall_kmp.kt
new file mode 100644
index 00000000..60ca7772
--- /dev/null
+++ b/code_blocks/tools/paywalls/hard_paywall_kmp.kt
@@ -0,0 +1,16 @@
+import com.revenuecat.purchases.ui.revenuecatui.Paywall
+import com.revenuecat.purchases.ui.revenuecatui.PaywallOptions
+
+@Composable
+fun HardPaywall() {
+ val options = remember {
+ PaywallOptions(dismissRequest = {
+ // Hard paywall - no dismissal allowed
+ // Only dismiss on successful purchase or restore
+ }) {
+ shouldDisplayDismissButton = false // No dismiss button for hard paywall
+ }
+ }
+
+ Paywall(options)
+}
\ No newline at end of file
diff --git a/code_blocks/tools/paywalls/hard_paywall_react_native.tsx b/code_blocks/tools/paywalls/hard_paywall_react_native.tsx
new file mode 100644
index 00000000..84466e17
--- /dev/null
+++ b/code_blocks/tools/paywalls/hard_paywall_react_native.tsx
@@ -0,0 +1,38 @@
+import React from 'react';
+import { View } from 'react-native';
+import RevenueCatUI from 'react-native-purchases-ui';
+
+// Method 1: Hard paywall with current offering
+function HardPaywall() {
+ return (
+
+ {
+ // Hard paywall - no dismissal allowed
+ // Only dismiss on successful purchase or restore
+ }}
+ />
+
+ );
+}
+
+// Method 2: Hard paywall with specific offering
+function HardPaywallWithOffering() {
+ return (
+
+ {
+ // Optional listener. Called when a restore has been completed.
+ // This may be called even if no entitlements have been granted.
+ }}
+ onDismiss={() => {
+ // Hard paywall - no dismissal allowed
+ // Only dismiss on successful purchase or restore
+ }}
+ />
+
+ );
+}
\ No newline at end of file
diff --git a/code_blocks/tools/paywalls/hard_paywall_swiftui.swift b/code_blocks/tools/paywalls/hard_paywall_swiftui.swift
new file mode 100644
index 00000000..3c5c4926
--- /dev/null
+++ b/code_blocks/tools/paywalls/hard_paywall_swiftui.swift
@@ -0,0 +1,11 @@
+import SwiftUI
+import RevenueCat
+import RevenueCatUI
+
+struct App: View {
+ var body: some View {
+ ContentView()
+ .presentPaywallIfNeeded(requiredEntitlementIdentifier: "premium")
+ .presentationMode(.fullScreenCover)
+ }
+}
diff --git a/code_blocks/tools/paywalls/manual_dismissal.swift b/code_blocks/tools/paywalls/manual_dismissal.swift
new file mode 100644
index 00000000..4bf7b626
--- /dev/null
+++ b/code_blocks/tools/paywalls/manual_dismissal.swift
@@ -0,0 +1,20 @@
+import SwiftUI
+import RevenueCat
+import Combine
+
+class PaywallManager: ObservableObject {
+ private var cancellables = Set()
+
+ func setupCustomerInfoListener() {
+ Purchases.shared.customerInfoStream.sink { customerInfo in
+ if customerInfo.entitlements["premium"]?.isActive == true {
+ dismissPaywall()
+ }
+ }
+ .store(in: &cancellables)
+ }
+
+ private func dismissPaywall() {
+ // Handle paywall dismissal
+ }
+}
\ No newline at end of file
diff --git a/code_blocks/tools/paywalls/modal_sheet_android.kt b/code_blocks/tools/paywalls/modal_sheet_android.kt
new file mode 100644
index 00000000..48d1fb16
--- /dev/null
+++ b/code_blocks/tools/paywalls/modal_sheet_android.kt
@@ -0,0 +1,17 @@
+@OptIn(ExperimentalPreviewRevenueCatUIPurchasesAPI::class)
+@Composable
+private fun LockedScreen() {
+ YourContent()
+
+ PaywallDialog(
+ PaywallDialogOptions.Builder()
+ .setRequiredEntitlementIdentifier("premium")
+ .setListener(
+ object : PaywallListener {
+ override fun onPurchaseCompleted(customerInfo: CustomerInfo, storeTransaction: StoreTransaction) {}
+ override fun onRestoreCompleted(customerInfo: CustomerInfo) {}
+ }
+ )
+ .build()
+ )
+}
\ No newline at end of file
diff --git a/code_blocks/tools/paywalls/modal_sheet_flutter.dart b/code_blocks/tools/paywalls/modal_sheet_flutter.dart
new file mode 100644
index 00000000..eaeab740
--- /dev/null
+++ b/code_blocks/tools/paywalls/modal_sheet_flutter.dart
@@ -0,0 +1,21 @@
+import 'dart:async';
+import 'dart:developer';
+import 'package:purchases_ui_flutter/purchases_ui_flutter.dart';
+
+Future presentModalPaywall() async {
+ final paywallResult = await RevenueCatUI.presentPaywallIfNeeded("premium");
+ log('Paywall result: $paywallResult');
+
+ switch (paywallResult) {
+ case PaywallResult.purchased:
+ case PaywallResult.restored:
+ print('Access granted');
+ break;
+ case PaywallResult.cancelled:
+ print('User cancelled');
+ break;
+ case PaywallResult.error:
+ print('Error occurred');
+ break;
+ }
+}
\ No newline at end of file
diff --git a/code_blocks/tools/paywalls/modal_sheet_kmp.kt b/code_blocks/tools/paywalls/modal_sheet_kmp.kt
new file mode 100644
index 00000000..95f7cb9e
--- /dev/null
+++ b/code_blocks/tools/paywalls/modal_sheet_kmp.kt
@@ -0,0 +1,15 @@
+import com.revenuecat.purchases.ui.revenuecatui.Paywall
+import com.revenuecat.purchases.ui.revenuecatui.PaywallOptions
+
+@Composable
+fun ModalPaywall() {
+ val options = remember {
+ PaywallOptions(dismissRequest = {
+ // Handle dismiss - this makes it a modal sheet
+ }) {
+ shouldDisplayDismissButton = true
+ }
+ }
+
+ Paywall(options)
+}
\ No newline at end of file
diff --git a/code_blocks/tools/paywalls/modal_sheet_react_native.tsx b/code_blocks/tools/paywalls/modal_sheet_react_native.tsx
new file mode 100644
index 00000000..b34ca54b
--- /dev/null
+++ b/code_blocks/tools/paywalls/modal_sheet_react_native.tsx
@@ -0,0 +1,21 @@
+import React from 'react';
+import RevenueCatUI, { PAYWALL_RESULT } from 'react-native-purchases-ui';
+
+async function presentModalPaywall() {
+ const paywallResult: PAYWALL_RESULT = await RevenueCatUI.presentPaywallIfNeeded({
+ requiredEntitlementIdentifier: "premium"
+ });
+
+ switch (paywallResult) {
+ case PAYWALL_RESULT.PURCHASED:
+ case PAYWALL_RESULT.RESTORED:
+ console.log('Access granted');
+ break;
+ case PAYWALL_RESULT.CANCELLED:
+ console.log('User cancelled');
+ break;
+ case PAYWALL_RESULT.ERROR:
+ console.log('Error occurred');
+ break;
+ }
+}
\ No newline at end of file
diff --git a/code_blocks/tools/paywalls/modal_sheet_swiftui.swift b/code_blocks/tools/paywalls/modal_sheet_swiftui.swift
new file mode 100644
index 00000000..9a087d09
--- /dev/null
+++ b/code_blocks/tools/paywalls/modal_sheet_swiftui.swift
@@ -0,0 +1,10 @@
+import SwiftUI
+import RevenueCat
+import RevenueCatUI
+
+struct App: View {
+ var body: some View {
+ ContentView()
+ .presentPaywallIfNeeded(requiredEntitlementIdentifier: "premium")
+ }
+}
\ No newline at end of file
diff --git a/code_blocks/tools/paywalls/prefetch_offerings.swift b/code_blocks/tools/paywalls/prefetch_offerings.swift
new file mode 100644
index 00000000..61341644
--- /dev/null
+++ b/code_blocks/tools/paywalls/prefetch_offerings.swift
@@ -0,0 +1,12 @@
+import SwiftUI
+import RevenueCat
+
+class OfferingsManager: ObservableObject {
+ func preloadOfferings() {
+ Purchases.shared.getOfferings { offerings, error in
+ if let offerings = offerings {
+ print("Offerings loaded: \(offerings)")
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/docs/tools/paywalls/displaying-paywalls-current.mdx b/docs/tools/paywalls/displaying-paywalls-current.mdx
new file mode 100644
index 00000000..6293f475
--- /dev/null
+++ b/docs/tools/paywalls/displaying-paywalls-current.mdx
@@ -0,0 +1,275 @@
+---
+title: Displaying Paywalls (current)
+slug: displaying-paywalls-current
+hidden: false
+---
+
+## Platform specific instructions
+
+### iOS
+
+RevenueCat Paywalls will show paywalls in a sheet or fullscreen on iPhone, and there are multiple ways to do this with SwiftUI and UIKit.
+
+- Depending on an entitlement with `presentPaywallIfNeeded`
+- Custom logic with `presentPaywallIfNeeded`
+- Manually with `PaywallView` or `PaywallViewController`
+
+import swift1 from '@site/code_blocks/tools/paywalls_1.swift?raw';
+import swift2 from '@site/code_blocks/tools/paywalls_2.swift?raw';
+import swift3 from '@site/code_blocks/tools/paywalls_3.swift?raw';
+import swift4 from '@site/code_blocks/tools/paywalls_4.swift?raw';
+import objc5 from '@site/code_blocks/tools/paywalls_5.m?raw';
+
+
+
+#### Paywalls on iPad
+When using `presentPaywallIfNeeded` to display a paywall on iPad, we'll automatically show a paywall in a modal that is roughly iPhone sized. If instead you prefer to show a paywall that is full screen on iPad, you can use the `PaywallView` or `PaywallViewController` methods instead.
+
+
+
+### Android
+
+RevenueCat Paywalls will, by default, show paywalls fullscreen and there are multiple ways to do this with `Activity`s and Jetpack Compose.
+
+- Depending on an entitlement with `PaywallDialog`
+- Custom logic with `PaywallDialog`
+- Manually with `Paywall`, `PaywallDialog`, or `PaywallActivityLauncher`
+
+import kotlin1 from '@site/code_blocks/tools/paywalls_1.kt?raw';
+import kotlin2 from '@site/code_blocks/tools/paywalls_2.kt?raw';
+import kotlin3 from '@site/code_blocks/tools/paywalls_3.kt?raw';
+import kotlin4 from '@site/code_blocks/tools/paywalls_4.kt?raw';
+import paywallsActivityJava from '@site/code_blocks/tools/paywalls_activity_java.java?raw';
+
+
+
+### React Native
+
+There are several ways to present paywalls:
+
+- Using `RevenueCatUI.presentPaywall`: this will display a paywall when invoked.
+- Using `RevenueCatUI.presentPaywallIfNeeded`: this will present a paywall only if the customer does not have an unlocked entitlement.
+- Manually presenting ``: this gives you more flexibility on how the paywall is presented.
+
+import rn1 from '@site/code_blocks/tools/paywalls_rn_1.ts.txt?raw';
+import rn2 from '@site/code_blocks/tools/paywalls_rn_2.ts.txt?raw';
+
+
+
+There are also several listeners that can be used to handle the paywall lifecycle, such as `onPurchaseStarted`, `onPurchaseCompleted`, and `onRestoreStarted`.
+
+#### Listeners
+
+When using `RevenueCatUI.Paywall`, you may use one of the provided listeners to react to user actions.
+
+Available listeners at this time are:
+
+- onPurchaseStarted
+- onPurchaseCompleted
+- onPurchaseError
+- onPurchaseCancelled
+- onRestoreStarted
+- onRestoreCompleted
+- onRestoreError
+- onDismiss
+
+### Flutter
+
+There are several ways to present paywalls:
+
+- Using `RevenueCatUI.presentPaywall`: this will display a paywall when invoked.
+- Using `RevenueCatUI.presentPaywallIfNeeded`: this will present a paywall only if the customer does not have an unlocked entitlement.
+- Manually presenting `PaywallView`: this gives you more flexibility on how the paywall is presented.
+
+import flutter1 from '@site/code_blocks/tools/paywalls_flutter_1.dart?raw';
+import flutter2 from '@site/code_blocks/tools/paywalls_flutter_2.dart?raw';
+
+
+
+#### Listeners
+
+When using `PaywallView`, you may use one of the provided listeners to react to user actions.
+Available listeners at this time are:
+
+- onPurchaseStarted
+- onPurchaseCompleted
+- onPurchaseError
+- onRestoreCompleted
+- onRestoreError
+- onDismiss
+
+### Kotlin Multiplatform
+
+You can present a fullscreen Paywall using the `Paywall` composable. You have the flexibility to decide when to call this. You could, for instance, add it to your navigation graph.
+
+import kmp1 from "@site/code_blocks/tools/paywalls_kmp_1.kts?raw";
+
+
+
+#### Listeners
+
+When using `Paywall`, you may use one of the provided listeners to react to user actions.
+Available listeners at this time are:
+
+- onPurchaseStarted
+- onPurchaseCompleted
+- onPurchaseError
+- onPurchaseCancelled
+- onRestoreStarted
+- onRestoreCompleted
+- onRestoreError
+
+### Capacitor
+
+There are several ways to present paywalls:
+
+- Using `RevenueCatUI.presentPaywall`: this will display a paywall when invoked.
+- Using `RevenueCatUI.presentPaywallIfNeeded`: this will present a paywall only if the customer does not have an unlocked entitlement.
+
+import cap1 from '@site/code_blocks/tools/paywalls_cap_1.ts.txt?raw';
+
+
+
+## Handling paywall navigation
+
+When creating a paywall, consider whether it will be presented in a sheet, or as a full screen view. Sheets won't require a dedicated close button. Full screen views should have either a close button (if presented modally) or a back button (if part of a navigation stack or host) unless you intend to provide a hard paywall to your customers that cannot be bypassed.
+
+## Custom fonts
+
+Using custom fonts in your paywall can now be done by uploading font files directly to RevenueCat. See the [Custom fonts](/tools/paywalls/creating-paywalls/components#custom-fonts) section for more information.
+
+### Including custom fonts in your app
+
+To improve the performance and reduce loading times of your paywall using custom fonts, you can add the font to your app's resources using the instructions below.
+
+#### Android
+To add a custom font to your Android app, place the font file in the `res/font` folder. Make sure that the filename (without the extension) corresponds to the font name in the paywall editor. See [the official Android documentation](https://developer.android.com/develop/ui/views/text-and-emoji/fonts-in-xml) for more information.
+
+#### iOS
+To add a custom font to your iOS app, go to _File_ and then _Add Files to “Your Project Name”_. The font file should be a target member of your app, and be registered with iOS by adding the "Fonts provided by the application" key to your _Info.plist_ file. Make sure that the filename (without the extension) corresponds to the font name in the paywall editor. See [the official iOS documentation](https://developer.apple.com/documentation/uikit/adding-a-custom-font-to-your-app) for more information.
+
+#### Kotlin Multiplatform, React Native, and Flutter
+Adding custom fonts to a hybrid app involves adding the font files to the underlying Android and iOS projects following the instructions above.
+
+## Changes from legacy Paywalls
+
+#### Footer Paywalls
+
+Our current Paywalls no longer support footer Paywalls. If your app requests the Paywall for an Offering to display that has a current Paywall, it will display a default version of that paywall instead (see below). Footer mode can still be used on legacy Paywalls templates using the existing method, or the new `.originalTemplatePaywallFooter()` method on SDK versions that support our current Paywalls.
+
+#### Close buttons
+
+Our current Paywalls do not require the `displayCloseButton` parameter (or equivalent for other platforms), and it will have no effect if used, since close buttons can be optionally added directly to your paywall as a component if desired.
+
+#### Font provider
+
+Our current Paywalls do not support passing in a custom font provider as legacy Paywalls did. Instead, you can now configure Paywalls to use the fonts you've already installed in your app directly from the Dashboard. Using the original handler will have no effect on current Paywalls. For more information, [click here](/tools/paywalls/creating-paywalls/components#custom-fonts)
+
+## Default Paywall
+
+If you attempt to display a Paywall for an Offering that doesn't have one configured, or that has a Paywall configured which is not supported on the installed SDK version, the RevenueCatUI SDK will display a default Paywall.
+
+The default paywall displays all packages in the Offering.
+
+On iOS it uses the app's `accentColor` for styling.
+On Android, it uses the app's `Material3`'s `ColorScheme`.
+
+:::tip Targeting
+If your app supports our legacy Paywall templates, consider using Targeting to create an audience that only receives your new Paywall if they're using an SDK version that does not support our current Paywalls. This will ensure that older app versions continue to receive the Offering and Paywall that they support, while any app versions running a supported RC SDK version receive your new Paywall. [Learn more about Targeting.](/tools/targeting)
+:::
\ No newline at end of file
diff --git a/docs/tools/paywalls/displaying-paywalls.mdx b/docs/tools/paywalls/displaying-paywalls.mdx
index c63cdf5f..6bbe233f 100644
--- a/docs/tools/paywalls/displaying-paywalls.mdx
+++ b/docs/tools/paywalls/displaying-paywalls.mdx
@@ -1,250 +1,290 @@
---
-title: Displaying Paywalls
+title: Displaying Paywalls (new)
slug: displaying-paywalls
hidden: false
---
-## Platform specific instructions
-
-### iOS
-
-RevenueCat Paywalls will show paywalls in a sheet or fullscreen on iPhone, and there are multiple ways to do this with SwiftUI and UIKit.
-
-- Depending on an entitlement with `presentPaywallIfNeeded`
-- Custom logic with `presentPaywallIfNeeded`
-- Manually with `PaywallView` or `PaywallViewController`
-
-import swift1 from '@site/code_blocks/tools/paywalls_1.swift?raw';
-import swift2 from '@site/code_blocks/tools/paywalls_2.swift?raw';
-import swift3 from '@site/code_blocks/tools/paywalls_3.swift?raw';
-import swift4 from '@site/code_blocks/tools/paywalls_4.swift?raw';
-import objc5 from '@site/code_blocks/tools/paywalls_5.m?raw';
+import modalSheetSwiftUI from '@site/code_blocks/tools/paywalls/modal_sheet_swiftui.swift?raw';
+import modalSheetAndroid from '@site/code_blocks/tools/paywalls/modal_sheet_android.kt?raw';
+import fullscreenSwiftUI from '@site/code_blocks/tools/paywalls/fullscreen_swiftui.swift?raw';
+import fullscreenUIKit from '@site/code_blocks/tools/paywalls/fullscreen_uikit.swift?raw';
+import fullscreenAndroid from '@site/code_blocks/tools/paywalls/fullscreen_android.kt?raw';
+import hardPaywallSwiftUI from '@site/code_blocks/tools/paywalls/hard_paywall_swiftui.swift?raw';
+import embeddedReactNative from '@site/code_blocks/tools/paywalls/embedded_react_native.tsx?raw';
+import embeddedFlutter from '@site/code_blocks/tools/paywalls/embedded_flutter.dart?raw';
+import embeddedKMP from '@site/code_blocks/tools/paywalls/embedded_kmp.kt?raw';
+import modalSheetReactNative from '@site/code_blocks/tools/paywalls/modal_sheet_react_native.tsx?raw';
+import modalSheetFlutter from '@site/code_blocks/tools/paywalls/modal_sheet_flutter.dart?raw';
+import modalSheetKMP from '@site/code_blocks/tools/paywalls/modal_sheet_kmp.kt?raw';
+import fullscreenReactNative from '@site/code_blocks/tools/paywalls/fullscreen_react_native.tsx?raw';
+import fullscreenFlutter from '@site/code_blocks/tools/paywalls/fullscreen_flutter.dart?raw';
+import fullscreenKMP from '@site/code_blocks/tools/paywalls/fullscreen_kmp.kt?raw';
+import hardPaywallReactNative from '@site/code_blocks/tools/paywalls/hard_paywall_react_native.tsx?raw';
+import hardPaywallFlutter from '@site/code_blocks/tools/paywalls/hard_paywall_flutter.dart?raw';
+import hardPaywallKMP from '@site/code_blocks/tools/paywalls/hard_paywall_kmp.kt?raw';
+import automaticDismissal from '@site/code_blocks/tools/paywalls/automatic_dismissal.swift?raw';
+import manualDismissal from '@site/code_blocks/tools/paywalls/manual_dismissal.swift?raw';
+import prefetchOfferings from '@site/code_blocks/tools/paywalls/prefetch_offerings.swift?raw';
+import errorHandling from '@site/code_blocks/tools/paywalls/error_handling.js?raw';
+import callbacksSwiftUI from '@site/code_blocks/tools/paywalls/callbacks_swiftui.swift?raw';
+
+Use RevenueCat Paywalls to easily manage, display, and handle your paywall experiences across platforms.
+
+## Key concepts
+
+| Concept | Description |
+| ------------------- | ----------------------------------------------------------------------------------------------------------------- |
+| Paywall display modes | Various ways paywalls can be displayed, including: modal sheets, full-screen views, etc. |
+| Callbacks | Events provided by the SDK to handle user interactions and outcomes (purchase completed, canceled, errors, etc.) |
+| Restore behavior | Automatic handling of purchase restoration within the paywall interface |
+| Error handling | Managing scenarios where paywalls fail to load, purchases fail, or restores encounter issues |
+
+## Display methods
+
+RevenueCat Paywalls can be displayed in several ways to fit your application's UI/UX:
+
+### Modal sheet
+
+Modal sheets present the paywall in an overlay that can typically be swiped away by the user unless configured otherwise.
#### Paywalls on iPad
-When using `presentPaywallIfNeeded` to display a paywall on iPad, we'll automatically show a paywall in a modal that is roughly iPhone sized. If instead you prefer to show a paywall that is full screen on iPad, you can use the `PaywallView` or `PaywallViewController` methods instead.
-
-
-### Android
+When using `presentPaywallIfNeeded` to display a paywall on iPad, we'll automatically show a paywall in a modal that is roughly iPhone sized. If instead you prefer to show a paywall that is full screen on iPad, you can use the methods described below.
-RevenueCat Paywalls will, by default, show paywalls fullscreen and there are multiple ways to do this with `Activity`s and Jetpack Compose.
+
-- Depending on an entitlement with `PaywallDialog`
-- Custom logic with `PaywallDialog`
-- Manually with `Paywall`, `PaywallDialog`, or `PaywallActivityLauncher`
+### Full-screen view
-import kotlin1 from '@site/code_blocks/tools/paywalls_1.kt?raw';
-import kotlin2 from '@site/code_blocks/tools/paywalls_2.kt?raw';
-import kotlin3 from '@site/code_blocks/tools/paywalls_3.kt?raw';
-import kotlin4 from '@site/code_blocks/tools/paywalls_4.kt?raw';
-import paywallsActivityJava from '@site/code_blocks/tools/paywalls_activity_java.java?raw';
+Full-screen views offer an immersive experience by covering the entire screen.
-### React Native
+### Hard paywall
-There are several ways to present paywalls:
+Hard paywalls prevent dismissal unless the user makes a purchase or restores access. To present a hard paywall, you must:
-- Using `RevenueCatUI.presentPaywall`: this will display a paywall when invoked.
-- Using `RevenueCatUI.presentPaywallIfNeeded`: this will present a paywall only if the customer does not have an unlocked entitlement.
-- Manually presenting ``: this gives you more flexibility on how the paywall is presented.
+1. Configure your paywall without dismiss or close buttons in the RevenueCat Paywall Editor
+2. Present the paywall in a full-screen view (not as a dismissable sheet)
-import rn1 from '@site/code_blocks/tools/paywalls_rn_1.ts.txt?raw';
-import rn2 from '@site/code_blocks/tools/paywalls_rn_2.ts.txt?raw';
+For example, in SwiftUI:
-
-
-There are also several listeners that can be used to handle the paywall lifecycle, such as `onPurchaseStarted`, `onPurchaseCompleted`, and `onRestoreStarted`.
+ {
+ type: 'dart',
+ title: "Flutter",
+ content: hardPaywallFlutter,
+ },
+ {
+ type: 'kotlin',
+ title: "Kotlin Multiplatform",
+ content: hardPaywallKMP,
+ },
+]}/>
-#### Listeners
+### Embedded component
-When using `RevenueCatUI.Paywall`, you may use one of the provided listeners to react to user actions.
+Embed paywalls within an existing screen or navigation stack for more integrated experiences.
-Available listeners at this time are:
+
-- onPurchaseStarted
-- onPurchaseCompleted
-- onPurchaseError
-- onPurchaseCancelled
-- onRestoreStarted
-- onRestoreCompleted
-- onRestoreError
-- onDismiss
+## Handling restore behavior
-### Flutter
+RevenueCat Paywalls handle restoring purchases seamlessly. Be sure to include a **Restore Purchases** button in your paywall design via the Paywall Editor to give your customers the ability to restore their purchases.
-There are several ways to present paywalls:
+Restores automatically trigger callbacks (`onRestoreStarted`, `onRestoreCompleted`, `onRestoreError`). [Learn more about callbacks](/tools/paywalls/displaying-paywalls#listening-to-callbacks).
-- Using `RevenueCatUI.presentPaywall`: this will display a paywall when invoked.
-- Using `RevenueCatUI.presentPaywallIfNeeded`: this will present a paywall only if the customer does not have an unlocked entitlement.
-- Manually presenting `PaywallView`: this gives you more flexibility on how the paywall is presented.
+### Automatic dismissal
-import flutter1 from '@site/code_blocks/tools/paywalls_flutter_1.dart?raw';
-import flutter2 from '@site/code_blocks/tools/paywalls_flutter_2.dart?raw';
+The `presentPaywallIfNeeded` method supports passing an entitlement to achieve automatic dismissal behavior upon successful restoration. If the restored entitlement satisfies the paywall requirement, the paywall will automatically dismiss.
-
+]}/>
-#### Listeners
+### Manual dismissal
-When using `PaywallView`, you may use one of the provided listeners to react to user actions.
-Available listeners at this time are:
+For other SDK methods of displaying paywalls, automatic dismissal upon entitlement restoration is not supported. Therefore, you need to listen for changes to `CustomerInfo` to determine if an entitlement has been granted and manually dismiss the paywall in your app's logic.
-- onPurchaseStarted
-- onPurchaseCompleted
-- onPurchaseError
-- onRestoreCompleted
-- onRestoreError
-- onDismiss
+Example manual dismissal handling:
-### Kotlin Multiplatform
+
-You can present a fullscreen Paywall using the `Paywall` composable. You have the flexibility to decide when to call this. You could, for instance, add it to your navigation graph.
+## Paywall loading
-import kmp1 from "@site/code_blocks/tools/paywalls_kmp_1.kts?raw";
+Ensuring your paywalls load quickly provides a better user experience and reduces friction during the purchasing process. Follow these best practices:
-
+]}/>
-#### Listeners
+- **Reduce Asset Sizes:** Optimize and minimize asset sizes (images, fonts) used in your paywall design to ensure faster loading times.
-When using `Paywall`, you may use one of the provided listeners to react to user actions.
-Available listeners at this time are:
+- **Cache Data:** Utilize local caching mechanisms provided by RevenueCat SDKs to minimize network requests and improve loading speed.
-- onPurchaseStarted
-- onPurchaseCompleted
-- onPurchaseError
-- onPurchaseCancelled
-- onRestoreStarted
-- onRestoreCompleted
-- onRestoreError
+- **Use SDK Latest Version:** Always keep your RevenueCat SDK up to date to benefit from performance improvements and optimizations.
-### Capacitor
+## Handling errors
-There are several ways to present paywalls:
+Manage paywall errors gracefully to ensure smooth user experience:
-- Using `RevenueCatUI.presentPaywall`: this will display a paywall when invoked.
-- Using `RevenueCatUI.presentPaywallIfNeeded`: this will present a paywall only if the customer does not have an unlocked entitlement.
+- **Paywall load failure:** Triggered if offerings fail to load. Handle by informing users or retrying.
+- **Purchase errors:** Occur during transaction issues or cancellations. Notify users and allow retry.
+- **Restore errors:** Handle by displaying an appropriate error message.
-import cap1 from '@site/code_blocks/tools/paywalls_cap_1.ts.txt?raw';
+Example error handling in React Native:
-
+]}/>
+
+## Listening to callbacks
-## Handling paywall navigation
+RevenueCat provides extensive callbacks for user interactions:
-When creating a paywall, consider whether it will be presented in a sheet, or as a full screen view. Sheets won't require a dedicated close button. Full screen views should have either a close button (if presented modally) or a back button (if part of a navigation stack or host) unless you intend to provide a hard paywall to your customers that cannot be bypassed.
+| Callback | Description |
+| ----------------------- | --------------------------------------------------------------- |
+| **onPurchaseStarted** | Triggered when a purchase attempt begins. |
+| **onPurchaseCompleted** | Called after successful purchase completion. |
+| **onPurchaseCancelled** | Called if user cancels purchase. |
+| **onPurchaseError** | Triggered on transaction error. |
+| **onRestoreStarted** | Fires when restore begins. |
+| **onRestoreCompleted** | Called after successful restoration (even if nothing restored). |
+| **onRestoreError** | Triggered if restoration encounters an error. |
+| **onDismiss** | Fires when paywall is dismissed. |
+
+Example SwiftUI callback implementation:
+
+
## Custom fonts
-
+
Using custom fonts in your paywall can now be done by uploading font files directly to RevenueCat. See the [Custom fonts](/tools/paywalls/creating-paywalls/components#custom-fonts) section for more information.
### Including custom fonts in your app
-To improve the performance and reduce loading times of your paywall using custom fonts, you can add the font to your app's resources using the instructions below.
+To improve the performance and reduce loading times of your paywall using custom fonts, you can also add the font to your app's resources using the instructions below.
#### Android
+
To add a custom font to your Android app, place the font file in the `res/font` folder. Make sure that the filename (without the extension) corresponds to the font name in the paywall editor. See [the official Android documentation](https://developer.android.com/develop/ui/views/text-and-emoji/fonts-in-xml) for more information.
#### iOS
-To add a custom font to your iOS app, go to _File_ and then _Add Files to “Your Project Name”_. The font file should be a target member of your app, and be registered with iOS by adding the "Fonts provided by the application" key to your _Info.plist_ file. Make sure that the filename (without the extension) corresponds to the font name in the paywall editor. See [the official iOS documentation](https://developer.apple.com/documentation/uikit/adding-a-custom-font-to-your-app) for more information.
+
+To add a custom font to your iOS app, go to _File_ and then _Add Files to "Your Project Name"_. The font file should be a target member of your app, and be registered with iOS by adding the "Fonts provided by the application" key to your _Info.plist_ file. Make sure that the filename (without the extension) corresponds to the font name in the paywall editor. See [the official iOS documentation](https://developer.apple.com/documentation/uikit/adding-a-custom-font-to-your-app) for more information.
#### Kotlin Multiplatform, React Native, and Flutter
+
Adding custom fonts to a hybrid app involves adding the font files to the underlying Android and iOS projects following the instructions above.
## Changes from legacy Paywalls
@@ -272,4 +312,6 @@ On Android, it uses the app's `Material3`'s `ColorScheme`.
:::tip Targeting
If your app supports our legacy Paywall templates, consider using Targeting to create an audience that only receives your new Paywall if they're using an SDK version that does not support our current Paywalls. This will ensure that older app versions continue to receive the Offering and Paywall that they support, while any app versions running a supported RC SDK version receive your new Paywall. [Learn more about Targeting.](/tools/targeting)
-:::
\ No newline at end of file
+:::
+
+[Learn more about creating Paywalls.](/tools/paywalls/creating-paywalls)
\ No newline at end of file