Shopify combined listings analytics: track swatch clicks in GA4, Pixel, and custom JS

shopify combined listings analytics ga4 tracking

You enable combined product listings on your Shopify store and traffic to the site increases with subsequent sales. However, you can’t seem to tell which individual swatch or color was selected, or which color was ignored. Additionally, you are unable to find out if the carousel end had an impact on sales. All metrics are available in your Shopify report, but none of them can tell you the specifics you’re looking for regarding product variations.

The native Shopify combined listings feature does not fire any events. Zero. No track page view, no event for a swatch click. You can hook up GA4 until your eyes hurt and still get nothing. Unlike the native shopify combined listings which does not fire any events, Rubik Combined Listings fires a custom ‘rubikcombinedlistings:swatchclick’ event on the window every time a swatch is clicked. You can then listen for this event once, and forward it wherever you need to, and suddenly your combined listings in your shopify product page actually gives you some data in your overall analytics stack.

Event shape, 3 forwarding scenarios (GA4, Facebook, Custom Backend), naming convention that won’t bite you in the future and how to track swatch clicks and attribute to conversion. No vendor lock-in. One event that you can forward and make vendors you already pay for do cool things with.

In this post

Why native Shopify combined listings are analytically blind

Shopify renders product combined listings with swatches in a very simple form, where clicking on the swatch renders to a new URL for the product. No dataLayer.push event is triggered. The Shopify swipe/scroll based swatches do not get a click event either. Even if you tried to implement a generic link click event listener on the swatch, you would then be capturing links all over the site and would need to try to filter by id or class name, which would of course break with any theme refresh and changes to the site widgets.

Then what is a retailer to do? The retailer looks at GA4 landing page reports to try to inspect for themselves whether the variant’s swatch is performing better. But we’ve already established that sessions do not equal swatch clicks. One can imagine a customer landing on the red variant of a product entirely from a Google ad without ever clicking on a swatch. The complete picture is missing.

Why does Shopify default this to OFF? It makes no sense. Merchants pay for analytics tools and are then forced to have blank swatch rows.

The event Rubik fires

In addition to sending data to via your own dataLayer push, also fires a CustomEvent on window for every swatch click. This vanilla browser event can also be listened to and I leave that up to you to decide how you want to use it. Here’s how to catch it.

window.addEventListener('rcl:swatch_click', function(e) {
  var d = e.detail;
  // d.group_handle, d.option_name, d.option_value,
  // d.from_product_id, d.to_product_id, d.position,
  // d.layout, d.page_type, d.price, d.currency
  console.log('swatch clicked', d);
});

The detail payload contains all the relevant information needed to render the Swatch option. This includes the group handle for the option, the name of the option (Color, Material, Size, etc.), the actual value for the option (Navy, Oat, Charcoal, etc.), the source and destination product ID and the index of the swatch within the row it appears on. Additionally, the detail payload contains the layout for the option (grid or carousel) and the type of page the option is being rendered on (product, collection, grouped product, etc.). The price and currency for the variant are also included in the detail payload so that the correct revenue can be calculated.

One listener. Every page. Every swatch. That is the whole contract.

Forward to GA4

GA4 (Universal) – If you use GA4 (and you probably should, because Universal is dead), push the event into the dataLayer and let Google Tag Manager handle the rest. /* Google Analytics for Firebase */ gtag(‘event’, ‘page_view’); // put this code in your theme.liquid right before the closing </body> tag

window.addEventListener('rcl:swatch_click', function(e) {
  var d = e.detail || {};
  window.dataLayer = window.dataLayer || [];
  window.dataLayer.push({
    event: 'swatch_click',
    swatch_group: d.group_handle,
    swatch_option: d.option_name,
    swatch_value: d.option_value,
    swatch_position: d.position,
    swatch_layout: d.layout,
    swatch_page_type: d.page_type,
    to_product_id: d.to_product_id
  });
});

Create Google Analytics tag in GTM for GA4 Event, triggered by swatch_click custom event, linking all data layer variables to Event Parameters. Register the Event Parameters in GA4 Admin as custom dimensions, and you will be able to include them in your Explore reports. You will be able to see real engagement metrics of swatch values within 24 hours.

Forward to Facebook Pixel

Meta’s Pixel doesn’t have a built-in swatch event, so you can use a custom event with trackCustom:.

