
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
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 |
dependencies {
implementation("io.github.androidpoet:superwall:0.2.0")
implementation("io.github.androidpoet:superwall-compose:0.2.0")
}Superwall.configure(
apiKey = "pk_...",
platformDependencies = superwallAndroidDependencies(
context = applicationContext,
activityProvider = { currentActivity },
),
)Superwall.configure(
apiKey = "pk_...",
platformDependencies = superwallIOSDependencies,
)SuperwallCompanion.shared.configure(
apiKey: "pk_...",
platformDependencies: IOSModuleKt.superwallIOSDependencies
)Superwall.instance.register("premium_feature") {
println("Unlocked")
}Superwall.instance.identify("user_123")
Superwall.instance.setUserAttributes(mapOf("plan" to "pro", "age" to 25))Superwall.instance.setSubscriptionStatus(
SubscriptionStatus.Active(entitlements = setOf(Entitlement("premium")))
)SuperwallGate(placement = "premium_feature") {
Text("This is premium content!")
}
SuperwallPaywall(
placement = "upgrade_prompt",
onFeatureUnlocked = { /* navigate */ },
onDismiss = { /* handle dismiss */ },
)val controller = object : PurchaseController {
override suspend fun purchase(product: StoreProduct): PurchaseResult {
return PurchaseResult.Purchased
}
override suspend fun restorePurchases(): RestorationResult {
return RestorationResult.Restored
}
}
Superwall.configure(
apiKey = "pk_...",
options = SuperwallOptions(purchaseController = controller),
platformDependencies = superwallAndroidDependencies(context, activityProvider),
)Superwall.instance.events.collect { eventInfo ->
println("${eventInfo.event} at ${eventInfo.timestamp}")
}
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/ ← dependency factory
│ ├── 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
| Layer | Library |
|---|---|
| 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
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 |
dependencies {
implementation("io.github.androidpoet:superwall:0.2.0")
implementation("io.github.androidpoet:superwall-compose:0.2.0")
}Superwall.configure(
apiKey = "pk_...",
platformDependencies = superwallAndroidDependencies(
context = applicationContext,
activityProvider = { currentActivity },
),
)Superwall.configure(
apiKey = "pk_...",
platformDependencies = superwallIOSDependencies,
)SuperwallCompanion.shared.configure(
apiKey: "pk_...",
platformDependencies: IOSModuleKt.superwallIOSDependencies
)Superwall.instance.register("premium_feature") {
println("Unlocked")
}Superwall.instance.identify("user_123")
Superwall.instance.setUserAttributes(mapOf("plan" to "pro", "age" to 25))Superwall.instance.setSubscriptionStatus(
SubscriptionStatus.Active(entitlements = setOf(Entitlement("premium")))
)SuperwallGate(placement = "premium_feature") {
Text("This is premium content!")
}
SuperwallPaywall(
placement = "upgrade_prompt",
onFeatureUnlocked = { /* navigate */ },
onDismiss = { /* handle dismiss */ },
)val controller = object : PurchaseController {
override suspend fun purchase(product: StoreProduct): PurchaseResult {
return PurchaseResult.Purchased
}
override suspend fun restorePurchases(): RestorationResult {
return RestorationResult.Restored
}
}
Superwall.configure(
apiKey = "pk_...",
options = SuperwallOptions(purchaseController = controller),
platformDependencies = superwallAndroidDependencies(context, activityProvider),
)Superwall.instance.events.collect { eventInfo ->
println("${eventInfo.event} at ${eventInfo.timestamp}")
}
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/ ← dependency factory
│ ├── 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
| Layer | Library |
|---|---|
| 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.