All entries

October 8, 2026

fdd8d9944e74ed2cb992b6b08fcae765246b7f4b886cda20feeb67a240117b82
Previous: c23dfa58 Bundle: 97.7 KB

Adds app-modal module that detects open Polaris modals, allowlists Shopify CDN hosts, and extends telemetry.

Highlights

  • New app-modal module watches the Polaris portals container and notifies the host through appModal.setOpen when a Polaris modal opens or closes.
  • Script tag validation now accepts any host from a Shopify CDN allowlist (cdn.shopify.com plus shopify-assets domains) over HTTPS instead of only cdn.shopify.com.
  • The fetch module uses the same CDN allowlist check when deciding whether a request targets the Shopify CDN.
  • Telemetry now reports reactMajorVersion for ui-component usage and exposes a global hook so already-connected custom elements are also counted.
21 files changed +132 -116

Infrastructure Changes

REPORT.md
+13 -12
@@ -1,24 +1,25 @@
# Shopify App Bridge — Unminification Report

Generated: 2026-10-01T08:04:35.805Z
Generated: 2026-10-08T08:06:15.850Z

## Files

| File | Size | Lines | Type |
|------|------|-------|------|
| _bootstrap.js | 30.9KB | 1076 | Infrastructure |
| _bootstrap.js | 30.7KB | 1071 | Infrastructure |
| _remote-ui.js | 6.1KB | 257 | Infrastructure |
| _remote-ui.js | 6.4KB | 267 | Infrastructure |
| _utilities.js | 78.0KB | 2924 | Infrastructure |
| _utilities.js | 78.2KB | 2930 | Infrastructure |
| _web-vitals.js | 11.6KB | 527 | Infrastructure |
| analytics.js | 211B | 11 | Module |
| app-modal.js | 1.2KB | 60 | Module |
| app.js | 346B | 15 | Module |
| client.js | 303B | 15 | Module |
| environment.js | 241B | 14 | Module |
| fetch.js | 3.0KB | 94 | Module |
| id-token.js | 929B | 34 | Module |
| id-token.js | 941B | 34 | Module |
| index.js | 2.2KB | 48 | Index |
| index.js | 2.2KB | 49 | Index |
| intents.js | 3.1KB | 106 | Module |
| internal-only.js | 534B | 26 | Module |
| internal-only.js | 528B | 26 | Module |
| loading.js | 605B | 31 | Module |
| navigation.js | 408B | 19 | Module |
| picker.js | 451B | 18 | Module |
@@ -29,15 +30,15 @@ Generated: 2026-10-01T08:04:35.805Z
| reviews.js | 365B | 16 | Module |
| s-app-nav.js | 175B | 10 | Module |
| s-app-window.js | 228B | 12 | Module |
| save-bar.js | 3.4KB | 138 | Module |
| save-bar.js | 3.5KB | 138 | Module |
| scanner.js | 2.9KB | 101 | Module |
| scanner.js | 2.8KB | 101 | Module |
| scopes.js | 797B | 26 | Module |
| share.js | 1.3KB | 58 | Module |
| shopifyQL.js | 1.2KB | 55 | Module |
| shortcut.js | 474B | 24 | Module |
| shortcut.js | 461B | 24 | Module |
| sidekick.js | 5.3KB | 179 | Module |
| support.js | 508B | 21 | Module |
| telemetry.js | 935B | 35 | Module |
| telemetry.js | 1.1KB | 46 | Module |
| title-bar.js | 8.4KB | 317 | Module |
| toast.js | 1.6KB | 71 | Module |
| tools.js | 2.2KB | 75 | Module |
@@ -46,7 +47,7 @@ Generated: 2026-10-01T08:04:35.805Z
| user.js | 940B | 37 | Module |
| visibility.js | 973B | 34 | Module |
| web-vitals.js | 1.9KB | 66 | Module |
| **Total** | **181.9KB** | **6912** | |
| **Total** | **183.5KB** | **6995** | |

## Pipeline Stages


