GA4 User Property (shopify_customer_id) Not Sending via Custom Pixel GTM Implementation

Hi everyone,

I’m very close to finalizing my GA4 and Shopify integration but have hit a wall with sending a user-scoped Custom Dimension (shopify_customer_id) when loading GTM via a Custom Pixel.

The basic GA4 integration (via Google & YouTube app) is working. My goal is to send the Shopify Customer ID to GA4 as a User Property to enable cohort analysis.


What I Have Done

  1. GA4 Custom Dimension:

    • I’ve set up a User-scoped Custom Dimension in GA4 (see screenshot 1).

    • Dimension Name: shopify_customer_id

    • Scope: User

    • User Property: shopify_customer_id

  2. GTM Setup (via Custom Pixel):

    • I am loading the GTM container using the standard GTM snippet inside a Custom Pixel.

    • I am NOT injecting GTM into theme.liquid to ensure tracking works on the thank-you page (Checkout Extensibility).

  3. Custom Pixel Code:

    • My Custom Pixel loads GTM and then tries to send the Customer ID during the init event.

Here is my current Custom Pixel code:

JavaScript

// Load GTM Container
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer', 'GTM-XXXXXXX'); // My GTM ID

// ... (Consent mode settings) ...

// Attempt to send Customer ID
analytics.subscribe('init', (event) => {
  const customer = event.data.customer;

  if (customer && customer.id) {
    const customerId = String(customer.id);

    // Send User Property directly to GA4
    gtag('set', 'user_properties', {
      'shopify_customer_id': customerId  // Matches GA4 setup
    });

    // (I also tried sending a GTM event, but it didn't work)
    // gtag('event', 'shopify_customer_id', {});
  }
});

// ... (Other analytics.subscribe events like 'checkout_completed' using dataLayer.push) ...


The Problem

The shopify_customer_id user property is never received by GA4.

  • Standard events (like page_viewed, checkout_completed which use dataLayer.push) are working correctly.

  • Only the gtag('set', 'user_properties', ...) call from the init event seems to fail.

  • When using GTM Preview Mode, I do not see the shopify_customer_id event or any user properties being set on initialization.

My Hypothesis (The Core Question)

I believe this is a timing issue. The analytics.subscribe('init', ...) callback fires before the GTM snippet (gtm.js) has fully loaded and defined the global gtag function. This results in a “gtag is not a function” error, and the command is lost.

I tried to fix this by creating a polling function to wait for gtag (based on an old implementation I had), but it also doesn’t seem to work reliably in the pixel:

JavaScript

// My attempt to fix the timing issue
analytics.subscribe('init', (event) => {
  const customer = event.data.customer;

  if (customer && customer.id) {
    const customerId = String(customer.id);

    const checkGtagAndSend = () => {
      if (typeof gtag === 'function') {
        // gtag is loaded, now send
        gtag('set', 'user_properties', {
          'shopify_customer_id': customerId
        });
      } else {
        // gtag not ready, wait and retry
        setTimeout(checkGtagAndSend, 100);
      }
    };
    // Start checking
    checkGtagAndSend();
  }
});

Even with this fix, the user property does not arrive in GA4.

What is the correct, recommended way within a Custom Pixel to get the event.data.customer.id from the init event and reliably send it to GA4 after GTM (gtag) has fully loaded?

Thank you for any guidance!

1 Like

Hello Riku,

First, let me confirm to you that the gtag function is synchronously instantiated, meaning that it is available as soon as the pixel loads, even before the first analytics.subscribe line is reached.

You can see the implementation sets up a queue for Google Analytics’ gtm.js script to read on load.

function gtag(){dataLayer.push(arguments);}

So that should not be the issue.


Next, the init event. This event is not a standard Shopify Web Pixel event, so it’s either a custom event configured on your store, or an event that never gets published, and never triggers its callback.

If you’re thinking of the init data referenced in the documentation, this data is available on pixel load, right away, in the scope of the pixel.

// No need to subscribe to anything
const customer = init.data.customer;

gtag('set', 'user_properties', {
  'shopify_customer_id': customerId  // Matches GA4 setup
});

Hope this helps!

Hi emileber,

Thank you so much for your reply and the clarification regarding the synchronous instantiation of gtag and the init data! That was very helpful.

Regarding the init data: You were absolutely right. I’ve reviewed the official documentation (standard-api/init) and confirmed that init is indeed globally available data upon pixel load, not an event to subscribe to via analytics.subscribe. I also see the structure init.data.customer.id defined there.

Our Findings & “Protected Customer Data”: While considering accessing init.data.customer.id directly as you suggested, we noticed the documentation states that init.data.customer returns null “…or if the customer is accessing protected customer data.” (We understand customer.id falls under this “Protected Customer Data” category.)

This aligns perfectly with all our other client-side testing results. We extensively tried fetching event.data.customer.id and event.data.checkout.customer.id (including correcting potential path mistakes) within analytics.subscribe callbacks for page_viewed, checkout_started, and checkout_completed. In every single test, even when logged in, the value pushed to the dataLayer and observed via GTM Debug Mode / GA4 Realtime reports was consistently null.

We also reviewed Shopify’s Pixel Privacy documentation, which confirms that the pixel sandbox intentionally restricts access to “protected customer data” (PII) for privacy compliance (GDPR, CCPA, etc.).

Based on this, we’ve concluded that fetching the customer.id client-side via Custom Pixels seems intentionally impossible due to Shopify’s privacy design.

Shifting to Server-Side: Given this conclusion, we are now exploring the server-side approach. Specifically, using the orders/create Webhook to receive order data (which includes customer.id) and then forwarding it to GA4 via Server-side GTM (sGTM) or the Measurement Protocol.

Question to the Community (Revisited): So, I’d like to ask the community again: Does anyone have experience implementing this server-side approach (Webhook + sGTM/MP) for linking Shopify Customer IDs with GA4 data?

We are particularly interested in best practices for associating the server-received customer.id with the corresponding GA4 client_id. Any insights, potential challenges, or tips would be incredibly valuable.

Thank you again for your input!