Create Free APK

JavaScript bridge API

The push inbox and audience

Messages the creator sent from a dashboard, kept on the device so the page can show a history rather than only a tray pop-up - and the signed-in user id and tags that let the send center target one user or a tag, and fire automations such as an abandoned-cart reminder.

AppMint.trackEvent helper#

window.AppMint.trackEvent

Example

Documented together with WebToApk.trackEvent - the example there shows this one too.

Tells the app that something happened ("signed_up", "checkout_done", "level:3"). An in-app message the creator made in the AppMint Push send center with the trigger "When an event happens" and the same name shows now (or after the wait the creator picked).

Returns: true when the event was taken; false for a name outside letters, digits and _ . : - (up to 64), and in an app built without AppMint Push. AppMint.trackEvent(name, props) is the same call with props as an object. Needs: AppMint Push turned on in the Integrate step (Step 3) when you build. propsJson is optional ('') and is not stored.

// After a purchase: the creator's "Thanks, rate us?" message is set to trigger on "purchase_done".
function onPurchaseDone(order) {
  if (window.AppMint && AppMint.trackEvent) {
    AppMint.trackEvent('purchase_done', { total: order.total });
  }
}

The raw bridge takes the props as a JSON string (pass '' for none):

function track(name, props) {
  if (!(window.WebToApk && window.WebToApk.trackEvent)) return false;   // a browser: nothing to show
  return WebToApk.trackEvent(name, props ? JSON.stringify(props) : '');
}
track('level:3');
track('tutorial_skipped', { step: 2 });

A screen change the address does not show (tabs, a wizard, a modal "page") can be reported as a route, so the creator's "On a page" messages see it. The page can also dispatch the appmint:route event with the path; the app listens for it:

function showTab(name) {
  document.querySelectorAll('.tab').forEach(function (t) { t.hidden = t.id !== name; });
  if (window.AppMint && AppMint.trackEvent) AppMint.trackEvent('appmint:route', { path: '/' + name });
}

// Same thing, without touching the bridge at all:
window.dispatchEvent(new CustomEvent('appmint:route', { detail: { path: '/cart' } }));

