GA4 E-Commerce Data Layer Bridge (GA4 to JENTIS)

In this article we will create a translation script that takes your website’s window.dataLayer (GTM Data Layer) and translates ecommerce events using the GA4 ecommerce data model into the JENTIS native data layer.

This creates a fast and easy solution to get started without the need to change your website.

⚠️ Important: Direct integration via _jts.push is always preferred. Only use this bridge if direct integration is not feasible.

The Why and The Mission

Google Analytics 4 (GA4) is a powerful tool for tracking and analyzing user behavior. Its ecommerce capabilities give businesses deep insights into purchasing behavior.

However, GA4’s native model does not always align with your existing infrastructure. Migrating from the familiar GTM dataLayer format to GA4’s ecommerce model can be complex.

Our custom translation script bridges the gap: it allows you to keep using your existing dataLayer and seamlessly translates events into the JENTIS tool-agnostic data model (JENTIS Data Layer: Push Data).

Goal: simplify the integration of ecommerce tracking into JENTIS without major redevelopment, while ensuring consistency and accuracy.

Key Benefits and Features

  • Seamless Integration Works with your existing dataLayer — no need to rebuild data structures.

  • Familiar Workflow Mimics the GTM/GA4 push mechanism for an easy transition.

  • Default Settings Ready All JENTIS tools, tags, triggers, and variables work out of the box.

  • Tool-agnostic Approach Instead of a GA4-only model, JENTIS translates your data once into a uniform structure that scales across tools (e.g. Facebook CAPI, TikTok, etc.).

How It Works

The script acts as a middleware: it intercepts pushes to window.dataLayer, maps GA4 ecommerce events and properties, and forwards them into the JENTIS _jts data layer.

This way, ecommerce actions such as product views, add-to-cart, or purchases are captured automatically and transformed into JENTIS’ ecommerce tracking model.

Drawbacks and Pitfalls

Implementing a bridge code should be considered a temporary or MVP solution. Native JENTIS integrations are more stable and performant. Be aware of:

  • Hidden Dependency: Developers may assume dataLayer is optional once GTM is removed. This approach creates a dependency.

  • Data Consistency Risks: Translation may introduce discrepancies between dataLayer and _jts.

  • Customization Limits: Highly specialized use cases may require more than this script can cover.

  • Performance Overhead: Adds a small amount of extra JavaScript execution.

  • Maintenance: GA4 evolves, so updates may be needed.

  • Debugging Complexity: Troubleshooting requires knowledge of both GA4 and JENTIS mappings.

Event Mappings

GA4 event → JENTIS event (product.type):

  • view_item_listproductlist

  • select_itemproductlistclick

  • view_itemproductview

  • add_to_wishlistaddtowishlist

  • add_to_cartaddtocart

  • remove_from_cartremovefromcart

  • view_cartcartview

  • begin_checkoutcheckout (step 1)

  • add_payment_infocheckout (step 2)

  • add_shipping_infocheckout (step 3)

  • refundrefund

  • purchaseorder

  • view_promotionpromotionimpression

  • select_promotionpromotionclick

  • Default (if mapping fails) → jtm_customcode_mapping_failed

Property Mappings

Each GA4 item property is mapped into a JENTIS product attribute:

{
  "track": "product",
  "type": "<from event mapping>",
  "id": "item.item_id",
  "name": "item.item_name",
  "group": [
    "item.item_category",
    "item.item_category2",
    "item.item_category3",
    "item.item_category4",
    "item.item_category5"
  ],
  "variant": "item.item_variant",
  "quantity": "item.quantity",
  "brutto": "item.price - item.discount (if present)",
  "discount": "item.discount",
  "position": "item.index",
  "oldbrutto": "item.price",
  "brand": "item.item_brand",
  "coupon": "item.coupon"
}

Implementing the Custom Code

To add the script:

  1. In JENTIS Tag Manager, navigate to Codes.

  2. Add a new Code element, give it a descriptive name, and select the containers where it should run.

  3. Copy and paste the following code into the JS Code field:

// Step 1: Ensure the existence of both dataLayer arrays
window.dataLayer = window.dataLayer || [];
window._jts = window._jts || [];

// Step 2: Intercept push calls to window.dataLayer
(function(originalPush) {
  window.dataLayer.push = function() {
    // Convert arguments to a real array to work with
    var args = Array.prototype.slice.call(arguments);
    
    // Process each object being pushed
    for (var i = 0; i < args.length; i++) {
      try{
        var obj = args[i];
      
        // Step 3: Process and transform the intercepted data
        var jtsPushCommands = transformDataLayerObject(obj);
        
        // Step 4: Forward the transformed data to window._jts
        for(var j = 0; j < jtsPushCommands.length; j++){
          window._jts.push(jtsPushCommands[j]);
        }
        
      }catch(e){
        console.warn("JENTIS GA4 DataLayer BRIDGE FAILED");
      }

    }
    
    // Continue with the normal dataLayer push process
    originalPush.apply(window.dataLayer, arguments);
  };
})(window.dataLayer.push);

