docs

Tracking and Analytics

In order to track usage of the Add-on in your storefront and get valuable insights into funnel metrics (e.g. customers adding a product to the basket after virtually trying it on), we recommend setting up tracking events.

The Virtual Try-On widget emits a full analytics funnel onto the Google Tag Manager (GTM) dataLayer. This page describes the events the storefront pushes and the GTM and Google Analytics 4 (GA4) configuration you need to report on them.

Storefront Events

All event shaping lives in a single composable: useVirtualTryOnEvents.

The useVirtualTryOnEvents Composable

track accepts either of the following:

  • The native vto:track CustomEvent dispatched by the widget bundle, so you can bind it directly with @vto:track="track".
  • A plain { name, params } object, so the host can originate events such as vto_impression.

track unwraps the payload and forwards it to useTracking().push as follows:

useTracking().push then automatically merges the storefront page and session context. It's also SSR-guarded and consent-gated. As a result, Virtual Try-On events inherit the same envelope and privacy guarantees as every other storefront event.

For local debugging, a development-only [VTO] console.log, guarded by import.meta.dev, mirrors each push. It's removed from production builds by tree shaking.

Impression Event

The wrapper emits vto_impression once per PDP view of a product variant. It fires through the v-element-visibility directive from @vueuse/components, so placements below the fold count only once they're scrolled into view. When the selected variant changes while the widget is still visible, a new impression is reported.

The impression carries the part of the common envelope that the host knows:

The impression doesn't carry an entry_label, because it measures only that the widget was shown. The distinction between try_it_on and my_look depends on the saved-look state, which the host can't see. The widget reports it on the vto_open event instead.

Event Catalog

The following event names form the VtoEventName union:

  • vto_impression (emitted by the host)
  • vto_open
  • vto_step_view
  • vto_consent_given
  • vto_photo_selected
  • vto_generation_started
  • vto_generation_completed
  • vto_generation_failed
  • vto_result_view
  • vto_result_enlarge
  • vto_look_saved
  • vto_look_downloaded
  • vto_retry
  • vto_add_to_cart
  • vto_close

The widget bundle emits all events except vto_impression through @vto:track. Each of these events carries event-specific params, for example, { entry_label, has_saved_look }, in addition to the full common envelope. The host emits vto_impression with the reduced envelope described in Impression Event.

Commerce Attribution with Dual add_to_cart

When a customer adds a product to the basket from within the widget, the host does the following:

1

Perform the basket mutation

It performs the real basket mutation through useBasketActions().addItem, which fires its own cart state event. If this step fails, the host skips the next step, so the funnel is never counted twice.

2

Mirror the GA4-shaped event

It mirrors the standard GA4-shaped add_to_cart event, built from the product and variant props, through trackAddToBasket(...). The event is tagged with vto_source: true and interaction_source: 'virtual_try_on'.

This keeps Virtual Try-On add-to-carts inside the normal basket funnel while keeping them attributable. The widget's own vto_add_to_cart funnel event flows separately through vto:track.

Engagement Flag

On the first vto_generation_completed event per session, the composable pushes vto_engaged: true as a bare dataLayer variable. Because the push has no event key, GTM never treats it as a funnel event.

The variable stays available in the dataLayer, so you can use it to segment your own purchase and refund events by Virtual Try-On exposure. The widget itself never sees orders.

The flag is pushed only once per session. The guard uses sessionStorage, which survives client-side navigation and reloads within the tab, plus an in-memory flag. If sessionStorage is unavailable, for example, in private browsing mode, the in-memory guard is used.

Required Tracking Module Changes

To keep the pushes type-safe, make the following backward-compatible changes to the storefront tracking module:

  • In modules/tracking/runtime/types/tracking.ts:
    • Add 'virtual_try_on' to TrackingInteractionSource.
    • Add a `vto_${string}` member to TrackingEventName. This accepts all vto_* event names without listing each one.
  • In modules/tracking/runtime/composables/useBasketEvents.ts, add an optional extraParams?: Record<string, unknown> argument to trackAddToBasket and spread it first into the push. This lets the mirrored add_to_cart event carry { vto_source: true }. Because the argument is optional, existing call sites aren't affected.

