Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.dema.ai/llms.txt

Use this file to discover all available pages before exploring further.

Supported event types

The V2 tracking uses a single generic dema.event() method. At minimum, send the following events:

page_view

Page visits across your site

view_item

Product details page

purchase

Purchase transactions
The following event types are optional but recommended:

view_item_list

Product list viewed

select_item

Item selected from a list

add_to_cart

Item added to cart

remove_from_cart

Item removed from cart

view_cart

Cart page viewed

add_to_wishlist

Item added to wishlist

remove_from_wishlist

Item removed from wishlist

begin_checkout

Checkout started
The API is extensible — you can send any event_name value beyond the ones listed above, and Dema can build custom metrics on top of it. The available fields per event are defined by the event data schema.

Prerequisites

  • Google Tag Manager must be installed and running on your website
  • A data layer populated with product, cart, and transaction information (Google Analytics 4 ecommerce data layer structure works directly)
  • Your unique Dema ID, formatted as DV-XXXXXXXXXXXXXXXXXXXXXXXX
On a Product Detail Page (PDP), both page_view and view_item should fire. This is a change from the legacy V1 tag, where a single pageview event covered both.

GTM variables

Set up the following variables once in GTM. All tags below reference these by name.

{{Dema > ID}} — Constant

  • Variable Type: Constant
  • Value: Your unique Dema frontend ID, e.g. DV-12345

{{JS > Referrer}} — Custom JavaScript

Tracking accuracy depends on a consistent referrer value across all events fired on the same page view. If your site is a single-page application (SPA) and you don’t already have a referrer variable, create a Custom JavaScript variable named JS > Referrer. Two templates are available — pick based on whether GTM’s History Change listener reliably populates gtm.oldUrl / gtm.newUrl on your site. Verify in GTM Preview before choosing: open your site with the container in Preview mode, navigate between two SPA routes, and check the Data Layer panel for a gtm.historyChange event with both gtm.oldUrl and gtm.newUrl keys. Don’t guess from the framework name — Next.js, Vue, etc. don’t all behave the same.
  • gtm.oldUrl-based (preferred when the keys are present) — reads the previous URL from GTM’s History Change event. Requires two Data Layer variables: {{DLV > gtm.oldUrl}} and {{DLV > gtm.newUrl}}.
  • sessionStorage-only fallback — use when gtm.historyChange doesn’t fire (or you can’t enable the History Change listener). No companion DLVs needed. Caveat: this version reads location.href at evaluation time, so it assumes the SPA updates the URL before the dataLayer event that triggers the tag fires. The normal pushState → dataLayer.push({event: 'page_view'}) order satisfies this; if your framework pushes the dataLayer event before updating the URL, reorder the pushes — no JS workaround helps.
function(){
  var oldUrl = {{DLV > gtm.oldUrl}};
  var newUrl = {{DLV > gtm.newUrl}};

  // SPA navigation — previous page URL is the referrer
  if (oldUrl && newUrl && oldUrl !== newUrl) {
    sessionStorage.setItem('cjs_page_referrer', oldUrl);
    return oldUrl;
  }

  // Same page (cookie consent re-fire, other events, page reload) — return cached value
  var stored = sessionStorage.getItem('cjs_page_referrer');
  if (stored) {
    return stored;
  }

  // First page load — cache and return document.referrer
  sessionStorage.setItem('cjs_page_referrer', document.referrer);
  return document.referrer;
}
Both templates handle the same four cases consistently:
  1. SPA navigation — the previous URL is captured and returned as referrer (gtm.oldUrl reads it from the History Change event; the fallback compares the cached last_url against location.href).
  2. Cookie consent re-fire / other events on the same page — returns the cached value from sessionStorage, keeping the referrer consistent across all events on the same page view.
  3. Page reload after SPA navigationsessionStorage survives the reload, so the SPA referrer is preserved.
  4. First page load — caches and returns document.referrer (external referrer).
The sessionStorage keys (cjs_page_referrer, cjs_page_referrer_last_url) are intentionally vendor-neutral so the same variable can back any analytics tag.
V2 events must be gated by cookie consent. There are two strategies — Stage + flush is the default and recommended choice; GTM native per-tag consent is a simpler alternative for setups where consent is guaranteed to be resolved before any tracked event ever fires.

Why Stage + flush is the default

Tracking events routinely fire before consent is settled, in two common ways:
  1. Events fire before the user has decided. A first-time visitor lands on a category page and the view_item_list trigger fires while the consent banner is still showing. The user may accept consent seconds later — but with GTM-native gating the initial event was already blocked and is gone. With Stage + flush, the event is queued in sessionStorage and replayed the moment consent is granted.
  2. Events fire before the CMP has written consent into a GTM variable. Many CMPs resolve consent asynchronously after the page has already started pushing dataLayer events, or write the consent cookie before GTM’s consent variable has read it back in. Triggers that fire in that window see “consent denied” even though the user already accepted. GTM-native gating will block those events; Stage + flush queues them and flushes when the consent state is finally read into the GTM variable.
