dataLayer & Google Tag Manager mit Shopify Checkout Extensibility
Der neue Checkout von Shopify mit seiner JavaScript Sandbox benötigt diese Anpassungen, um den Google Tag Manager und den dataLayer zu implementieren.
Justus
owntag Gründer
veröffentlicht am 30. Juli 2024
Ab dem 13. August 2024 ist es nicht mehr möglich, den HTML & JavaScript Codes des Checkouts von Shopify zu bearbeiten. Bislang ging das über Anpassungen in der Datei checkout.liquid
.
Mit dem komplett neu entwickelten Checkout (“Checkout Extensibility”) geht das aber nicht mehr.
Für Shopify ist es ein nachvollziehbarer Schritt, mehr technische Kontrolle innerhalb des Checkouts zu erzwingen, für die Implementierung von Tracking-Code ist es aber eine echte Herausforderung.
Sandbox
Die einzige Möglichkeit, eigenen Code im Checkout zu implementieren, sind nun die “Web Pixels”.
Diese Codeschnipsel sind als Sandbox umgesetzt.
Das bedeutet, dass der Code in einem eigenen Bereich ausgeführt wird und absichtlich eingeschränkten Zugriff auf einige Browserfunktionen hat. Innerhalb der Web Pixel wird noch einmal unterschieden zwischen den App Pixeln, die durch Shopify Apps erstellt werden, und den Custom Web Pixeln, die manuell vom Shopbetreiber angelegt werden.
Custom Web Pixel
Die App Pixel sind noch stärker eingeschränkt und haben z. B. keinen Zugriff auf das window
Objekt im Browser, das aber für viele Trackingskripte und auch für den Tag Manager essentiell ist.
Das führt u. a. dazu, dass Shopify Apps keine direkte Möglichkeit haben, den Google Tag Manager und dataLayer Events zu integrieren. Es gibt zwar einige, die es versprechen, schaffen es aber tatsächlich nur für die Seiten außerhalb des Checkouts und verweisen für den Checkout dann auf die unten näher beschriebene Lösung.
Für alle Events, die im Checkout ausgelöst werden, ist also die einzige mögliche Lösung das Anlegen eines Custom Web Pixels “per Hand”:
- Die “Customer Events” befinden sich in den Shopify Einstellungen unter “Settings” > “Customer events”
- Dort klickst du auf “Add custom pixel”
- Dann vergibst du noch einen eindeutigen Namen, z. B. “GTM & dataLayer”
Die Consent-Einstellungen beziehen sich auf das eigene Consent Management von Shopify. Falls du das nutzt, wähle hier die Einstellung aus, die zu dem passen, was du im GTM umsetzen möchtest.
Falls du ein eigenes Consent Management wie z. B. Cookiebot oder Usercentrics nutzt, kannst du die Einstellungen hier auf “Not required” und “Data collection does not qualify as data sale” setzen und die notwendigen Einwilligungen stattdessen in deinem Consent Management Tool einholen.
- Nun zum eigentlich wichtigen Teil: Der Code, der sowohl den Google Tag Manager lädt als auch den dataLayer implementiert.
Er sorgt dafür, dass diese dataLayer Events ausgelöst werden:
init
- Wird immer zuerst ausgelöst und enthält die grundlegenden Informationen zum Shop wie z. B. die Länderzuordnung des Shopspage_view
- Seitenaufrufview_item
- Aufruf einer Produktdetailseiteadd_to_cart
- Hinzufügen eines Produkts zum Warenkorbbegin_checkout
- Betreten des Checkoutsadd_payment_info
- Hinzufügen von Zahlungsinformationenpurchase
- Kaufabschluss
Das init
Event ist hier nur das Vehikel, um die grundlegenden Shop-Informationen in den dataLayer zu übermitteln, die dann bei den nachfolgenden Events weiterhin zur Verfügung stehen.
Ebenfalls darin enthalten sind die gehashten Nutzerdaten im user_data
Objekt, zumindest sofern der Nutzer eingeloggt ist.
// Google Tag Manager (GTM) & dataLayer integration for Shopify Checkout Extensibility
// provided by owntag.eu
// Configuration
const gtmContainerId = "GTM-XXXXXXX";
const gtmContainerUrl = "https://www.googletagmanager.com/gtm.js";
(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 = gtmContainerUrl + "?id=" + i + dl;
f.parentNode.insertBefore(j, f);
})(window, document, "script", "dataLayer", gtmContainerId);
const pushToDataLayer = (eventName, eventData) => {
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
event: eventName,
...eventData,
});
};
async function sha256(message) {
const msgBuffer = new TextEncoder().encode(message);
const hashBuffer = await crypto.subtle.digest("SHA-256", msgBuffer);
const hashArray = Array.from(new Uint8Array(hashBuffer));
const hashHex = hashArray
.map((b) => b.toString(16).padStart(2, "0"))
.join("");
return hashHex;
}
async function hashUserData(userData) {
const hashPromises = Object.entries(userData).map(async ([key, value]) => {
if (value) {
return [key, await sha256(value)];
}
return [key, null];
});
return Object.fromEntries(await Promise.all(hashPromises));
}
analytics.subscribe("page_viewed", (event) => {
const userData = {
sha256_email_address: init.data.customer?.email || null,
sha256_phone_number: init.data.customer?.phone || null,
sha256_first_name: init.data.customer?.firstName || null,
sha256_last_name: init.data.customer?.lastName || null,
};
hashUserData(userData).then((hashedUserData) => {
pushToDataLayer("init", {
shop_country: init.data.shop.countryCode,
shop_name: init.data.shop.name,
shop_domain: init.data.shop.myShopifyDomain,
shop_storefront_url: init.data.shop.storefrontUrl,
user_data: hashedUserData,
user_id: init.data.customer?.id,
orders_count: init.data.customer?.ordersCount,
});
pushToDataLayer("page_view", {
page_location: event.context.window.location.href,
page_title: event.context.document.title,
});
});
});
const createItemData = (productVariant) => {
return {
item_id: productVariant.sku,
item_name: productVariant.product.title,
item_variant: productVariant.title,
currency: productVariant.price.currencyCode,
item_brand: productVariant.product.vendor,
price: productVariant.price.amount,
};
};
const createLineItemsData = (lineItems) => {
return lineItems.map((item) => {
return {
item_id: item.variant.sku,
item_name: item.title,
item_variant: item?.variant.title,
currency: item.variant.price.currencyCode,
item_brand: item.variant.product.vendor,
price: item.variant.price.amount,
quantity: item.quantity,
};
});
};
// view_item
analytics.subscribe("product_viewed", (event) => {
const { productVariant } = event.data;
const itemData = {
item_id: productVariant.sku,
item_name: productVariant.product.title,
item_variant: productVariant.title,
currency: productVariant.price.currencyCode,
item_brand: productVariant.product.vendor,
price: productVariant.price.amount,
};
pushToDataLayer("view_item", {
ecommerce: {
currency: productVariant.price.currencyCode,
value: productVariant.price.amount,
items: [itemData],
},
_clear: true,
});
});
// add_to_cart
analytics.subscribe("product_added_to_cart", (event) => {
const { cartLine } = event.data;
const subtotalPrice = cartLine.cost.totalAmount.amount;
const itemData = {
item_id: cartLine.merchandise.sku,
item_name: cartLine.merchandise.product.title,
item_variant: cartLine.merchandise.title,
currency: cartLine.merchandise.price.currencyCode,
item_brand: cartLine.merchandise.product.vendor,
price: cartLine.merchandise.price.amount,
};
pushToDataLayer("add_to_cart", {
ecommerce: {
currency: cartLine.merchandise.price.currencyCode,
value: subtotalPrice.toFixed(2),
items: [Object.assign(itemData, { quantity: cartLine.quantity })],
},
_clear: true,
});
});
analytics.subscribe("checkout_started", (event) => {
const checkoutData = ga4CheckoutEvents(event);
pushToDataLayer("begin_checkout", checkoutData);
});
analytics.subscribe("payment_info_submitted", (event) => {
const checkoutData = ga4CheckoutEvents(event);
pushToDataLayer("add_payment_info", checkoutData);
});
analytics.subscribe("checkout_completed", (event) => {
const checkoutData = ga4CheckoutEvents(event);
const { checkout } = event.data;
checkoutData.ecommerce.transaction_id =
checkout.order?.id || checkout.token;
checkoutData.ecommerce.shipping =
checkout.shippingLine?.price.amount ||
checkout.shipping_line?.price.amount ||
0;
checkoutData.ecommerce.tax = checkout.totalTax?.amount || 0;
pushToDataLayer("purchase", checkoutData);
});
function ga4CheckoutEvents(event) {
const { checkout } = event.data;
const lineItems = createLineItemsData(checkout.lineItems);
return {
ecommerce: {
currency: checkout.subtotalPrice.currencyCode,
value: checkout.subtotalPrice.amount,
items: lineItems,
},
_clear: true,
};
}
Damit auch wirklich dein GTM geladen wird, musst du in Zeile 5 noch deine Container-ID (GTM-…
) einsetzen.
Die gtmContainerUrl
kannst du optional anpassen, wenn du schon Server Side Tagging nutzt oder den Google First Party Mode verwendest und das Skript deines GTM Web Containers über eine eigene URL statt von Googles Servern laden möchtest.
Nach dem Einfügen des Codes musst du die Änderungen noch speichern und dann einmalig das Pixel über den “Connect” Button aktivieren:
Testen der dataLayer Events
Durch die Sandbox-Umgebung mit ihrem eingeschränkten Funktionsumfang funktioniert der praktische Preview Modus des Google Tag Managers nicht – daran führt leider kein Weg vorbei.
Welche Tags ausgelöst wurden, sieht man also nur an den ausgehenden HTTP Requests.
Eine Möglichkeit, immerhin die dataLayer Events zu sehen, die im Checkout ausgelöst werden, ist die JavaScript-Konsole des Browsers.
Dort, wo standardmäßig “top” steht, was die oberste Ebene des Browserfensters ist, kannst du einen anderen Frame auswählen, dessen Name dem Muster web-pixel-sandbox-CUSTOM-…-LAX
folgt:
Falls du neben dem GTM/dataLayer Pixel noch andere Custom Web Pixels angelegt hast, musst du evtl. mehrere ausprobieren bis du den richtigen gefunden hast.
Der richtige ist der, in dem dataLayer
definiert ist und du eine ähnliche Ausgabe wie hier siehst, wenn das dataLayer
in der JS Konsole eingibst und Enter drückst:
Testen der ausgehenden Requests
Wenn im Google Tag Manager Tags eingerichtet sind, durch die dataLayer Events erfolgreich ausgelöst werden und die Daten an externe Dienste wie z. B. GA4 versenden, kann man diese HTTP Requests wie gewohnt in der Developer Console des Browsers sehen.
Aber Achtung: Wenn Shopify den Nutzer zuverlässig erkannt hat und das firmeneigene “Shop Pay” als Zahlungsmethode aktiviert ist, wird man evtl. direkt zur Domain “shop.app” weitergeleitet. Das kann nach Checkout aussehen, ist aber keiner. In dem Fall muss man zunächst “Als Gast auschecken” am Seitenende anklicken.
Werde zum Server Side Tagging Profi mit owntag
Übernimm die Kontrolle über deine digitale Datenerhebung mit Server Side Tagging und dem Server Side GTM – einfach gehostet mit owntag.