Frontend dataLayer

A neutral, versioned in-browser contract for partner web tracking

Alongside the REST API, every Eshop-rychle store publishes a neutral frontend dataLayer — structured data and events available directly in the visitor's browser. You connect a small JavaScript loader (deployed as a measuring code through the API), read the data and map it to your own tracking format. It covers visitor activity, cart contents, customer identification and completed orders. The data lives in its own global, window.eshopDataLayer, so the contract stays stable and is versioned through the version field.

How it works

  1. On every page the store renders a snapshot of the current state into window.eshopDataLayer.eshop and fires the EshopDataLayerUpdated event on document.
  2. State changes (cart add / remove / update, consent change, order completion) are pushed as events onto the window.eshopDataLayer array and the snapshot is refreshed.
  3. Your loader reads the snapshot, subscribes to the events and forwards whatever it needs to your own endpoint. The platform ships no per-partner code — you own the mapping.

Getting started

  • Register the store connection through the API and deploy your loader as a measuring code. It is injected into every page automatically and removed when your add-on is deactivated.
  • Partner measuring codes are always present regardless of the store's cookie-bar settings — so respecting consent is your responsibility (see Consent & self-gating).
  • Everything you need is client-side; no server callbacks are required to read the dataLayer.

Accessing the data

  • Current page snapshot: window.eshopDataLayer.eshop
  • Helper: window.getEshopDataLayer(key) (or without an argument for the whole snapshot)
  • Ready signal: the EshopDataLayerUpdated event on document (e.detail is the snapshot)

Events

Events are published as entries in the window.eshopDataLayer array. An event is only a signal — read the details from the snapshot. Some events (pageVisit, loggedUser, categoryView, productView, orderSubmitted, the initial cookieConsent) are queued at page load, before your loader runs, so iterate the existing array first and then wrap push to catch later ones — the loader skeleton below does both.

EventWhenPayload
cookieConsentInitial load and on every consent change{ event, cookieConsent: { marketing, analytics } }
pageVisitEvery page load{ event, pageType }
loggedUserPage load for a signed-in customer{ event, customer }
categoryViewCategory / listing page load{ event, category }
productViewProduct detail page load, and again when the shopper switches to another variant (the page does not reload; eshop is refreshed first){ event, product }
cartUpdatedItem added / removed / quantity changed{ event, action: 'add' | 'remove' | 'update', cart, product }
orderSubmittedThank-you page (completed order){ event, order }

Snapshot schema

Fields marked PII are present only with granted consent (see below). The order block is present on the thank-you page only, category on category pages and product on product detail pages.

Note on types: prices, totals, orderNumber and all product ids are strings; orderId and quantities are numbers.