Stage + flush also survives navigation — sessionStorage persists across pages within the tab, so an event triggered on a page the user left before accepting is still flushed once they accept later. Stage + flush gates dema.event() on {{Consent - Statistics}} — a Custom JavaScript GTM variable that returns 'granted' or 'denied' based on the CMP’s current statistics/analytics consent state.
Per-event tag — wrap each dema.event(...) call with a consent check, queueing the payload when consent isn’t granted yet:
if ({{Consent - Statistics}} === 'granted') {
  dema.event(payload, options);
} else {
  dema.addToQueue(payload, options);
}
dema.addToQueue persists the payload to sessionStorage (key dema_pending_events). Flush tag — one shared Custom HTML tag that calls dema.emitPendingEvents() when consent is granted:
<script type="text/javascript">
  !function(){
    var t=window.dema=window.dema||[];
    if(t.bInitialized)return;
    t.init=function(e,v){
      if(!t.bInitialized){
        var i=document.createElement("script");
        i.setAttribute("async",true);
        i.setAttribute("src","https://tag.dema.ai/tag.js?id="+e+"&v="+encodeURIComponent(v));
        document.head.appendChild(i);
      }
    };
    t.functions=["event","addToQueue","emitPendingEvents"];
    for(var e=0;e<t.functions.length;e++){
      t[t.functions[e]]=(function(fn){return function(){Array.prototype.unshift.call(arguments,fn);t.push(arguments);return t;};})(t.functions[e]);
    }
    t.init("{{Dema > ID}}","2.0.0");
  }();

  if ({{Consent - Statistics}} === 'granted') {
    dema.emitPendingEvents();
  }
</script>
Trigger the flush tag on the CMP’s consent dataLayer event (e.g. cookie_consent_update for Cookiebot, OneTrustGroupsUpdated for OneTrust) — Custom Event trigger. The flush must fire whenever the consent state is read into the GTM consent variable, not only at the moment the user clicks accept. That covers both:
  1. First-time grant — the user accepts the banner, the CMP pushes the consent event, and {{Consent - Statistics}} flips to 'granted'.
  2. Returning visits — on page load the CMP reads its existing cookie and pushes the same consent event so the GTM variable is populated. Any events queued earlier in that page load (because their triggers fired before the CMP had populated the consent variable) are flushed at that moment.
Without the second case, events that fired on the initial page load before the CMP had read its cookie would sit in the queue indefinitely. Do not attach per-event triggers (view_item_list, purchase, …) to the flush tag — its only job is to drain the queue.
With Stage + flush, leave Advanced Settings → Consent Settings → Require additional consent for tag to fire set to Not set on every event tag and on the flush tag. The consent decision lives inside the tag body — if ({{Consent - Statistics}} === 'granted') { … } else { dema.addToQueue(…); }. If GTM blocks the tag at the harness level, the JavaScript never runs, the queue stays empty, and pre-consent events are dropped silently.
Queue behaviour:
  • Stored in sessionStorage under key dema_pending_events. Persists across navigations within the tab.
  • Capped at 100 entries — oldest dropped on overflow, so recent funnel state survives.
  • Cleared on a successful emitPendingEvents() replay.
With Stage + flush, set page_location: location.href and page_referrer explicitly on every staged event. The library auto-captures these only at the moment dema.event(...) actually runs — for queued events that’s the flush-tag firing time, potentially a different page than the one that triggered the event.
Configure each event tag’s Advanced Settings → Consent Settings → Require additional consent for tag to fire to the appropriate category (typically analytics_storage). GTM defers or blocks the tag until consent is granted; no change to the tag bodies. Use this only when all of the following hold:
  • The CMP is fully integrated with GTM’s consent model.
  • The consent state is reliably resolved into the GTM consent variable before any tracked event triggers fire.
  • You accept losing pre-consent events on a given page — GTM’s per-tag defer only survives as long as the tag is waiting on the same page, so if the user navigates away before accepting, those events are gone.
If any of those don’t hold, prefer Stage + flush.

Tag implementation

Each event below is implemented as a Custom HTML tag in GTM. Every tag includes a self-contained stub initializer — this is intentional and recommended.
Always include the stub in every tag that calls dema.*. If an event tag fires before the library is initialized (for example, a user landing directly on an order confirmation page from an email), dema will be undefined and the event will be lost. The stub is idempotent and safe to include in every tag.
For events that carry a list of items (purchase, view_cart, view_item_list, begin_checkout), pass {method: 'POST'} as the second argument to dema.event() (and to dema.addToQueue()). This avoids hitting URL length limits on GET requests.
Each snippet below has two tabs:
  • Stage + flush (default, recommended) — applies the consent-aware staging pattern from the Cookie consent section. Captures events that fire before consent is resolved and replays them on the flush tag.
  • GTM-native consent — the bare dema.event() call, suitable only when relying on GTM’s per-tag consent gating (Strategy 2).

Core event tags

page_view

Fires on every page view — both PDP and non-PDP pages.
  • Tag Type: Custom HTML
  • Trigger: All Pages
  • Data layer fields: none required — only uses the page referrer
<script type="text/javascript">
  !function(){
    var t=window.dema=window.dema||[];
    if(t.bInitialized)return;
    t.init=function(e,v){
      if(!t.bInitialized){
        var i=document.createElement("script");
        i.setAttribute("async",true);
        i.setAttribute("src","https://tag.dema.ai/tag.js?id="+e+"&v="+encodeURIComponent(v));
        document.head.appendChild(i);
      }
    };
    t.functions=["event","addToQueue","emitPendingEvents"];
    for(var e=0;e<t.functions.length;e++){
      t[t.functions[e]]=(function(fn){return function(){Array.prototype.unshift.call(arguments,fn);t.push(arguments);return t;};})(t.functions[e]);
    }
    t.init("{{Dema > ID}}","2.0.0");
  }();

  var payload = {
    event_name: 'page_view',
    page_location: location.href,
    page_referrer: {{JS > Referrer}}
  };

  if ({{Consent - Statistics}} === 'granted') {
    dema.event(payload);
  } else {
    dema.addToQueue(payload);
  }
</script>
↑ Back to supported event types

view_item

