Create Free APK

JavaScript bridge API

Ads

Showing an interstitial or a rewarded ad on your own cue, and telling the shell when a navigation starts and settles so it does not interrupt one.

AppMintAds.appOpen helper#

window.AppMintAds.appOpen.setEnabled(on)

Example

Pauses or resumes the app-open ad, the full-screen ad shown at launch and when the user returns from another app.

Returns: nothing. Needs: Ad Control β†’ Developer-controlled and an app-open ad unit ID.

Pause it while the user leaves the app on purpose for a short moment (a payment app, the camera, a share sheet), so coming back does not show an ad:

var ads = window.AppMintAds;

async function payWithUpi(link) {
  if (ads) ads.appOpen.setEnabled(false);
  try {
    await openPaymentApp(link);           // your own code; the user switches apps and returns
  } finally {
    if (ads) ads.appOpen.setEnabled(true);
  }
}

Check whether the build has an app-open unit at all:

var hasAppOpen = !!(window.AppMintAds && window.AppMintAds.config().formats.app_open);

Pause it while the user picks a photo, since the system photo picker takes them out of the app for a moment:

var ads = window.AppMintAds;
var input = document.getElementById('photo-input');   // <input type="file" accept="image/*" hidden>
var pausedForPicker = false;

document.getElementById('pick-photo').addEventListener('click', function () {
  if (ads) { ads.appOpen.setEnabled(false); pausedForPicker = true; }
  input.click();
});

input.addEventListener('change', function () {
  if (input.files[0]) uploadPhoto(input.files[0]);      // your own code
});

// Back in the app (a photo was picked or the picker was cancelled): turn it on again.
document.addEventListener('visibilitychange', function () {
  if (document.hidden || !pausedForPicker) return;
  pausedForPicker = false;
  setTimeout(function () { ads.appOpen.setEnabled(true); }, 1000);
});

Notes: It is on by default. At cold start the ad shows when the page is ready, or when it finishes loading within 15 seconds; a later load is kept for next time. An app-open ad older than 4 hours is never shown. You can also show one yourself with AppMintAds.show('app_open').

AppMintAds.banner helper#

window.AppMintAds.banner.attach(target, opts) / .detach(slotOrElement)

Example

Places a banner or a 300Γ—250 medium-rectangle ad anywhere in your page, on top of an element you choose.

Returns: attach returns a Promise of {ok:true, slot, width, height} (CSS px) once the ad has loaded, or {ok:false, slot?, code, message?}. detach returns nothing. Needs: Ad Control β†’ Developer-controlled and a banner (or medium rectangle) ad unit ID.

The element only reserves the space. The ad is drawn over it and follows it when the page scrolls or resizes:

<div id="ad-slot"></div>

<script>
var ads = window.AppMintAds;
var slotEl = document.getElementById('ad-slot');

if (ads) {
  ads.banner.attach(slotEl, { size: 'banner' }).then(function (res) {
    // On success the helper sets slotEl.style.minHeight to res.height (pass keepHeight:true to stop that).
    if (!res.ok) {                         // no_fill, network, too_many_slots, ...
      if (res.slot) ads.banner.detach(res.slot);   // a failed slot still counts toward the limit of two
      slotEl.hidden = true;
    }
  });
} else {
  slotEl.hidden = true;                    // browser: no ads here
}
</script>

Medium rectangle (one at a time), and removing a banner again:

var box = document.querySelector('.article-end-ad');
var mrecSlot = null;

if (window.AppMintAds) {
  window.AppMintAds.banner.attach(box, { size: 'medium_rectangle' }).then(function (res) {
    if (res.ok) mrecSlot = res.slot;
    else { if (res.slot) window.AppMintAds.banner.detach(res.slot); box.hidden = true; }
  });
}

function leaveArticle() {
  if (window.AppMintAds && mrecSlot) window.AppMintAds.banner.detach(mrecSlot);   // or detach(box)
  mrecSlot = null;
}

Try a slot again later when no ad was available, freeing the failed slot first so it does not use up one of the two:

function fillSlot(el, triesLeft) {
  var ads = window.AppMintAds;
  if (!ads) { el.hidden = true; return; }             // browser: no ads here
  ads.banner.attach(el, { size: 'banner' }).then(function (res) {
    if (res.ok) return;
    if (res.slot) ads.banner.detach(res.slot);
    if ((res.code === 'no_fill' || res.code === 'network') && triesLeft > 0) {
      setTimeout(function () { fillSlot(el, triesLeft - 1); }, 60000);
    } else {
      el.hidden = true;                                 // ad_free, disabled, consent, ...
    }
  });
}

fillSlot(document.getElementById('footer-ad'), 2);

Notes: target is an element or a CSS selector; size is 'banner' (default, adaptive width, at most 90 dp tall), 'medium_rectangle' or 'mrec'. At most two slots at once, and a slot whose ad failed to load still counts until you detach it. detach needs the element or the slot id from attach, not a selector. Removing the element from the DOM detaches it too. Codes: no_element, duplicate_slot, too_many_slots, detached, no_fill, network, invalid_unit, ad_free, disabled, consent, automatic_mode, internal.

AppMintAds.config helper#

window.AppMintAds.config()

Example

Tells the page which ad control mode the build uses and which ad formats it can serve.

Returns: an object, synchronously: {control: 'automatic' | 'developer', formats: {banner, mrec, interstitial, rewarded, rewarded_interstitial, app_open}} (each format true / false). On an internal error it returns {control:'automatic', formats:{}}. Needs: nothing; window.AppMintAds exists only inside the installed app.

Show only the ad UI this build can really serve:

var ads = window.AppMintAds;               // undefined in a browser
var cfg = ads ? ads.config() : null;

var developer = !!(cfg && cfg.control === 'developer');
document.getElementById('watch-ad').hidden = !(developer && cfg.formats.rewarded);
document.getElementById('ad-slot').hidden  = !(developer && cfg.formats.banner);

Pick the rewarded format the build has for a "bonus" button, and hide the button when it has neither:

