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.
window.eshopDataLayer.eshop and fires the EshopDataLayerUpdated event on
document.window.eshopDataLayer array and the snapshot is refreshed.window.eshopDataLayer.eshopwindow.getEshopDataLayer(key) (or without an argument for the whole snapshot)EshopDataLayerUpdated event on document (e.detail is the snapshot)
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.
| Event | When | Payload |
|---|---|---|
cookieConsent | Initial load and on every consent change | { event, cookieConsent: { marketing, analytics } } |
pageVisit | Every page load | { event, pageType } |
loggedUser | Page load for a signed-in customer | { event, customer } |
categoryView | Category / listing page load | { event, category } |
productView | Product detail page load, and again when the shopper switches to another variant (the page does not reload; eshop is refreshed first) | { event, product } |
cartUpdated | Item added / removed / quantity changed | { event, action: 'add' | 'remove' | 'update', cart, product } |
orderSubmitted | Thank-you page (completed order) | { event, order } |
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| Value | Page |
|---|---|
homepage | Store homepage |
category | Category / product listing |
productDetail | Product detail |
cart | Cart |
checkout | Checkout steps |
thankYou | Order confirmation — carries the order block |
| Field | Type | Description |
|---|---|---|
items[].id | string | Product id (internal) |
items[].productUuid | string | null | Product UUID — the same id as in the REST API (/products/{id}) and webhooks |
items[].productItemUuid | string | null | UUID of the specific item — the variant for a variant, otherwise the product’s single item; same as REST /product-items/{id} |
items[].productNumber | string | null | Product number set by the store owner — use this to match your own catalogue / feed |
items[].ean | string | null | EAN of the product or the selected variant |
items[].name | string | Product name |
items[].price | string | Unit price excl. VAT |
items[].priceWithVat | string | Unit price incl. VAT |
items[].quantity | number | Quantity in the cart |
items[].variant | string | null | Selected variant label, joined into one string |
items[].variantOptions | array | null | Same variant as machine-readable pairs: [{ name, value }], in the store’s option order |
items[].category | string | null | Product category |
items[].brand | string | null | Product brand |
totalWithVat | string | Cart total incl. VAT |
totalWithoutVat | string | Cart total excl. VAT |
coupon | string | Applied coupon code ("" if none) |
| Field | Type | Description |
|---|---|---|
isLoggedIn | boolean | Whether a customer is signed in |
group | string | null | Customer pricing / loyalty group |
registered | boolean | Whether the customer has an account |
city | string | null | Customer city |
countryCode | string | null | ISO country code |
email PII | string | null | Only with granted consent |
name PII | string | null | Only with granted consent |
| Field | Type | Description |
|---|---|---|
name | string | null | Viewed category name (last breadcrumb node) |
path | string[] | Breadcrumb category names, root first |
products[].id | string | Product id |
products[].productUuid | string | null | Product UUID — the same id as in the REST API (/products/{id}) and webhooks |
products[].productItemUuid | string | null | UUID 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[].name | string | null | Product name |
products[].price | string | Unit price excl. VAT |
products[].position | number | 1-based position in the listing |
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}
});| Field | Type | Description |
|---|---|---|
id | string | Product id (internal) |
productUuid | string | null | Product UUID — the same id as in the REST API (/products/{id}) and webhooks |
productItemUuid | string | null | UUID of the specific item — the variant for a variant, otherwise the product’s single item; same as REST /product-items/{id} |
productNumber | string | null | Product number set by the store owner — use this to match your own catalogue / feed |
ean | string | null | EAN of the product or the selected variant |
name | string | Product name |
price | string | Unit price excl. VAT |
priceWithVat | string | Unit price incl. VAT |
currency | string | ISO currency code |
availability | string | InStock | BackOrder | OutOfStock |
description | string | null | Short plain-text description |
brand | string | null | Product brand |
imageUrl | string | null | Absolute URL of the main product image |
category | string | null | Product category |
variant | string | null | Selected variant label, joined into one string |
variantOptions | array | null | Same variant as machine-readable pairs: [{ name, value }], in the store’s option order |
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.
| Field | Type | Description |
|---|---|---|
orderId | number | Internal order id |
orderNumber | string | Human-readable order number |
priceWithVat / priceWithoutVat | string | Order total incl. / excl. VAT |
tax | string | VAT amount |
shipping | string | Shipping price |
coupon | string | Applied coupon code ("" if none) |
currency | string | ISO currency code |
items[] | array | Line 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. | ||
customer | object | group, registered, city, countryCode, and PII email, name, street, zip |
Your loader is always injected into the page; the store owner does not gate it with their consent settings. Respecting consent is therefore your responsibility:
cookieConsent.marketing (or analytics) is granted.cookieConsent event when the visitor changes their choice.email, name, street, zip) appear only with granted consent.function consentGranted() {
var c = window.getEshopDataLayer && window.getEshopDataLayer('cookieConsent');
return !!c && (c.marketing === 'granted' || c.analytics === 'granted');
}
// initial state + react to later changes (the cookieConsent event fires on every change)
if (consentGranted()) startTracking();
window.eshopDataLayer = window.eshopDataLayer || [];
var push = window.eshopDataLayer.push;
window.eshopDataLayer.push = function (e) {
if (e && e.event === 'cookieConsent') { consentGranted() ? startTracking() : stopTracking(); }
return push.apply(this, arguments);
};(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);
};
})();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.
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).