The storefront only pushes the vto_* events and vto_engaged onto window.dataLayer. Nothing consumes them until your GTM container has matching triggers and tags. For more information, see Google Tag Manager Setup.

Google Tag Manager Setup

This section describes only the Virtual Try-On-specific configuration you add on top of a working GTM setup. It requires no storefront code changes.

Before you start, make sure the standard GTM and dataLayer integration of your storefront is in place: the container is installed, connected to GA4, and consent is wired. For more information, see modules/tracking/TRACKING.md in your storefront.

Create the Trigger

All funnel events, from vto_open to vto_close plus the host-emitted vto_impression, share the vto_ prefix. This means a single Custom Event trigger consumes the whole funnel.

To create the trigger, configure the following in GTM:

  • Trigger type – "Custom Event"
  • Event name – ^vto_, with Use regex matching enabled
  • This trigger fires on – "All Custom Events"

Per-event triggers also work. However, we recommend the single regex trigger, because you maintain it in one place and it picks up new vto_* events automatically.

Forward Events to GA4

To forward the events, fire a GA4 Event tag, or your own analytics tag, on the trigger:

  • Event Name – {{Event}}, the built-in GTM variable that holds the dataLayer event value. This forwards each vto_* event name unchanged.
  • Event parameters – map the parameters you want to report on from Data Layer Variables. The page and session envelope is already on every push, added by useTracking, so page context is included without extra mapping.

Register Custom Dimensions

GA4 reports only on parameters that are registered as custom dimensions. For each Virtual Try-On field you want to segment or report on, create a Data Layer Variable and a matching GA4 custom dimension. The following table lists the recommended fields:

FieldSourceSuggested GA4 ScopePurpose
vto_engagedBare dataLayer variable, without eventUser-scoped (user property)Segment your own purchase and refund events by Virtual Try-On exposure. This is the key attribution dimension.
vto_sourceMirrored standard add_to_cart event, value trueEvent-scopedDistinguish add-to-carts that originate from Virtual Try-On within the normal basket funnel, without double counting.
try_on_session_idCommon parameter, from vto_generation_started onwardEvent-scopedJoin the client funnel to the server-side generation_log or the Generation Usage API.
vto_interaction_idCommon parameter on all widget events, not on the host-emitted vto_impressionEvent-scopedConnect a single widget engagement across the pre-generation funnel.

customer_type
scayle_product_id
scayle_variant_id
shop_country_id

Common parameters on every vto_* event, including vto_impressionEvent-scopedStandard segmentation by customer type (guest or registered), product, variant, and shop.
widget_versionCommon parameter on widget events only, because the host can't know itEvent-scopedAttribute funnel behavior to a widget release.

vto_engaged is the only field that must persist beyond the event that set it. Register it as a user-scoped custom dimension (user property), sourced from the vto_engaged Data Layer Variable. This makes it available on your later purchase and refund events, because user scope keeps the value on all later events from the same user, including later sessions.

Troubleshooting

GTM Doesn't Start (gtm_debug 403)

If the GTM runtime doesn't start, it looks the same as a tracking bug, because the storefront side keeps working: window.dataLayer fills with every storefront and vto_* event, nothing is logged, and no console error appears. Only the container is missing, so nothing consumes the events.

The most common cause is NUXT_PUBLIC_TRACKING_GTM_DEBUG=true. This setting sets runtimeConfig.public.tracking.gtm.debug, which @nuxt/scripts turns into &gtm_debug=x on the container URL. Outside a live Tag Assistant session, Google responds to that URL with 403:

The script then fires onerror, and window.google_tag_manager is never created.

To fix this, keep the flag set to false. You don't need it for debugging: when you connect through Preview, Tag Assistant adds its own gtm_auth and gtm_preview parameters.

To check whether GTM is running, run the following in the browser console on a page that should be tracked:

gtm.js pushes gtm.dom and gtm.load itself. If they're missing, the runtime never ran, no matter how complete the rest of the dataLayer looks.

A blocked request produces the same symptom, for example, when an ad blocker or privacy extension cancels requests to googletagmanager.com. To rule this out, test in an incognito window, where extensions are disabled by default.