Menu
Feedback
Guides
API Reference

Guides

Tracking search analytics events in headless web stores

Learn how to add data-af-* tracking attributes to capture Intelligent Search analytics events in your headless web store using Activity Flow.

In this guide, you will learn how to add tracking attributes to search screens in a headless web store so that Activity Flow can capture Intelligent Search analytics events: impressions and clicks on search results and autocomplete suggestions.

The Activity Flow web script reads data-af-* attributes directly from the DOM. You don't need to write a listener or call any function: render the attributes on the right element, and the script takes care of the rest. The data-af-element attribute defines the elementSource, sent at the top level of the event payload, while any other data-af-<name> attribute becomes an extra parameter inside the attributes object.

Before you begin

This guide assumes Activity Flow is already installed in your headless web store. If you haven't set it up yet, see Installing Activity Flow in headless stores.

Understanding the tracking attributes

Trigger attributes define which event fires:

AttributeFunction
data-af-elementRequired. Identifies the origin of the event. Without it, impressions and clicks are discarded by the script.
data-af-onimpressionFires an impression when the element is rendered, and again when it's re-rendered with different parameters.
data-af-onclickFires a click when the element or any of its descendants is clicked.

data-af-element must be one of the following values:

ValueScreen
search-resultSearch results gallery, including the no-results search screen
search-autocompleteAutocomplete product suggestions

Extra parameters:

AttributeRequiredDescription
data-af-search-idYes, in every eventThe search identifier, returned by the Intelligent Search response itself.
data-af-product-idYes, on clickThe ID of the clicked product.
data-af-product-positionYes, on clickThe product's position in the list, 1-based, absolute across pages.
data-af-product-specificationNoThe product's specification value, when the API returns it.

searchId is generated by Intelligent Search and returned in the search response (productSearch.searchId on the results gallery, productSuggestions.searchId on autocomplete). Never create, derive, or reuse this value.

Follow the steps below to track impressions and clicks on the search results gallery, including the no-results screen.

  1. Compute the tracking gate from the search response. A search that resulted in a redirect never shows a results gallery, so it must not render any attributes:


    _10
    const { searchId, redirect } = searchQuery?.data?.productSearch || {}
    _10
    _10
    const shouldAddAFAttr = searchId && !redirect

    If searchId didn't come back in the response or is empty, don't render the attributes. Passing undefined to all of them, as shown throughout this guide, omits them from the HTML instead of rendering them with an empty value. It's better to lose the event than to record one that can't be attributed to a search.

  2. Render the following attributes on the gallery container to fire the impression. Use the same container and attributes on the no-results screen, since a search without results still counts as exactly one impression. In that case, the element carries only these trigger attributes and search-id, since there's no product to report:


    _10
    <div
    _10
    data-af-element={shouldAddAFAttr ? 'search-result' : undefined}
    _10
    data-af-onimpression={shouldAddAFAttr ? true : undefined}
    _10
    data-af-search-id={shouldAddAFAttr ? searchId : undefined}
    _10
    className={/* ... */}
    _10
    >
    _10
    {children}
    _10
    </div>

    Each new query, such as applying a filter, changing the sort order, or paging, produces a new searchId. Since the script re-fires the impression whenever the attributes change, rendering the new searchId automatically triggers a new impression. Don't group interactions from different queries under the same searchId.

  3. Render the following attributes on each gallery item's wrapper to fire the click. Note that the item's gate also requires productId:


    _19
    const absoluteProductIndex =
    _19
    productsPositionOffset + rowIndex * itemsPerRow + index + 1
    _19
    _19
    const shouldAddAFAttr = searchId && !redirect && product.productId
    _19
    _19
    return (
    _19
    <div
    _19
    data-af-element={shouldAddAFAttr ? 'search-result' : undefined}
    _19
    data-af-onclick={shouldAddAFAttr ? true : undefined}
    _19
    data-af-search-id={shouldAddAFAttr ? searchId : undefined}
    _19
    data-af-product-position={shouldAddAFAttr ? absoluteProductIndex : undefined}
    _19
    data-af-product-id={shouldAddAFAttr ? product.productId : undefined}
    _19
    data-af-product-specification={
    _19
    shouldAddAFAttr ? product.specification : undefined
    _19
    }
    _19
    >
    _19
    <GalleryItem item={product} position={absoluteProductIndex} />
    _19
    </div>
    _19
    )

    Click attributes go on the wrapper that contains the clickable area. The script looks for the closest ancestor with data-af-onclick, so you only need to add the attributes to the element that wraps the product card, without touching internal components. This also works when an internal component stops click propagation.

    productsPositionOffset is what keeps the position absolute across pages: it represents how many products came before the current page, accounting for pagination, infinite scroll, and direct loads via ?page=N. Without it, the first product on any page would be reported as 1, and position-based relevance metrics would be skewed.

Tracking autocomplete

Autocomplete follows the same pattern as the results gallery, but with its own data-af-element value and no pagination.

  1. Render the following attributes on the suggestions list container to fire the impression:


    _19
    <section
    _19
    data-af-element={searchId ? 'search-autocomplete' : undefined}
    _19
    data-af-onimpression={searchId ? true : undefined}
    _19
    data-af-search-id={searchId}
    _19
    >
    _19
    {products.map((product, index) => (
    _19
    <li
    _19
    key={product.productId}
    _19
    data-af-element={searchId ? 'search-autocomplete' : undefined}
    _19
    data-af-onclick={searchId && product.productId ? true : undefined}
    _19
    data-af-search-id={searchId}
    _19
    data-af-product-position={index + 1}
    _19
    data-af-product-id={product.productId}
    _19
    data-af-product-specification={product.specification}
    _19
    >
    _19
    {/* product card */}
    _19
    </li>
    _19
    ))}
    _19
    </section>

    The position is simply index + 1, since autocomplete has no pagination.

    Autocomplete and the results gallery are distinct searches. Don't propagate the suggestions' searchId to the results screen when the shopper submits the search: that screen has its own searchId.

Checking the resulting payload

A click on a gallery item generates a payload similar to the following:


_10
{
_10
"eventType": "click",
_10
"element": "div",
_10
"elementSource": "search-result",
_10
"attributes": {
_10
"data-af-search-id": "b3f1c9d2-...",
_10
"data-af-product-id": "309",
_10
"data-af-product-position": "3"
_10
}
_10
}

The remaining fields (session, account, URL, referrer, timestamp) are filled in automatically by the script. Unlike mobile apps, on the storefront the keys inside attributes preserve the full HTML attribute name, with the data-af- prefix.

Checklist

  • Impression rendered on every search, including the no-results layout.
  • data-af-element present on every instrumented element, with the value search-result or search-autocomplete.
  • data-af-search-id coming from the API and present on both the impression and the click.
  • data-af-product-position is 1-based and correct after pagination or infinite scroll.
  • No attributes rendered on a search that redirects or has no searchId.
Contributors
1
Photo of the contributor julia-rabello
+ 1 contributors
Was this helpful?
Yes
No
Suggest Edits (GitHub)
See also
VTEX Activity Flow
Guides
Installing Activity Flow in headless stores
Guides
Tracking search analytics events in mobile apps
Guides
Contributors
1
Photo of the contributor julia-rabello
+ 1 contributors
On this page
Was this helpful?
Suggest edits (GitHub)
On this page