var ads = window.AppMintAds;
var cfg = ads ? ads.config() : null;
var bonusBtn = document.getElementById('bonus');

var bonusType = null;
if (cfg && cfg.control === 'developer') {
  if (cfg.formats.rewarded) bonusType = 'rewarded';
  else if (cfg.formats.rewarded_interstitial) bonusType = 'rewarded_interstitial';
}
bonusBtn.hidden = !bonusType;

bonusBtn.addEventListener('click', async function () {
  var res = await ads.show(bonusType);                  // inside the tap: plays at once
  if (res.earned) addCoins(res.reward.amount);
});

Choose the ad size for the end of an article: a medium rectangle when the build has one, otherwise a banner:

var ads = window.AppMintAds;
var cfg = ads ? ads.config() : null;
var box = document.getElementById('article-end-ad');

var size = null;
if (cfg && cfg.control === 'developer') {
  size = cfg.formats.mrec ? 'medium_rectangle' : (cfg.formats.banner ? 'banner' : null);
}

if (!size) box.hidden = true;
else ads.banner.attach(box, { size: size }).then(function (res) {
  if (!res.ok) { if (res.slot) ads.banner.detach(res.slot); box.hidden = true; }
});

Notes: A format is true when ads are on and the build has an ad unit ID for it. In Automatic mode (control: 'automatic') every load / show / banner.attach call answers code: 'automatic_mode'; use WebToApk.showRewardedAd() / showInterstitialAd() there. Choose Ad Control β†’ Developer-controlled in the build wizard to drive ads from the page.

AppMintAds.isReady helper#

window.AppMintAds.isReady(type)

Example

Tells you whether a full-screen ad of this type is loaded and can be shown right now.

Returns: true / false, synchronously. type: 'interstitial', 'rewarded', 'rewarded_interstitial' or 'app_open'. Needs: Ad Control β†’ Developer-controlled and an ad unit ID for the type.

Keep a "Watch ad" button in step with the ad, using the appmint:ad status event:

var ads = window.AppMintAds;
var btn = document.getElementById('watch-ad');

function refresh() { btn.disabled = !(ads && ads.isReady('rewarded')); }

if (ads) {
  refresh();
  window.addEventListener('appmint:ad', function (e) {
    if (e.detail.type === 'rewarded') refresh();    // phases: loaded, failed, closed, ...
  });
} else {
  btn.hidden = true;                                // browser: no ads here
}

Offer "double your coins" on the results screen only when a rewarded ad is already waiting:

function showResults(coins) {
  var ads = window.AppMintAds;
  var offer = document.getElementById('double-coins');
  renderResults(coins);                                 // your own screen
  offer.hidden = !(ads && ads.isReady('rewarded'));
  offer.onclick = async function () {
    offer.hidden = true;
    var res = await ads.show('rewarded');               // inside the tap: plays at once
    addCoins(res.earned ? coins : 0);                   // the bonus: the same amount again
  };
}

In a hint button, play whichever rewarded format is loaded right now:

document.getElementById('hint').addEventListener('click', async function () {
  var ads = window.AppMintAds;
  if (!ads) return;                                     // browser: no ads here
  var type = ads.isReady('rewarded') ? 'rewarded'
           : ads.isReady('rewarded_interstitial') ? 'rewarded_interstitial'
           : null;
  if (!type) { showMessage('No hint video right now. Try again in a minute.'); return; }
  var res = await ads.show(type);
  if (res.earned) revealHint();
});

Notes: banner and mrec always answer false (they are attached, not shown). An interstitial in its 60-second cooldown answers false; an app-open ad older than 4 hours answers false. In Automatic mode this is always false.

AppMintAds.load helper#

window.AppMintAds.load(type)

Example

Loads a full-screen ad so that a later show(type) can play it at once.

Returns: a Promise that resolves (never rejects) with {ok:true}, {ok:true, cached:true} when one is already waiting, or {ok:false, code, reason, message?, adCode?}. Needs: Ad Control β†’ Developer-controlled and an ad unit ID for the type (interstitial, rewarded, rewarded_interstitial, app_open).

var ads = window.AppMintAds;
var btn = document.getElementById('watch-ad');
btn.disabled = true;

async function prepareRewarded() {
  if (!ads) { btn.hidden = true; return; }          // browser: no ads
  var res = await ads.load('rewarded');
  if (res.ok) { btn.disabled = false; return; }
  if (res.code === 'no_fill' || res.code === 'network') setTimeout(prepareRewarded, 30000);
  else console.log('rewarded ad unavailable:', res.code, res.message);
}
prepareRewarded();

The whole life of every request also streams on the appmint:ad event:

window.addEventListener('appmint:ad', function (e) {
  var a = e.detail;   // {type, phase, slot?, code?, adCode?, message?, width?, height?, reward?, earned?}
  // phase: loading, loaded, failed, opened, impression, clicked, closed, rewarded, show_failed, opt_in, declined
  if (a.phase === 'failed') console.log(a.type, 'failed to load:', a.code, a.adCode, a.message);
});
// The same object also goes to window.onAppMintAd, if you define that function.
window.onAppMintAd = function (a) { console.log('ad', a.type, a.phase); };

Load the interstitial when a level starts, so it is there when the level ends, and stop asking once the user has removed ads:

var ads = window.AppMintAds;
var interstitialsOff = false;

async function startLevel(n) {
  runLevel(n);                                          // your own game
  if (!ads || interstitialsOff) return;
  var res = await ads.load('interstitial');
  if (!res.ok && (res.code === 'ad_free' || res.code === 'disabled' || res.code === 'automatic_mode')) {
    interstitialsOff = true;                            // no interstitials in this app session
  }
}

async function levelEnded() {
  if (ads && !interstitialsOff && ads.isReady('interstitial')) await ads.show('interstitial');
  showLevelSummary();
}

