
Server-driven native paywall rendering backend JSON into Material 3 UI, with A/B testing, subscription management, variable templates, expression evaluator, DI, billing integrations and WebView fallback.
Kotlin Multiplatform SDK for Superwall — remote paywall configuration, A/B testing, and subscription management from a single codebase.
commonMain
SuperwallPaywall, SuperwallGate, and SuperwallNativePaywall composablesThe backend defines the entire paywall UI as a JSON component tree — the SDK renders it as native Material 3 composables across all platforms. No WebView, no HTML, no CSS.
Backend JSON Native UI
───────────── ─────────
{ "type": "stack", Column {
"dimension": "vertical", → Text("Unlock Premium")
"components": [ PackageSelector(...)
{ "type": "text", ... }, Button("Start Free Trial")
{ "type": "package", ... }, }
{ "type": "purchase_button" }
]
}
| Component | Renders As | Purpose |
|---|---|---|
stack |
Column / Row / Box | Layout container (vertical, horizontal, z-layer) |
text |
Material 3 Text | With {{ product.price }} variable resolution |
image |
Platform image loader | Remote URL or local asset |
button |
Clickable Box | Fires actions (close, open-url, custom) |
purchase_button |
Clickable Box | Triggers purchase flow |
package |
Selectable card | Product option with animated selection style |
spacer |
Spacer | Flexible or fixed spacing |
divider |
HorizontalDivider | Thin separator line |
close_button |
Icon button | Dismisses the paywall |
badge |
Styled label | "BEST VALUE", "SAVE 58%", etc. |
icon |
Icon or symbol | Named icon or URL |
{{ product.price }} → $49.99
{{ product.period }} → year
{{ product.trial_days }} → 14
{{ product.price_per_month }} → USD 4.16
{{ product.name }} → Yearly
// Place at your app root — auto-presents native paywalls
Box {
MyApp()
SuperwallNativePaywall()
}When componentsConfig is present in the backend response, the SDK renders natively. When absent, it falls back to WebView — fully backwards compatible.
| Platform | Target | Billing | Rendering |
|---|---|---|---|
| Android | androidTarget |
Google Play Billing 7.1 | Native Compose + WebView fallback |
| iOS |
iosArm64, iosX64, iosSimulatorArm64
|
StoreKit 1 | Native Compose + WKWebView fallback |
| macOS |
macosArm64, macosX64
|
— | Native Compose |
| Desktop | jvm("desktop") |
— | Native Compose |
// build.gradle.kts
dependencies {
implementation("io.github.androidpoet:superwall:0.2.0")
implementation("io.github.androidpoet:superwall-compose:0.2.0") // native renderer
}// Application.onCreate
Superwall.configure(
apiKey = "pk_...",
platformModule = superwallAndroidModule(
context = applicationContext,
activityProvider = { currentActivity },
),
)// Shared Kotlin
Superwall.configure(
apiKey = "pk_...",
platformModule = superwallIOSModule,
)// Swift via Kotlin interop
SuperwallCompanion.shared.configure(
apiKey: "pk_...",
platformModule: IOSModuleKt.superwallIOSModule
)Superwall.instance.register("premium_feature") {
// Feature unlocked — no paywall shown, or user purchased
}Superwall.instance.identify("user_123")
Superwall.instance.setUserAttributes(mapOf("plan" to "pro", "age" to 25))Superwall.instance.setSubscriptionStatus(
SubscriptionStatus.Active(entitlements = setOf(Entitlement("premium")))
)// Gate content behind a paywall
SuperwallGate(placement = "premium_feature") {
Text("This is premium content!")
}
// Or present manually
SuperwallPaywall(
placement = "upgrade_prompt",
onFeatureUnlocked = { /* navigate */ },
onDismiss = { /* handle dismiss */ },
)val controller = object : PurchaseController {
override suspend fun purchase(product: StoreProduct): PurchaseResult {
// Your RevenueCat / custom logic here
return PurchaseResult.Purchased
}
override suspend fun restorePurchases(): RestorationResult {
return RestorationResult.Restored
}
}
Superwall.configure(
apiKey = "pk_...",
options = SuperwallOptions(purchaseController = controller),
platformModule = superwallAndroidModule(context, activityProvider),
)// Observe all SDK events
Superwall.instance.events.collect { eventInfo ->
println("${eventInfo.event} at ${eventInfo.timestamp}")
}
// Or use the delegate
Superwall.instance.delegate = object : SuperwallDelegate {
override fun handleSuperwallEvent(eventInfo: SuperwallEventInfo) { }
override fun handleCustomPaywallAction(name: String) { }
override fun subscriptionStatusDidChange(status: SubscriptionStatus) { }
}superwall-kmp/
├── superwall/ ← Core SDK (KMP)
│ ├── commonMain/ ← Shared business logic
│ │ ├── Superwall.kt ← Entry point
│ │ ├── models/ ← Domain models
│ │ │ └── components/ ← Server-driven UI component model
│ │ │ ├── PaywallComponent ← 11 sealed component types
│ │ │ ├── Styling ← Colors, padding, borders, shadows
│ │ │ ├── Action ← Purchase, close, restore, navigate
│ │ │ └── VariableResolver ← {{ product.price }} template engine
│ │ ├── config/ ← Remote config + caching
│ │ ├── identity/ ← User identity + attributes
│ │ ├── analytics/ ← Event tracking + batching
│ │ ├── placement/ ← Trigger evaluation + expression engine
│ │ ├── network/ ← Ktor API client
│ │ └── di/ ← Koin core module
│ ├── androidMain/ ← Android implementations
│ │ ├── billing/ ← Google Play Billing
│ │ └── webview/ ← WebView + JS bridge + Activity
│ └── iosMain/ ← iOS implementations
│ ├── storekit/ ← StoreKit 1
│ └── webview/ ← WKWebView + message handlers
├── superwall-compose/ ← Compose Multiplatform UI
│ ├── PaywallComposable.kt ← SuperwallPaywall, SuperwallGate
│ └── renderer/ ← Server-driven native renderer
│ ├── ComponentRenderer ← Recursive component → Compose mapper
│ ├── NativePaywall ← Top-level rendering composable
│ ├── PaywallRenderState ← Variable resolution + product state
│ └── SuperwallNativePaywall ← Auto-presenting drop-in composable
├── desktop-sample/ ← Desktop JVM sample (3 paywall designs)
└── app/ ← Android sample
superwallCoreModule ← Shared: ConfigManager, IdentityManager, AnalyticsTracker, etc.
+ superwallAndroidModule ← Android: SharedPrefs, Play Billing, WebView
OR superwallIOSModule ← iOS: UserDefaults, StoreKit, WKWebView
| Layer | Library |
|---|---|
| DI | Koin 4.0 |
| Networking | Ktor 3.0 |
| Serialization | kotlinx.serialization 1.7 |
| Async | kotlinx.coroutines 1.9 |
| Date/Time | kotlinx-datetime 0.6 |
| Build | Gradle 8.9, Kotlin 2.1.0, AGP 8.5.2 |
| Publishing | Maven Central via vanniktech |
# All targets
./gradlew build
# Android only
./gradlew :superwall:compileReleaseKotlinAndroid
# iOS only
./gradlew :superwall:compileKotlinIosArm64
# Desktop (JVM)
./gradlew :superwall:compileKotlinDesktopCopyright 2026 androidpoet (Ranbir Singh)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Kotlin Multiplatform SDK for Superwall — remote paywall configuration, A/B testing, and subscription management from a single codebase.
commonMain
SuperwallPaywall, SuperwallGate, and SuperwallNativePaywall composablesThe backend defines the entire paywall UI as a JSON component tree — the SDK renders it as native Material 3 composables across all platforms. No WebView, no HTML, no CSS.
Backend JSON Native UI
───────────── ─────────
{ "type": "stack", Column {
"dimension": "vertical", → Text("Unlock Premium")
"components": [ PackageSelector(...)
{ "type": "text", ... }, Button("Start Free Trial")
{ "type": "package", ... }, }
{ "type": "purchase_button" }
]
}
| Component | Renders As | Purpose |
|---|---|---|
stack |
Column / Row / Box | Layout container (vertical, horizontal, z-layer) |
text |
Material 3 Text | With {{ product.price }} variable resolution |
image |
Platform image loader | Remote URL or local asset |
button |
Clickable Box | Fires actions (close, open-url, custom) |
purchase_button |
Clickable Box | Triggers purchase flow |
package |
Selectable card | Product option with animated selection style |
spacer |
Spacer | Flexible or fixed spacing |
divider |
HorizontalDivider | Thin separator line |
close_button |
Icon button | Dismisses the paywall |
badge |
Styled label | "BEST VALUE", "SAVE 58%", etc. |
icon |
Icon or symbol | Named icon or URL |
{{ product.price }} → $49.99
{{ product.period }} → year
{{ product.trial_days }} → 14
{{ product.price_per_month }} → USD 4.16
{{ product.name }} → Yearly
// Place at your app root — auto-presents native paywalls
Box {
MyApp()
SuperwallNativePaywall()
}When componentsConfig is present in the backend response, the SDK renders natively. When absent, it falls back to WebView — fully backwards compatible.
| Platform | Target | Billing | Rendering |
|---|---|---|---|
| Android | androidTarget |
Google Play Billing 7.1 | Native Compose + WebView fallback |
| iOS |
iosArm64, iosX64, iosSimulatorArm64
|
StoreKit 1 | Native Compose + WKWebView fallback |
| macOS |
macosArm64, macosX64
|
— | Native Compose |
| Desktop | jvm("desktop") |
— | Native Compose |
// build.gradle.kts
dependencies {
implementation("io.github.androidpoet:superwall:0.2.0")
implementation("io.github.androidpoet:superwall-compose:0.2.0") // native renderer
}// Application.onCreate
Superwall.configure(
apiKey = "pk_...",
platformModule = superwallAndroidModule(
context = applicationContext,
activityProvider = { currentActivity },
),
)// Shared Kotlin
Superwall.configure(
apiKey = "pk_...",
platformModule = superwallIOSModule,
)// Swift via Kotlin interop
SuperwallCompanion.shared.configure(
apiKey: "pk_...",
platformModule: IOSModuleKt.superwallIOSModule
)Superwall.instance.register("premium_feature") {
// Feature unlocked — no paywall shown, or user purchased
}Superwall.instance.identify("user_123")
Superwall.instance.setUserAttributes(mapOf("plan" to "pro", "age" to 25))Superwall.instance.setSubscriptionStatus(
SubscriptionStatus.Active(entitlements = setOf(Entitlement("premium")))
)// Gate content behind a paywall
SuperwallGate(placement = "premium_feature") {
Text("This is premium content!")
}
// Or present manually
SuperwallPaywall(
placement = "upgrade_prompt",
onFeatureUnlocked = { /* navigate */ },
onDismiss = { /* handle dismiss */ },
)val controller = object : PurchaseController {
override suspend fun purchase(product: StoreProduct): PurchaseResult {
// Your RevenueCat / custom logic here
return PurchaseResult.Purchased
}
override suspend fun restorePurchases(): RestorationResult {
return RestorationResult.Restored
}
}
Superwall.configure(
apiKey = "pk_...",
options = SuperwallOptions(purchaseController = controller),
platformModule = superwallAndroidModule(context, activityProvider),
)// Observe all SDK events
Superwall.instance.events.collect { eventInfo ->
println("${eventInfo.event} at ${eventInfo.timestamp}")
}
// Or use the delegate
Superwall.instance.delegate = object : SuperwallDelegate {
override fun handleSuperwallEvent(eventInfo: SuperwallEventInfo) { }
override fun handleCustomPaywallAction(name: String) { }
override fun subscriptionStatusDidChange(status: SubscriptionStatus) { }
}superwall-kmp/
├── superwall/ ← Core SDK (KMP)
│ ├── commonMain/ ← Shared business logic
│ │ ├── Superwall.kt ← Entry point
│ │ ├── models/ ← Domain models
│ │ │ └── components/ ← Server-driven UI component model
│ │ │ ├── PaywallComponent ← 11 sealed component types
│ │ │ ├── Styling ← Colors, padding, borders, shadows
│ │ │ ├── Action ← Purchase, close, restore, navigate
│ │ │ └── VariableResolver ← {{ product.price }} template engine
│ │ ├── config/ ← Remote config + caching
│ │ ├── identity/ ← User identity + attributes
│ │ ├── analytics/ ← Event tracking + batching
│ │ ├── placement/ ← Trigger evaluation + expression engine
│ │ ├── network/ ← Ktor API client
│ │ └── di/ ← Koin core module
│ ├── androidMain/ ← Android implementations
│ │ ├── billing/ ← Google Play Billing
│ │ └── webview/ ← WebView + JS bridge + Activity
│ └── iosMain/ ← iOS implementations
│ ├── storekit/ ← StoreKit 1
│ └── webview/ ← WKWebView + message handlers
├── superwall-compose/ ← Compose Multiplatform UI
│ ├── PaywallComposable.kt ← SuperwallPaywall, SuperwallGate
│ └── renderer/ ← Server-driven native renderer
│ ├── ComponentRenderer ← Recursive component → Compose mapper
│ ├── NativePaywall ← Top-level rendering composable
│ ├── PaywallRenderState ← Variable resolution + product state
│ └── SuperwallNativePaywall ← Auto-presenting drop-in composable
├── desktop-sample/ ← Desktop JVM sample (3 paywall designs)
└── app/ ← Android sample
superwallCoreModule ← Shared: ConfigManager, IdentityManager, AnalyticsTracker, etc.
+ superwallAndroidModule ← Android: SharedPrefs, Play Billing, WebView
OR superwallIOSModule ← iOS: UserDefaults, StoreKit, WKWebView
| Layer | Library |
|---|---|
| DI | Koin 4.0 |
| Networking | Ktor 3.0 |
| Serialization | kotlinx.serialization 1.7 |
| Async | kotlinx.coroutines 1.9 |
| Date/Time | kotlinx-datetime 0.6 |
| Build | Gradle 8.9, Kotlin 2.1.0, AGP 8.5.2 |
| Publishing | Maven Central via vanniktech |
# All targets
./gradlew build
# Android only
./gradlew :superwall:compileReleaseKotlinAndroid
# iOS only
./gradlew :superwall:compileKotlinIosArm64
# Desktop (JVM)
./gradlew :superwall:compileKotlinDesktopCopyright 2026 androidpoet (Ranbir Singh)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.