npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

tauri-plugin-ad2mob

v0.2.0

Published

Google AdMob (Mobile Ads) integration for Tauri v2 apps on Android and iOS

Readme

tauri-plugin-ad2mob

CI Crates.io docs.rs npm License

Google AdMob (Mobile Ads) for Tauri v2 apps on Android and iOS: banners (native views, never inside the WebView DOM), interstitials, rewarded ads, Google UMP consent and iOS App Tracking Transparency — behind one strongly typed TypeScript API.

Independent community project, created and maintained by marrionesa. Not affiliated with, endorsed by or sponsored by Google LLC or the Tauri Programme within The Commons Conservancy. Google AdMob, Google Mobile Ads and Google UMP are products of Google LLC and are subject to Google's own terms and policies.

import { AdMob } from "tauri-plugin-ad2mob";

await AdMob.initialize({ isTesting: true });

await AdMob.requestConsent();
await AdMob.requestTrackingAuthorization(); // iOS only

await AdMob.showBanner({ position: "bottom", size: "adaptive" });

await AdMob.loadInterstitial();
if (await AdMob.isInterstitialReady()) {
  await AdMob.showInterstitial();
}

await AdMob.loadRewarded();
const unlisten = await AdMob.on("admob://rewarded-earned", ({ payload }) => {
  console.log(`Reward: ${payload.amount} ${payload.type}`);
});
if (await AdMob.isRewardedReady()) {
  await AdMob.showRewarded();
}