function itemToProductMapping(item, jtsType){
  return {
    "track"       : "product",
    "type"        : jtsType,
    "id"          : item.item_id,
    "name"        : item.item_name,
    "group"       : [
        item.item_category,
        item.item_category2,
        item.item_category3,
        item.item_category4,
        item.item_category5
      ],
    "variant"     : item.item_variant,
    "quantity"    : item.quantity,
    "brutto"      : item.price && item.discount ? item.price - item.discount : item.price,
    "discount"    : item.discount,
    "position"    : item.index,
    "oldbrutto"   : item.price,
    "brand"       : item.item_brand,
    "coupon"      : item.coupon
  };
}

function ecommerceToDocumentMapping(ecomm, event){
  var jtsTrack = "";
  switch(event) {
    case "view_item_list":
      jtsTrack = "productlist";
      break;
    case "select_item":
      jtsTrack = "productlistclick";
      break;
    case "view_item":
      jtsTrack = "productview";
      break;
    case "add_to_wishlist":
      jtsTrack = "addtowishlist";
      break;
    case "add_to_cart":
      jtsTrack = "addtocart";
      break;
    case "remove_from_cart":
      jtsTrack = "removefromcart";
      break;
    case "view_cart":
      jtsTrack = "cartview";
      break;
    case "begin_checkout":
      jtsTrack = "checkout";
      break;
    case "add_shipping_info":
      jtsTrack = "checkout";
      break;
    case "add_payment_info":
      jtsTrack = "checkout";
      break;
    case "refund":
      jtsTrack = "refund";
      break;
    case "purchase":
      jtsTrack = "order";
      break;
    case "view_promotion":
      jtsTrack = "promotionimpression";
      break;
    case "select_promotion":
      jtsTrack = "promotionclick";
      break;
    default:
      jtsTrack = "jtm_customcode_mapping_failed";
  }

  var jtsDoc = {
    track: jtsTrack
  };

  if(jtsTrack == "checkout"){
    switch(event) {
      case "begin_checkout":
        jtsDoc.step = "1";
        break;
      case "add_payment_info":
        jtsDoc.step = "2";
        break;
      case "add_shipping_info":
        jtsDoc.step = "3";
        break;
    }
  }

  if(ecomm.item_list_id)
    jtsDoc.id = ecomm.item_list_id;
  if(ecomm.item_list_name)
    jtsDoc.name = ecomm.item_list_name;
  if(ecomm.currency)
    jtsDoc.currency = ecomm.currency;
  if(ecomm.value)
    jtsDoc.value = ecomm.value;
  if(ecomm.shipping_tier)
    jtsDoc.shipping_tier = ecomm.shipping_tier;
  if(ecomm.payment_type)
    jtsDoc.paytype = ecomm.payment_type;
  if(ecomm.transaction_id)
    jtsDoc.orderid = ecomm.transaction_id;
  if(ecomm.tax)
    jtsDoc.tax = ecomm.tax;
  if(ecomm.shipping)
    jtsDoc.shipping = ecomm.shipping;
  if(ecomm.coupon)
    jtsDoc.vouchers = [{"code":ecomm.coupon}];

  if(jtsTrack == "order")
    jtsDoc.brutto = ecomm.value;


  return jtsDoc;
}

// Function to transform dataLayer object
function transformDataLayerObject(obj) {
  var jtsCommands = [];
  if(obj.ecommerce && obj.event){
    var jtsDocument = ecommerceToDocumentMapping(obj.ecommerce, obj.event);

    if(Array.isArray(obj.ecommerce.items)) {
      // Process each product item
      obj.ecommerce.items.forEach(function(item) {
        // Transform each product into the required format and push to _jts
        var transformedProduct = itemToProductMapping(item, jtsDocument.track);

        // Push the transformed product to _jts
        jtsCommands.push(transformedProduct);
      });
    }

    jtsCommands.push(jtsDocument);

    if(obj.event == "view_promotion" || obj.event == "select_promotion"){
      var jtsPromo = {
        track           : "promotion",
        id              : obj.ecommerce.promotion_id,
        name            : obj.ecommerce.promotion_name,
        creative        : obj.ecommerce.creative_name,
        creativeslow    : obj.ecommerce.creative_slot
      }
      jtsPromo.type = obj.event == "view_promotion" ? "promotionimpression" : "promotionclick";

      jtsCommands.push(jtsPromo);
      jtsCommands.push({track:jtsPromo.type});
    }

    jtsCommands.push({track:"submit"});
  }

  return jtsCommands;
}

// Step 5: Handle existing data in window.dataLayer
// Process each existing object in dataLayer
window.dataLayer.forEach(function(obj) {
  var jtsPushCommands = transformDataLayerObject(obj);
  for(var j = 0; j < jtsPushCommands.length; j++){
    window._jts.push(jtsPushCommands[j]);
  }
});

Conclusion

This bridging script provides a fast way to integrate GA4 ecommerce tracking into JENTIS without rebuilding your site’s data layer.

It enables businesses to benefit from GA4’s enhanced capabilities while leveraging the JENTIS tool-agnostic data layer for long-term scalability.

For production-ready setups, we recommend planning a full migration to the native JENTIS Data Layer.

Last updated

Was this helpful?