Fires when a user views a product detail page. Send in addition to page_view on PDPs.
  • Tag Type: Custom HTML
  • Trigger: view_item event
  • Data layer fields:
    • ecommerce.items[0].item_id — product ID
    • ecommerce.items[0].price — unit price
    • ecommerce.currency — ISO 4217 currency code
<script type="text/javascript">
  !function(){
    var t=window.dema=window.dema||[];
    if(t.bInitialized)return;
    t.init=function(e,v){
      if(!t.bInitialized){
        var i=document.createElement("script");
        i.setAttribute("async",true);
        i.setAttribute("src","https://tag.dema.ai/tag.js?id="+e+"&v="+encodeURIComponent(v));
        document.head.appendChild(i);
      }
    };
    t.functions=["event","addToQueue","emitPendingEvents"];
    for(var e=0;e<t.functions.length;e++){
      t[t.functions[e]]=(function(fn){return function(){Array.prototype.unshift.call(arguments,fn);t.push(arguments);return t;};})(t.functions[e]);
    }
    t.init("{{Dema > ID}}","2.0.0");
  }();

  var item = {{DLV > ecommerce}}.items && {{DLV > ecommerce}}.items.length > 0 ? {{DLV > ecommerce}}.items[0] : null;
  if (item) {
    var payload = {
      event_name: 'view_item',
      page_location: location.href,
      page_referrer: {{JS > Referrer}},
      items: [{
        product_id: item.item_id ? item.item_id : "",
        price: item.price,
        quantity: 1
      }],
      currency: {{DLV > ecommerce}}.currency
    };

    if ({{Consent - Statistics}} === 'granted') {
      dema.event(payload);
    } else {
      dema.addToQueue(payload);
    }
  }
</script>
↑ Back to supported event types

purchase

Fires when a user completes a transaction. Uses POST to avoid URL length limits on item lists.
  • Tag Type: Custom HTML
  • Trigger: purchase event
  • Data layer fields:
    • ecommerce.items[] — each item with item_id, item_sku, index, price, quantity
    • ecommerce.currency — ISO 4217 currency code
    • ecommerce.transaction_id — order ID
    • ecommerce.value — order total
    • ecommerce.shipping — shipping cost
    • ecommerce.tax — tax amount
    • ecommerce.coupon — voucher code (optional)
    • customer.id — customer identifier
    • ecommerce.chosen_shipping_method — shipping provider (optional)
    • ecommerce.payment_provider — payment provider, e.g. "klarna", "stripe", "paypal" (optional)
<script type="text/javascript">
  !function(){
    var t=window.dema=window.dema||[];
    if(t.bInitialized)return;
    t.init=function(e,v){
      if(!t.bInitialized){
        var i=document.createElement("script");
        i.setAttribute("async",true);
        i.setAttribute("src","https://tag.dema.ai/tag.js?id="+e+"&v="+encodeURIComponent(v));
        document.head.appendChild(i);
      }
    };
    t.functions=["event","addToQueue","emitPendingEvents"];
    for(var e=0;e<t.functions.length;e++){
      t[t.functions[e]]=(function(fn){return function(){Array.prototype.unshift.call(arguments,fn);t.push(arguments);return t;};})(t.functions[e]);
    }
    t.init("{{Dema > ID}}","2.0.0");
  }();

  var eventItems = [];
  var dataLayerItems = {{DLV > ecommerce}}.items || [];
  for (var i = 0; i < dataLayerItems.length; i++) {
    var item = dataLayerItems[i];
    eventItems.push({
      product_id: item.item_id ? item.item_id : "",
      variant_no: item.item_sku ? item.item_sku : "",
      index: item.index != null ? item.index : i,
      price: item.price ? item.price : 0,
      quantity: item.quantity ? item.quantity : 1
    });
  }

  var eventVouchers = [];
  if ({{DLV > ecommerce}}.coupon) {
    eventVouchers.push({code: {{DLV > ecommerce}}.coupon});
  }

  var payload = {
    event_name: 'purchase',
    page_location: location.href,
    page_referrer: {{JS > Referrer}},
    items: eventItems,
    currency: {{DLV > ecommerce}}.currency,
    order_id: {{DLV > ecommerce}}.transaction_id,
    total: {{DLV > ecommerce}}.value,
    shipping: {{DLV > ecommerce}}.shipping,
    tax: {{DLV > ecommerce}}.tax,
    vouchers: eventVouchers,
    customer_id: {{DLV > customer.id}},
    shipping_provider: {{DLV > ecommerce.chosen_shipping_method}},
    payment_provider: {{DLV > ecommerce.payment_provider}}
  };
  var options = {method: 'POST'};

  if ({{Consent - Statistics}} === 'granted') {
    dema.event(payload, options);
  } else {
    dema.addToQueue(payload, options);
  }
</script>
↑ Back to supported event types

Optional event tags

view_item_list

Fires when a product list (category page, search results, recommendations) is displayed. Can fire once for all items on the page, or multiple times as the user scrolls and more items become visible.
  • Tag Type: Custom HTML
  • Trigger: view_item_list event
  • Data layer fields:
    • ecommerce.items[] — each item with item_id, index (position in the list), price
    • ecommerce.currency — ISO 4217 currency code