Notes: Codes: no_fill, network, invalid_unit, internal (from AdMob), automatic_mode, unknown_type, ad_free, disabled, consent, and use_slot for banner / mrec (use AppMintAds.banner.attach). The app already preloads each full-screen type with a unit ID and loads the next one after every show, so load is mostly for knowing when the button may be enabled.

AppMintAds.show helper#

window.AppMintAds.show(type)

Example

Shows a loaded full-screen ad: interstitial, rewarded, rewarded_interstitial or app_open.

Returns: a Promise that resolves (never rejects) when the ad has closed: {ok:true, shown:true, earned, reward?} (reward = {type, amount} for the rewarded formats), or {ok:false, shown:false, code, reason, message?} when nothing was shown. Needs: Ad Control β†’ Developer-controlled and an ad unit ID for the type.

Rewarded: call it inside the user's own tap. That tap is the user's opt-in, so the ad plays at once. Grant only when earned is true:

var ads = window.AppMintAds;

document.getElementById('watch-ad').addEventListener('click', async function () {
  if (!ads) return;                                  // browser: no ads here
  var res = await ads.show('rewarded');
  if (res.earned) addCoins(res.reward.amount);
  else if (res.reason === 'ad_free') addCoins(50);   // user paid to remove ads: reward anyway
  else if (res.shown) showMessage('Watch to the end to get the reward.');
  else if (res.reason !== 'declined') showMessage('No ad available right now.');
});

Interstitial: at a natural break. Continue whatever the answer is:

async function levelFinished() {
  if (window.AppMintAds && window.AppMintAds.isReady('interstitial')) {
    await window.AppMintAds.show('interstitial');    // resolves when the ad closes
  }
  showNextLevel();
}

Rewarded interstitial behind your own intro screen with a clear "No thanks". The tap on "Watch" is the opt-in:

var ads = window.AppMintAds;
var intro = document.getElementById('free-life-intro');   // your own dialog: Watch / No thanks

function offerFreeLife() {
  if (ads && ads.isReady('rewarded_interstitial')) intro.hidden = false;
  else gameOver();
}

document.getElementById('free-life-watch').addEventListener('click', async function () {
  intro.hidden = true;
  var res = await ads.show('rewarded_interstitial');
  if (res.earned) continueWithExtraLife();
  else gameOver();
});

document.getElementById('free-life-skip').addEventListener('click', function () {
  intro.hidden = true;
  gameOver();
});

Notes: A rewarded or rewarded-interstitial show() that does NOT come from a user tap (a timer, page load, route change) first gets a "Watch an ad? / No thanks" screen, and "No thanks" answers declined. Never trigger rewarded formats that way. Other codes: automatic_mode, unknown_type, ad_free, disabled, consent, not_full_screen, showing, cooldown (60 s between interstitials), not_loaded (a new load starts for next time), show_failed, internal. In Automatic mode use WebToApk.showRewardedAd().

adsAppOpenEnable bridge#

window.WebToApk.adsAppOpenEnable(enabled: Boolean)

Lets the page pause/resume the app-open ad on return from another app (on by default when a unit is set).

Example

The native half of AppMintAds.appOpen.setEnabled(on): it pauses or resumes the app-open ad. Pages use AppMintAds.appOpen.setEnabled.

Returns: nothing. Needs: Ad Control β†’ Developer-controlled and an app-open ad unit ID.

function setAppOpenAds(on) {
  if (window.AppMintAds) window.AppMintAds.appOpen.setEnabled(on);
}

// Pause while the user is in the middle of something, resume afterwards.
function startCheckout() { setAppOpenAds(false); showCheckout(); }
function checkoutDone()  { setAppOpenAds(true); }

Picking a photo leaves the app for the system picker, and coming back would count as a return. Pause the app-open ad around it:

var input = document.getElementById('photo');

function appOpenAds(on) {
  if (window.AppMintAds) window.AppMintAds.appOpen.setEnabled(on);
}

input.addEventListener('click', function () { appOpenAds(false); });
function pickerDone() { setTimeout(function () { appOpenAds(true); }, 1000); }
input.addEventListener('change', pickerDone);
input.addEventListener('cancel', pickerDone);

Members of your own paid plan never see it. The switch is not saved, so set it early on every launch:

// Runs from a <head> script: window.AppMintAds is already there at document start.
var plan = null;
try { plan = localStorage.getItem('plan'); } catch (e) {}

if (window.AppMintAds && plan === 'pro') {
  window.AppMintAds.appOpen.setEnabled(false);
}

Notes: Raw signature adsAppOpenEnable(enabled). On by default when an app-open unit is set. The app-open ad shows at cold start (if it loads within 15 seconds) and when the user comes back from another app; an ad older than 4 hours is never shown.

adsConfig bridge#

window.WebToApk.adsConfig(): String

`{control, formats:{banner, mrec, interstitial, rewarded, rewarded_interstitial, app_open}}` - the ad formats this build can serve.

Example

Reads which ad formats this build can serve and which ad control mode it uses. Pages normally call AppMintAds.config(), which parses this for you.

Returns: a JSON string: {"control":"automatic"|"developer","formats":{"banner":bool,"mrec":bool,"interstitial":bool,"rewarded":bool,"rewarded_interstitial":bool,"app_open":bool}}. A format is true when ads are on and the build has an ad unit ID for it. Needs: nothing.

The public API first:

if (window.AppMintAds) {
  var cfg = window.AppMintAds.config();      // already parsed; {control:'automatic', formats:{}} on error
  if (cfg.control === 'developer' && cfg.formats.rewarded) {
    document.getElementById('watch-ad').hidden = false;
  }
}

The raw call, if you do not use AppMintAds:

var b = window.WebToApk;
if (b && typeof b.adsConfig === 'function') {
  try {
    var cfg = JSON.parse(b.adsConfig());
    console.log(cfg.control, cfg.formats);
  } catch (e) { console.log('could not read ad config', e); }
}

Reserve space only for the formats this build really has, and give the empty box back to the page otherwise:

var box = document.getElementById('sidebar-ad');
var cfg = window.AppMintAds ? window.AppMintAds.config() : { control: 'automatic', formats: {} };

