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

v3v4
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?)
PaywallViewControllerFlowViewController
EventHandlers (type)FlowEventHandlers
CreatePaywallViewParamsInputCreateFlowViewParamsInput
onRenderingFailedonError

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

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 fieldv4 AdaptyFlow fieldAction
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').
productsflow.paywalls[i].productIdentifiersProduct identifiers now live on each flow variation, not on the flow.
webPurchaseUrl?flow.paywalls[i].webPurchaseUrlMoved from the flow to each paywall variation.
version?: numberflowVersionId?: stringRenamed, and the type changed from number to string.
hasViewConfigurationremovedRemove any hasViewConfiguration check from your code — createFlowView now throws instead (see Displaying flows).
requestLocaleremovedThe locale is no longer part of the model.
(new)paywalls: AdaptyFlowPaywall[]Each entry is one paywall variation in the flow.
(new)responseCreatedAt: numberServer 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 }) and adapty.requestAppReview() methods — these back the default onUrlPress and onRequestAppReview handlers, 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 / onObserverRestoreInitiated handlers. 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, return true from 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, return purchaseResult.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, return true from 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. Call createFlowView again to present the flow once more.

Removed APIs

Removed exports

These symbols are no longer exported from @adapty/capacitor. Remove their imports:

  • AdaptyPaywall: Use AdaptyFlow and AdaptyFlowPaywall instead.
  • ProductReference: Use AdaptyProductIdentifier, read from flow.paywalls[i].productIdentifiers.
  • AdaptyPaywallBuilder: Removed. Flows and paywalls render natively.
  • AdaptyAndroidSubscriptionUpdateParameters: Use the nested android purchase 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.