<script type="text/javascript">
  !function(){
    var t=window.dema=window.dema||[];
    if(t.bInitialized)return;
    t.init=function(e,v){
      if(!t.bInitialized){
        var i=document.createElement("script");
        i.setAttribute("async",true);
        i.setAttribute("src","https://tag.dema.ai/tag.js?id="+e+"&v="+encodeURIComponent(v));
        document.head.appendChild(i);
      }
    };
    t.functions=["event","addToQueue","emitPendingEvents"];
    for(var e=0;e<t.functions.length;e++){
      t[t.functions[e]]=(function(fn){return function(){Array.prototype.unshift.call(arguments,fn);t.push(arguments);return t;};})(t.functions[e]);
    }
    t.init("{{Dema > ID}}","2.0.0");
  }();

  var eventItems = [];
  var dataLayerItems = {{DLV > ecommerce}}.items || [];
  for (var i = 0; i < dataLayerItems.length; i++) {
    var item = dataLayerItems[i];
    eventItems.push({
      product_id: item.item_id ? item.item_id : "",
      index: item.index != null ? item.index : i, // position in the list
      price: item.price ? item.price : 0,
      quantity: 1
    });
  }

  if (eventItems.length > 0) {
    var payload = {
      event_name: 'view_item_list',
      page_location: location.href,
      page_referrer: {{JS > Referrer}},
      items: eventItems,
      currency: {{DLV > ecommerce}}.currency
    };
    var options = {method: 'POST'};

    if ({{Consent - Statistics}} === 'granted') {
      dema.event(payload, options);
    } else {
      dema.addToQueue(payload, options);
    }
  }
</script>
↑ Back to supported event types

select_item

Fires when a user selects a product from a list. The index field is important — it captures the position at which the item was presented, which powers list performance and click-through analytics.
  • Tag Type: Custom HTML
  • Trigger: select_item event
  • Data layer fields:
    • ecommerce.items[0].item_id — product ID
    • ecommerce.items[0].index — position in the list
    • ecommerce.items[0].price — unit price
    • ecommerce.currency — ISO 4217 currency code
<script type="text/javascript">
  !function(){
    var t=window.dema=window.dema||[];
    if(t.bInitialized)return;
    t.init=function(e,v){
      if(!t.bInitialized){
        var i=document.createElement("script");
        i.setAttribute("async",true);
        i.setAttribute("src","https://tag.dema.ai/tag.js?id="+e+"&v="+encodeURIComponent(v));
        document.head.appendChild(i);
      }
    };
    t.functions=["event","addToQueue","emitPendingEvents"];
    for(var e=0;e<t.functions.length;e++){
      t[t.functions[e]]=(function(fn){return function(){Array.prototype.unshift.call(arguments,fn);t.push(arguments);return t;};})(t.functions[e]);
    }
    t.init("{{Dema > ID}}","2.0.0");
  }();

  var item = {{DLV > ecommerce}}.items && {{DLV > ecommerce}}.items.length > 0 ? {{DLV > ecommerce}}.items[0] : null;
  if (item) {
    var payload = {
      event_name: 'select_item',
      page_location: location.href,
      page_referrer: {{JS > Referrer}},
      items: [{
        product_id: item.item_id ? item.item_id : "",
        index: item.index, // position in the list
        price: item.price,
        quantity: 1
      }],
      currency: {{DLV > ecommerce}}.currency
    };

    if ({{Consent - Statistics}} === 'granted') {
      dema.event(payload);
    } else {
      dema.addToQueue(payload);
    }
  }
</script>
↑ Back to supported event types

add_to_cart

Fires when a user adds a product to the cart. quantity should be the delta (how many were added), not the total in the cart.
  • Tag Type: Custom HTML
  • Trigger: add_to_cart event
  • Data layer fields:
    • ecommerce.items[0].item_id — product ID
    • ecommerce.items[0].item_sku — variant/SKU identifier
    • ecommerce.items[0].index — position in the cart (hardcode 0 if not supported)
    • ecommerce.items[0].price — unit price
    • ecommerce.items[0].quantity — quantity added (delta)
    • ecommerce.currency — ISO 4217 currency code
<script type="text/javascript">
  !function(){
    var t=window.dema=window.dema||[];
    if(t.bInitialized)return;
    t.init=function(e,v){
      if(!t.bInitialized){
        var i=document.createElement("script");
        i.setAttribute("async",true);
        i.setAttribute("src","https://tag.dema.ai/tag.js?id="+e+"&v="+encodeURIComponent(v));
        document.head.appendChild(i);
      }
    };
    t.functions=["event","addToQueue","emitPendingEvents"];
    for(var e=0;e<t.functions.length;e++){
      t[t.functions[e]]=(function(fn){return function(){Array.prototype.unshift.call(arguments,fn);t.push(arguments);return t;};})(t.functions[e]);
    }
    t.init("{{Dema > ID}}","2.0.0");
  }();

  var item = {{DLV > ecommerce}}.items && {{DLV > ecommerce}}.items.length > 0 ? {{DLV > ecommerce}}.items[0] : null;
  if (item) {
    var payload = {
      event_name: 'add_to_cart',
      page_location: location.href,
      page_referrer: {{JS > Referrer}},
      items: [{
        product_id: item.item_id ? item.item_id : "",
        variant_no: item.item_sku ? item.item_sku : "",
        index: item.index, // hardcode as 0 when cart index is not supported
        price: item.price,
        quantity: item.quantity
      }],
      currency: {{DLV > ecommerce}}.currency
    };

    if ({{Consent - Statistics}} === 'granted') {
      dema.event(payload);
    } else {
      dema.addToQueue(payload);
    }
  }
</script>
↑ Back to supported event types

remove_from_cart