if (cfg.control === 'developer' && cfg.formats.mrec) {
  window.AppMintAds.banner.attach(box, { size: 'mrec' });
} else if (cfg.control === 'developer' && cfg.formats.banner) {
  window.AppMintAds.banner.attach(box, { size: 'banner' });
} else {
  box.remove();                                   // browser, Automatic mode, or no unit: no empty gap
}

Notes: In Automatic mode formats still tells you which units exist, but AppMintAds.load/show answer code: 'automatic_mode'; use WebToApk.showRewardedAd() / showInterstitialAd() there.

adsIsReady bridge#

window.WebToApk.adsIsReady(type: String): Boolean

True when a full-screen [type] is loaded and can be shown right now.

Example

Tells you whether a full-screen ad of one type is loaded and can show now (Developer-controlled ads). Pages normally call AppMintAds.isReady(type).

Returns: true / false, synchronously. type is 'interstitial', 'rewarded', 'rewarded_interstitial' or 'app_open'; 'banner' / 'mrec' always answer false. Needs: Ad Control β†’ Developer-controlled and an ad unit ID for that type.

The public API first:

var watchBtn = document.getElementById('watch-ad');
function refresh() {
  watchBtn.disabled = !(window.AppMintAds && window.AppMintAds.isReady('rewarded'));
}
refresh();
window.addEventListener('appmint:ad', refresh);     // loaded / closed / failed all change readiness

The raw call:

var b = window.WebToApk;
var ready = !!(b && typeof b.adsIsReady === 'function' && b.adsIsReady('interstitial'));

Show an interstitial at the end of a level only when one is waiting, and never hold the player back when it is not:

function onLevelComplete() {
  var ads = window.AppMintAds;
  if (ads && ads.isReady('interstitial')) {
    // resolves after the ad closes, or at once with {ok:false, code} if it could not show
    ads.show('interstitial').then(startNextLevel);
  } else {
    startNextLevel();                             // not loaded yet, or inside the 60-second cooldown
  }
}

Notes: An interstitial inside its 60-second cooldown answers false. An app-open ad older than 4 hours answers false (Google says not to show it). In Automatic mode this is always false; use WebToApk.isRewardedAdReady() there.

adsLoad bridge#

window.WebToApk.adsLoad(type: String, callbackId: String)

Loads a full-screen [type]; the promise for [callbackId] resolves with the outcome.

Example

The native half of AppMintAds.load(type): it starts loading a full-screen ad and answers the shim's callback. Pages use AppMintAds.load(type), which returns a Promise; do not call this directly.

Returns: (through AppMintAds.load) a Promise of {ok:true}, {ok:true, cached:true} when one is already loaded, or {ok:false, code, reason, message?, adCode?}. Needs: Ad Control β†’ Developer-controlled and an ad unit ID for the type.

var watchBtn = document.getElementById('watch-ad');
watchBtn.disabled = true;

if (window.AppMintAds) {
  window.AppMintAds.load('rewarded').then(function (res) {
    if (res.ok) watchBtn.disabled = false;
    else console.log('rewarded ad not loaded:', res.code, res.message);
  });
} else {
  watchBtn.hidden = true;                       // browser: there are no ads here
}

Retry a load that failed for a passing reason, and stop for the reasons a retry cannot fix:

function loadWithRetry(type, attempt) {
  attempt = attempt || 0;
  return window.AppMintAds.load(type).then(function (res) {
    if (res.ok) return res;
    if ((res.code === 'no_fill' || res.code === 'network') && attempt < 3) {
      return new Promise(function (wait) { setTimeout(wait, 5000 * (attempt + 1)); })
        .then(function () { return loadWithRetry(type, attempt + 1); });
    }
    return res;                                   // invalid_unit, consent, ad_free, disabled ...
  });
}

if (window.AppMintAds) {
  loadWithRetry('rewarded').then(function (res) {
    document.getElementById('watch-ad').disabled = !res.ok;
  });
}

Warm two formats before a game session starts and hide every ad button when the user has no ads at all:

if (window.AppMintAds) {
  Promise.all([
    window.AppMintAds.load('interstitial'),
    window.AppMintAds.load('rewarded_interstitial')
  ]).then(function (results) {
    var noAds = results.some(function (r) { return r.code === 'ad_free' || r.code === 'disabled'; });
    if (noAds) hideAllAdButtons();                // paid to remove ads, or ads are off in this build
    results.forEach(function (r) {
      if (!r.ok && r.adCode !== undefined) console.log('ad load failed', r.code, r.adCode, r.message);
    });
  });
}

Notes: Failure codes: no_fill, network, invalid_unit, internal (from AdMob, with adCode), and automatic_mode, unknown_type, ad_free, disabled, consent, and use_slot for banner / mrec (those load with AppMintAds.banner.attach). The app already preloads every full-screen type that has a unit ID, and reloads after each show. The raw signature is adsLoad(type, callbackId); its answer goes to an internal resolver, so a direct call gives you no result.

adsShow bridge#

window.WebToApk.adsShow(type: String, callbackId: String, userGesture: Boolean)

Shows a loaded full-screen [type]; resolves when it closes, with earned/reward for rewarded formats. [userGesture] is the shim's `navigator.userActivation` reading - a rewarded format asked for without one gets AdMob's intro screen first (see DeveloperAds.offerRewarded).

Example

The native half of AppMintAds.show(type): it shows a loaded full-screen ad and answers when it closes. Pages use AppMintAds.show(type); do not call this directly.

Returns: (through AppMintAds.show) a Promise of {ok:true, shown:true, earned, reward?} after the ad closes (reward = {type, amount} for rewarded formats), or {ok:false, shown:false, code, reason, message?}. Needs: Ad Control β†’ Developer-controlled and an ad unit ID for the type.

Call it from the user's tap. The shim passes navigator.userActivation.isActive as the third argument (userGesture):

