Embedded app check still failing after migrating to App Bridge React 4.x

Hi everyone,

I migrated my embedded app to the new Shopify App Bridge React 4.x flow, but the Embedded app check still shows a :cross_mark: for “Using the latest App Bridge script loaded from Shopify’s CDN” after multiple two-hour refresh cycles.

Environment:

  • Laravel + Vite (React 18.3)
  • @shopify/app-bridge-react@^4.2.2
  • App loads inside Shopify Admin with ?host= and ?shop= query params

Here’s the rendered HTML head from resources/views/app.blade.php:

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Shopify Gallery App</title>
    <meta name="csrf-token" content="...">
    <meta name="shopify-api-key" content="example-token" />
    <script src="https://cdn.shopify.com/shopifycloud/app-bridge.js"></script>
    <script>
        window.shopifyHost = "{{ request('host') }}";
        window.shopifyShop = "{{ request('shop') }}";
        window.shopifyApiKey = "{{ config('shopify.api_key') }}";
    </script>
    @viteReactRefresh
    @vite(['resources/css/app.css', 'resources/js/app.tsx'])
</head>

The frontend calls a helper that waits for window.appBridge.createApp, resolves the host/api key, and requests a session token via shopify.idToken() as soon as App Bridge is ready. API calls use that token in an Authorization header.

The CSP response header allows script-src https://cdn.shopify.com and frame-ancestors https://admin.shopify.com https://*.myshopify.com.

I can load the app publicly and see the CDN script executing, and the network tab shows only the CDN copy. Still, the automated check refuses to flip to :white_check_mark:.

Is there anything else the checker looks for that I might be missing? Do I need to initialize App Bridge differently, or is this likely a false negative I should escalate to support?

Thanks in advance!

Hey,
Think theres a bit of confusion on the upgrade.

You shouldn’t need to set these:
window.shopifyHost = "{{ request('host') }}";
window.shopifyShop = "{{ request('shop') }}";
window.shopifyApiKey = "{{ config('shopify.api_key') }}"

As you won’t need them with app bridge v4. It doesn’t use the createApp method you mention, it automatically injects a shopify object on the window which you can use and is already populated with the store info.

For example on the migration guide Migrate your app to Shopify App Bridge

@JordanFinners

Hey, thanks for clearing up the confusion about App Bridge v4 not needing the manual window assignments or createApp. I stripped those out, but the Embedded app check is still showing a :cross_mark: for “Using the latest App Bridge script loaded from Shopify’s CDN” even after a couple of refresh windows.

Updated HTML head from resources/views/app.blade.php:

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Shopify Gallery App</title>
    <meta name="csrf-token" content="...">
    <meta name="shopify-api-key" content="example-token" />
    <script src="https://cdn.shopify.com/shopifycloud/app-bridge.js"></script>
    @viteReactRefresh
    @vite(['resources/css/app.css', 'resources/js/app.tsx'])
</head>

No more custom globals—window.shopify now comes from the CDN script and carries the populated host, shop, and apiKey. My helper (resources/js/app-bridge.ts) simply waits for window.shopify.ready, primes a session token, and caches the host/shop from the query string if they’re present:

export async function ensureAppBridge(): Promise<ShopifyGlobal | null> {
  const win = window as ShopifyWindow;
  await waitForShopifyGlobal(win);
  const shopify = win.shopify;
  if (!shopify) return null;

  if (!shopify.config) {
    shopify.config = {
      apiKey: readMetaContent(win, 'shopify-api-key') ?? shopify.config?.apiKey,
      host: resolveHost(win) ?? shopify.config?.host,
      shop: resolveShop(win) ?? shopify.config?.shop,
    };
  }

  await shopify.ready;
  console.log('Shopify config', shopify.config);
  await shopify.idToken?.();
  return shopify;
}

When Shopify finishes initializing, I see the expected console output:

Shopify global detected
{host: 'example-host' shop: 'example.myshopify.com'}

and in the browser devtools network tab the only app-bridge.js request is to https://cdn.shopify.com/shopifycloud/app-bridge.js (no legacy bundle served from my domain). The app otherwise behaves normally inside Admin.

Is there another signature the automated checker is looking for beyond the CDN script tag and the global?

Thanks again!

Hey all,

Is there anything else I should do at this point?

It’s been about 19 hours (multiple refresh windows) since I cut over to this setup, and the automated Embedded app check still fails. This is the last blocker keeping me from submitting for review, so I’m getting a bit anxious.

I’d really appreciate any pointers.

Ask Shopify support if they can rerun the tests, if you believe its all set up correctly

1 Like

hi, did you set the token for shopify-api-key?
you have to replace example-tokenwith your apps Client ID.