Fires when a user removes a product from the cart. quantity should be the delta (how many were removed), not the total remaining.
  • Tag Type: Custom HTML
  • Trigger: remove_from_cart event
  • Data layer fields:
    • ecommerce.items[0].item_id — product ID
    • ecommerce.items[0].item_sku — variant/SKU identifier
    • ecommerce.items[0].index — position in the cart (hardcode 0 if not supported)
    • ecommerce.items[0].price — unit price
    • ecommerce.items[0].quantity — quantity removed (delta)
    • ecommerce.currency — ISO 4217 currency code
<script type="text/javascript">
  !function(){
    var t=window.dema=window.dema||[];
    if(t.bInitialized)return;
    t.init=function(e,v){
      if(!t.bInitialized){
        var i=document.createElement("script");
        i.setAttribute("async",true);
        i.setAttribute("src","https://tag.dema.ai/tag.js?id="+e+"&v="+encodeURIComponent(v));
        document.head.appendChild(i);
      }
    };
    t.functions=["event","addToQueue","emitPendingEvents"];
    for(var e=0;e<t.functions.length;e++){
      t[t.functions[e]]=(function(fn){return function(){Array.prototype.unshift.call(arguments,fn);t.push(arguments);return t;};})(t.functions[e]);
    }
    t.init("{{Dema > ID}}","2.0.0");
  }();

  var item = {{DLV > ecommerce}}.items && {{DLV > ecommerce}}.items.length > 0 ? {{DLV > ecommerce}}.items[0] : null;
  if (item) {
    var payload = {
      event_name: 'remove_from_cart',
      page_location: location.href,
      page_referrer: {{JS > Referrer}},
      items: [{
        product_id: item.item_id ? item.item_id : "",
        variant_no: item.item_sku ? item.item_sku : "",
        index: item.index, // hardcode as 0 when cart index is not supported
        price: item.price,
        quantity: item.quantity
      }],
      currency: {{DLV > ecommerce}}.currency
    };

    if ({{Consent - Statistics}} === 'granted') {
      dema.event(payload);
    } else {
      dema.addToQueue(payload);
    }
  }
</script>
↑ Back to supported event types

view_cart

Fires when the cart page is viewed. Uses POST to avoid URL length limits.
  • Tag Type: Custom HTML
  • Trigger: view_cart event
  • Data layer fields:
    • ecommerce.items[] — each item with item_id, item_sku, index, price, quantity
    • ecommerce.currency — ISO 4217 currency code
    • ecommerce.value — cart total
<script type="text/javascript">
  !function(){
    var t=window.dema=window.dema||[];
    if(t.bInitialized)return;
    t.init=function(e,v){
      if(!t.bInitialized){
        var i=document.createElement("script");
        i.setAttribute("async",true);
        i.setAttribute("src","https://tag.dema.ai/tag.js?id="+e+"&v="+encodeURIComponent(v));
        document.head.appendChild(i);
      }
    };
    t.functions=["event","addToQueue","emitPendingEvents"];
    for(var e=0;e<t.functions.length;e++){
      t[t.functions[e]]=(function(fn){return function(){Array.prototype.unshift.call(arguments,fn);t.push(arguments);return t;};})(t.functions[e]);
    }
    t.init("{{Dema > ID}}","2.0.0");
  }();

  var eventItems = [];
  var dataLayerItems = {{DLV > ecommerce}}.items || [];
  for (var i = 0; i < dataLayerItems.length; i++) {
    var item = dataLayerItems[i];
    eventItems.push({
      product_id: item.item_id ? item.item_id : "",
      variant_no: item.item_sku ? item.item_sku : "",
      index: item.index != null ? item.index : i,
      price: item.price ? item.price : 0,
      quantity: item.quantity ? item.quantity : 1
    });
  }

  var payload = {
    event_name: 'view_cart',
    page_location: location.href,
    page_referrer: {{JS > Referrer}},
    items: eventItems,
    currency: {{DLV > ecommerce}}.currency,
    total: {{DLV > ecommerce}}.value
  };
  var options = {method: 'POST'};

  if ({{Consent - Statistics}} === 'granted') {
    dema.event(payload, options);
  } else {
    dema.addToQueue(payload, options);
  }
</script>
↑ Back to supported event types

add_to_wishlist

Fires when a user adds a product to the wishlist.
  • Tag Type: Custom HTML
  • Trigger: add_to_wishlist event
  • Data layer fields:
    • ecommerce.items[0].item_id — product ID
    • ecommerce.items[0].index — position in the wishlist (hardcode 0 if not supported)
    • ecommerce.items[0].price — unit price
    • ecommerce.currency — ISO 4217 currency code
<script type="text/javascript">
  !function(){
    var t=window.dema=window.dema||[];
    if(t.bInitialized)return;
    t.init=function(e,v){
      if(!t.bInitialized){
        var i=document.createElement("script");
        i.setAttribute("async",true);
        i.setAttribute("src","https://tag.dema.ai/tag.js?id="+e+"&v="+encodeURIComponent(v));
        document.head.appendChild(i);
      }
    };
    t.functions=["event","addToQueue","emitPendingEvents"];
    for(var e=0;e<t.functions.length;e++){
      t[t.functions[e]]=(function(fn){return function(){Array.prototype.unshift.call(arguments,fn);t.push(arguments);return t;};})(t.functions[e]);
    }
    t.init("{{Dema > ID}}","2.0.0");
  }();

  var item = {{DLV > ecommerce}}.items && {{DLV > ecommerce}}.items.length > 0 ? {{DLV > ecommerce}}.items[0] : null;
  if (item) {
    var payload = {
      event_name: 'add_to_wishlist',
      page_location: location.href,
      page_referrer: {{JS > Referrer}},
      items: [{
        product_id: item.item_id ? item.item_id : "",
        index: item.index, // hardcode as 0 when wishlist index is not supported
        price: item.price,
        quantity: 1
      }],
      currency: {{DLV > ecommerce}}.currency
    };

    if ({{Consent - Statistics}} === 'granted') {
      dema.event(payload);
    } else {
      dema.addToQueue(payload);
    }
  }
