← Back to Home

Universal Ecommerce Tracking for GHL with a Custom Data Layer

tags: ghl, gtm, ecommerce, tracking, ga4, google tag manager, data layer

GoHighLevel (GHL) is a fantastic platform for marketers, but it has a notable gap: it doesn’t provide a native ecommerce data layer. This makes it challenging to implement accurate conversion tracking with tools like Google Analytics 4 (GA4), Facebook Pixel, or other advertising platforms. Without a standardized data layer, you’re often left with manual, brittle solutions that are hard to scale and maintain.

This article provides a robust, copy-paste solution: a custom JavaScript snippet that acts as a universal ecommerce data formatter. It reads the purchase data GHL saves in the browser’s local storage and transforms it into a standard GA4-compatible ecommerce object. We’ll then use Google Tag Manager (GTM) to deploy this script and create a custom trigger, enabling you to fire your tracking tags reliably on every purchase.

The Problem: No Standard Ecommerce Data Layer in GHL

When a customer completes a purchase in a GHL funnel, the order details are saved locally in their browser. However, this information isn’t exposed in a way that analytics tools can easily consume. The consequences are:

The ideal solution is to create a standardized dataLayer object that all your tags can reference.

The Solution: A Universal Data Layer Formatter

The script below is designed to be placed in a Custom HTML tag within GTM. It inspects GHL’s local storage for order information, formats it into a GA4-compliant structure, and pushes it to the dataLayer.

Here is the full snippet:

<script>
// Initialize dataLayer if it doesn't exist
window.dataLayer = window.dataLayer || [];
function getDataLayerPayload(data) {
var event = data.event;
var items = data.items;
var currency = data.currency;
var transaction = typeof data.transaction !== 'undefined' ? data.transaction : {};
var totalValue = items.reduce(function(sum, item) {
return sum + (item.price * item.quantity);
}, 0);
var ecommerce = {
currency: currency,
items: items
};
if (event === 'purchase') {
Object.assign(ecommerce, {
transaction_id: transaction.id,
affiliation: transaction.affiliation,
value: transaction.total,
tax: transaction.tax,
shipping: transaction.shipping,
coupon: transaction.coupon
});
} else {
// For view_item, begin_checkout, add_to_cart
ecommerce.value = totalValue;
}
return {
event: event,
ecommerce: ecommerce
};
}
// --- EVENT FUNCTIONS ---
function pushPurchaseFromLocalStorage(funnelId) {
if (!funnelId) {
console.error('Funnel ID is required to fetch order details from local storage.');
return;
}
var lsKey = '_pl_' + funnelId;
var orderDetailsString = localStorage.getItem(lsKey);
if (!orderDetailsString) {
console.warn('No order details found in local storage with key: ' + lsKey);
return;
}
var orderDetails = JSON.parse(orderDetailsString);
// 1. Transform GHL products into GA4 items
var orderItems = orderDetails.products.map(function(product) {
return {
item_id: product.product._id,
item_name: product.name,
price: product.amount,
quantity: product.qty
};
});
// 2. Extract currency (assuming all items have the same currency)
var orderCurrency = 'USD'; // Default currency
if (orderDetails.products && orderDetails.products.length > 0 && orderDetails.products[0] && orderDetails.products[0].price && orderDetails.products[0].price.currency) {
orderCurrency = orderDetails.products[0].price.currency;
}
// 3. Create the GA4 transaction object
var transaction = {
id: orderDetails.orderIds[0],
total: orderDetails.total,
tax: orderDetails.tax.reduce(function(sum, taxItem) {
return sum + (taxItem.amount || 0);
}, 0),
shipping: 0, // GHL object doesn't seem to provide shipping. Set to 0 or a default.
coupon: '' // GHL object doesn't seem to provide coupon. Set to empty.
};
// 4. Build and push the payload
var payload = getDataLayerPayload({
event: 'purchase',
items: orderItems,
currency: orderCurrency,
transaction: transaction
});
window.dataLayer = window.dataLayer || [];
window.dataLayer.push(payload);
console.log('DataLayer Push from Local Storage: purchase', window.dataLayer);
}
// Push the order details to data layer, assuming `window.funnelId` is available
pushPurchaseFromLocalStorage(window.funnelId);
</script>