window.eshopDataLayer.eshop = { version: "1.2", pageType: "homepage" | "category" | "productDetail" | "cart" | "checkout" | "thankYou", currency: "CZK", // ISO currency code language: "cs", // cs | sk cart: { items: [{ id, productUuid, productItemUuid, productNumber, ean, name, price /* excl. VAT */, priceWithVat, quantity, variant, variantOptions, category, brand }], totalWithVat, totalWithoutVat, coupon }, customer: { isLoggedIn, group, registered, city, countryCode, email*, name* }, category: { // category / listing pages only name, path: [/* breadcrumb category names */], products: [{ id, productUuid, productItemUuid, name, price /* excl. VAT */, position }] }, product: { // product detail pages only id, productUuid, productItemUuid, productNumber, ean, name, price /* excl. VAT */, priceWithVat, currency, availability: "InStock" | "BackOrder" | "OutOfStock", description, brand, imageUrl, category, variant, variantOptions: [{ name: "Colour", value: "red" }] }, order: { // thankYou only orderId, orderNumber, priceWithVat, priceWithoutVat, tax, shipping, coupon, currency, items: [{ id, productUuid, productItemUuid, productNumber, ean, name, price /* excl. VAT */, quantity, variant, variantOptions, category, brand }], customer: { group, registered, city, countryCode, email*, name*, street*, zip* } }, cookieConsent: { marketing: "granted" | "denied", analytics: "granted" | "denied" } } // * PII — present in the snapshot only with granted marketing/analytics consent

pageType values

ValuePage
homepageStore homepage
categoryCategory / product listing
productDetailProduct detail
cartCart
checkoutCheckout steps
thankYouOrder confirmation — carries the order block

Cart fields

FieldTypeDescription
items[].idstringProduct id (internal)
items[].productUuidstring | nullProduct UUID — the same id as in the REST API (/products/{id}) and webhooks
items[].productItemUuidstring | nullUUID of the specific item — the variant for a variant, otherwise the product’s single item; same as REST /product-items/{id}
items[].productNumberstring | nullProduct number set by the store owner — use this to match your own catalogue / feed
items[].eanstring | nullEAN of the product or the selected variant
items[].namestringProduct name
items[].pricestringUnit price excl. VAT
items[].priceWithVatstringUnit price incl. VAT
items[].quantitynumberQuantity in the cart
items[].variantstring | nullSelected variant label, joined into one string
items[].variantOptionsarray | nullSame variant as machine-readable pairs: [{ name, value }], in the store’s option order
items[].categorystring | nullProduct category
items[].brandstring | nullProduct brand
totalWithVatstringCart total incl. VAT
totalWithoutVatstringCart total excl. VAT
couponstringApplied coupon code ("" if none)

Customer fields

FieldTypeDescription
isLoggedInbooleanWhether a customer is signed in
groupstring | nullCustomer pricing / loyalty group
registeredbooleanWhether the customer has an account
citystring | nullCustomer city
countryCodestring | nullISO country code
email PIIstring | nullOnly with granted consent
name PIIstring | nullOnly with granted consent

Category fields category only

FieldTypeDescription
namestring | nullViewed category name (last breadcrumb node)
pathstring[]Breadcrumb category names, root first
products[].idstringProduct id
products[].productUuidstring | nullProduct UUID — the same id as in the REST API (/products/{id}) and webhooks
products[].productItemUuidstring | nullUUID of the listed item — the variant when the listing shows a variant, otherwise the product’s single item; same as REST /product-items/{id}
products[].namestring | nullProduct name
products[].pricestringUnit price excl. VAT
products[].positionnumber1-based position in the listing

Product boxes in the page HTML

category.products lists what is shown, but not which box on the page is which product. To place your own content into a box (a badge, a second price), read the ids from the box itself: every product box ([data-selector="product"]) in listings, search results, the homepage, related products and wishlists carries data-product-uuid and data-product-item-uuid, the same values as productUuid and productItemUuid above.

“Load more” appends boxes without a page reload and without a dataLayer event, so watch the listing with a MutationObserver rather than reading the boxes once.

document.querySelectorAll('[data-selector="product"][data-product-item-uuid]').forEach(function (box) { var itemUuid = box.dataset.productItemUuid; // REST /product-items/{itemUuid} });

Product fields productDetail only

FieldTypeDescription
idstringProduct id (internal)
productUuidstring | nullProduct UUID — the same id as in the REST API (/products/{id}) and webhooks
productItemUuidstring | nullUUID of the specific item — the variant for a variant, otherwise the product’s single item; same as REST /product-items/{id}
productNumberstring | nullProduct number set by the store owner — use this to match your own catalogue / feed
eanstring | nullEAN of the product or the selected variant
namestringProduct name
pricestringUnit price excl. VAT
priceWithVatstringUnit price incl. VAT
currencystringISO currency code
availabilitystringInStock | BackOrder | OutOfStock
descriptionstring | nullShort plain-text description
brandstring | nullProduct brand
imageUrlstring | nullAbsolute URL of the main product image
categorystring | nullProduct category
variantstring | nullSelected variant label, joined into one string
variantOptionsarray | nullSame variant as machine-readable pairs: [{ name, value }], in the store’s option order

cartUpdated product field

On add and remove the cartUpdated event carries a product object for the specific item changed: { id, productUuid, productItemUuid, productNumber, ean, name, price /* excl. VAT */, quantity, variant, variantOptions, category, brand }. On a quantity update there is no single changed item, so product is null. The full cart is always in cart.

Order fields thankYou only

FieldTypeDescription
orderIdnumberInternal order id
orderNumberstringHuman-readable order number
priceWithVat / priceWithoutVatstringOrder total incl. / excl. VAT
taxstringVAT amount
shippingstringShipping price
couponstringApplied coupon code ("" if none)
currencystringISO currency code
items[]arrayLine items: id, productUuid, productItemUuid, productNumber, ean, name, price (excl. VAT), quantity, variant, variantOptions, category, brand
productNumber is stored with the order, so it stays as it was at checkout time. ean is read from the current catalogue and is null if the product has since been deleted.
customerobjectgroup, registered, city, countryCode, and PII email, name, street, zip

Loader skeleton

(function () { // 1) Current state — read the snapshot (available as soon as the page renders it). function read() { var eshop = window.getEshopDataLayer && window.getEshopDataLayer(); if (eshop) { /* map eshop.* to your format */ } } if (window.getEshopDataLayer) { read(); } else { document.addEventListener('EshopDataLayerUpdated', read, { once: true }); } // 2) Events — handle those already queued at page load, then catch every later push. window.eshopDataLayer = window.eshopDataLayer || []; function handle(entry) { if (entry && entry.event) { // entry.event: cookieConsent | pageVisit | loggedUser | categoryView | productView | cartUpdated | orderSubmitted // the entry carries its own payload; the full snapshot is at window.getEshopDataLayer() } } window.eshopDataLayer.forEach(handle); var push = window.eshopDataLayer.push; window.eshopDataLayer.push = function () { for (var i = 0; i < arguments.length; i++) { handle(arguments[i]); } return push.apply(this, arguments); }; })();

Testing your integration

Verify everything straight from the browser console on any store page:

// full current snapshot window.getEshopDataLayer(); // a single block window.getEshopDataLayer('cart'); // log every snapshot refresh document.addEventListener('EshopDataLayerUpdated', function (e) { console.log('snapshot', e.detail); }); // log every event var push = window.eshopDataLayer.push; window.eshopDataLayer.push = function () { console.log('event', arguments[0]); return push.apply(this, arguments); };

Add a product to the cart and you should see a cartUpdated event followed by a refreshed snapshot; complete an order and the thank-you page carries the order block together with an orderSubmitted event.

Versioning

eshop.version guards backward compatibility of the contract. A breaking schema change increments the major version — react to it in your loader (at least log an unknown major).