</script>
↑ Back to supported event types

remove_from_wishlist

Fires when a user removes a product from the wishlist.
  • Tag Type: Custom HTML
  • Trigger: remove_from_wishlist event
  • Data layer fields:
    • ecommerce.items[0].item_id — product ID
    • ecommerce.items[0].index — position in the wishlist (hardcode 0 if not supported)
    • ecommerce.items[0].price — unit price
    • ecommerce.currency — ISO 4217 currency code
<script type="text/javascript">
  !function(){
    var t=window.dema=window.dema||[];
    if(t.bInitialized)return;
    t.init=function(e,v){
      if(!t.bInitialized){
        var i=document.createElement("script");
        i.setAttribute("async",true);
        i.setAttribute("src","https://tag.dema.ai/tag.js?id="+e+"&v="+encodeURIComponent(v));
        document.head.appendChild(i);
      }
    };
    t.functions=["event","addToQueue","emitPendingEvents"];
    for(var e=0;e<t.functions.length;e++){
      t[t.functions[e]]=(function(fn){return function(){Array.prototype.unshift.call(arguments,fn);t.push(arguments);return t;};})(t.functions[e]);
    }
    t.init("{{Dema > ID}}","2.0.0");
  }();

  var item = {{DLV > ecommerce}}.items && {{DLV > ecommerce}}.items.length > 0 ? {{DLV > ecommerce}}.items[0] : null;
  if (item) {
    var payload = {
      event_name: 'remove_from_wishlist',
      page_location: location.href,
      page_referrer: {{JS > Referrer}},
      items: [{
        product_id: item.item_id ? item.item_id : "",
        index: item.index, // hardcode as 0 when wishlist index is not supported
        price: item.price,
        quantity: 1
      }],
      currency: {{DLV > ecommerce}}.currency
    };

    if ({{Consent - Statistics}} === 'granted') {
      dema.event(payload);
    } else {
      dema.addToQueue(payload);
    }
  }
</script>
↑ Back to supported event types

begin_checkout

Fires when the user starts the checkout process. Uses POST to avoid URL length limits.
  • Tag Type: Custom HTML
  • Trigger: begin_checkout event
  • Data layer fields:
    • ecommerce.items[] — each item with item_id, item_sku, index, price, quantity
    • ecommerce.currency — ISO 4217 currency code
    • ecommerce.value — cart total
<script type="text/javascript">
  !function(){
    var t=window.dema=window.dema||[];
    if(t.bInitialized)return;
    t.init=function(e,v){
      if(!t.bInitialized){
        var i=document.createElement("script");
        i.setAttribute("async",true);
        i.setAttribute("src","https://tag.dema.ai/tag.js?id="+e+"&v="+encodeURIComponent(v));
        document.head.appendChild(i);
      }
    };
    t.functions=["event","addToQueue","emitPendingEvents"];
    for(var e=0;e<t.functions.length;e++){
      t[t.functions[e]]=(function(fn){return function(){Array.prototype.unshift.call(arguments,fn);t.push(arguments);return t;};})(t.functions[e]);
    }
    t.init("{{Dema > ID}}","2.0.0");
  }();

  var eventItems = [];
  var dataLayerItems = {{DLV > ecommerce}}.items || [];
  for (var i = 0; i < dataLayerItems.length; i++) {
    var item = dataLayerItems[i];
    eventItems.push({
      product_id: item.item_id ? item.item_id : "",
      variant_no: item.item_sku ? item.item_sku : "",
      index: item.index != null ? item.index : i,
      price: item.price ? item.price : 0,
      quantity: item.quantity ? item.quantity : 1
    });
  }

  var payload = {
    event_name: 'begin_checkout',
    page_location: location.href,
    page_referrer: {{JS > Referrer}},
    items: eventItems,
    currency: {{DLV > ecommerce}}.currency,
    total: {{DLV > ecommerce}}.value
  };
  var options = {method: 'POST'};

  if ({{Consent - Statistics}} === 'granted') {
    dema.event(payload, options);
  } else {
    dema.addToQueue(payload, options);
  }
</script>
↑ Back to supported event types

Event data schema reference