Features

  • Initialization — idempotent initialize() with generic and per-platform App IDs, development mode (isTesting) that automatically resolves to the official Google test IDs, and optional startup initialization from tauri.conf.json.
  • Privacy first — Google UMP consent flow (requestConsent, getConsentStatus) with a normalized ConsentStatus (unknown / required / notRequired / obtained), and iOS ATT (requestTrackingAuthorization) with a normalized TrackingAuthorizationStatus. Ad requests are rejected with CONSENT_REQUIRED while the UMP state is required.
  • Interstitial & rewarded — explicit load → ready → show → closed state machines, real SDK reward callbacks (admob://rewarded-earned is only emitted from OnUserEarnedRewardListener / GADRewardedAdDelegate, never synthesized from dismissal), is*Ready() queries and explicit destroy.
  • Native banners — platform-native AdView / GADBannerView overlaid on the requested edge of the WebView (top/bottom), safe-area aware (status bar, navigation bar, home indicator, edge-to-edge), six sizes including anchored adaptive banners, plus hide / reposition / destroy.
  • Typed events — every SDK callback is forwarded as a namespaced event (admob://banner-loaded, admob://interstitial-failed, admob://rewarded-earned, …) with structured payloads.
  • Desktop safe — the crate compiles for Windows/macOS/Linux; every mobile operation returns a controlled UNSUPPORTED_PLATFORM error instead of breaking the build or crashing, isSupported() reports false.
  • Concurrency safe — all state transitions go through a Mutex-guarded state machine; concurrent loadInterstitial() calls never corrupt state.

Install

npm add tauri-plugin-ad2mob          # or: pnpm / bun / yarn
cargo add tauri-plugin-ad2mob

Setup

1. Register the Rust plugin

fn main() {
    tauri::Builder::default()
        .plugin(tauri_plugin_ad2mob::init())
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

Optional inline configuration (equivalent to the plugins > ad2mob section of tauri.conf.json):

.plugin(tauri_plugin_ad2mob::init_with_config(serde_json::json!({
    "isTesting": true,
    "initializeOnStartup": true,
})))

2. Grant the capability

Add the default permission to your capability file (e.g. src-tauri/capabilities/default.json):

{
  "permissions": [
    "ad2mob:default"
  ]
}

ad2mob:default allows every command, including the plugin event listeners used by AdMob.on(). Ads are not a security-sensitive surface (the plugin never exposes filesystem, network or device capabilities to the webview), so a single entry keeps the setup simple.

3. Android configuration

The AdMob App ID is app-owned; the plugin validates it at runtime and fails with a descriptive INVALID_CONFIGURATION error when missing.

Add to gen/android/app/src/main/AndroidManifest.xml inside <application> (create the file through tauri android init):

<meta-data
    android:name="com.google.android.gms.ads.APPLICATION_ID"
    android:value="ca-app-pub-XXXXXXXXXXXXXXXX~YYYYYYYYYY" />

While developing, use Google's test App ID ca-app-pub-3940256099942544~3347511713.

The Google Mobile Ads SDK and UMP are linked automatically from the plugin's android/ Gradle module (play-services-ads 24.x, user-messaging-platform 3.x); no extra permissions are needed (the SDK's manifest declares INTERNET/ACCESS_NETWORK_STATE). If you enable R8 minification, the plugin ships consumer keep-rules for its own classes — nothing to configure.

4. iOS configuration

Add to the generated Info.plist (e.g. via gen/apple project settings or Xcode):

<key>GADApplicationIdentifier</key>
<string>ca-app-pub-XXXXXXXXXXXXXXXX~YYYYYYYYYY</string>
<key>NSUserTrackingUsageDescription</key>
<string>Your data will be used to show you more relevant ads.</string>
<key>SKAdNetworkItems</key>
<!-- Google's SKAdNetwork IDs, see the AdMob docs for the current list -->
</plist-entry>

While developing, use Google's test App ID ca-app-pub-3940256099942544~1458002511.

The GoogleMobileAds (13.x) and UMP (3.x) frameworks are resolved through Swift Package Manager from the plugin's ios/Package.swift automatically when the Tauri CLI generates the Xcode project. ATT usage requires the NSUserTrackingUsageDescription entry above — App Store submission with ads additionally requires the App Tracking Transparency and advertising identifiers declarations in your App Privacy answers; the plugin cannot do that part for you.

Usage

Initialize (idempotent)

// Option A: one App ID for both platforms
await AdMob.initialize({
  appId: "ca-app-pub-XXXXXXXXXXXXXXXX~YYYYYYYYYY",
  isTesting: true,
});

// Option B: per-platform App IDs
await AdMob.initialize({
  androidAppId: "ca-app-pub-…~…",
  iosAppId: "ca-app-pub-…~…",
  isTesting: false,
  automaticallyRequestConsent: true,   // default
  requestTrackingAuthorization: false, // iOS: prompt ATT right after init
  debug: true,                         // verbose logs in development
});

Calling initialize() again is a safe no-op that returns the current state — it never re-runs the native SDK flow. Use AdMob.configure() for Ad Unit IDs.

Ad Unit IDs

await AdMob.configure({
  adUnitIds: {
    banner: "ca-app-pub-…/…",
    interstitial: "ca-app-pub-…/…",
    rewarded: "ca-app-pub-…/…",
  },
});

// Per-call overrides are also possible:
await AdMob.loadInterstitial({ adUnitId: "ca-app-pub-…/…" });

Resolution order: explicit argument → configure() value → official Google test ID while isTesting: trueINVALID_CONFIGURATION error.

Consent (Google UMP)

const { status } = await AdMob.requestConsent(); // update + form when required
const current = await AdMob.getConsentStatus();  // "unknown" | "required" | "notRequired" | "obtained"

With automaticallyRequestConsent: true (default) the flow already runs during initialize(). While the status is required, ad requests reject with CONSENT_REQUIRED so ads are never requested before consent. There is no way — and no need — to persist consent yourself: the UMP SDK stores it per-device.

App Tracking Transparency (iOS)

const status = await AdMob.requestTrackingAuthorization();
// "notDetermined" | "restricted" | "denied" | "authorized"
// Android and desktop always report "notAvailable" — the API is safe to call
// unconditionally and never throws there.

ATT is never requested automatically; set requestTrackingAuthorization: true in initialize() or call the method explicitly from a user gesture.

Interstitial

await AdMob.loadInterstitial();          // resolves when loaded
if (await AdMob.isInterstitialReady()) {
  await AdMob.showInterstitial();        // resolves when the user dismisses it
}
await AdMob.destroyInterstitial();

showInterstitial() rejects with AD_NOT_READY when nothing is loaded and with LOAD_FAILED/SHOW_FAILED (or a typed AdMobError) on SDK failures.

Rewarded

const unlisten = await AdMob.on("admob://rewarded-earned", ({ payload }) => {
  // payload: { amount: number, type: string } — from the REAL SDK callback
  grantReward(payload.amount);
});

await AdMob.loadRewarded();
if (await AdMob.isRewardedReady()) {
  await AdMob.showRewarded();            // resolves when dismissed
}
// unlisten(); when done

Rewards are only reported by the SDK's own reward callback. Dismissing the ad without earning it emits admob://rewarded-closed but never a reward.

Native banner

await AdMob.showBanner({ position: "bottom", size: "adaptive" });
await AdMob.hideBanner();                 // keeps the view for a re-show
await AdMob.showBanner();                 // re-shows / refreshes
await AdMob.setBannerPosition({ position: "top" });
await AdMob.isBannerVisible();            // true while visible
await AdMob.destroyBanner();

The banner is a native view (AdView / GADBannerView) attached to the requested edge of the window content, above the home indicator / navigation bar (safe areas are respected). It intentionally is not a DOM element.

Layout note (documented limitation): the banner floats above the WebView content. Reserve matching space in your UI (for example a bottom padding on your app shell equal to the banner height) so the ad never covers interactive controls. The WebView itself is not resized: doing so would fight the Tauri runtime's layout management on both platforms.

Events

const unlisten = await AdMob.on("admob://interstitial-failed", ({ payload }) => {
  console.log(payload.stage, payload.code, payload.message);
});
await unlisten(); // remove the listener

All events, with stable names and payload shapes:

| Event | Payload | | --- | --- | | admob://initialized | { platform, testing } | | admob://consent-changed | { status } | | admob://tracking-authorization-changed | { status } | | admob://banner-loaded | { adUnitId } | | admob://banner-failed | { adUnitId?, code?, message, domain? } | | admob://banner-opened / -clicked / -impression / -closed | { adUnitId? } | | admob://interstitial-loaded / admob://rewarded-loaded | { adUnitId } | | admob://interstitial-failed / admob://rewarded-failed | { adUnitId?, code?, message, domain?, stage: "load" \| "show" } | | admob://interstitial-opened / -impression / -clicked / -closed | { adUnitId? } | | admob://rewarded-opened / -impression / -clicked / -closed | { adUnitId? } | | admob://rewarded-earned | { adUnitId?, amount, type } |

Delivery: on Android/iOS events travel through the Tauri plugin event channel (addPluginListener); on desktop they are emitted on the global event system (listen). AdMob.on() picks the right mechanism automatically.

Status, detection and cleanup

await AdMob.isSupported(); // false on Windows/macOS/Linux
await AdMob.getStatus();
// {
//   initialized, platform, testing, consentStatus, trackingStatus,
//   interstitialReady, rewardedReady, bannerVisible
// }
await AdMob.destroy(); // destroys every native ad resource, resets state

destroy() is the global cleanup: banner, interstitial and rewarded resources are released, listeners are dropped and the plugin can be initialized again.

Error handling

Every method throws an AdMobError (an Error subclass) with a stable code:

| Code | Meaning | | --- | --- | | NOT_INITIALIZED | ad operation attempted before initialize() | | INVALID_CONFIGURATION | missing/invalid App ID, Ad Unit ID or platform config | | INVALID_ARGUMENT | malformed arguments | | AD_NOT_READY | show*() without a loaded ad | | LOAD_FAILED | the SDK failed to load the ad (details in message) | | SHOW_FAILED | the SDK failed to present the ad | | CONSENT_REQUIRED | UMP state is required; run requestConsent() first | | UNSUPPORTED_PLATFORM | mobile-only API used on desktop | | NATIVE_ERROR | anything else reported by the native layer |

import { AdMob, isAdMobError } from "tauri-plugin-ad2mob";

try {
  await AdMob.showInterstitial();
} catch (error) {
  if (isAdMobError(error) && error.code === "AD_NOT_READY") {
    await AdMob.loadInterstitial();
  }
}

Testing

While isTesting: true, missing Ad Unit IDs resolve to Google's official test IDs (documented at Android and iOS):

| Format | Android | iOS | | --- | --- | --- | | Banner | ca-app-pub-3940256099942544/6300978111 | ca-app-pub-3940256099942544/2934735716 | | Interstitial | ca-app-pub-3940256099942544/1033173712 | ca-app-pub-3940256099942544/441146891 | | Rewarded | ca-app-pub-3940256099942544/5224354917 | ca-app-pub-3940256099942544/1717083536 |

This is for development and automated testing only. Clicking your own production ads violates the AdMob policy and can get the account suspended.

Notes on real testing:

  • Android emulators work with the test IDs; real ads require a device with Google Play services and a configured test device ID (MobileAds.setRequestConfiguration / testDeviceIds).
  • The iOS simulator works with the test IDs. Real-device testing is required for final validation; UMP consent behavior and ATT prompts can differ between simulator and device.
  • The bundled example app (examples/admob-demo) exercises every feature against the test IDs and prints all events on screen.

Desktop behavior

| API | Desktop | | --- | --- | | initialize() | no-op success (state tracked, admob://initialized emitted) | | configure(), getStatus(), isSupported(), status queries | work normally | | requestTrackingAuthorization() | resolves notAvailable | | requestConsent() and every ad operation | reject UNSUPPORTED_PLATFORM | | destroy() | no-op success |

The plugin crate never links the desktop WebView runtime, so adding it does not change your desktop build dependencies.

API reference

| Method | Returns | | --- | --- | | AdMob.initialize(options?) | InitializationResult | | AdMob.configure({ adUnitIds }) | void | | AdMob.isSupported() | boolean | | AdMob.getStatus() | AdMobStatus | | AdMob.requestConsent() | { status } | | AdMob.getConsentStatus() | ConsentStatus | | AdMob.requestTrackingAuthorization() | TrackingAuthorizationStatus | | AdMob.getTrackingAuthorizationStatus() | TrackingAuthorizationStatus | | AdMob.loadInterstitial(options?) | void (resolves when loaded) | | AdMob.showInterstitial() | void (resolves when dismissed) | | AdMob.isInterstitialReady() | boolean | | AdMob.destroyInterstitial() | void | | AdMob.loadRewarded(options?) | void (resolves when loaded) | | AdMob.showRewarded() | void (resolves when dismissed) | | AdMob.isRewardedReady() | boolean | | AdMob.destroyRewarded() | void | | AdMob.showBanner(options?) | void (resolves when loaded) | | AdMob.hideBanner() | void | | AdMob.isBannerVisible() | boolean | | AdMob.setBannerPosition({ position }) | void | | AdMob.destroyBanner() | void | | AdMob.on(event, handler) | Unlisten (awaitable) | | AdMob.destroy() | void |

Troubleshooting

Ad not loading / no fill. Test IDs return occasional no-fill; retry or switch formats. In production check the AdMob console for the app/ad-unit status and traffic. Also verify the device can reach googleads.g.doubleclick.net.

INVALID_CONFIGURATION on Android. The <meta-data android:name="com.google.android.gms.ads.APPLICATION_ID"> entry is missing from the app manifest. Add it inside <application> (see Setup) and rebuild.

INVALID_CONFIGURATION on iOS. GADApplicationIdentifier is missing from Info.plist — the GoogleMobileAds SDK aborts without it.

Consent required before ads. The UMP status is required. Call AdMob.requestConsent() (or keep automaticallyRequestConsent: true) and complete the form once; the status is cached per-device by the UMP SDK.

ATT prompt not appearing (iOS). ATT must be requested while the app is active and only prompts once per install (reinstall or reset the simulator privacy settings to see it again). Check that NSUserTrackingUsageDescription exists in Info.plist.

Banner covering app UI. The native banner overlays the requested edge of the WebView. Reserve padding in your layout (see the banner section) instead of relying on the plugin to shrink the WebView.

Banner not visible. showBanner() resolves only after the ad loaded; a banner-failed event means nothing was attached. Also check that hideBanner() was not called earlier and that the safe-area insets are not consuming the banner (e.g. gesture navigation bars on Android).

Interstitial/Rewarded AD_NOT_READY. Ads must load before showing and are consumed after one show — reload after every *-closed event. Loads can take seconds on poor connections; the load* promise resolves on completion.

Reward not received. The reward only fires from the SDK callback while the ad is being watched (admob://rewarded-earned). Closing the ad early, SDK no-fill quirks or mediation adapters can legitimately skip it — never grant rewards from rewarded-closed.

Main Thread violations. If you extend the native code, keep every SDK/UI call on the main thread (runOnUiThread / DispatchQueue.main) — the shipped managers already do.

Desktop UNSUPPORTED_PLATFORM. Expected: ads only exist on mobile. Gate the calls with AdMob.isSupported() if you prefer skipping silently.

Known limitations

  • The banner overlays the WebView instead of resizing it (see above).
  • A showBanner while another banner load is in flight replaces the previous banner; the superseded promise rejects with LOAD_FAILED.
  • iOS full-screen ads keep their pending show promise across destroy() so the SDK can still report the dismissal — the promise always settles.
  • Mediation networks are supported only as far as the base Google Mobile Ads SDK supports them (no per-network adapters are configured by this plugin).
  • Android/iOS native code is validated by review + unit-tested pure logic in this repository; run the example app on real hardware before shipping.

Versioning

SemVer. 0.x releases may still refine the API before 1.0.0; see CHANGELOG.md for the current status.

Contributing

Issues and PRs are welcome. Run the full validation suite before submitting:

cargo fmt --check
cargo clippy --all-targets -- -D warnings
cargo test
bun run build && bun run typecheck && bun run test   # guest bindings

Documentation website

The standalone documentation site lives in ad2mob-website and deploys automatically to GitHub Pages from the master branch through .github/workflows/pages.yml. Its expected URL is https://marrionesa.github.io/tauri-plugin-ad2mob/.

To update the repository Homepage field and its short GitHub description automatically, configure the optional REPO_SETTINGS_TOKEN repository secret with permission to edit repository metadata. The Pages deployment itself only needs the standard Pages workflow permissions.

License

Dual-licensed under MIT or Apache-2.0, at your option. Google Mobile Ads, Google UMP and related SDKs are Google products distributed under their own terms — using this plugin does not change your obligations under the Google AdMob program policies.