document.getElementById('watch-ad').addEventListener('click', function () {
  if (!window.AppMintAds) return;
  window.AppMintAds.show('rewarded').then(function (res) {
    if (res.earned) addCoins(res.reward.amount);
    else if (res.reason === 'ad_free') addCoins(50);      // paid to remove ads: reward anyway
    else if (res.reason !== 'declined') showMessage('No ad available right now.');
  });
});

Offer a rewarded interstitial from the user's own "Double my bonus" tap, and move on whatever the answer:

document.getElementById('double-bonus').addEventListener('click', function () {
  if (!window.AppMintAds) return goToNextLevel();
  window.AppMintAds.show('rewarded_interstitial').then(function (res) {
    if (res.earned) doubleLevelBonus();
    else if (!res.shown && res.code !== 'declined') console.log('no ad:', res.code);
    goToNextLevel();                              // shown or not, the player keeps going
  });
});

Pause your game music while any full-screen ad is on screen, using the appmint:ad stream:

window.addEventListener('appmint:ad', function (e) {
  var d = e.detail;                               // {type, phase, code?, adCode?, message?, earned?, reward?}
  if (d.type === 'banner' || d.type === 'mrec') return;
  if (d.phase === 'opened') music.pause();
  if (d.phase === 'closed' || d.phase === 'show_failed') music.play();
});

function onChapterSaved() {
  if (window.AppMintAds) window.AppMintAds.show('interstitial');   // a natural break
}

Notes: Raw signature adsShow(type, callbackId, userGesture). When userGesture is false for rewarded or rewarded_interstitial, the app first asks "Watch an ad? / No thanks" and a "no" answers declined. Other codes: automatic_mode, unknown_type, ad_free, disabled, consent, not_full_screen, showing, cooldown (interstitial, 60 s), not_loaded (a load starts for next time), show_failed, internal.

adsSlotAttach bridge#

window.WebToApk.adsSlotAttach(slotId: String, type: String, widthCss: Int, callbackId: String)

Creates a banner/mrec overlay for the page's slot [slotId], sized to [widthCss]; resolves with the ad's size once loaded.

Example

The native half of AppMintAds.banner.attach(element, {size}): it creates a banner or medium-rectangle ad over a page element. Pages use AppMintAds.banner.attach; do not call this directly.

Returns: (through AppMintAds.banner.attach) a Promise of {ok:true, slot, width, height} in CSS pixels once the ad has loaded, or {ok:false, slot, code, message?}. Needs: Ad Control β†’ Developer-controlled and a banner (or medium rectangle) ad unit ID.

<div id="ad-slot"></div>

<script>
if (window.AppMintAds) {
  window.AppMintAds.banner.attach('#ad-slot', { size: 'banner' }).then(function (res) {
    if (!res.ok) console.log('no banner:', res.code);   // e.g. no_fill, network, too_many_slots
  });
}
</script>

A medium rectangle on a results screen that gives its space back when no ad comes:

var box = document.getElementById('result-ad');

if (window.AppMintAds) {
  window.AppMintAds.banner.attach(box, { size: 'mrec' }).then(function (res) {
    if (res.ok) return;                           // the box now has min-height = res.height
    window.AppMintAds.banner.detach(res.slot);    // free the slot for another banner
    box.hidden = true;
    if (res.code === 'too_many_slots') console.log('detach a banner first:', res.message);
  });
} else {
  box.hidden = true;
}

Count impressions and clicks per slot from the appmint:ad stream, keyed by the slot id attach returned:

var slotNames = {};

if (window.AppMintAds) {
  window.AppMintAds.banner.attach('#footer-ad', { size: 'banner' }).then(function (res) {
    if (res.ok) slotNames[res.slot] = 'footer';
  });
}

window.addEventListener('appmint:ad', function (e) {
  var d = e.detail;                               // banner events carry {type, phase, slot}
  if (d.slot && (d.phase === 'impression' || d.phase === 'clicked')) {
    track('ad_' + d.phase, slotNames[d.slot] || d.slot);
  }
});

Notes: Raw signature adsSlotAttach(slotId, type, widthCss, callbackId); type is 'banner' or 'mrec'. A WebView cannot hold a native view inside the page, so the ad is drawn over your element and follows it on scroll and resize (see adsSlotRect). At most two slots at once, and only one medium rectangle. Extra codes: duplicate_slot, too_many_slots, detached, internal, plus the load codes (no_fill, network, invalid_unit, ad_free, disabled, consent, automatic_mode).

adsSlotDetach bridge#

window.WebToApk.adsSlotDetach(slotId: String)

Removes a banner overlay.

Example

The native half of AppMintAds.banner.detach(slotOrElement): it removes a banner overlay and frees its slot. Pages use AppMintAds.banner.detach.

Returns: nothing. Needs: Ad Control β†’ Developer-controlled.

Remove the banner before a screen where an ad does not belong (a video player, a checkout):

var slotEl = document.getElementById('ad-slot');

function openPlayer() {
  if (window.AppMintAds) window.AppMintAds.banner.detach(slotEl);   // the element, or the slot id from attach()
  showPlayer();
}

function closePlayer() {
  hidePlayer();
  if (window.AppMintAds) window.AppMintAds.banner.attach(slotEl, { size: 'banner' });
}

In a single-page app, free the banner when the route that holds it goes away. attach marks the element straight away, so detaching by element works even while the ad is still loading:

function onRouteChange(route) {
  if (!window.AppMintAds) return renderRoute(route);
  var old = document.getElementById('article-ad');
  if (old) window.AppMintAds.banner.detach(old);
  renderRoute(route);
  if (route === '/articles') {
    window.AppMintAds.banner.attach('#article-ad', { size: 'banner' });
  }
}

After a "Remove Ads" purchase the overlays are gone; detach the slots too and drop the space they reserved:

window.addEventListener('appmint:premium', function (e) {
  if (!e.detail.premium || !window.AppMintAds) return;
  document.querySelectorAll('[data-appmint-ad-slot]').forEach(function (el) {
    window.AppMintAds.banner.detach(el);
    el.remove();
  });
});