modules/_bootstrap.js Truncated
+19 -26
@@ -19,30 +19,25 @@
          ? (console.error('The script tag loading App Bridge has `async`'), false)
          : t.defer
            ? (console.error('The script tag loading App Bridge has `defer`.'), false)
            : t.src
            : t.src && n(new URL(t.src))
              ? new URL(t.src).hostname != mt
              ? ([...document.scripts]
                ? (console.error(
                  .filter((t) =>
                    'The script tag loading App Bridge is not loading App Bridge from the Shopify CDN.',
                    (function (t) {
                      return (
                        t.src &&
                        !t.defer &&
                        !t.async &&
                        t.type !== 'module' &&
                        !t.dataset.appBridgeCompatible &&
                        /^ *(|(text|application)\/(x-)?(java|ecma)script) *$/i.test(t.type)
                      );
                    })(t),
                  )
                  .indexOf(t) !== 0 &&
                  console.warn(
                    'The script tag loading App Bridge should be the first script tag in the document. Loading other blocking scripts first can cause unexpected behavior.',
                  ),
                  false)
                true)
                : ([...document.scripts]
                    .filter((t) =>
                      (function (t) {
                        return (
                          t.src &&
                          !t.defer &&
                          !t.async &&
                          t.type !== 'module' &&
                          !t.dataset.appBridgeCompatible &&
                          /^ *(|(text|application)\/(x-)?(java|ecma)script) *$/i.test(t.type)
                        );
                      })(t),
                    )
                    .indexOf(t) !== 0 &&
                    console.warn(
                      'The script tag loading App Bridge should be the first script tag in the document. Loading other blocking scripts first can cause unexpected behavior.',
                    ),
                  true)
              : (console.error(
                  'The script tag loading App Bridge is not loading App Bridge from the Shopify CDN.',
                ),
@@ -75,52 +70,52 @@
        }
      })(),
    );
      (function () {
        const t = Array.from(document.getElementsByTagName('script'));
        if (document.currentScript) {
          t.unshift(document.currentScript);
        }
            try {
              if (url.hostname === mt && lt.test(url.pathname)) {
              if (n(url) && dt.test(url.pathname)) {
                  if (t) {
                  }
                });
              }
            } catch (err) {}
            try {
            } catch (err) {
              console.warn('App Bridge Next: failed to parse configuration. ' + err);
            }
      })(),
    );
      (function () {
        const t = Array.from(document.querySelectorAll('meta[name^="shopify-"i]'));
        const n = {};
        for (const e of t) {
          if (!e.hasAttribute('name')) continue;
        }
        return n;
      })(),
    );
      (function (t) {
        return {
          shop: t.get('shop'),
@@ -129,47 +124,47 @@
        };
      })(searchParams),
    );
      if (n.length !== 0)
        throw Error('App Bridge Next: missing required configuration fields: ' + n);
      return t;
    return {
      params: searchParams,
    };
  })();
  (function (t) {
    try {
      sessionStorage.setItem('app-bridge-config', JSON.stringify(t));
    } catch (err) {}
        try {
        } catch {}
      }
      try {
      } catch {
        return;
      }
    })();
    }
  const i = e.host ? atob(e.host) : e.shop;
  const a = new URL('https://' + i).origin;
... (truncated)

Diff truncated at 200 lines

modules/_remote-ui.js Truncated
+13 -3
@@ -4,15 +4,25 @@
 * @description Remote procedure call and memory management for remote rendering
 */

const t = 'admin_theme';
const set = new Set([
const n = 'app-bridge-admin-theme';
  'cdn.shopify.com',
  'shopify-assets.shopifycdn.com',
  'shopify-assets.shopifystagingcdn.cn',
  'shopify-assets.shopifycdn.cn',
  'shopify-assets.alejandrochinashopifytest742501.com',
]);
function n(n) {
  return n.protocol === 'https:' && set.has(n.hostname.toLowerCase());
}
const e = 'admin_theme';
const o = 'app-bridge-admin-theme';
const retainSymbol =
  Symbol.for('RemoteUi::Retain'); /* Symbol for retaining a remote object in memory */
const releaseSymbol =
  Symbol.for('RemoteUi::Release'); /* Symbol for releasing a remote object from memory */
const retainedBySymbol =
  Symbol.for('RemoteUi::RetainedBy'); /* Symbol for tracking which retainers hold this object */
  constructor() {
    this.memoryManaged = new Set();
  }
@@ -29,186 +39,186 @@ class r {
    this.memoryManaged.clear();
  }
}
  return !!(t && t[retainSymbol] && t[releaseSymbol]);
}
}
  if (r) {
    t[retainSymbol]();
  }
  if (n) {
    if (Array.isArray(t)) {
    }
    }
  }
  return r;
}
}
    t[releaseSymbol]();
  }
  if (n) {
    if (Array.isArray(t)) {
      e.set(t, o);
      return o;
    }
      const o = Object.keys(t).reduce((o, i) => l(t[i], n, e) || o, r);
      const o = Object.keys(t).reduce((o, i) => d(t[i], n, e) || o, i);
      e.set(t, o);
      return o;
    }
  }
}
  if (t == null || typeof t != 'object') return false;
  const n = Object.getPrototypeOf(t);
  return n == null || n === Object.prototype;
}
const remoteFunctionMarker = '_@f'; /* Function marker for RPC serialization */
  const map = new Map();
  return {
          const t = [];
          const n = [
            }),
            t,
          ];
          return n;
        }
          const t = [];
          const n = [
              t.push(...s);
              return {
                ...n,
              };
            }, {}),
            t,
          ];
          return n;
        }
      }
          const e = [
            {
              [remoteFunctionMarker]: t,
            },
          ];
