Google runs two Android ad SDKs in parallel right now, and one of them has an expiry date. If you are picking an AdMob library for a Capacitor app this year, that single fact should decide it for you.
We've released the Capacitor AdMob plugin, the first Capacitor AdMob plugin built on Google's Next-Gen Mobile Ads SDK. This is our plugin, and it ships as part of Capawesome Insiders, a paid subscription. Everything below is the short version of the full announcement on our blog.
Why the Next-Gen SDK Matters Now
The legacy Google Mobile Ads SDK on Android sits at major versions 24 and 25 today, and Google has published a deprecation schedule for both. They reach their deprecation date on June 30, 2027 and their sunset date on June 30, 2028. Google's own wording for a sunset version is that ads are "at risk of not serving", with sunset ad requests returning a no fill.
The successor is the GMA Next-Gen SDK. It ships as a new Kotlin-first artifact, com.google.android.libraries.ads.mobile.sdk, with a different API surface instead of another major version of the old library, which is why plugin authors have to port rather than bump a version number.
Our plugin runs on the Next-Gen SDK on Android from its first release, defaulting to ads-mobile-sdk 1.2.1, and on the latest Google Mobile Ads SDK on iOS, where Google has not shipped a Next-Gen variant yet. Both dependency versions are Gradle project variables, so you can pin $adsMobileSdkVersion and $userMessagingPlatformVersion in variables.gradle when another plugin drags in a different version.
Two years sounds like plenty of runway. It is not, if the port lands in the same quarter as a store deadline. Starting a new monetization layer on a library with a published sunset date means paying for it twice.
Five Ad Formats, One Load and Show API
The plugin covers the five AdMob formats Google offers for mobile apps: banner, interstitial, rewarded, rewarded interstitial, and app open. Every full-screen format follows the same two steps, a load method that returns an identifier and a show method that takes it, so learning one teaches you the other three.
Here is a rewarded ad, with the reward listener registered before the ad goes on screen:
import { Admob } from '@capawesome-team/capacitor-admob';
const showRewardedAd = async () => {
await Admob.addListener('rewardEarned', ({ amount, type }) => {
grantReward(amount, type);
});
const { id } = await Admob.loadRewardedAd({
adUnitId: 'ca-app-pub-3940256099942544/5224354917',
});
await Admob.showRewardedAd({ id });
};
Swap loadRewardedAd for loadInterstitialAd, loadRewardedInterstitialAd, or loadAppOpenAd and the surrounding code stays the same. Because each load call hands back its own identifier, you can keep several ads in flight at once, say one interstitial preloaded for the end of a level and one rewarded ad sitting behind a "watch for coins" button. Pass your own id if you prefer names you control over generated ones.
Both rewarded formats accept a serverSideVerification option with userId and customData, forwarded to your verification callback so the reward is granted on what Google reports rather than what the client claims. App open ads get a shortcut of their own: enableAppOpenAutoShow(...) loads and shows one whenever the app returns to the foreground, with a minInterval frequency cap that defaults to 14400 seconds, and it stays quiet while a consent form or another full-screen ad is visible.
AdMob is a mobile product, so every method is Android and iOS only and rejects with an unimplemented error on the web.
Banner Ads in Three Modes
Banners are where most Capacitor ad integrations get ugly, because the banner is a native view sitting on top of a web view that knows nothing about it. The plugin gives you three placements, and the right one depends on how much your CSS already knows about the ad.
| Mode | What happens | When to use it |
|---|---|---|
mode: 'overlay' (default) |
Anchored to the top or bottom edge on top of the web view, aware of safe area insets | Your layout already reserves the space, for example a fixed footer |
mode: 'resize' |
The web view shrinks so the banner never covers web content | You want the banner clear of your content without touching your CSS |
frame: { x, y, width, height } |
The banner lands at a rectangle you measure in CSS pixels | Inline placement inside content, for example between list items |
Resize mode is the shortest path to a banner that never hides a button, and it needs the ad unit, a size, and a position:
import { Admob, BannerSize } from '@capawesome-team/capacitor-admob';
const showBanner = async () => {
const { id } = await Admob.showBanner({
adUnitId: 'ca-app-pub-3940256099942544/6300978111',
size: BannerSize.AdaptiveBanner,
position: 'bottom',
mode: 'resize',
});
return id;
};
For inline placement you measure an anchor element with getBoundingClientRect(), pass the rectangle as frame, and call setBannerFrame(...) when the layout shifts. Hold on to the returned identifier, because hideBanner(...), resumeBanner(...), and removeBanner(...) all operate on it, and it is what lets you run more than one banner at a time.
Seven sizes are available. AdaptiveBanner matches the screen or frame width with a height chosen by the SDK, InlineAdaptiveBanner is capped by the frame height you pass, and the fixed sizes cover 320x50, 320x100, 300x250, plus 468x60 and 728x90 for tablets. Set collapsible: true for a banner that expands into a larger ad and collapses back, then listen for bannerSizeChanged to keep your layout honest when it does.
Consent in a Single Call
Google requires User Messaging Platform consent before you request a single ad from users in the European Economic Area, the UK, or a regulated US state. The canonical flow is a request for consent information followed by a conditional form presentation, and requestConsent(...) does both:
import { Admob } from '@capawesome-team/capacitor-admob';
const setupAds = async () => {
const { canRequestAds, privacyOptionsRequired } = await Admob.requestConsent();
if (canRequestAds) {
await Admob.initialize();
}
return privacyOptionsRequired;
};
Call it on every app launch, before initialize(...), because the answer changes over time. Until the consent requirements are met, every load method rejects with CONSENT_NOT_GATHERED, so a non-compliant ad request never leaves the device. When privacyOptionsRequired comes back true, give the user a settings entry that calls showPrivacyOptionsForm(). For the AdMob console setup, the App Tracking Transparency ordering on iOS, and the error cases, we wrote a separate walkthrough on handling AdMob GDPR consent in a Capacitor app.
Impression-Level Revenue for Every Format
Ten events cover the ad lifecycle, most of them carrying the ad's id and format so one listener can serve all five formats. The one to wire up first is adRevenuePaid, because LTV models, ROAS dashboards, and cohort analyses all run on it:
import { Admob } from '@capawesome-team/capacitor-admob';
const trackAdRevenue = async () => {
await Admob.addListener('adRevenuePaid', ({ value, currencyCode, precision, format }) => {
analytics.track('ad_revenue', { value, currencyCode, precision, format });
});
};
value is the amount in the currency's standard unit, currencyCode is an ISO 4217 code, and precision tells you how much to trust the number: PRECISE, ESTIMATED, PUBLISHER_PROVIDED, or UNKNOWN. Mixing estimated and precise values into one total produces a figure you cannot reconcile against your AdMob reports later.
Should You Switch?
If you are starting an AdMob integration today, start on the Next-Gen SDK. If you already ship ads with @capacitor-community/admob and your setup is one anchored banner plus the occasional interstitial, there is no urgency. Revisit when you need resize or inline banners, several concurrent ad instances, or revenue events across every format. The API mapping is mechanical when you do: prepareInterstitial becomes loadInterstitialAd, prepareRewardVideoAd becomes loadRewardedAd, and adId becomes adUnitId.
The plugin is still marked experimental, so the API surface is complete and typed but has not been through heavy production testing, which makes bug reports genuinely valuable right now.
The plugin documentation has the full API reference, and the announcement post covers the parts I skipped here, including test ad units, typed error codes, and the native setup on both platforms. If you try it, tell me what breaks.
Top comments (0)