Notes: Raw signature adsSlotDetach(slotId). detach takes the element or the slot string that attach returned, not a CSS selector. An attach Promise still waiting for its ad answers {ok:false, code:'detached'}. Removing the element from the DOM also detaches the slot.

adsSlotRect bridge#

window.WebToApk.adsSlotRect(slotId: String, x: Float, y: Float, w: Float, h: Float, visible: Boolean)

Internal: the slot element's current viewport rectangle (CSS px) - moves the overlay.

Example

Internal: moves a banner overlay to its element's current position. The AppMintAds.banner helper calls it for you on scroll, resize, visibility change and every 500 ms; pages use AppMintAds.banner.attach and never call this.

Returns: nothing. Needs: Ad Control β†’ Developer-controlled, and a slot created by AppMintAds.banner.attach.

All a page does is give the slot element a place in the layout. The helper measures it and keeps the ad on top of it:

<main>
  <article id="story">…</article>
  <div id="ad-slot" style="margin: 16px 0"></div>   <!-- the ad follows this box while scrolling -->
</main>

<script>
if (window.AppMintAds) window.AppMintAds.banner.attach('#ad-slot', { size: 'banner' });
</script>

A banner inside a scrolling list, not the page: the helper listens to scroll in the capture phase, so the ad follows the inner container too:

<div id="feed" style="height: 100vh; overflow-y: auto">
  <div class="card">…</div>
  <div class="card">…</div>
  <div id="feed-ad" style="margin: 12px 0"></div>
  <div class="card">…</div>
</div>

<script>
if (window.AppMintAds) window.AppMintAds.banner.attach('#feed-ad', { size: 'banner' });
</script>

The ad is drawn above the page, so your own dialog would sit under it. Hide the slot while the dialog is open:

var slotEl = document.getElementById('ad-slot');
var dialog = document.getElementById('settings-dialog');

function openSettings() {
  slotEl.style.visibility = 'hidden';             // the helper hides the overlay on its next tick
  dialog.showModal();
}

dialog.addEventListener('close', function () {
  slotEl.style.visibility = '';                   // and puts it back over the box
});

Notes: Raw signature adsSlotRect(slotId, x, y, w, h, visible), in CSS pixels relative to the viewport. The overlay is hidden while the element is off screen, has zero size, has visibility: hidden, or the page is in the background. If the element leaves the DOM, the helper detaches the slot by itself.

isInterstitialAdReady bridge#

window.WebToApk.isInterstitialAdReady(): Boolean

True when an interstitial is loaded AND past its cooldown (matches exactly what showInterstitialAd() will accept - no "ready" that then refuses to show).

Example

Tells you whether an interstitial ad is loaded AND past its 60-second cooldown, so showInterstitialAd() would show it now (Automatic ad control).

Returns: true / false, synchronously. Needs: Ads on and Full Screen Ad β†’ Interstitial Ad.

function onLevelDone() {
  var b = window.WebToApk;
  if (b && typeof b.isInterstitialAdReady === 'function' && b.isInterstitialAdReady()) {
    b.showInterstitialAd();               // wait for 'appmint:ad-closed' before continuing
  } else {
    startNextLevel();                     // nothing ready: never block the user
  }
}

window.addEventListener('appmint:ad-closed', function (e) {
  if (e.detail.adType === 'interstitial') startNextLevel();
});

Show an ad after every third finished quiz, and only when one would really show:

var finished = 0;

function onQuizFinished() {
  finished++;
  var b = window.WebToApk;
  var ready = !!(b && typeof b.isInterstitialAdReady === 'function' && b.isInterstitialAdReady());
  if (finished % 3 === 0 && ready) b.showInterstitialAd();
  showResults();                                  // the results are there when the ad closes
}

Show a short "Ad in 3…" notice only when an ad is ready, then continue when it closes or cannot show:

var afterAd = null;

function adBreakThen(next) {
  var b = window.WebToApk;
  if (!b || typeof b.isInterstitialAdReady !== 'function' || !b.isInterstitialAdReady()) return next();
  var note = document.getElementById('ad-break'), n = 3;
  note.hidden = false; note.textContent = 'Ad in 3…';
  var t = setInterval(function () {
    if (--n > 0) { note.textContent = 'Ad in ' + n + '…'; return; }
    clearInterval(t); note.hidden = true;
    afterAd = next;
    b.showInterstitialAd();
  }, 1000);
}

function runAfterAd(e) {
  if (e.detail.adType !== 'interstitial' || !afterAd) return;
  var next = afterAd; afterAd = null; next();
}
window.addEventListener('appmint:ad-closed', runAfterAd);
window.addEventListener('appmint:ad-unavailable', runAfterAd);

Notes: true here means the show will not be refused for not_loaded or cooldown. Consent or a "Remove Ads" purchase can still stop it (appmint:ad-unavailable). In Developer-controlled builds use AppMintAds.isReady('interstitial').

isRewardedAdReady bridge#

window.WebToApk.isRewardedAdReady(): Boolean

True when a rewarded ad is loaded and can be shown right now. Let the page enable/disable its "Watch ad" button off this (poll or call before showing).

Example

Tells you whether a rewarded ad is loaded and can be shown right now (Automatic ad control).

Returns: true / false, synchronously. Needs: Ads on and Full Screen Ad β†’ Rewarded Ad.

Enable the "Watch an ad" button only when an ad is waiting:

var btn = document.getElementById('earn');

function refreshWatchButton() {
  var b = window.WebToApk;
  var ready = !!(b && typeof b.isRewardedAdReady === 'function' && b.isRewardedAdReady());
  btn.disabled = !ready;
  btn.textContent = ready ? 'Watch an ad for 10 coins' : 'Loading ad…';
}
refreshWatchButton();
setInterval(refreshWatchButton, 2000);                 // an ad can finish loading at any time
window.addEventListener('appmint:ad-closed', refreshWatchButton);

On game over, offer "Continue" only when an ad is waiting, so the button never leads nowhere:

