All entries

August 5, 2026

f90ae58509b27d428e1462e51ea8606bd4196b3af2b97599d8067dea1995f5fe
Previous: 740e2544 Bundle: 93.1 KB

App nav (v0.38.7) now validates anchor hrefs and warns on unsupported links instead of silently breaking.

Highlights

  • App nav parsing (used by s-app-nav and ui-nav-menu) now validates each anchor href and only accepts relative app paths, app: URLs, or same-origin http(s) URLs.
  • Anchors with unsupported hrefs are dropped from the nav and logged once via console.warn with a link to the app-nav docs, instead of silently producing a broken URL.
  • app: protocol URLs are now converted to a proper same-origin URL string before being sent in the Menu update payload.
  • The internal-only modal show() method and the intents module's configure/invoke helpers were renamed internally with no behavioral change.
  • Bumped the internal app-bridge utilities version from 0.38.6 to 0.38.7.
11 files changed +70 -43

Infrastructure Changes

REPORT.md
+4 -4
@@ -1,6 +1,6 @@
# Shopify App Bridge — Unminification Report

Generated: 2026-07-30T08:52:07.704Z
Generated: 2026-08-05T08:53:06.590Z

## Files

@@ -8,7 +8,7 @@ Generated: 2026-07-30T08:52:07.704Z
|------|------|-------|------|
| _bootstrap.js | 29.7KB | 1033 | Infrastructure |
| _remote-ui.js | 6.0KB | 255 | Infrastructure |
| _utilities.js | 75.1KB | 2815 | Infrastructure |
| _utilities.js | 76.0KB | 2848 | Infrastructure |
| _web-vitals.js | 11.6KB | 527 | Infrastructure |
| analytics.js | 211B | 11 | Module |
| app.js | 346B | 15 | Module |
@@ -18,7 +18,7 @@ Generated: 2026-07-30T08:52:07.704Z
| id-token.js | 908B | 34 | Module |
| index.js | 2.2KB | 48 | Index |
| intents.js | 3.1KB | 106 | Module |
| internal-only.js | 528B | 26 | Module |
| internal-only.js | 539B | 26 | Module |
| loading.js | 605B | 31 | Module |
| navigation.js | 408B | 19 | Module |
| picker.js | 451B | 18 | Module |
@@ -46,7 +46,7 @@ Generated: 2026-07-30T08:52:07.704Z
| user.js | 940B | 37 | Module |
| visibility.js | 973B | 34 | Module |
| web-vitals.js | 1.9KB | 66 | Module |
| **Total** | **175.3KB** | **6663** | |
| **Total** | **176.2KB** | **6696** | |

## Pipeline Stages


modules/_bootstrap.js
+5 -5
@@ -411,7 +411,7 @@
    value: y,
  });
  const b = new rt();
  const v = subscribeAllErrors();
  const v = U();
  const P = v.promise.then((t) => t?.internal);
  const C = k() && !S() && window === top;
  if ((top === window && !E() && !y.config.disabledFeatures?.includes('auto-redirect')) || C)