The event detail payload must conform to the TrackedEventData schema. Below is a summary of the available fields.
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "title": "TrackedEventData",
  "description": "Payload containing event data sent with a server-side tracking request.",
  "type": "object",
  "required": ["event_name"],
  "properties": {
    "event_name": {
      "type": "string",
      "description": "Name of the tracked event."
    },
    "type": {
      "type": "string",
      "description": "Event-specific type. For example, for order events the type can be set to `EXCLUDED` to prevent the order from appearing in the platform."
    },
    "user_agent": {
      "type": "string",
      "description": "User agent string of the client sending the tracked event."
    },
    "ip": {
      "type": "string",
      "description": "IP address sent in tracking event or resolved by the tracking system."
    },
    "country": {
      "type": "string",
      "description": "Two-letter ISO 3166-1 alpha-2 country code. When provided, overrides the country resolved from the IP address."
    },
    "region": {
      "type": "string",
      "description": "Region (state/province). When provided, overrides the region resolved from the IP address."
    },
    "zip_code": {
      "type": "string",
      "description": "ZIP/postal code. When provided, overrides the ZIP/postal code resolved from the IP address."
    },
    "city": {
      "type": "string",
      "description": "City. When provided, overrides the city resolved from the IP address."
    },
    "latitude": {
      "type": "number",
      "description": "Latitude coordinate. When provided, overrides the latitude resolved from the IP address."
    },
    "longitude": {
      "type": "number",
      "description": "Longitude coordinate. When provided, overrides the longitude resolved from the IP address."
    },
    "page_id": {
      "type": "string",
      "description": "Language-agnostic identifier of the page on which the event occurred."
    },
    "page_location": {
      "type": "string",
      "description": "Full URL of the page on which the event occurred."
    },
    "page_title": {
      "type": "string",
      "description": "Title of the page on which the event occurred."
    },
    "page_referrer": {
      "type": "string",
      "description": "Referrer URL from which the user navigated to the current page."
    },
    "cart_id": {
      "type": "string",
      "description": "Identifier of the shopping cart."
    },
    "wishlist_id": {
      "type": "string",
      "description": "Identifier of the wishlist."
    },
    "items": {
      "type": "array",
      "description": "List of items associated with the tracked event.",
      "items": {
        "$ref": "#/definitions/TrackedEventDataItem"
      }
    },
    "order_id": {
      "type": "string",
      "description": "Identifier of the order associated with the tracked event."
    },
    "currency": {
      "type": "string",
      "description": "Three letter ISO 4217 Currency code for the transaction."
    },
    "total": {
      "type": "number",
      "description": "Total value of the transaction, including items, tax and shipping."
    },
    "tax": {
      "type": "number",
      "description": "Total tax amount for the transaction including shipping tax."
    },
    "shipping": {
      "type": "number",
      "description": "Shipping cost for the transaction including tax."
    },
    "payment_provider": {
      "type": "string",
      "description": "Payment provider used for the transaction."
    },
    "shipping_provider": {
      "type": "string",
      "description": "Shipping provider used for the transaction."
    },
    "customer_id": {
      "type": "string",
      "description": "Identifier of the customer."
    },
    "vouchers": {
      "type": "array",
      "description": "List of vouchers applied in the transaction.",
      "items": {
        "$ref": "#/definitions/TrackedEventDataVoucher"
      }
    },
    "custom_attribute_1": {
      "type": "string",
      "description": "Custom attribute on tracked event level."
    },
    "custom_attribute_2": {
      "type": "string",
      "description": "Custom attribute on tracked event level."
    },
    "custom_attribute_3": {
      "type": "string",
      "description": "Custom attribute on tracked event level."
    },
    "custom_attribute_4": {
      "type": "string",
      "description": "Custom attribute on tracked event level."
    },
    "custom_attribute_5": {
      "type": "string",
      "description": "Custom attribute on tracked event level."
    },
    "custom_attribute_6": {
      "type": "string",
      "description": "Custom attribute on tracked event level."
    },
    "custom_attribute_7": {
      "type": "string",
      "description": "Custom attribute on tracked event level."
    },
    "custom_attribute_8": {
      "type": "string",
      "description": "Custom attribute on tracked event level."
    },
    "custom_attribute_9": {
      "type": "string",
      "description": "Custom attribute on tracked event level."
    },
    "custom_attribute_10": {
      "type": "string",
      "description": "Custom attribute on tracked event level."
    },
    "custom_attribute_11": {
      "type": "string",
      "description": "Custom attribute on tracked event level."
    },
    "custom_attribute_12": {
      "type": "string",
      "description": "Custom attribute on tracked event level."
    },
    "custom_attribute_13": {
      "type": "string",
      "description": "Custom attribute on tracked event level."
    },
    "custom_attribute_14": {
      "type": "string",
      "description": "Custom attribute on tracked event level."
    },
    "custom_attribute_15": {
      "type": "string",
      "description": "Custom attribute on tracked event level."
    },
    "custom_attribute_16": {
      "type": "string",
      "description": "Custom attribute on tracked event level."
    },
    "custom_attribute_17": {
      "type": "string",
      "description": "Custom attribute on tracked event level."
    },
    "custom_attribute_18": {
      "type": "string",
      "description": "Custom attribute on tracked event level."
    },
    "custom_attribute_19": {
      "type": "string",
      "description": "Custom attribute on tracked event level."
    },
    "custom_attribute_20": {
      "type": "string",
      "description": "Custom attribute on tracked event level."
    }
  },
  "additionalProperties": false,
  "definitions": {
    "TrackedEventDataItem": {
      "type": "object",
      "required": ["product_id"],
      "description": "Item line associated with a tracked event.",
      "properties": {
        "index": {
          "type": "integer",
          "description": "Index of the item line in the tracked event. This value is event context specific e.g. in select_item event it represents the index of the item in the list that was selected, in add_to_cart event it represents the index of the item in the cart etc."
        },
        "product_id": {
          "type": "string",
          "description": "Product identifier used for identifying the product in the external system."
        },
        "variant_no": {
          "type": "string",
          "description": "Identifier of the specific product variant."
        },
        "quantity": {
          "type": "number",
          "description": "Quantity of the item line."
        },
        "price": {
          "type": "number",
          "description": "Price of a single item unit."
        },
        "warehouse": {
          "type": "string",
          "description": "Warehouse associated with the item."
        },
        "custom_attribute_1": {
          "type": "string",
          "description": "Custom attribute on tracked event item level."
        },
        "custom_attribute_2": {
          "type": "string",
          "description": "Custom attribute on tracked event item level."
        },
        "custom_attribute_3": {
          "type": "string",
          "description": "Custom attribute on tracked event item level."
        },
        "custom_attribute_4": {
          "type": "string",
          "description": "Custom attribute on tracked event item level."
        },
        "custom_attribute_5": {
          "type": "string",
          "description": "Custom attribute on tracked event item level."
        },
        "custom_attribute_6": {
          "type": "string",
          "description": "Custom attribute on tracked event item level."
        },
        "custom_attribute_7": {
          "type": "string",
          "description": "Custom attribute on tracked event item level."
        },
        "custom_attribute_8": {
          "type": "string",
          "description": "Custom attribute on tracked event item level."
        },
        "custom_attribute_9": {
          "type": "string",
          "description": "Custom attribute on tracked event item level."
        },
        "custom_attribute_10": {
          "type": "string",
          "description": "Custom attribute on tracked event item level."
        },
        "custom_attribute_11": {
          "type": "string",
          "description": "Custom attribute on tracked event item level."
        },
        "custom_attribute_12": {
          "type": "string",
          "description": "Custom attribute on tracked event item level."
        },
        "custom_attribute_13": {
          "type": "string",
          "description": "Custom attribute on tracked event item level."
        },
        "custom_attribute_14": {
          "type": "string",
          "description": "Custom attribute on tracked event item level."
        },
        "custom_attribute_15": {
          "type": "string",
          "description": "Custom attribute on tracked event item level."
        },
        "custom_attribute_16": {
          "type": "string",
          "description": "Custom attribute on tracked event item level."
        },
        "custom_attribute_17": {
          "type": "string",
          "description": "Custom attribute on tracked event item level."
        },
        "custom_attribute_18": {
          "type": "string",
          "description": "Custom attribute on tracked event item level."
        },
        "custom_attribute_19": {
          "type": "string",
          "description": "Custom attribute on tracked event item level."
        },
        "custom_attribute_20": {
          "type": "string",
          "description": "Custom attribute on tracked event item level."
        }
      },
      "additionalProperties": false
    },
    "TrackedEventDataVoucher": {
      "type": "object",
      "description": "A voucher (discount) applied to the transaction.",
      "required": ["code"],
      "properties": {
        "code": {
          "type": "string",
          "description": "The voucher's redemption code."
        },
        "name": {
          "type": "string",
          "description": "Voucher name."
        },
        "discount": {
          "type": "number",
          "description": "Value of the voucher discount."
        },
        "type": {
          "type": "string",
          "description": "Type of the discount."
        }
      },
      "additionalProperties": false
    }
  }
}