var continueBtn = document.getElementById('continue-with-ad');

function onGameOver() {
  var b = window.WebToApk;
  continueBtn.hidden = !(b && typeof b.isRewardedAdReady === 'function' && b.isRewardedAdReady());
  showGameOverPanel();                            // "Restart" is always there
}

continueBtn.addEventListener('click', function () {   // the user's own tap
  continueBtn.hidden = true;
  window.WebToApk.showRewardedAd();
});

window.addEventListener('appmint:reward', function () { revivePlayer(); });

Check at the moment of the tap and tell the user what is happening instead of firing a show that will be refused:

document.getElementById('hint').addEventListener('click', function () {
  var b = window.WebToApk;
  if (!b || typeof b.isRewardedAdReady !== 'function') return showMessage('Hints need the app.');
  if (typeof b.isPremium === 'function' && b.isPremium()) return revealHint();   // paid to remove ads
  if (b.isRewardedAdReady()) b.showRewardedAd();
  else showMessage('The ad is still loading. Try again in a few seconds.');
});

window.addEventListener('appmint:reward', function () { revealHint(); });

Notes: Returns false in a browser, when ads were removed by purchase, and before the first ad has loaded. After an ad is shown the next one loads by itself. In Developer-controlled builds use AppMintAds.isReady('rewarded').

notifyUserAction bridge#

window.WebToApk.notifyUserAction()

Internal (called by the injected tap counter, not creator code): one meaningful user interaction happened in the page. Counts toward the "interstitial every N actions" cadence ONLY while no URL-based navigation has ever been observed - single-HTML apps that switch screens via DOM show/hide never change the URL, so without this their navigation counter stayed at 0 forever and configured ads never showed (TicTrek complaint class, 2026-07-26). URL/SPA apps are unaffected: their first real navigation permanently switches counting back to URL signals.

Example

Counts one meaningful user action toward the "full-screen ad every N actions" setting. The app calls this itself from its own tap counter; pages normally never call it.

Returns: nothing. Needs: Ads on, a Full Screen Ad with Trigger Mode β†’ After Navigations, and Automatic ad control.

