Migrate Adapty Capacitor SDK to v. 4.0
Adapty Capacitor SDK 4.0 (beta) introduces flows and renames the paywall APIs accordingly. The new APIs work with both the new Flow Builder and the existing Paywall Builder — no setup changes are required on the Adapty Dashboard side.
Quick reference
| v3 | v4 |
|---|---|
adapty.getPaywall({ placementId, locale?, params? }) | adapty.getFlow({ placementId, params? }) |
adapty.getPaywallForDefaultAudience({ placementId, locale?, params? }) | adapty.getFlowForDefaultAudience({ placementId, params? }) |
adapty.getPaywallProducts({ paywall }) | adapty.getPaywallProducts({ flow }) |
adapty.logShowPaywall({ paywall }) | adapty.logShowFlow({ flow }) |
AdaptyPaywall (type) | AdaptyFlow + AdaptyFlowPaywall |
createPaywallView(paywall, params?) | createFlowView(flow, params?) |
PaywallViewController | FlowViewController |
EventHandlers (type) | FlowEventHandlers |
CreatePaywallViewParamsInput | CreateFlowViewParamsInput |
onRenderingFailed | onError |
AdaptyPaywallProduct keeps its name — products still belong to a flow, and getPaywallProducts keeps its name too, now taking an AdaptyFlow. The getFlow and getFlowForDefaultAudience methods no longer take a locale parameter. The purchase and profile APIs (makePurchase, restorePurchases, getProfile, identify, updateProfile) and fallbacks via setFallback are unchanged. The view methods present, dismiss, setEventHandlers, and showDialog, and the event handlers onCloseButtonPress, onUrlPress, onCustomAction, onProductSelected, onPurchaseStarted, onPurchaseCompleted, onPurchaseFailed, onRestoreStarted, onRestoreCompleted, onRestoreFailed, onLoadingProductsFailed, onWebPaymentNavigationFinished, and onAndroidSystemBack keep the same names as in v3. The onboarding methods still work but are deprecated — see Onboarding API deprecation. Some default behaviors changed — see Default behavior changes.
Minimum versions
The runtime requirements are unchanged from v3.16+: iOS 15.0, Android minSdk 24, and Capacitor 8. No deployment-target changes are needed.
There is one new build requirement: Xcode 26 or later — the native Adapty iOS SDK 4.0.0-beta.2 bundled with this release uses Swift tools 6.2.
v4 bundles the native Adapty SDKs iOS 4.0.0-beta.2 and Android BOM 4.0.0-beta.1.
Installation
Update the package
v4.0 is a pre-release, so pin the exact version — npm does not select pre-release versions through caret/tilde ranges:
npm install @adapty/capacitor@4.0.0-beta.1
Then sync the native projects:
npx cap sync
iOS: Swift Package Manager only
CocoaPods’ spec repo goes read-only in December 2026, so starting with v4 the AdaptyCapacitor.podspec is removed and the SDK installs on iOS only through Swift Package Manager (SPM). Your app’s iOS project must use Capacitor’s SPM integration:
- New apps: add the iOS platform with the SPM package manager:
npx cap add ios --packagemanager SPM
- Existing CocoaPods-based apps: migrate the iOS project following Capacitor’s guide to using SPM in an existing project.
See Install Adapty SDK for the full setup.
Fetching flows
getPaywall → getFlow
The returned type changes from AdaptyPaywall to AdaptyFlow, and the locale option is removed — when you render a flow, the locale is resolved automatically; for custom paywalls, all locales are returned in flow.remoteConfigs:
- const paywall = await adapty.getPaywall({ placementId: 'YOUR_PLACEMENT_ID', locale: 'en' });
+ const flow = await adapty.getFlow({ placementId: 'YOUR_PLACEMENT_ID' });
getPaywallForDefaultAudience is renamed the same way:
- const paywall = await adapty.getPaywallForDefaultAudience({ placementId: 'YOUR_PLACEMENT_ID', locale: 'en' });
+ const flow = await adapty.getFlowForDefaultAudience({ placementId: 'YOUR_PLACEMENT_ID' });
getPaywallProducts(paywall) → getPaywallProducts(flow)
getPaywallProducts keeps its name but now takes an AdaptyFlow:
- const products = await adapty.getPaywallProducts({ paywall });
+ const products = await adapty.getPaywallProducts({ flow });
Data model
getFlow returns an AdaptyFlow instead of an AdaptyPaywall, and the object shape changed:
v3 AdaptyPaywall field | v4 AdaptyFlow field | Action |
|---|---|---|
remoteConfig? (single) | remoteConfigs?: AdaptyRemoteConfig[] (array) | A flow carries one remote config per configured language. Read the one that matches the user: flow.remoteConfigs?.find((c) => c.lang === 'en'). |
products | flow.paywalls[i].productIdentifiers | Product identifiers now live on each flow variation, not on the flow. |
webPurchaseUrl? | flow.paywalls[i].webPurchaseUrl | Moved from the flow to each paywall variation. |
version?: number | flowVersionId?: string | Renamed, and the type changed from number to string. |
hasViewConfiguration | removed | Remove any hasViewConfiguration check from your code — createFlowView now throws instead (see Displaying flows). |
requestLocale | removed | The locale is no longer part of the model. |
| (new) | paywalls: AdaptyFlowPaywall[] | Each entry is one paywall variation in the flow. |
| (new) | responseCreatedAt: number | Server response timestamp, in milliseconds. |
hasViewConfiguration and requestLocale remain on AdaptyOnboarding — only the flow model drops them.
Product identifiers moved from the flow to each variation:
- const ids = paywall.products;
+ const ids = flow.paywalls[0].productIdentifiers;
Web paywall methods
openWebPaywall and createWebPaywallUrl keep their names, but the paywallOrProduct option now takes an AdaptyFlowPaywall (a flow variation) instead of an AdaptyPaywall. You can still pass an AdaptyPaywallProduct. Check that flow.paywalls is non-empty before reading the first entry:
const flow = await adapty.getFlow({ placementId: 'YOUR_PLACEMENT_ID' });
- await adapty.openWebPaywall({ paywallOrProduct: paywall });
+ await adapty.openWebPaywall({ paywallOrProduct: flow.paywalls[0] });
Tracking flow views
logShowPaywall → logShowFlow
logShowPaywall is renamed to logShowFlow and now takes an AdaptyFlow. The event is still logged against the same variation, so existing funnel and A/B test metrics continue to work without dashboard changes.
- await adapty.logShowPaywall({ paywall });
+ await adapty.logShowFlow({ flow });
As in v3, you do not need to call this method when displaying flows or paywalls rendered by the Flow Builder or the Paywall Builder — Adapty tracks those views automatically.
Displaying flows
createPaywallView → createFlowView
Rename the factory function and pass the AdaptyFlow. The returned controller is renamed from PaywallViewController to FlowViewController, but its methods (present, dismiss, setEventHandlers, showDialog) are unchanged. The params type is renamed from CreatePaywallViewParamsInput to CreateFlowViewParamsInput:
- import { createPaywallView } from '@adapty/capacitor';
+ import { createFlowView } from '@adapty/capacitor';
- const view = await createPaywallView(paywall);
+ const view = await createFlowView(flow);
await view.present();
createFlowView throws an AdaptyError if the flow has no view configured — this replaces the v3 hasViewConfiguration check:
- if (paywall.hasViewConfiguration) {
- const view = await createPaywallView(paywall);
- await view.present();
- }
+ try {
+ const view = await createFlowView(flow);
+ await view.present();
+ } catch (error) {
+ // the flow has no view configured, or view creation failed
+ }
A flow view is single-use: after you call dismiss(), the view is destroyed and its event handlers are cleared, so call createFlowView again to present the flow once more.
Android safe-area paddings
CreateFlowViewParamsInput adds one new parameter: enableSafeArea, which controls Android safe-area paddings at runtime. It is nested under the android key and defaults to true:
const view = await createFlowView(flow, {
android: { enableSafeArea: true },
});
Handling events
The event-handler interface is renamed from EventHandlers to FlowEventHandlers, and one callback is renamed. Existing handler bodies don’t need code changes — just rename:
- onRenderingFailed: (error) => { /* … */ },
+ onError: (error) => { /* … */ },
All other event handlers keep their names. Two also gain a second argument: onPurchaseCompleted is now (purchaseResult, product) and onPurchaseFailed is now (error, product), where product is the AdaptyPaywallProduct involved. See Handle flow & paywall events for the full list.
v4 also adds a few capabilities you can opt into:
adapty.openWebUrl({ url, openIn })andadapty.requestAppReview()methods — these back the defaultonUrlPressandonRequestAppReviewhandlers, so URLs and app-review prompts are handled natively out of the box. Call them directly only if you override those handlers.- Observer-mode purchase handling inside flows via the new
onObserverPurchaseInitiated/onObserverRestoreInitiatedhandlers. See Present flows in Observer mode.
Default behavior changes
These changes do not cause compile errors, so test them at runtime:
onAndroidSystemBack: The default changed from closing the view to keeping it open. To restore the previous behavior, returntruefrom the handler.onPurchaseCompleted: The default changed from closing the view (unless the user canceled the purchase) to always keeping it open. To restore the previous behavior, returnpurchaseResult.type !== 'user_cancelled'from the handler.onRestoreCompleted: The default changed from closing the view after a successful restore to keeping it open. To restore the previous behavior, returntruefrom the handler.onUrlPress: The default now opens the URL through the native layer, honoring the in-app or external browser setting from the dashboard. Override the handler to open URLs yourself.- Views are single-use: After
dismiss(), the view is destroyed. CallcreateFlowViewagain to present the flow once more.
Removed APIs
Removed exports
These symbols are no longer exported from @adapty/capacitor. Remove their imports:
AdaptyPaywall: UseAdaptyFlowandAdaptyFlowPaywallinstead.ProductReference: UseAdaptyProductIdentifier, read fromflow.paywalls[i].productIdentifiers.AdaptyPaywallBuilder: Removed. Flows and paywalls render natively.AdaptyAndroidSubscriptionUpdateParameters: Use the nestedandroidpurchase params shape (see below).
activate: lockMethodsUntilReady
lockMethodsUntilReady (already a deprecated no-op in v3) is removed. Remove it from your activate call — keeping it no longer compiles:
- await adapty.activate({ apiKey: 'PUBLIC_SDK_KEY', params: { lockMethodsUntilReady: true } });
+ await adapty.activate({ apiKey: 'PUBLIC_SDK_KEY' });
makePurchase: Android parameters
The deprecated flat Android shape of MakePurchaseParamsInput is removed — only the nested form remains. Move any Android purchase parameters into params: { android: { ... } }. See Make purchases for the full example.
Onboarding API deprecation
The legacy onboarding API is deprecated in v4.0 in favor of the Flow Builder. It still works, but will be removed in a future release, so plan migration of your onboardings to the Flow Builder.
Deprecated symbols: getOnboarding, getOnboardingForDefaultAudience, createOnboardingView, and OnboardingViewController.