@@ -971,43 +971,43 @@
  h.send('Loading.stop');
  y.ready = Promise.resolve();
})();
var he;
var me;
var we;
var ye;
var be;
var ve = -1;
var ve;
var ge = function (t) {
var ge;
var Ae = -1;
var Ee = function (t) {
  addEventListener(
    'pageshow',
    function (n) {
      if (n.persisted) {
        t(n);
      }
    },
    true,
  );
};
  return (
    window.performance &&
    performance.getEntriesByType &&
    performance.getEntriesByType('navigation')[0]
  );
};
  return (t && t.activationStart) || 0;
};
  var o = 'navigate';
    o = 'back-forward-cache';
  } else {
    if (e) {
        o = 'prerender';
      } else {
        if (document.wasDiscarded) {

modules/_utilities.js Truncated
+49 -22
@@ -106,7 +106,7 @@ function $(t, n, e) {
    t.subscribe('Error.' + o, n, e);
  });
}
function subscribeAllErrors() {
function U() {
  let t;
  let n = false;
  const promise = new Promise((n) => {
@@ -125,7 +125,7 @@ function subscribeAllErrors() {
    },
  };
}
function _() {
function subscribeAllErrors() {
  let t = Promise.resolve();
  const n = {};
  return {
@@ -134,7 +134,7 @@ function _() {
    },
    has: (t) => !!n[t],
    add(e) {
      const o = subscribeAllErrors();
      const o = U();
      n[e] = o;
      t = t.then(() => o.promise);
    },
@@ -726,7 +726,7 @@ function ORIGINAL_SYMBOL(t = false) {
  };
}
ORIGINAL_SYMBOL(true);
const Lt = '0.38.6+00fb5d58';
const Lt = '0.38.7+8dcfe0ce';
const It = Symbol();
function Mt(t, n, e) {
  const o = t[n];
@@ -861,7 +861,7 @@ const xt = ({ api, protocol, internalApiPromise }) => {
  };
};
const SHOPIFY_PROTOCOLS = Symbol();
class WINDOW_TARGETS {
class Ut {
  constructor(t, n, e, o) {
    this.action = t;
    this.type = n;
@@ -872,19 +872,19 @@ class WINDOW_TARGETS {
    this[SHOPIFY_PROTOCOLS]();
  }
}
class ALL_PROTOCOLS {
class WINDOW_TARGETS {
  constructor(t) {
    this.complete = t;
  }
}
function Ut(t) {
function ALL_PROTOCOLS(t) {
  return typeof t != 'object' || t === null
    ? t
    : Array.isArray(t)
      ? t.map((t) => Ut(t))
      ? t.map((t) => ALL_PROTOCOLS(t))
      : Object.keys(t).reduce((n, e) => {
          const o = t[e];
          n[e] = Ut(o);
          n[e] = ALL_PROTOCOLS(o);
          return n;
        }, {});
}
@@ -1496,6 +1496,19 @@ const yn = {
  target: hn,
};
function bn(t) {
  const n = t.getAttribute('href');
  if (n)
    try {
      const t = Vt(n);
      if (t.protocol === 'app:' || t.origin === location.origin) return t;
    } catch {}
}
function vn(t) {
  return t.protocol === 'app:'
    ? new URL(`${t.pathname}${t.search}`, location.origin).toString()
    : t.toString();
}
function gn(t) {
  const n = {
    name: t,
    attributes: {},
@@ -1513,37 +1526,51 @@ function bn(t) {
    ],
  };
  return ({ protocol, internalApiPromise }) => {
    async function i() {
    const i = new Set();
    function r(n) {
      const e = n.getAttribute('href') ?? ('href' in n ? n.href : '');
      if (!i.has(e)) {
        i.add(e);
        console.warn(
          `[${t}] Dropped nav item with unsupported href "${e}". App nav only supports relative app paths (e.g. "/settings"), app: URLs, or same-origin http(s) URLs. See https://shopify.dev/docs/api/app-home/app-bridge-web-components/app-nav`,
        );
      }
    }
    async function a() {
      const { navigation: t } = (await internalApiPromise) || {};
      return !(t?.version !== 2);
    }
      }
    }
      const n = Array.from(document.querySelectorAll(t)).reverse()[0];
        const t = n.anchors
        const t = [];
          .filter((t) => !n.isHomeAnchor(t))
        for (const e of n.anchors) {
          .map((t) => {
          if (n.isHomeAnchor(e)) continue;
            const { pathname: n, search: e, textContent: o, rel: i } = t;
          const o = bn(e);
            return {
          if (o) {
              label: o ?? '',
            t.push({
              url: new URL(n + e, location.href).toString(),
              label: e.textContent ?? '',
              rel: i || undefined,
              url: vn(o),
            };
              rel: e.rel || undefined,
        a.set(t);
          } else {
        if (await i()) return;
            r(e);
          }
        }
        i.set(t);
        if (await a()) return;
      }
      const u = document.querySelectorAll('s-app-nav, ui-nav-menu');
      if (u.length > 1) {
        console.warn(
@@ -1554,10 +1581,10 @@ function bn(t) {
            .join(', ')}`,
        );
      }
        items: Array.from(document.querySelectorAll(t)).flatMap((t) => t.menuItems()),
      };
      protocol.send(`Menu.${s}_Menu.UPDATE`, l);
      protocol.send(`Menu.${c}_Menu.UPDATE`, f);
    }
    const promise = new Promise((t) => {
      protocol.subscribe(

Diff truncated at 200 lines

Module Changes

modules/intents.js
+2 -2
@@ -44,7 +44,7 @@ const intentsModule = ({ api, protocol, internalApiPromise, signalFactory }) =>
        'AppFrame.propertiesEvent',
        ({ properties }) => {
          const i = (function (t, n, e) {
            return new WINDOW_TARGETS('configure', 'gid://flow/stepReference/' + t, n, () =>
            return new Ut('configure', 'gid://flow/stepReference/' + t, n, () =>
              e.send('AppFrame.navigateBack'),
            );
          })(
@@ -67,7 +67,7 @@ const intentsModule = ({ api, protocol, internalApiPromise, signalFactory }) =>
      if (!o) throw Error('Cannot invoke intent');
      if (!o.intents?.invoke || typeof o.intents.invoke != 'function')
        throw Error('Intents are not supported');
      return new ALL_PROTOCOLS(o.intents.invoke(t, n));
      return new WINDOW_TARGETS(o.intents.invoke(t, n));
    },
  };
  internalApiPromise.then((t) => {

modules/internal-only.js
+1 -1
@@ -6,7 +6,7 @@
const internalOnlyModule = ({ api, internalApiPromise }) => {
  const e = {
    async show(t, e) {
      const o = Ut(e);
      const o = ALL_PROTOCOLS(e);
      const i = await internalApiPromise;
      if (i && i.internalModal) {
        await i.internalModal.show?.(t, o);

modules/pos.js
+4 -4
@@ -8,7 +8,7 @@ const posModule = ({ api, protocol }) => {
  async function o(t, o) {
    const i = on();
    const abortController = new AbortController();
    const a = subscribeAllErrors();
    const a = U();
    protocol.subscribe(
      'Cart.update',
      ({ data }) => {
@@ -32,7 +32,7 @@ const posModule = ({ api, protocol }) => {
      async fetch() {
        const t = on();
        const abortController = new AbortController();
        const o = subscribeAllErrors();
        const o = U();
        protocol.send('Cart.fetch', {
          id: t,
        });
@@ -197,7 +197,7 @@ const posModule = ({ api, protocol }) => {
      protocol.send('Pos.close');
    },
    async device() {
      const t = subscribeAllErrors();
      const t = U();
      protocol.subscribe(
        'getState',
        ({ pos }) => {
@@ -215,7 +215,7 @@ const posModule = ({ api, protocol }) => {
      return t.promise;
    },
    async location() {
      const t = subscribeAllErrors();
      const t = U();
      protocol.subscribe(
        'getState',
        ({ pos }) => {

modules/s-app-nav.js
+1 -1
@@ -3,7 +3,7 @@
 * Custom <s-app-nav> element for app navigation
 */

// Registry entry referenced as: vn
// Registry entry referenced as: An


modules/s-app-window.js
+1 -1
@@ -3,9 +3,9 @@
 * Custom <s-app-window> element for app window management
 */

// Registry entry referenced as: ne
// Registry entry referenced as: oe
  variantLock: 'app-window',
});


modules/title-bar.js
+1 -1
@@ -24,16 +24,16 @@ const titleBarModule = ({ protocol, internalApiPromise }) => {
  function r(t) {
    const n = document.querySelector('s-page');
    if (n) {
      const e = `${Dn}, ${qn}, ${Wn}`;
      const e = `${Wn}, ${Hn}, ${Vn}`;
      if (o) return void o.click();
      const i = Array.from(document.querySelectorAll('s-menu, s-button-group'));
      for (const n of i) {
        if (e) return void e.click();
      }
    }
    o?.click();
  }
  function a(t) {
modules/ui-modal.js
+1 -1
@@ -3,7 +3,7 @@
 * Custom <ui-modal> element
 */

// Registry entry referenced as: le
// Registry entry referenced as: de


modules/ui-nav-menu.js
+1 -1
@@ -3,7 +3,7 @@
 * Custom <ui-nav-menu> element
 */

// Registry entry referenced as: fe
// Registry entry referenced as: pe