capacitor-levelplay-ads
v0.1.15
Published
Unity LevelPlay mediation SDK for Capacitor with banner, interstitial and rewarded ads supported.
Downloads
1,175
Maintainers
Readme
capacitor-levelplay-ads
Unity LevelPlay mediation for Capacitor.
This plugin integrates the Unity LevelPlay mediation SDK (formerly ironSource) into Capacitor.
Banner, interstitial and rewarded ads are supported.
Ads can be served across every mediated demand source from a single, modular API with zero mandatory native configuration.
📦 SDK Versions
| Component | Platform | Version |
| :--- | :--- | :--- |
| LevelPlay Mediation SDK | Android | com.unity3d.ads-mediation:mediation-sdk:9.4.0 |
| IronSource SDK | iOS | IronSourceSDK (CocoaPods) |
✨ Key Features
- Mediation-first: One LevelPlay app key fans out to every network you enable — AdMob, AppLovin, Unity Ads, Vungle, Meta, Mintegral, Pangle.
- IAB TCF v2.2 consent (default): Bundles the InMobi Choice CMP for
GDPR-compliant consent — collects a TCF string and writes the standard
IABTCF_*keys every mediation adapter reads. Swappable for a built-in non-TCF modal when shipping outside the EU. - CCPA & COPPA: First-class
setCCPAConsent()andsetChildDirected(). - App Tracking Transparency:
requestTrackingAuthorization()prompts ATT on iOS and is a safe no-op on Android. - Impression-level revenue (ILRD): Subscribe to
onAdRevenuefor per-impression revenue across all networks. - Android 15 edge-to-edge ready: Automated lifecycle workaround so fullscreen ad close buttons are never hidden behind the system bars.
- Anti-spam rate limiting: Built-in per-ad load throttling protects your account from invalid-traffic penalties.
- Opt-in network injection: Mediation adapter pods/gradle deps are injected
at sync time from a single
levelplay.networkskey — no manual XML edits.
📦 Installation
npm install capacitor-levelplay-ads
npx cap sync⚙️ Configuration (Mediation Networks)
Mediation network adapters are opt-in. The plugin bundles only the core LevelPlay SDK; each demand source you want is wired in by a Capacitor CLI hook.
- Add a
levelplayblock to your app's rootpackage.json:
{
"name": "your-app-name",
"levelplay": {
"networks": ["admob", "applovin", "unityads"],
"userTrackingDescription": "This identifier is used to deliver personalized ads to you.",
"consentProvider": "inmobi",
"inmobi": {
"pCode": "YOUR_PCODE_HERE"
}
}
}Supported network keys: admob, applovin, unityads, vungle, meta,
mintegral, pangle.
Consent provider
The consentProvider key picks how the plugin collects GDPR consent:
| Value | What it does | When to use |
|---|---|---|
| inmobi (default) | Bundles InMobi Choice CMP. IAB TCF v2.2 compliant. Auto-shows the CMP on first launch and writes the standard IABTCF_* keys to SharedPreferences (Android) / NSUserDefaults (iOS). | Apps shipped in the EU/EEA. Required for GDPR audit compliance. |
| custom | Built-in alert dialog. Writes a permissive gdprApplies=0 stub. Not TCF compliant. | Apps that ship outside the EU only, or where you already integrate a different CMP. |
When consentProvider: "inmobi", set levelplay.inmobi.pCode to the pCode from
your InMobi Choice workspace (strip the leading
p-). Optionally set levelplay.inmobi.packageId to override the property
package id; defaults to the app's applicationId/bundle id.
Once configured, the plugin auto-initializes the CMP at process start
(Android: via a ContentProvider that runs in Application.onCreate; iOS: from
AppDelegate.didFinishLaunching via plugin load). You still call
LevelPlayAds.requestConsentInfo() from JS — under inmobi that call resolves
as soon as the user has interacted with the CMP UI.
- Register the hook in the
scriptssection of yourpackage.json:
{
"scripts": {
"capacitor:sync:after": "node node_modules/capacitor-levelplay-ads/scripts/levelplay-manifest.js"
}
}On every npx cap sync the hook injects, for each selected network:
- Android adapter
implementationlines intoandroid/app/build.gradle - iOS adapter pods into
ios/App/Podfile NSUserTrackingUsageDescription+ per-networkSKAdNetworkIDs intoInfo.plist
Transparency note: the injection script is strictly OPT-IN and only runs if you explicitly declare the hook above.
Manual alternative
If you'd rather not run the hook, do the equivalent edits yourself:
Android — add the adapters you need to android/app/build.gradle:
dependencies {
implementation 'com.unity3d.ads-mediation:admob-adapter:4.3.46'
implementation 'com.unity3d.ads-mediation:applovin-adapter:4.3.39'
implementation 'com.unity3d.ads-mediation:unityads-adapter:4.3.44'
// …one per network from the supported list above
}iOS — add the matching pods to ios/App/Podfile inside the App target:
pod 'IronSourceAdMobAdapter'
pod 'IronSourceAppLovinAdapter'
pod 'IronSourceUnityAdsAdapter'iOS — ios/App/App/Info.plist (required for ATT prompt + attribution):
<key>NSUserTrackingUsageDescription</key>
<string>This identifier will be used to deliver personalized ads to you.</string>
<key>SKAdNetworkItems</key>
<array>
<dict><key>SKAdNetworkIdentifier</key><string>su67r6k2v3.skadnetwork</string></dict> <!-- IronSource -->
<dict><key>SKAdNetworkIdentifier</key><string>cstr6suwn9.skadnetwork</string></dict> <!-- AdMob -->
<dict><key>SKAdNetworkIdentifier</key><string>ludvb6z3bs.skadnetwork</string></dict> <!-- AppLovin -->
<dict><key>SKAdNetworkIdentifier</key><string>4dzt52r2t5.skadnetwork</string></dict> <!-- UnityAds -->
<!-- Vungle: gta9lk7p23.skadnetwork -->
<!-- Meta: v9wttpbfk9.skadnetwork, n38lu8286q.skadnetwork -->
<!-- Mintegral: kbd757ywx3.skadnetwork -->
<!-- Pangle: 238da6jt44.skadnetwork, 22mmun2rn5.skadnetwork -->
</array>Without NSUserTrackingUsageDescription Apple will reject the build (the
IronSource SDK calls ATTrackingManager). Without the matching SKAdNetwork
IDs install attribution silently fails and mediation revenue reports go dark.
Adapter versions are pinned in scripts/levelplay-manifest.js — check that
file for the current set if you're maintaining the edits by hand.
Install
To use npm:
npm install capacitor-levelplay-adsTo use yarn:
yarn add capacitor-levelplay-adsSync native files:
npx cap sync🚀 Quick Setup
A minimal flow: initialize the SDK, ask for consent, then load and show an interstitial.
import { LevelPlayAds } from 'capacitor-levelplay-ads';
async function bootstrapAds() {
// 1. Initialize the LevelPlay SDK with your app key.
await LevelPlayAds.initialize({
appKey: 'YOUR_LEVELPLAY_APP_KEY',
isTesting: true, // remove or set to false in production
});
// 2. Request a GDPR / consent decision. The plugin shows a custom modal
// on first run and reuses the stored decision afterwards.
const consent = await LevelPlayAds.requestConsentInfo({
privacyPolicyUrl: 'https://example.com/privacy',
networks: ['admob', 'meta'], // optional — must match your levelplay.networks
});
if (!consent.canRequestAds) {
console.log('User declined personalised ads.');
}
// 3. (iOS only) Ask for App Tracking Transparency. Resolves with
// "NOT_APPLICABLE" on Android, so the same call works cross-platform.
await LevelPlayAds.requestTrackingAuthorization();
// 4. Pre-load an interstitial. Use AdEvent constants instead of raw
// event-name strings so typos surface at compile time.
LevelPlayAds.addListener(AdEvent.InterstitialLoaded, () => {
console.log('Interstitial ready.');
});
LevelPlayAds.addListener(AdEvent.InterstitialClosed, () => {
console.log('Interstitial closed — next ad is auto-reloading.');
});
// `autoShow: true` chains show() automatically once the ad loads —
// the same promise resolves on display or rejects on display failure.
await LevelPlayAds.loadInterstitial({
adUnitId: 'YOUR_INTERSTITIAL_AD_UNIT',
autoShow: false,
});
}
async function showInterstitial() {
const { isReady } = await LevelPlayAds.isInterstitialReady();
if (isReady) {
await LevelPlayAds.showInterstitial();
}
}
// Optional: a sticky bottom-right banner that flips to bottom-left on rotation.
async function bannerWithRotation() {
await LevelPlayAds.createBanner({
adUnitId: 'YOUR_BANNER_AD_UNIT',
adSize: 'ADAPTIVE',
position: 'BOTTOM_RIGHT',
isOverlap: false, // Android only — pushes the WebView up by banner height
});
LevelPlayAds.addListener(AdEvent.OrientationChanged, ({ orientation }) => {
LevelPlayAds.updateBannerStyle({
position: orientation === 'LANDSCAPE' ? 'BOTTOM_LEFT' : 'BOTTOM_RIGHT',
});
});
}Import
AdEventalongsideLevelPlayAds:import { LevelPlayAds, AdEvent } from 'capacitor-levelplay-ads';Position and size strings are case- and separator-insensitive at the JS layer —
'top-left','topLeft'and'TOP_LEFT'all canonicalize toTOP_LEFTbefore reaching the native side.
⚠️
initializeandrequestConsentInfomust complete before anyloadInterstitial/loadRewarded/createBannercall — ad loads are rejected otherwise.
⚠️ Important Notices
1. iOS integration paths
CocoaPods is the primary, supported iOS integration path — it pulls the
IronSource SDK via the podspec. Swift Package Manager is secondary; see
Package.swift for details. Without the SDK the plugin still builds: all
native SDK code is guarded and degrades to no-ops.
API
initialize(...)launchTestSuite()requestConsentInfo(...)showPrivacyOptions(...)getConsentData()resetConsent()setCCPAConsent(...)setChildDirected(...)requestTrackingAuthorization()getAdvertisingId()setDynamicUserId(...)createBanner(...)showBanner()hideBanner()destroyBanner()updateBannerStyle(...)loadInterstitial(...)isInterstitialReady()showInterstitial()loadRewarded(...)isRewardedReady()showRewarded()addListener(string, ...)- Interfaces
- Type Aliases
Capacitor LevelPlay Ads Plugin
Unity LevelPlay (formerly ironSource) mediation SDK for Capacitor.
CRITICAL: Proper Execution Order
To stay compliant with GDPR / CCPA / COPPA, follow this exact order at app start:
- Request Consent:
await LevelPlayAds.requestConsentInfo(...)Shows the custom consent modal if the user has not decided yet. - Initialize SDK:
await LevelPlayAds.initialize(...)The SDK boots up using the consent decision gathered in step 1. - Load Ads: e.g.
await LevelPlayAds.loadInterstitial(...) - Show Ads: e.g.
await LevelPlayAds.showInterstitial()
Ad loading is gated: initialize() must have run and a consent decision must
exist, otherwise load calls reject.
initialize(...)
initialize(options: InitializeOptions) => Promise<InitializeResult>Initializes the LevelPlay mediation SDK.
Call after requestConsentInfo().
| Param | Type |
| ------------- | --------------------------------------------------------------- |
| options | InitializeOptions |
Returns: Promise<InitializeResult>
launchTestSuite()
launchTestSuite() => Promise<void>Launches the LevelPlay Test Suite for verifying mediation integration. Debug builds only.
requestConsentInfo(...)
requestConsentInfo(options?: ConsentOptions | undefined) => Promise<ConsentData>Requests the current consent decision. If none exists, shows the custom consent modal. Must be called before loading ads.
| Param | Type |
| ------------- | --------------------------------------------------------- |
| options | ConsentOptions |
Returns: Promise<ConsentData>
showPrivacyOptions(...)
showPrivacyOptions(options?: ConsentOptions | undefined) => Promise<ConsentData>Re-presents the consent modal so the user can change a prior decision. Wire this to a button in your app's settings/privacy screen.
| Param | Type |
| ------------- | --------------------------------------------------------- |
| options | ConsentOptions |
Returns: Promise<ConsentData>
getConsentData()
getConsentData() => Promise<ConsentData>Returns the persisted consent decision without showing any UI.
Returns: Promise<ConsentData>
resetConsent()
resetConsent() => Promise<ConsentData>Clears the stored consent decision so the next requestConsentInfo()
re-shows the modal. Useful for QA flows and a "reset privacy choice"
button in your settings screen.
Returns: Promise<ConsentData>
setCCPAConsent(...)
setCCPAConsent(options: { doNotSell: boolean; }) => Promise<void>Sets the CCPA "do not sell my personal information" flag.
| Param | Type |
| ------------- | ------------------------------------ |
| options | { doNotSell: boolean; } |
setChildDirected(...)
setChildDirected(options: { isChildDirected: boolean; }) => Promise<void>Sets the COPPA child-directed treatment flag.
| Param | Type |
| ------------- | ------------------------------------------ |
| options | { isChildDirected: boolean; } |
requestTrackingAuthorization()
requestTrackingAuthorization() => Promise<TrackingAuthorizationResult>iOS only. Prompts the App Tracking Transparency (ATT) dialog.
No-op on Android (resolves with status NOT_APPLICABLE).
Returns: Promise<TrackingAuthorizationResult>
getAdvertisingId()
getAdvertisingId() => Promise<AdvertisingIdResult>Returns the platform advertising identifier:
- Android — Google Advertising ID (GAID), via Play Services.
limitedis theLimitAdTrackingflag. - iOS — IDFA, via
ASIdentifierManager. The OS returns an all-zero UUID until the user grants App Tracking Transparency authorization, in which caselimitedis true andidis"00000000-0000-0000-0000-000000000000".
Call requestTrackingAuthorization() first on iOS to get a real value.
Returns: Promise<AdvertisingIdResult>
setDynamicUserId(...)
setDynamicUserId(options: { userId: string; }) => Promise<void>Sets the dynamic user ID forwarded in server-to-server (S2S) reward
callbacks. Call before showRewarded() to tag each reward with a
verifiable token (e.g. a transaction ID or session nonce).
Can be changed between ad shows — the value active at show time is the one included in the S2S callback.
| Param | Type |
| ------------- | -------------------------------- |
| options | { userId: string; } |
createBanner(...)
createBanner(options: BannerOptions) => Promise<void>Creates and loads a banner ad.
| Param | Type |
| ------------- | ------------------------------------------------------- |
| options | BannerOptions |
showBanner()
showBanner() => Promise<void>Shows a previously created (and hidden) banner.
hideBanner()
hideBanner() => Promise<void>Temporarily hides the banner without destroying it.
destroyBanner()
destroyBanner() => Promise<void>Destroys the banner and removes it from the view hierarchy.
updateBannerStyle(...)
updateBannerStyle(options: BannerStyleOptions) => Promise<void>Reposition or restyle the active banner without destroying it.
Only the provided fields change; omitted fields keep their current value.
isOverlap only affects Android — iOS always overlays the WebView.
| Param | Type |
| ------------- | ----------------------------------------------------------------- |
| options | BannerStyleOptions |
loadInterstitial(...)
loadInterstitial(options: AdLoadOptions) => Promise<void>Loads an interstitial ad.
| Param | Type |
| ------------- | ------------------------------------------------------- |
| options | AdLoadOptions |
isInterstitialReady()
isInterstitialReady() => Promise<AdReadyResult>Synchronously checks whether an interstitial ad is loaded and ready.
Returns: Promise<AdReadyResult>
showInterstitial()
showInterstitial() => Promise<void>Shows the loaded interstitial ad.
loadRewarded(...)
loadRewarded(options: AdLoadOptions) => Promise<void>Loads a rewarded ad.
| Param | Type |
| ------------- | ------------------------------------------------------- |
| options | AdLoadOptions |
isRewardedReady()
isRewardedReady() => Promise<AdReadyResult>Synchronously checks whether a rewarded ad is loaded and ready.
Returns: Promise<AdReadyResult>
showRewarded()
showRewarded() => Promise<void>Shows the loaded rewarded ad.
addListener(string, ...)
addListener(eventName: AdEventName | string, listenerFunc: (info: any) => void) => Promise<PluginListenerHandle>Listens for native ad events.
Interstitial events
onInterstitialAdLoaded—AdInfoonInterstitialAdLoadFailed—AdErrorInfoonInterstitialAdDisplayed—AdInfoonInterstitialAdDisplayFailed—AdErrorInfoonInterstitialAdClicked—AdInfoonInterstitialAdClosed—AdInfoonInterstitialAdInfoChanged—AdInfo
Rewarded events
onRewardedAdLoaded—AdInfoonRewardedAdLoadFailed—AdErrorInfoonRewardedAdDisplayed—AdInfoonRewardedAdDisplayFailed—AdErrorInfoonRewardedAdClicked—AdInfoonRewardedAdClosed—AdInfoonRewardedAdInfoChanged—AdInfoonRewardedAdRewarded—AdRewardEvent
Banner events
onBannerAdLoaded—AdInfoonBannerAdLoadFailed—AdErrorInfoonBannerAdDisplayed—AdInfoonBannerAdDisplayFailed—AdErrorInfoonBannerAdClicked—AdInfoonBannerAdExpanded—AdInfoonBannerAdCollapsed—AdInfoonBannerAdLeftApplication—AdInfo
Revenue
onAdRevenue—AdRevenueEvent(impression-level ad revenue / ILRD)
Consent
onConsentStatusChanged—ConsentData
Orientation
onOrientationChanged—OrientationChangedEvent
Prefer the typed AdEvent constants (e.g. AdEvent.InterstitialLoaded)
over raw strings for compile-time safety.
| Param | Type |
| ------------------ | ----------------------------------- |
| eventName | string |
| listenerFunc | (info: any) => void |
Returns: Promise<PluginListenerHandle>
Interfaces
InitializeResult
| Prop | Type | Description |
| ------------ | ------------------- | -------------------------------------- |
| status | string | INITIALIZED_SUCCESSFULLY on success. |
InitializeOptions
| Prop | Type | Description |
| --------------- | -------------------- | -------------------------------------------------------------------------------------------------------- |
| appKey | string | LevelPlay app key from the Unity LevelPlay dashboard. Required. |
| userId | string | Optional publisher-defined user identifier (used for server-to-server rewarded callbacks and reporting). |
| isTesting | boolean | Enables LevelPlay test/integration mode. Set false before publishing. Default: false |
ConsentData
| Prop | Type | Description |
| ------------------- | ----------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| status | 'UNKNOWN' | 'GRANTED' | 'DENIED' | The recorded consent decision. - UNKNOWN — no decision yet (fresh install). - GRANTED — user granted consent. - DENIED — user denied consent. |
| granted | boolean | Simplified boolean: true when status === 'GRANTED'. |
| canRequestAds | boolean | True when ads may be requested (a decision exists, granted or denied). |
| provider | ConsentProvider | Which provider produced this decision: inmobi or custom. Useful for deciding whether to trust the tcString field. |
| tcString | string | IAB TCF v2.2 consent string. Populated only when the inmobi provider is active and the user has interacted with the CMP. Undefined under custom (which doesn't produce a real TCF payload). |
ConsentOptions
| Prop | Type | Description |
| ----------------------- | --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| privacyPolicyUrl | string | URL opened when the user taps the privacy policy link in the modal. Only used by the custom consent provider — ignored under InMobi (which renders its own privacy policy link inside the CMP UI). |
| title | string | Custom modal title. custom provider only. |
| message | string | Custom modal body text. custom provider only. |
| acceptButtonText | string | Label for the accept/grant button. custom provider only. |
| declineButtonText | string | Label for the decline/deny button. custom provider only. |
| networks | string[] | Mediation network keys the consent decision should be applied to. When omitted, consent is applied globally. |
TrackingAuthorizationResult
| Prop | Type | Description |
| ------------ | ------------------- | ------------------------------------------------------------------------------------------------------ |
| status | string | iOS ATT status: AUTHORIZED, DENIED, RESTRICTED, NOT_DETERMINED, or NOT_APPLICABLE (Android). |
AdvertisingIdResult
| Prop | Type | Description |
| ------------- | -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| id | string | The advertising identifier. Empty string when unavailable; on iOS the all-zeros UUID "00000000-0000-0000-0000-000000000000" when ATT is not authorized. |
| limited | boolean | True when the user has limited / opted out of ad tracking. Android: LimitAdTracking flag. iOS: true when ATT is not authorized. |
BannerOptions
| Prop | Type | Description |
| ------------------- | ------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| adUnitId | string | LevelPlay banner ad unit ID. Required. |
| adSize | 'BANNER' | 'LARGE' | 'MEDIUM_RECTANGLE' | 'LEADERBOARD' | 'ADAPTIVE' | Banner size. Default: ADAPTIVE. |
| position | BannerPosition | Banner position on screen. Default: BOTTOM. |
| isAutoShow | boolean | Show the banner automatically once loaded. Default: true. |
| isOverlap | boolean | If true the banner overlaps the webview; if false it pushes the webview. Android only — iOS always overlays the WebView. Default: true. |
| retryInterval | number | Minimum delay between consecutive load() calls, in milliseconds. Throttles invalid traffic. Default: 5000 (5 seconds). |
BannerStyleOptions
| Prop | Type | Description |
| --------------- | --------------------------------------------------------- | -------------------------------------------- |
| position | BannerPosition | New banner position. |
| isOverlap | boolean | Android-only overlap flag. iOS ignores this. |
AdLoadOptions
| Prop | Type | Description |
| ------------------- | -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| adUnitId | string | LevelPlay ad unit ID. Required. |
| autoShow | boolean | If true, the ad is shown immediately after a successful load. The returned promise then resolves on display (or rejects with "Auto-show failed: …"). Default: false. |
| retryInterval | number | Minimum delay between consecutive load() calls, in milliseconds. Throttles invalid traffic. Default: 5000 (5 seconds). |
AdReadyResult
| Prop | Type | Description |
| ------------- | -------------------- | -------------------------------------------- |
| isReady | boolean | True when the ad is loaded and can be shown. |
PluginListenerHandle
| Prop | Type |
| ------------ | ----------------------------------------- |
| remove | () => Promise<void> |
Type Aliases
ConsentProvider
Which consent UI the plugin shows. Configured at install time via
levelplay.consentProvider in the host app's package.json — not via JS.
inmobi(default): IAB TCF v2.2 compliant. Bundles InMobi Choice CMP. Requireslevelplay.inmobi.pCodefrom https://choice.inmobi.com/.custom: built-in alert dialog. Not TCF compliant; do not ship to EU.
'inmobi' | 'custom'
BannerPosition
Banner placement on screen. TOP_LEFT / TOP_RIGHT / BOTTOM_LEFT /
BOTTOM_RIGHT anchor the banner to the corresponding screen corner;
CENTER places it in the middle.
'TOP' | 'BOTTOM' | 'TOP_LEFT' | 'TOP_RIGHT' | 'BOTTOM_LEFT' | 'BOTTOM_RIGHT' | 'CENTER'
AdEventName
(typeof AdEvent)[keyof typeof AdEvent]