... (truncated)

Diff truncated at 200 lines

modules/_utilities.js Truncated
+26 -26
@@ -4,16 +4,16 @@
 *           error types, DOM validation framework, navigation menu base
 */

  return `${w()}-${w()}-${w()}-${w()}`;
  return `${b()}-${b()}-${b()}-${b()}`;
}
  return Math.floor(Math.random() * Number.MAX_SAFE_INTEGER).toString(16);
}
  return t.toLowerCase().replace(/-+(.)/g, (t, n) => n.toUpperCase());
}
  if (n)
    for (let e in n) {
      const o = n[e];
@@ -36,41 +36,41 @@ class A extends g {
    }).innerHTML = '<style>:host{display: none;}</style><slot></slot>';
  }
}
  return t.replace(/-([a-z])/gi, (t, n) => n.toUpperCase());
}
  try {
    return navigator.userAgent.toLowerCase().includes(t.toLowerCase());
  } catch (err) {
    return false;
  }
}
}
function S() {
  return k('Shopify Mobile');
}
function T() {
  return k('Extensibility');
  return S('Shopify Mobile');
}
  return k('Shopify POS');
  return S('Extensibility');
}
const L = 'app-iframe';
function I() {
const I = /frame:\/*([^/]+)\/([^/]+)(?:\/([^/]+))?(?:\/([^/]+))?$/;
  return S('Shopify POS');
const $ = (() => {
}
  const [, t, n, e] = window.name.match(I) ?? [];
const $ = 'app-iframe';
const M = /frame:\/*([^/]+)\/([^/]+)(?:\/([^/]+))?(?:\/([^/]+))?$/;
const R = (() => {
  const [, t, n, e] = window.name.match(M) ?? [];
  return {
    apiKey: t,
    scope: n,
    mode: e,
  };
})();
  'hmac',
  'locale',
  'protocol',
@@ -82,14 +82,14 @@ const x = [
  'embedded',
  'appLoadId',
  'link_source',
];
function O(t) {
function subscribeAllErrors(t) {
  const url = new URL(t);
  return url;
}
  'FailedAuthentication',
  'InvalidAction',
  'InvalidActionType',
@@ -102,12 +102,12 @@ const _ = [
  'UnexpectedAction',
  'UnsupportedOperation',
];
function subscribeAllErrors(t, n, e) {
function j(t, n, e) {
    t.subscribe('Error.' + o, n, e);
  });
}
  let t;
  let n = false;
  const promise = new Promise((n) => {
@@ -126,7 +126,7 @@ function U() {
    },
  };
}
function j() {
function createDeferred() {
  let t = Promise.resolve();
  const n = {};
  return {
@@ -152,7 +152,7 @@ function j() {
    },
  };
}
function createDeferred({ keys, held, handler, keyEvent: o = 'keydown' }) {
function N({ keys, held, handler, keyEvent: o = 'keydown' }) {
  let i = [];
  const r = (o) => {
    if (keys.flat().includes(o.key)) {
@@ -194,31 +194,31 @@ function createDeferred({ keys, held, handler, keyEvent: o = 'keydown' }) {
    });
  };
}
  const t = window.shopify.config.host;
  return 'https://' + atob(t);
}
const N = Symbol();
const q = Symbol();
const V = Symbol();
const W = Symbol();
const H = 'data-save-bar';
const H = Symbol();
const z = 'data-discard-confirmation';
const z = Symbol();
const J = 'ui-save-bar';
const J = 'data-save-bar';
const X = 'update';
const X = 'data-discard-confirmation';
const Q = 'valuemodified';
const Q = 'ui-save-bar';
const set = new Set();
const Y = 'update';
function G() {
const G = 'valuemodified';
  for (const t of set) t.settle();
const K = new Set();
}
function Z() {
function K(t, { onChange, filter: e = () => true }) {
  for (const t of K) t.settle();
}
function tt(t, { onChange, filter: e = () => true }) {
  let o = false;
... (truncated)

Diff truncated at 200 lines

modules/index.js
+1 -0
@@ -12,6 +12,7 @@
// Modules
// analytics.js
// app.js  — App lifecycle and state management
// app-modal.js
// client.js  — Client initialization and RPC connection
// environment.js  — Environment detection (embedded, mobile, POS)
// fetch.js  — Intercepted fetch with auth headers and session token refresh

Module Changes

modules/fetch.js
+4 -4
@@ -3,17 +3,17 @@
 * Intercepted fetch with auth headers and session token refresh
 */

// Registry entry referenced as: xt
// Registry entry referenced as: deepClone
const xt = ({ api, protocol, internalApiPromise }) => {
const deepClone = ({ api, protocol, internalApiPromise }) => {
    try {
      return await api.idToken();
    } catch (err) {
      return void console.error('Failed to fetch an idToken', err);
    }
  }
    const e = new Headers(n.headers).get('Shopify-Challenge-Required');
    return e && t?.isChallengeUrl && (await t.isChallengeUrl(e)) && t?.startChallenge
      ? {
@@ -23,19 +23,19 @@ const xt = ({ api, protocol, internalApiPromise }) => {
          verified: false,
        };
  }
    const url = new URL(request.url);
      (url.protocol === location.protocol &&
        (url.hostname === location.hostname || url.hostname.endsWith('.' + location.hostname))) ||
    const d = url.hostname === 'cdn.shopify.com';
    const p = n(url);
      const t = Array.from(request.headers.entries());
      if (n?.intercept) {
        const n = {
          method: request.method,
@@ -43,51 +43,51 @@ const xt = ({ api, protocol, internalApiPromise }) => {
          headers: t,
          body: (await request.text()) ?? undefined,
        };
        if (o) {
          return new Response(t.body, t);
        }
        return new Response(e.body, e);
      }
    }
      if (t) {
        request.headers.set('Authorization', 'Bearer ' + t);
      }
    }
      request.headers.set('X-Requested-With', 'XMLHttpRequest');
    }
      request.headers.set('Accept-Language', api.config.locale);
    }
      if (t) {
      }
    }
      protocol.send('Navigation.redirect.remote', {
      });
      return new Promise(() => {});
    }
    }
  });
};