window.addEventListener('rcl:swatch_click', function(e) {
  var d = e.detail || {};
  if (typeof fbq !== 'function') return;
  fbq('trackCustom', 'SwatchClick', {
    content_ids: [d.to_product_id],
    content_name: d.option_value,
    option: d.option_name,
    value: d.price,
    currency: d.currency
  });
});

You can create a custom conversion event within Meta Events Manager for SwatchClick and then build retargeting audiences of people who swatched certain colours but did not complete the purchase. For example, a shopper might hover over three different reds but not purchase. You could then create a red ad for that shopper based off their clear intent to purchase a red shoe. Signals like that are very difficult to come by.

Custom backend or warehouse

Why you might want to post raw events to BigQuery, Snowflake, Segment or a location on your own endpoints – the mechanism is identical. Listen, batch, POST. Here is the minimal code to do that

window.addEventListener('rcl:swatch_click', function(e) {
  fetch('/apps/proxy/track', {
    method: 'POST',
    headers: {'Content-Type': 'application/json'},
    body: JSON.stringify({
      type: 'swatch_click',
      ts: Date.now(),
      data: e.detail
    }),
    keepalive: true
  });
});

Keepalive flag is required. The request would die if the browser went to the new variant product without it. Shopify’s app proxy is a nice place to direct the request if you don’t want to expose a bare endpoint to the public internet.

Event naming that scales

Things to keep consistent, specific, and mindful of: 1. You are trying to accomplish, 2. The prefix you decide on, and 3. The future you. I suggest swatch_ as a good default, and you really should avoidever having to run a analytics cleanup where swatch_click, ColorSelected, and variant_picker_click all somehow get treated as distinct events. Future you will hate past you for this.

ContextEvent nameNotes
Product page clickswatch_click_pdpMain signal
Collection card clickswatch_click_collectionIntent earlier in funnel
Hover (optional)swatch_hoverOnly if you measure micro-interest
Carousel scroll endswatch_carousel_endDid they see every option

Use a single code to register custom dimensions, and do them once, not six times with different casing – GA4 will not merge them.

Conversion attribution

›Interesting – once we have logged the swatch clicks with the destination product ID, we can then join those to the appropriate purchase event in GA4 and see things like:

  • What percent of shoppers who click a swatch end up buying?
  • Which color has the highest click-to-purchase ratio? (the cheap ones, usually)
  • Do carousel positions past index 6 ever convert? (rarely)
  • Which swatch position is worth featuring first in paid ads?

We find that the first three images of a set of swatches receive 70-80% of the clicks on them. Because of position bias, it is a good idea for stores to reorder their groups of swatches so the top sellers appear first in the grid format. You can read more about this phenomenon in our post on grid vs carousel.

See the in-depth GA4 walkthrough with all the dashboard views in track swatch clicks in Google Analytics. This post provides the equivalent tool-agnostic tutorial.

See the live demo store, watch the tutorial video, or read the getting started guide.

Frequently asked questions

Does native Shopify combined listings support click tracking?

No.>Swatches in native combined product listings render as plain links and do not track any clicks. You would need to wrap the product listing in html or switch to an app that tracks the click through events.

What event name does Rubik Combined Listings fire?

When you click on a swatch, the library dispatches a CustomEvent on the window object (this can be retrieved via the ctx.window object) with name rcl:swatch_click and a detail property that contains the full swatch object.

Can I send swatch clicks to both GA4 and Facebook Pixel?

a class=”new” href=”/web/20120103213530/http://stackoverflow.com/questions/3451091/jquery-selector-not-working-as-expected/3451137#3451137″>Yes. Add two listeners (or one listener that fires both calls). There is no conflict because the browser event is just read, not consumed.

Do I need GTM to track Rubik swatch clicks?

You don’t have to use GTM. You can call the gtag function, push to the dataLayer or POST to your own endpoint. GTM is merely convenient for the typical tag setup.

Does the swatch click event fire on collection pages too?

The Event is triggered on any page that renders a Rubik product widget – including the product page, collection page and group page. See page_type property below.

Will adding listeners slow down my store?

Event listeners have no real overhead since Rubik already uses metafield-based loading and does this internally without ever hitting an external API call.

Can I filter out bot clicks?

GA4Filter out bot traffic directly without a backend filter. Use a custom backend filter to handle traffic if you need to check the user agent and filter out known crawlers before writing to the warehouse.