How It Works

  1. getDataLayerPayload: This is a helper function that constructs the final object to be pushed to the dataLayer. It’s designed to be flexible for different ecommerce events (purchase, view_item, etc.), but here we focus on purchase.
  2. pushPurchaseFromLocalStorage: This is the core function.
    • It takes a funnelId as an argument. GHL stores order data in a local storage key that includes the funnel ID (e.g., _pl_xxxx).
    • It retrieves and parses the order details JSON string from local storage.
    • It maps the GHL products array to the GA4 items array format.
    • It extracts key transaction details like transaction_id, total, and tax. Note that shipping and coupon data are not readily available in the GHL object, so they are set to default values.
    • Finally, it pushes a purchase event to the dataLayer with the fully formed ecommerce object.
  3. Execution: The script calls pushPurchaseFromLocalStorage(window.funnelId) to run the logic as soon as the GTM tag is fired. You must ensure window.funnelId is available on the page where this runs (typically the order confirmation page).

Implementation with Google Tag Manager

Now, let’s deploy this script using GTM.

Step 1: Create a Custom HTML Tag

  1. In your GTM container, go to Tags > New.
  2. Give your tag a descriptive name, like Custom HTML - GHL Ecommerce Formatter.
  3. Click on Tag Configuration and choose Custom HTML.
  4. Copy and paste the entire JavaScript snippet (including the <script> tags) into the HTML field.
  5. Save the tag.

Step 2: Create a Custom Event Trigger

We don’t want this script to run on every page. It should only run when a purchase actually happens. To achieve this, we’ll create a trigger that listens for a custom event that we will fire from the GHL order confirmation page.

  1. While still in the tag editor, click on Triggering.
  2. Click the + icon in the top right to create a new trigger.
  3. Name the trigger, for example, Custom Event - ghl_purchase.
  4. For Trigger Configuration, choose Custom Event.
  5. In the Event name field, enter ghl_purchase. This name must match exactly what you’ll use on your website.
  6. Leave “All Custom Events” selected.
  7. Save the trigger. Your Custom HTML tag should now be linked to this new trigger.

Triggering the Purchase Event from GHL

The final step is to tell GTM when to fire our tag. On your GHL order confirmation or “thank you” page, you need to add a small piece of code. This code pushes the ghl_purchase event to the dataLayer, which our GTM trigger is listening for.

You can add this code in the <body> or <footer> section of your GHL funnel page settings:

<script>
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
'event': 'ghl_purchase'
});
</script>

With this setup, the flow is as follows:

  1. A user lands on the order confirmation page.
  2. The small snippet fires, pushing ghl_purchase to the dataLayer.
  3. GTM detects this event and fires our “Custom HTML - GHL Ecommerce Formatter” tag.
  4. The formatter script runs, reads the local storage, builds the ecommerce object, and pushes the purchase event to the dataLayer.
  5. Now, all your other tags (GA4, Facebook, etc.) can be triggered by the purchase event and can use the rich data in the dataLayer.

Bonus: User Identification for Enhanced Tracking

To get a complete picture of your customer journey, you often need to identify users with a stable identifier like an email address. GHL also stores user information in local storage. You can create a GTM variable to safely extract this information.

Create a Custom JavaScript Variable in GTM

  1. In GTM, go to Variables > User-Defined Variables > New.
  2. Name the variable cjs - Local Storage User Email.
  3. Choose Custom JavaScript as the variable type.
  4. Paste the following code into the field:
function () {
var LSraw = localStorage.getItem('_ud');
if (!LSraw) {
return "";
}
try {
var LSparsed = JSON.parse(LSraw);
var LSemail = LSparsed.email;
if (!LSemail) {
return "";
}
return LSemail;
} catch (e) {
return "";
}
}

This function safely reads the _ud key from local storage, parses it, and returns the user’s email if it exists. If anything fails, it returns an empty string. You can now use this GTM variable ({{cjs - Local Storage User Email}}) in any of your tags to send user-specific data to your analytics platforms.

Conclusion

By implementing this custom data layer formatter, you bridge a critical gap in GHL’s marketing capabilities. This GTM-based solution is:

This approach empowers you to build a professional-grade analytics setup on GHL, giving you the data you need to make smarter marketing decisions.