const fetchModule = xt;
const fetchModule = deepClone;

modules/id-token.js
+3 -3
@@ -3,8 +3,8 @@
 * Session token (ID token) request/response
 */

// Registry entry referenced as: deepClone
// Registry entry referenced as: ALL_PROTOCOLS
const deepClone = ({ api, protocol, internalApiPromise }) => {
const ALL_PROTOCOLS = ({ api, protocol, internalApiPromise }) => {
  api.idToken = async function () {
    const { idToken: t } = (await internalApiPromise) || {};
    const o = t
@@ -30,4 +30,4 @@ const deepClone = ({ api, protocol, internalApiPromise }) => {
  };
};

const idTokenModule = deepClone;
const idTokenModule = ALL_PROTOCOLS;

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 Ut(o.intents.invoke(t, n));
      return new parseUrl(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 = parseUrl(e);
      const o = Dt(e);
      const i = await internalApiPromise;
      if (i && i.internalModal) {
        await i.internalModal.show?.(t, o);

modules/resource-picker.js
+1 -1
@@ -78,7 +78,7 @@ const resourcePickerModule = ({ api, protocol, internalApiPromise }) => {
            signal: h,
          },
        );
        subscribeAllErrors(
        j(
          protocol,
          (t) => {
            m();

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

// Registry entry referenced as: Mn
// Registry entry referenced as: xn


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

// Registry entry referenced as: Ee
// Registry entry referenced as: ke
  variantLock: 'app-window',
});


modules/scanner.js
+2 -2
@@ -22,7 +22,7 @@ const scannerModule = ({ api, protocol, internalApiPromise }) => {
              );
            }
            function s() {
              subscribeAllErrors(protocol, a, {
              j(protocol, a, {
                signal: r,
                id: o,
              });
@@ -50,14 +50,14 @@ const scannerModule = ({ api, protocol, internalApiPromise }) => {
            protocol.subscribe(
              'getState',
              ({ features }) => {
                  s();
                } else {
                  (function () {
                    const e = new AbortController();
                    abortController.signal.addEventListener('abort', () => e.abort());
                    subscribeAllErrors(
                    j(
                      protocol,
                      (t) => {
                        e.abort();
modules/share.js
+1 -1
@@ -23,7 +23,7 @@ const shareModule = ({ protocol, internalApiPromise }) => {
            }),
          );
        }
        subscribeAllErrors(protocol, u, {
        j(protocol, u, {
          signal: c,
          id: o,
        });

modules/shortcut.js
+1 -1
@@ -4,10 +4,10 @@
 */

const shortcutModule = ({ protocol, internalApiPromise }) => {
      createDeferred({
      N({
        ...e,
        handler: async () => {
          const { shortcut: o } = (await internalApiPromise) || {};

modules/telemetry.js
+35 -24
@@ -4,31 +4,42 @@
 */

const telemetryModule = async ({ internalApiPromise }) => {
  if (typeof window != 'undefined') {
  async function n(n, e) {
    window.addEventListener('_PreactCustomElement:connected', async (n) => {
    const o = n.tagName.toLowerCase();
      const e = n.target;
    const i = (function (t) {
      const o = e.tagName.toLowerCase();
      const n = [];
      const i = (function (t) {
      for (let e = 0; e < t.attributes.length; e++) {
        const n = [];
        const o = t.attributes[e];
        for (let e = 0; e < t.attributes.length; e++) {
        n.push(o.name);
          const o = t.attributes[e];
      }
          n.push(o.name);
      return n;
        }
    })(n);
        return n;
    const r = JSON.stringify(i);
      })(e);
    const a = await internalApiPromise;
      const r = JSON.stringify(i);
    if (!a || !a.telemetry || typeof a.telemetry.increment != 'function') return;
      const a = await internalApiPromise;
    const s = globalThis.polaris?.version;
      if (!a || !a.telemetry || typeof a.telemetry.increment != 'function') return;
    const c = e?.reactMajorVersion;
      const s = globalThis.polaris?.version;
    a.telemetry.increment('ui-component', {
      a.telemetry.increment('ui-component', {
      component: o,
        attributes: r,
      ...(s
        ...(s
        ? {
          ? {
            version: s,
              version: s,
          }
            }
        : {}),
          : {}),
      ...(typeof c == 'string'
      });
        ? {
            reactMajorVersion: c,
          }
        : {}),
    });
  }
  if (typeof window != 'undefined') {
    globalThis[xe] = (t, e) => {
      if (t.getRootNode() === t.ownerDocument) {
        n(t, e);
      }
    };
    window.addEventListener(Re, (t) => n(t.target, t));
  }
};

modules/title-bar.js
+3 -3
@@ -22,18 +22,18 @@ const titleBarModule = ({ protocol, internalApiPromise }) => {
    if (e) return e.getAttribute('title') ?? document.title;
  }
  function r(t) {
    if (n) {
      const e = `${ie}, ${re}, ${ae}`;
      const e = `${re}, ${ae}, ${se}`;
      if (o) return void o.click();
      for (const n of i) {
        if (e) return void e.click();
      }
    }
    o?.click();
  }
  function a(t) {
@@ -67,11 +67,11 @@ const titleBarModule = ({ protocol, internalApiPromise }) => {
          const n = [t];
          const e = t.startsWith('s-internal-');
          return [
            ...Dt('s-menu')
            ...SIMULATING_CLICK('s-menu')
              .filter((t) => t.startsWith('s-internal-') === e)
              .map((t) => t + '.title-bar-menu'),
          ].join(', ');
@@ -114,27 +114,27 @@ const titleBarModule = ({ protocol, internalApiPromise }) => {
        };
      }
    } else {
        hideElements: false,
        context: document,
      });
      c(t && n?.localName === SIMULATING_CLICK ? SIMULATING_CLICK : undefined);
      c(t && n?.localName === qt ? qt : undefined);
      if (t) {
        r = {
          title: t.title || i(),
        };
        if (t.breadcrumb) {
        }
        if (t.primaryAction) {
          r.buttons = Object.assign(r.buttons ?? {}, {
            primary: n,
          });
        }
          hideElements: false,
          context: document,
        });
modules/tools.js
+1 -1
@@ -18,15 +18,15 @@ const toolsModule = async ({ api, internalApiPromise }) => {
          (function (t) {
            return async (...n) => {
              (function () {
                for (const t of set) t.capture();
                for (const t of K) t.capture();
              })();
              try {
                return await t(...n);
              } finally {
                if (typeof requestAnimationFrame == 'function') {
                } else {
                }
              }
            };

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

// Registry entry referenced as: Me
// Registry entry referenced as: _e


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

// Registry entry referenced as: xe
// Registry entry referenced as: Fe


modules/web-vitals.js
+2 -2
@@ -33,14 +33,14 @@ const webVitalsModule = async ({ api, protocol, internalApiPromise, rpcEventTarg
            id: o.id,
            name: o.name,
            value: o.value,
            version: It,
            version: SHOPIFY_PROTOCOLS,
          });
        } else {
          protocol.send('WebVitals.' + t, {
            id: o.id,
            metricName: o.name,
            value: o.value,
            version: It,
            version: SHOPIFY_PROTOCOLS,
          });
        }
        if (l && o.name === 'LCP') {