Notes: route messages already follow real URL changes (page loads, history.pushState, #/hash routes) on their own; report a route only for views the address does not change. Events are not counted or stored anywhere; they only decide whether a message shows.

clearPushInbox bridge#

window.WebToApk.clearPushInbox(): Boolean

Empties the push inbox (the tray and server history are untouched).

Example

Empties the push inbox on this phone.

Returns: true when done; false on an internal error. Needs: turn on AppMint Push in the Integrate step (Step 3) when you build.

document.getElementById('clear-inbox').addEventListener('click', function () {
  if (!(window.WebToApk && window.WebToApk.clearPushInbox)) return;
  if (!confirm('Delete all messages? This cannot be undone.')) return;
  if (WebToApk.clearPushInbox()) renderInbox();
});

Empty the inbox when the user signs out, so the next person on this phone does not see their messages:

async function signOut() {
  await myAuth.signOut();
  if (window.WebToApk && window.WebToApk.clearPushInbox) {
    WebToApk.clearPushInbox();
  }
  document.getElementById('bell-count').textContent = '';
  showLoginScreen();
}

Show a settings row with how many messages are stored, disabled when there is nothing to delete:

function paintClearRow() {
  const btn = document.getElementById('clear-inbox');
  if (!(window.WebToApk && window.WebToApk.clearPushInbox)) { btn.hidden = true; return; }
  const count = JSON.parse(WebToApk.getPushInbox() || '[]').length;
  btn.textContent = count ? 'Delete ' + count + ' messages' : 'No messages';
  btn.disabled = count === 0;
}

document.getElementById('clear-inbox').addEventListener('click', function () {
  if (WebToApk.clearPushInbox()) paintClearRow();
  else alert('Could not delete the messages. Please try again.');
});

paintClearRow();

Notes: this is permanent - unlike dismissPushMessage, there is no undo. Tray notifications and your send history in AppMint are not touched. New messages still arrive.

dismissPushMessage bridge#

window.WebToApk.dismissPushMessage(id: String): Boolean

Dismisses ONE message from the inbox - the per-row swipe-away an inbox screen needs (clearPushInbox is all-or-nothing). REVERSIBLE by design: the entry is hidden, not destroyed, so the UI can offer "Undo" for a few seconds after the swipe. Local only: the tray notification and the creator's history are untouched.

Example

Hides one message from the push inbox (a swipe-away). It can be undone with restorePushMessage(id).

Returns: true if the message was hidden; false if the id is unknown or it was already hidden. Needs: turn on AppMint Push in the Integrate step (Step 3) when you build.

Swipe away with a 5-second Undo:

function dismissRow(id, rowEl) {
  if (!(window.WebToApk && window.WebToApk.dismissPushMessage)) return;
  if (!WebToApk.dismissPushMessage(id)) return;
  rowEl.remove();

  const bar = document.getElementById('undo-bar');
  bar.hidden = false;
  const timer = setTimeout(function () { bar.hidden = true; }, 5000);
  document.getElementById('undo-btn').onclick = function () {
    clearTimeout(timer);
    bar.hidden = true;
    WebToApk.restorePushMessage(id);         // back in its old place
    renderInbox();
  };
}

Tidy the inbox when it opens by hiding messages that are read and older than 30 days:

function hideOldReadMessages() {
  if (!(window.WebToApk && window.WebToApk.dismissPushMessage)) return 0;
  const cutoff = Date.now() - 30 * 24 * 60 * 60 * 1000;
  let hidden = 0;
  JSON.parse(WebToApk.getPushInbox() || '[]').forEach(function (m) {
    if (m.read && m.receivedAt < cutoff && WebToApk.dismissPushMessage(m.id)) hidden++;
  });
  return hidden;
}

if (hideOldReadMessages() > 0) renderInbox();

Dismiss a row with a real finger swipe (drag it more than 40% of its width):

function makeSwipeable(rowEl, id) {
  let startX = null;
  rowEl.addEventListener('pointerdown', function (e) { startX = e.clientX; rowEl.setPointerCapture(e.pointerId); });
  rowEl.addEventListener('pointermove', function (e) {
    if (startX !== null) rowEl.style.transform = 'translateX(' + (e.clientX - startX) + 'px)';
  });
  rowEl.addEventListener('pointerup', function (e) {
    const dx = startX === null ? 0 : e.clientX - startX;
    startX = null;
    if (Math.abs(dx) > rowEl.offsetWidth * 0.4 && window.WebToApk && WebToApk.dismissPushMessage(id)) {
      rowEl.remove();
    } else {
      rowEl.style.transform = '';            // not far enough: snap back
    }
  });
}

Notes: only this phone's inbox changes. The tray notification and your send history in AppMint stay. Hidden messages are not returned by getPushInbox().

getInAppCampaigns bridge#

window.WebToApk.getInAppCampaigns(): String

In-app messages this install holds: JSON [{id, name}] ("[]" without AppMint Push).

Example

Lists the in-app messages this install holds right now (the creator's campaigns from the AppMint Push send center that this user is in the audience for) as a JSON string [{"id": "...", "name": "..."}].

Returns: a JSON array string; "[]" when there are none and in an app built without AppMint Push. Needs: AppMint Push turned on in the Integrate step (Step 3) when you build. The list refreshes on each app start (and about every hour in the foreground).

var list = [];
if (window.WebToApk && window.WebToApk.getInAppCampaigns) {
  try { list = JSON.parse(WebToApk.getInAppCampaigns()); } catch (e) {}
}
console.log('in-app messages on this phone:', list.map(function (c) { return c.name; }));

Show a debug list in your own admin screen:

function renderInAppDebug(el) {
  var raw = (window.WebToApk && WebToApk.getInAppCampaigns) ? WebToApk.getInAppCampaigns() : '[]';
  var items = JSON.parse(raw);
  el.innerHTML = items.length
    ? items.map(function (c) { return '<li>' + c.name + ' <code>' + c.id + '</code></li>'; }).join('')
    : '<li>No in-app messages for this install</li>';
}
renderInAppDebug(document.getElementById('inapp-debug'));

Skip your own welcome hint when the creator already runs a message with that name:

function hasCampaign(name) {
  try { return JSON.parse(WebToApk.getInAppCampaigns()).some(function (c) { return c.name === name; }); }
  catch (e) { return false; }
}
if (!hasCampaign('Welcome tour')) showMyOwnWelcome();

Notes: only the id and name are exposed; the message itself reaches the page through the appmint:inapp event when it shows (see setInAppPresentation).

getPushInbox bridge#

window.WebToApk.getPushInbox(): String

AppMint Push inbox (creator-sent messages, newest first, max 50): JSON array of {id, title, body, imageUrl, link, receivedAt, read}. Always the full current truth - an app that mounts late loses nothing by having missed the live `appmint:push` events.

Example

Reads the in-app inbox of push messages you sent with AppMint Push: newest first, up to 50, without the ones the user dismissed.

Returns: a JSON string, synchronously. Parse it. Each item: { id, title, body, imageUrl, link, receivedAt, read } (receivedAt in epoch ms; imageUrl and link are '' when the message has none), plus optional design fields when you used a template in AppMint: template, accent, iconUrl, buttons: [{label, link}], badge, inapp, countdownAt, progress, lines, collapse. '[]' when empty. Needs: turn on AppMint Push in the Integrate step (Step 3) when you build; without it the inbox is always empty.

Render the inbox, and refresh it live with appmint:push:

function loadInbox() {
  if (!(window.WebToApk && window.WebToApk.getPushInbox)) return [];
  try { return JSON.parse(WebToApk.getPushInbox() || '[]'); } catch (e) { return []; }
}

function renderInbox() {
  const inbox = loadInbox();
  const unread = inbox.filter(function (m) { return !m.read; }).length;
  document.getElementById('bell-count').textContent = unread ? String(unread) : '';
  const ul = document.getElementById('inbox');
  ul.innerHTML = '';
  inbox.forEach(function (m) {
    const li = document.createElement('li');
    li.className = m.read ? 'read' : 'unread';
    li.textContent = m.title + ' — ' + m.body;
    li.onclick = function () {
      WebToApk.markPushRead(m.id);
      if (m.link) location.href = m.link;
      renderInbox();
    };
    ul.appendChild(li);
  });
}

// While the app is open:
window.addEventListener('appmint:push', function (e) {
  // { type: 'received', message: {id, title, body, ...}, presented: true|false }
  // { type: 'revoked', id }  — you retracted the message
  // presented = the app already showed it on screen (see setPushPresentation)
  if (e.detail.type === 'received' && !e.detail.presented) showBanner(e.detail.message.title);
  renderInbox();
});

renderInbox();

Update the unread count on your bell whenever the user comes back to the app (messages that arrived while it was in the background are already in the inbox):

function unreadCount() {
  if (!(window.WebToApk && window.WebToApk.getPushInbox)) return 0;
  try {
    return JSON.parse(WebToApk.getPushInbox() || '[]').filter(function (m) { return !m.read; }).length;
  } catch (e) { return 0; }
}

function paintBell() {
  const n = unreadCount();
  const badge = document.getElementById('bell-count');
  badge.textContent = n > 9 ? '9+' : (n ? String(n) : '');
  badge.hidden = n === 0;
}

document.addEventListener('visibilitychange', function () {
  if (document.visibilityState === 'visible') paintBell();
});
paintBell();

Show a message's picture and its buttons (only when you designed it with them), with the time it arrived:

function renderCard(m) {
  const card = document.createElement('article');
  if (m.accent) card.style.borderLeft = '4px solid ' + m.accent;
  if (m.imageUrl) {
    const img = document.createElement('img');
    img.src = m.imageUrl; img.alt = '';
    card.appendChild(img);
  }
  const h = document.createElement('h3');
  h.textContent = m.title;
  const p = document.createElement('p');
  p.textContent = m.body;
  const time = document.createElement('time');
  time.textContent = new Date(m.receivedAt).toLocaleString();
  card.append(h, p, time);
  (m.buttons || []).forEach(function (b) {
    const btn = document.createElement('button');
    btn.textContent = b.label;
    btn.onclick = function () { WebToApk.markPushRead(m.id); if (b.link) location.href = b.link; };
    card.appendChild(btn);
  });
  return card;
}

if (window.WebToApk && window.WebToApk.getPushInbox) {
  const list = document.getElementById('inbox');
  JSON.parse(WebToApk.getPushInbox() || '[]').forEach(function (m) { list.appendChild(renderCard(m)); });
}

Notes: the inbox is kept on the phone, so read it when your screen opens - events only arrive while the app is on screen. A message you retract from AppMint disappears from the inbox and the tray. Messages sent as "push only" (tray) are not added to the inbox. The same detail is also passed to window.onAppMintPush(detail) if you define it.

getPushTags bridge#

window.WebToApk.getPushTags(): String

This install's tags as a JSON object string, e.g. {"plan":"pro"}.

Example

Reads this install's AppMint Push tags.

Returns: a JSON string, synchronously - an object of text values, e.g. '{"plan":"pro","cart":"open"}'; '{}' when there are none. Parse it. Needs: turn on AppMint Push in the Integrate step (Step 3) when you build.

Read one tag:

function pushTags() {
  if (!(window.WebToApk && window.WebToApk.getPushTags)) return {};
  try { return JSON.parse(WebToApk.getPushTags() || '{}'); } catch (e) { return {}; }
}

const plan = pushTags().plan || 'free';

Set a tag only when it changes - every setPushTag call makes the install check in with AppMint Push, so skip the ones that change nothing:

function ensureTag(key, value) {
  if (!(window.WebToApk && window.WebToApk.getPushTags)) return;
  const tags = JSON.parse(WebToApk.getPushTags() || '{}');
  if ((tags[key] || '') !== value) WebToApk.setPushTag(key, value);
}

ensureTag('plan', user.isPro ? 'pro' : 'free');

List them on a debug screen while you test sending to a tag:

const list = document.getElementById('push-tags');
if (window.WebToApk && window.WebToApk.getPushTags) {
  const tags = JSON.parse(WebToApk.getPushTags() || '{}');
  list.textContent = Object.keys(tags).length
    ? Object.keys(tags).map(function (k) { return k + ' = ' + tags[k]; }).join('\n')
    : 'No tags yet';
}

Notes: this is what the phone holds; the send center sees it after the install's next check-in, a few seconds after a change when the phone is online.

getPushUser bridge#

window.WebToApk.getPushUser(): String

The user id last set with setPushUser ("" when signed out or never set).

Example

Reads the user id this install last gave AppMint Push with setPushUser.

Returns: a string, synchronously - the saved id, or '' when nobody is signed in or it was never set. Needs: turn on AppMint Push in the Integrate step (Step 3) when you build.

Set the id only when it changed (on every app start):

function syncPushUser(currentUserId) {
  if (!(window.WebToApk && window.WebToApk.getPushUser)) return;
  const want = currentUserId || '';
  if (WebToApk.getPushUser() !== want) WebToApk.setPushUser(want);
}

syncPushUser(localStorage.getItem('userId'));

Clear a stale id when your own session expired while the app was closed:

async function checkSession() {
  const session = await myBackend.currentSession();       // null when expired
  if (!session && window.WebToApk && window.WebToApk.getPushUser && WebToApk.getPushUser() !== '') {
    WebToApk.setPushUser('');                              // stop personal messages to this phone
  }
}

checkSession();

Show it on a debug screen while you test sending to one user:

const info = document.getElementById('push-debug');
if (window.WebToApk && window.WebToApk.getPushUser) {
  const id = WebToApk.getPushUser();
  info.textContent = id ? 'Push user: ' + id : 'Push user: nobody signed in';
} else {
  info.textContent = 'Not inside the app';
}

Notes: this is what the phone holds. The send center sees a change after the install's next check-in, a few seconds later when the phone is online.

markPushRead bridge#

window.WebToApk.markPushRead(id: String): Boolean

Marks one push message read; pass "" to mark every message read.

Example

Marks one push inbox message as read, or all of them when you pass ''.

Returns: true if a message changed; false if nothing changed (unknown id, already read, or AppMint Push not enabled). Needs: turn on AppMint Push in the Integrate step (Step 3) when you build.

function openMessage(id) {
  if (!(window.WebToApk && window.WebToApk.markPushRead)) return;
  WebToApk.markPushRead(id);                 // one message
  updateBellCount();
}

document.getElementById('mark-all-read').addEventListener('click', function () {
  if (!(window.WebToApk && window.WebToApk.markPushRead)) return;
  WebToApk.markPushRead('');                 // every message
  updateBellCount();
});

function updateBellCount() {
  const inbox = JSON.parse(WebToApk.getPushInbox() || '[]');
  const unread = inbox.filter(function (m) { return !m.read; }).length;
  document.getElementById('bell-count').textContent = unread ? String(unread) : '';
}

Mark a message read once the user has actually seen it on screen, not just when they tap it:

if (window.WebToApk && window.WebToApk.markPushRead) {
  const seen = new IntersectionObserver(function (entries) {
    entries.forEach(function (entry) {
      if (!entry.isIntersecting) return;
      const id = entry.target.dataset.msgId;
      seen.unobserve(entry.target);
      if (WebToApk.markPushRead(id)) {       // true only when it was unread
        entry.target.classList.replace('unread', 'read');
        updateBellCount();
      }
    });
  }, { threshold: 0.6 });

  document.querySelectorAll('#inbox li.unread').forEach(function (li) { seen.observe(li); });
}

Clear the badge when the inbox screen opens, and only redraw when something was actually unread:

function openInboxScreen() {
  showScreen('inbox');
  if (!(window.WebToApk && window.WebToApk.markPushRead)) return;
  const changed = WebToApk.markPushRead('');  // false: nothing was unread
  if (changed) {
    document.getElementById('bell-count').textContent = '';
    renderInbox();
  }
}

Notes: read state is stored only on this phone. It does not remove the tray notification.

restorePushMessage bridge#

window.WebToApk.restorePushMessage(id: String): Boolean

Undo a dismiss: puts the message back in its place in the inbox.

Example

Undoes dismissPushMessage(id): the message comes back to the push inbox in its old place.

Returns: true if the message was restored; false if the id is unknown or it was not hidden. Needs: turn on AppMint Push in the Integrate step (Step 3) when you build.

let lastDismissed = null;

function onSwipe(id) {
  if (!(window.WebToApk && window.WebToApk.dismissPushMessage)) return;
  if (WebToApk.dismissPushMessage(id)) {
    lastDismissed = id;
    showUndo();
    renderInbox();
  }
}

document.getElementById('undo-btn').addEventListener('click', function () {
  if (!lastDismissed || !window.WebToApk) return;
  if (WebToApk.restorePushMessage(lastDismissed)) renderInbox();
  lastDismissed = null;
});

Undo several swipes at once: remember every message hidden on this screen and bring them all back:

const hiddenThisVisit = [];

function hide(id) {
  if (window.WebToApk && WebToApk.dismissPushMessage(id)) {
    hiddenThisVisit.push(id);
    document.getElementById('undo-all').hidden = false;
    renderInbox();
  }
}

document.getElementById('undo-all').addEventListener('click', function () {
  if (!(window.WebToApk && window.WebToApk.restorePushMessage)) return;
  hiddenThisVisit.splice(0).forEach(function (id) { WebToApk.restorePushMessage(id); });
  this.hidden = true;
  renderInbox();
});

Offer a "Recently hidden" list the user can restore from later. getPushInbox() does not return hidden messages, so keep their ids and titles yourself:

function loadHidden() {
  try { return JSON.parse(localStorage.getItem('hiddenMsgs') || '[]'); } catch (e) { return []; }
}

function renderHidden() {
  const ul = document.getElementById('recently-hidden');
  ul.innerHTML = '';
  loadHidden().forEach(function (h) {
    const li = document.createElement('li');
    li.textContent = h.title + ' ';
    const btn = document.createElement('button');
    btn.textContent = 'Restore';
    btn.onclick = function () {
      WebToApk.restorePushMessage(h.id);     // false: retracted, cleared or already back
      const rest = loadHidden().filter(function (x) { return x.id !== h.id; });
      try { localStorage.setItem('hiddenMsgs', JSON.stringify(rest)); } catch (e) {}
      renderHidden();
      renderInbox();
    };
    li.appendChild(btn);
    ul.appendChild(li);
  });
}

Notes: a message cleared with clearPushInbox() or retracted by you from AppMint cannot be restored.

setInAppPresentation bridge#

window.WebToApk.setInAppPresentation(mode: String): Boolean

In-app message presentation: "auto" (default) lets the shell draw the creator's design; "none" (persisted) hands each message to the page as `appmint:inapp` {type:'shown', presented:false, message} to draw itself.

Example

Chooses who draws an in-app message (a campaign from the AppMint Push send center) when its trigger happens: the app ('auto', the default: the creator's banner, centre card, bottom sheet or full-screen design) or your own page ('none').

Returns: true when the mode was saved (it stays on the phone until changed); false for any other value and in an app built without AppMint Push. Needs: AppMint Push turned on in the Integrate step (Step 3) when you build.

Every in-app message raises the appmint:inapp event (and calls window.onAppMintInApp(detail) if you defined it) with type 'shown', 'clicked', 'dismissed' or 'answered' (a star or 0-10 score answer, with question and value):

window.addEventListener('appmint:inapp', function (e) {
  var d = e.detail;   // { type, id, name, template?, presented?, message?, button?, link?, question?, value? }
  if (d.type === 'shown') analytics('inapp_shown', d.name);
  if (d.type === 'clicked') analytics('inapp_clicked', d.name + ' #' + d.button);
  if (d.type === 'dismissed') analytics('inapp_closed', d.name);
  if (d.type === 'answered') analytics('inapp_answer', d.name + ' ' + d.question + '=' + d.value);
});

Draw the messages yourself; the app hands each one over instead of showing it:

if (window.WebToApk && WebToApk.setInAppPresentation) WebToApk.setInAppPresentation('none');

window.onAppMintInApp = function (d) {
  if (d.type !== 'shown' || d.presented) return;   // presented = the app already drew it
  var m = d.message;                                // { title, body, imageUrl, link, inapp, accent?, buttons?, emoji?, question?, lines? }
  showMyDialog(m.title, m.body, (m.buttons || []).map(function (b) {
    return { label: b.label, onClick: function () { if (b.link) location.href = b.link; } };
  }));
};

Give the choice back to the app (for example from a settings switch):

document.getElementById('native-popups').addEventListener('change', function (e) {
  if (window.WebToApk && WebToApk.setInAppPresentation) {
    WebToApk.setInAppPresentation(e.target.checked ? 'auto' : 'none');
  }
});

Notes: with 'none' a message still counts as shown (its frequency limits apply) the moment it is handed to the page. Setting 'none' also closes a message the app is showing now. Push messages have their own switch, setPushPresentation.

setPushPresentation bridge#

window.WebToApk.setPushPresentation(mode: String): Boolean

Native in-app presentation of push messages: "auto" (default) lets the shell draw the banner / modal / sheet / full-screen card the creator chose while the app is open; "none" turns it off for good (persisted) - for apps that draw their own. The `appmint:push` event and the inbox are unaffected either way; the received event's `presented` flag says whether the shell showed it.

Example

Turns the app's own on-screen display of AppMint Push messages on ('auto', the default) or off ('none') - for apps that draw their own banner from the appmint:push event.

Returns: true when the mode was saved; false for any value other than 'auto' or 'none'. The choice is saved on the phone and stays until changed. Needs: turn on AppMint Push in the Integrate step (Step 3) when you build.

Draw your own banner instead of the shell's:

if (window.WebToApk && window.WebToApk.setPushPresentation) {
  WebToApk.setPushPresentation('none');       // 'auto' to give it back to the shell
}

window.addEventListener('appmint:push', function (e) {
  const d = e.detail;
  // { type: 'received', message: {...}, presented: false } — presented = the shell already showed it
  if (d.type === 'received' && !d.presented) showMyBanner(d.message.title, d.message.body, d.message.link);
  if (d.type === 'revoked') hideMyBanner(d.id);
});

Let the user switch pop-ups off in your settings screen. There is no getter, so keep your own copy of the choice to draw the switch:

const toggle = document.getElementById('show-popups');
let saved = null;
try { saved = localStorage.getItem('pushPopups'); } catch (e) {}
toggle.checked = saved !== 'none';

toggle.addEventListener('change', function () {
  if (!(window.WebToApk && window.WebToApk.setPushPresentation)) return;
  const mode = toggle.checked ? 'auto' : 'none';
  if (WebToApk.setPushPresentation(mode)) {
    try { localStorage.setItem('pushPopups', mode); } catch (e) {}
  }
});

With 'auto', find out what the user did with a message the app showed on screen: opened when they tapped it (action is -1 for the message itself, else the button index), closed when they dismissed it without tapping:

window.addEventListener('appmint:push', function (e) {
  const d = e.detail;
  if (d.type === 'opened') {
    // the message is already marked read in the inbox
    track('push_opened', { id: d.id, button: d.action, link: d.link });
    updateBellCount();
  } else if (d.type === 'closed') {
    track('push_closed', { id: d.id });
  }
});

Notes: with 'auto', a message you sent with an in-app style is shown over the app while it is open (and then not also put in the tray). With 'none' the shell never draws it; the inbox (getPushInbox) and the appmint:push event work the same either way. Setting 'none' also closes a message the shell is showing now.

setPushTag bridge#

window.WebToApk.setPushTag(key: String, value: String): Boolean

Sets one AppMint Push tag (key: letters, digits, _ up to 32; value up to 128) - the send center can target "plan = pro", and an automation can fire a set time after a tag is set (an abandoned cart: set cart=open, clear it at checkout). An empty value removes the tag. Up to 50 tags per install.

Example

Sets one AppMint Push tag on this install, such as plan = pro or city = pune. The send center (Home > Notifications) can send to everyone with a tag, and a tag automation can send a message a set time after a tag is set - an abandoned cart, a finished level. An empty value removes the tag.

Returns: true when saved; false when the key is not 1-32 letters, digits or _, the value is longer than 128 characters, or the install would have more than 50 tags (nothing is saved then). Tags are kept on the phone and reported to AppMint Push a moment later (or on the next launch when offline). Needs: turn on AppMint Push in the Integrate step (Step 3) when you build. Tag targeting and automations reach phones running an app built with AppMint from 30 Sep 2026 on.

Tag what the user bought or chose, so you can send to "plan = pro" only:

function pushTag(key, value) {
  if (!(window.WebToApk && window.WebToApk.setPushTag)) return false;
  return WebToApk.setPushTag(key, value);
}

pushTag('plan', user.isPro ? 'pro' : 'free');
pushTag('city', user.city.toLowerCase());

Abandoned cart: set a tag when items are in the cart and clear it at checkout. In the send center, add a tag automation on cart with a delay (say 60 minutes); a phone gets the reminder only if cart is still set when the time is up:

function onCartChanged(items) {
  if (!(window.WebToApk && window.WebToApk.setPushTag)) return;
  // Setting 'open' again while it is already 'open' does not restart the automation's clock.
  WebToApk.setPushTag('cart', items.length > 0 ? 'open' : '');
}

function onCheckoutComplete() {
  if (window.WebToApk && window.WebToApk.setPushTag) WebToApk.setPushTag('cart', '');   // bought: no reminder
}

Clear user-specific tags on sign-out, together with the user id:

function signOutOfPush() {
  if (!(window.WebToApk && window.WebToApk.setPushUser)) return;
  WebToApk.setPushUser('');
  WebToApk.setPushTag('plan', '');
  WebToApk.setPushTag('cart', '');
}

Notes: tags belong to this install, not to the signed-in user. Setting a tag to the value it already has changes nothing on the server. After a tag is removed, setting it again lets a tag automation send once more. Never put an email address, phone number or anything private in a tag.

setPushTags bridge#

window.WebToApk.setPushTags(json: String): Boolean

Sets several tags at once from a JSON object string; "" values remove.

Example

Sets several AppMint Push tags at once from a JSON object string, e.g. '{"plan":"pro","team":"red"}'. A "" value removes that tag; tags you do not name are kept.

Returns: true when saved; false when the string is not a JSON object, any key is not 1-32 letters, digits or _, any value is longer than 128 characters, or the install would end up with more than 50 tags - then nothing at all is saved. Needs: turn on AppMint Push in the Integrate step (Step 3) when you build. Tag targeting and automations reach phones running an app built with AppMint from 30 Sep 2026 on.

After sign-in, tag the profile in one call:

function tagProfile(profile) {
  if (!(window.WebToApk && window.WebToApk.setPushTags)) return false;
  return WebToApk.setPushTags(JSON.stringify({
    plan: profile.plan,                   // 'free' | 'pro'
    level: String(profile.level),         // values are text: turn numbers into strings
    team: profile.team || ''              // '' removes the tag
  }));
}

On sign-out, remove them all (read the current tags, send each back as ''):

function clearPushTags() {
  if (!(window.WebToApk && window.WebToApk.getPushTags)) return;
  const tags = JSON.parse(WebToApk.getPushTags() || '{}');
  Object.keys(tags).forEach(function (k) { tags[k] = ''; });
  WebToApk.setPushTags(JSON.stringify(tags));
  WebToApk.setPushUser('');
}

Finished a level: tag it, and let a tag automation on level_done (say 10 minutes) send the next challenge:

function onLevelComplete(level, score) {
  if (!(window.WebToApk && window.WebToApk.setPushTags)) return;
  WebToApk.setPushTags(JSON.stringify({
    level_done: String(level),            // a new value each level, so the automation can fire again
    last_score: String(score)
  }));
}

Notes: one call is one change: all of it is saved or none of it. The install reports the new tags to AppMint Push a moment later (or on the next launch when offline).

setPushUser bridge#

window.WebToApk.setPushUser(id: String): Boolean

AppMint Push audience: tells the creator's send center WHO this install is, so a message can go to one signed-in user ("u_42") or to everyone with a tag. Call it after sign-in with your own stable account id; pass "" on sign-out. Letters, digits and _ @ . : + - (up to 128). Returns false for anything else, and in an app that does not use AppMint Push (OneSignal / own Firebase: nothing is sent, 2026-10-03). Never pass an email address or phone number - use your database id.

Example

Tells AppMint Push which of YOUR users is signed in on this phone, so the send center (Home > Notifications) can send a message to that one user. Call it after sign-in with your own stable account id; call it with '' on sign-out.

Returns: true when the id was saved; false for an id with anything other than letters, digits and _ @ . : + -, or longer than 128 characters (nothing is saved then). The id is kept on the phone until you change it, and this install reports it to AppMint Push a moment later (or on the next launch when it is offline). Needs: turn on AppMint Push in the Integrate step (Step 3) when you build. Sending to a user reaches phones running an app built with AppMint from 30 Sep 2026 on; older builds still get messages sent to Everyone.

After sign-in and on sign-out - use your database id, never an email address or phone number:

function pushUser(id) {
  if (!(window.WebToApk && window.WebToApk.setPushUser)) return false;   // browser, or an older build
  return WebToApk.setPushUser(id);
}

async function signIn(email, password) {
  const user = await myBackend.signIn(email, password);   // your own sign-in
  pushUser(user.id);                                      // e.g. 'u_42' or a UUID
}

async function signOut() {
  await myBackend.signOut();
  pushUser('');                                           // this phone is nobody's now
}

Supabase: follow the session, so a restored session counts as well as a fresh sign-in:

supabase.auth.onAuthStateChange(function (event, session) {
  if (!(window.WebToApk && window.WebToApk.setPushUser)) return;
  const id = session ? session.user.id : '';     // the auth UUID: letters, digits and '-'
  if (WebToApk.getPushUser() !== id) WebToApk.setPushUser(id);
});

Make an id acceptable when yours may contain other characters (spaces, /, #), and log the rare one that is still refused:

function pushUserIdFrom(rawId) {
  return String(rawId).trim().replace(/[^A-Za-z0-9_@.:+\-]/g, '_').slice(0, 128);
}

if (window.WebToApk && window.WebToApk.setPushUser) {
  const ok = WebToApk.setPushUser(pushUserIdFrom(currentUser.id));
  if (!ok) console.warn('push user id refused', currentUser.id);
}

Notes: the same user signed in on two phones is reached on both. The id belongs to this install, not to the account - sign-out must clear it, or the phone keeps receiving the previous user's messages. setPushUser('') does not clear tags set with setPushTag; clear user-specific tags on sign-out too.

trackEvent bridge#

window.WebToApk.trackEvent(name: String, propsJson: String): Boolean

In-app messages: reports an event the creator's campaigns can trigger on ("event" trigger, same name). propsJson is optional JSON. The name `appmint:route` with {"path":"/x"} reports a view change the URL does not show. False for a name outside [A-Za-z0-9_.:-]{1,64}, and without AppMint Push.

Example

Tells the app that something happened ("signed_up", "checkout_done", "level:3"). An in-app message the creator made in the AppMint Push send center with the trigger "When an event happens" and the same name shows now (or after the wait the creator picked).

Returns: true when the event was taken; false for a name outside letters, digits and _ . : - (up to 64), and in an app built without AppMint Push. AppMint.trackEvent(name, props) is the same call with props as an object. Needs: AppMint Push turned on in the Integrate step (Step 3) when you build. propsJson is optional ('') and is not stored.

// After a purchase: the creator's "Thanks, rate us?" message is set to trigger on "purchase_done".
function onPurchaseDone(order) {
  if (window.AppMint && AppMint.trackEvent) {
    AppMint.trackEvent('purchase_done', { total: order.total });
  }
}

The raw bridge takes the props as a JSON string (pass '' for none):

function track(name, props) {
  if (!(window.WebToApk && window.WebToApk.trackEvent)) return false;   // a browser: nothing to show
  return WebToApk.trackEvent(name, props ? JSON.stringify(props) : '');
}
track('level:3');
track('tutorial_skipped', { step: 2 });

A screen change the address does not show (tabs, a wizard, a modal "page") can be reported as a route, so the creator's "On a page" messages see it. The page can also dispatch the appmint:route event with the path; the app listens for it:

function showTab(name) {
  document.querySelectorAll('.tab').forEach(function (t) { t.hidden = t.id !== name; });
  if (window.AppMint && AppMint.trackEvent) AppMint.trackEvent('appmint:route', { path: '/' + name });
}

// Same thing, without touching the bridge at all:
window.dispatchEvent(new CustomEvent('appmint:route', { detail: { path: '/cart' } }));

Notes: route messages already follow real URL changes (page loads, history.pushState, #/hash routes) on their own; report a route only for views the address does not change. Events are not counted or stored anywhere; they only decide whether a message shows.

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.