Event fields

event_name
string
required
Name of the tracked event (e.g., page_view, view_item, purchase).
page_location
string
Full URL of the page where the event occurred.
page_referrer
string
Referrer URL from which the user navigated to the current page.
page_title
string
Title of the page where the event occurred.
page_id
string
Language-agnostic identifier of the page on which the event occurred.
user_agent
string
User agent string of the client sending the tracked event.
ip
string
IP address of the client. Used to resolve location fields if not provided explicitly.
country
string
Two-letter ISO 3166-1 alpha-2 country code. When provided, overrides the country resolved from the IP address.
region
string
Region (state/province). When provided, overrides the region resolved from the IP address.
city
string
City. When provided, overrides the city resolved from the IP address.
zip_code
string
ZIP/postal code. When provided, overrides the ZIP/postal code resolved from the IP address.
latitude
number
Latitude coordinate. When provided, overrides the latitude resolved from the IP address.
longitude
number
Longitude coordinate. When provided, overrides the longitude resolved from the IP address.
type
string
Event-specific type. For example, for order events the type can be set to EXCLUDED to prevent the order from appearing in the platform.
order_id
string
Identifier of the order associated with the tracked event.
customer_id
string
Identifier of the customer. Typically an email address, but can be any consistent identifier.
currency
string
Three-letter ISO 4217 currency code (e.g., EUR, USD).
total
number
Total value of the transaction, including items, tax, and shipping.
tax
number
Total tax amount for the transaction including shipping tax.
shipping
number
Shipping cost for the transaction including tax.
payment_provider
string
Payment provider used for the transaction.
shipping_provider
string
Shipping provider used for the transaction.
cart_id
string
Identifier of the shopping cart.
wishlist_id
string
Identifier of the wishlist.
items
array
List of items associated with the event. See item fields below.
vouchers
array
List of vouchers applied to the transaction. See voucher fields below.
custom_attribute_1 – custom_attribute_20
string
Custom attributes for event-level data.

Item fields (items[])

product_id
string
required
Product identifier used for identifying the product in the external system.
index
integer
Index of the item line in the tracked event. Event context specific — e.g., in select_item it represents the item’s position in the list; in add_to_cart it represents the item’s position in the cart.
variant_no
string
Identifier of the specific product variant.
quantity
number
Quantity of the item line.
price
number
Price of a single item unit.
warehouse
string
Warehouse associated with the item.
custom_attribute_1 – custom_attribute_20
string
Custom attributes for item-level data.

Voucher fields (vouchers[])

code
string
required
The voucher’s redemption code.
name
string
Voucher name.
discount
number
Value of the voucher discount.
type
string
Type of the discount.

Additional notes

  • page_referrer consistency: The same page_referrer value should be passed across all events fired on the same page view. The {{JS > Referrer}} variable above handles this via sessionStorage.
  • SPA navigation: On single-page applications, document.referrer does not update on client-side navigation. The {{JS > Referrer}} variable bridges this gap — either by reading gtm.oldUrl from GTM’s History Change events, or via the sessionStorage fallback template above.
  • Custom event types: The event_name field is open-ended. You can emit custom events (e.g. quiz_completed, search_performed) and Dema can build custom metrics on top. Use snake_case and avoid collisions with built-in event names.