How it works: a page that switches screens by hiding and showing DOM elements never changes its URL, so the app cannot see a "navigation". The app therefore injects a tap counter (at most one count per 2 seconds) that calls this method. As soon as the app sees one real URL change (a page load, pushState or a #hash), it counts URL changes instead and this method stops counting.

Only if your screen changes do not come from a tap (for example a keyboard or gamepad move), report them yourself:

function goToScreen(name) {
  showScreen(name);                                   // your own DOM show/hide
  var b = window.WebToApk;
  if (b && typeof b.notifyUserAction === 'function') b.notifyUserAction();
}

document.addEventListener('keydown', function (e) {
  if (e.key === 'Enter') goToScreen('next');
});

A card-style app where the user swipes to the next screen (a swipe is not a tap on a button, so the tap counter never sees it):

var startX = null;

document.addEventListener('touchstart', function (e) { startX = e.touches[0].clientX; }, { passive: true });

document.addEventListener('touchend', function (e) {
  if (startX === null) return;
  var dx = e.changedTouches[0].clientX - startX;
  startX = null;
  if (Math.abs(dx) < 80) return;                       // not a swipe
  showScreen(dx < 0 ? 'next' : 'previous');            // your own DOM show/hide
  var b = window.WebToApk;
  if (b && typeof b.notifyUserAction === 'function') b.notifyUserAction();
});

An HTML5 game played with a gamepad, counting one action per level the player starts with the A button:

var aWasDown = false;

function pollPad() {
  var pad = navigator.getGamepads ? navigator.getGamepads()[0] : null;
  var aDown = !!(pad && pad.buttons[0] && pad.buttons[0].pressed);
  if (aDown && !aWasDown && isOnLevelSelect()) {       // your own game state
    startSelectedLevel();
    var b = window.WebToApk;
    if (b && typeof b.notifyUserAction === 'function') b.notifyUserAction();
  }
  aWasDown = aDown;
  requestAnimationFrame(pollPad);
}

window.addEventListener('gamepadconnected', function () { requestAnimationFrame(pollPad); }, { once: true });

Notes: It does nothing when ads are off, removed by purchase, or when the build uses Ad Control β†’ Developer-controlled (there you call AppMintAds.show() yourself). A rewarded ad reached this way is always offered with a "Watch an ad? / No thanks" question, never forced. Do not call it on every tap; that would show ads more often than you configured.

showInterstitialAd bridge#

window.WebToApk.showInterstitialAd()

Show an interstitial NOW (e.g. between levels / on a deliberate action). Not showable right now β†’ skipped + 'appmint:ad-unavailable' with the reason.

Example

Shows a full-screen interstitial ad right now, for example between two game levels (Automatic ad control).

Returns: nothing. When the ad closes, appmint:ad-closed fires with {adType:'interstitial', earned:false}. When it cannot show, appmint:ad-unavailable fires with {adType:'interstitial', reason}. Needs: Ads on and Full Screen Ad β†’ Interstitial Ad. Choose Trigger Mode β†’ On Demand if only your page should decide when.

var bridge = window.WebToApk;
var waitingForAd = false;

function levelFinished() {
  if (bridge && typeof bridge.isInterstitialAdReady === 'function' && bridge.isInterstitialAdReady()) {
    waitingForAd = true;
    bridge.showInterstitialAd();          // the next level starts when the ad closes
  } else {
    showNextLevel();                      // no ad ready: just continue
  }
}

window.addEventListener('appmint:ad-closed', function (e) {
  if (e.detail.adType === 'interstitial' && waitingForAd) { waitingForAd = false; showNextLevel(); }
});

window.addEventListener('appmint:ad-unavailable', function (e) {
  // reason: ad_free | disabled | consent | not_loaded | cooldown | show_failed
  if (e.detail.adType === 'interstitial' && waitingForAd) { waitingForAd = false; showNextLevel(); }
});

Pause the game and its sound before the ad, and resume on either answer:

var pausedForAd = false;

function adBetweenRounds() {
  var b = window.WebToApk;
  if (!b || typeof b.showInterstitialAd !== 'function') return;
  pausedForAd = true;
  pauseGame(); music.pause();
  b.showInterstitialAd();
}

function resumeAfterAd(e) {
  if (e.detail.adType !== 'interstitial' || !pausedForAd) return;
  pausedForAd = false;
  music.play(); resumeGame();
}
window.addEventListener('appmint:ad-closed', resumeAfterAd);
window.addEventListener('appmint:ad-unavailable', resumeAfterAd);

Open an article after an ad, as one Promise that resolves true when the ad was shown and false when it was skipped:

function interstitialThen() {
  return new Promise(function (resolve) {
    var b = window.WebToApk;
    if (!b || typeof b.isInterstitialAdReady !== 'function' || !b.isInterstitialAdReady()) return resolve(false);
    function done(e) {
      if (e.detail.adType !== 'interstitial') return;
      window.removeEventListener('appmint:ad-closed', done);
      window.removeEventListener('appmint:ad-unavailable', done);
      resolve(e.type === 'appmint:ad-closed');
    }
    window.addEventListener('appmint:ad-closed', done);
    window.addEventListener('appmint:ad-unavailable', done);
    b.showInterstitialAd();
  });
}

interstitialThen().then(function () { openArticle(articleId); });

Notes: The app keeps at least 60 seconds between two interstitials; a call inside that gap answers cooldown. isInterstitialAdReady() already includes the cooldown, so checking it first avoids the event. Show interstitials at natural breaks, never on every tap. In Developer-controlled builds use AppMintAds.show('interstitial').

showRewardedAd bridge#

window.WebToApk.showRewardedAd()

Show a rewarded ad NOW (user-initiated - the only policy-compliant way to show rewarded). Pair it with trigger mode "On Demand" in the wizard to get rewarded ads with NO automatic app-open/navigation ads at all. The reward is delivered back to the page via deliverRewardToPage(): window.addEventListener('appmint:reward', e => { /* e.detail = {type,amount} */ }) When the ad closes, 'appmint:ad-closed' fires with {adType:'rewarded', earned} - earned is false if the user backed out early, so grant nothing in that case. Not showable right now β†’ skipped + 'appmint:ad-unavailable' fired with the reason.

Example

Shows a rewarded ad right now, from the user's own "Watch an ad" tap (Automatic ad control).

Returns: nothing. The outcome arrives later as events: appmint:reward {type, amount} when the ad was watched to the end, appmint:ad-closed {adType:'rewarded', earned} when it closes, or appmint:ad-unavailable {adType:'rewarded', reason} when nothing could show. Needs: Ads on, Full Screen Ad β†’ Rewarded Ad, and Trigger Mode β†’ On Demand so nothing shows by itself.

Grant the reward only in the reward handler, never on the tap:

var btn = document.getElementById('earn');
var bridge = window.WebToApk;

function refresh() {
  btn.disabled = !(bridge && typeof bridge.isRewardedAdReady === 'function' && bridge.isRewardedAdReady());
}
refresh();
setInterval(refresh, 2000);

btn.addEventListener('click', function () {        // a real user tap: the only place to call it
  if (!bridge || typeof bridge.showRewardedAd !== 'function') return;
  btn.disabled = true;
  bridge.showRewardedAd();
});

// GRANT HERE ONLY: the ad was watched to the end.
window.addEventListener('appmint:reward', function (e) {
  addCoins(e.detail.amount);                        // e.detail = {type, amount}
});

// The ad closed. earned === false means the user left early: grant nothing.
window.addEventListener('appmint:ad-closed', function (e) {
  if (e.detail.adType === 'rewarded' && !e.detail.earned) showMessage('No reward: the ad was closed early.');
  refresh();
});

// Nothing could be shown.
window.addEventListener('appmint:ad-unavailable', function (e) {
  // e.detail = {adType, reason}; reason: ad_free | disabled | consent | not_loaded | show_failed
  if (e.detail.reason === 'ad_free') addCoins(1);   // the user paid to remove ads: reward anyway
  else showMessage('No ad right now. Try again in a moment.');
  refresh();
});

The same three moments also call page hooks, if you prefer one function each (each gets the same object as e.detail):

window.onAppMintReward = function (reward) { addCoins(reward.amount); };      // {type, amount}
window.onAppMintAdClosed = function (info) { console.log(info.adType, info.earned); };
window.onAppMintAdUnavailable = function (info) { console.log(info.adType, info.reason); };

Wrap the call and its events in one Promise, so each "Watch an ad" button reads like a normal async call:

function watchRewardedAd() {                      // call it inside the user's tap
  return new Promise(function (resolve) {
    var b = window.WebToApk;
    if (!b || typeof b.showRewardedAd !== 'function') return resolve({ earned: false, reason: 'no_app' });
    function done(e) {
      if (e.detail.adType !== 'rewarded') return;
      window.removeEventListener('appmint:ad-closed', done);
      window.removeEventListener('appmint:ad-unavailable', done);
      resolve(e.type === 'appmint:ad-closed' ? { earned: e.detail.earned } : { earned: false, reason: e.detail.reason });
    }
    window.addEventListener('appmint:ad-closed', done);
    window.addEventListener('appmint:ad-unavailable', done);
    b.showRewardedAd();
  });
}

document.getElementById('revive').addEventListener('click', function () {
  watchRewardedAd().then(function (r) {
    if (r.earned || r.reason === 'ad_free') revivePlayer();
    else if (r.reason) showMessage('No ad right now.');
  });
});

Notes: Call it only from a click handler. Never call it from a timer, page load or route change, and never put a core feature behind it. not_loaded also starts a new load, so the next tap usually works. In Ad Control β†’ Developer-controlled builds use AppMintAds.show('rewarded') instead, which answers with a Promise.

Generated from the app runtime and its example files on every docs build. Read it as Markdown Β· All families.

Checked against the shipped bridge on 2026-11-04.