Discovery OS Shopify headless implementation guide
This guide is for developers building custom frontend components (headless) for Shopify stores that use the Discovery OS Shopify app. Follow it so your UI stays compatible with the app’s automatic tracking and analytics (ATS): impressions, clicks, and session-aware search payloads.
Before you start: Enable the app embed and read route/widget ids from pa-discoveryos-config.
1. HTML structure and attributes
For the app embed to track your products, add the data tags and classes below.
1.1. The wrapper Element (container)
| Attribute | Type | Source / value |
|---|---|---|
data-pa-route-id | string | Use searchRouteId, serpRouteId, or collectionRouteId from recs in pa-discoveryos-config. |
data-pa-widget-id | string | Use the matching widget id from recs: searchWidgetId, serpWidgetId, or collectionWidgetId from recs in pa-discoveryos-config. |
data-pa-search-context | string | Context string for the listing: e.g. the search query ("mountain bike"), or for collections a stable handle / label the app expects (examples below use query terms or collection.handle). |
1.2. The product card
| Attribute / class | Type | Description |
|---|---|---|
pa-product-card | class | Required on every product tile the app should track. |
data-pa-ref-id | attribute | Product reference id (Shopify product id as used by PA). |
data-pa-product-index | attribute | 1-based index in the list (iterator index + 1). |
1.3. Example code
1.3.1. Search preview
<div
class="search-preview-wrapper"
data-pa-search-context="{{ search.terms }}"
data-pa-route-id="000-000-000-000" // <searchRouteId>
data-pa-widget-id="aaa-aaa-aaa-aaa" // <searchWidgetId>
>
{% for item in search.results %}
{% if item.object_type == 'product' %}
<div
class="pa-product-card"
data-pa-ref-id="{{ item.id }}"
data-pa-product-index="{{ forloop.index }}"
>
<a href="{{ item.url }}">{{ item.title }}</a>
</div>
{% endif %}
{% endfor %}
</div>
1.3.2. Search results page (SERP) — Liquid
<div
class="search-page-result-wrapper"
data-pa-search-context="{{ search.terms }}"
data-pa-route-id="111-111-111-111" // <serpRouteId>
data-pa-widget-id="bbb-bbb-bbb-bbb" // <serpWidgetId>
>
{% for item in search.results %}
{% if item.object_type == 'product' %}
<div
class="pa-product-card"
data-pa-ref-id="{{ item.id }}"
data-pa-product-index="{{ forloop.index }}"
>
<a href="{{ item.url }}">{{ item.title }}</a>
</div>
{% endif %}
{% endfor %}
</div>
1.3.3. Collection page — Liquid
<div
class="collection-page-result-wrapper"
data-pa-search-context="{{ collection.handle }}"
data-pa-route-id="222-222-222-222" // <collectionRouteId>
data-pa-widget-id="bbb-bbb-bbb-bbb" // <collectionWidgetId>
>
{% for product in collection.products %}
<div
class="pa-product-card"
data-pa-ref-id="{{ product.id }}"
data-pa-product-index="{{ forloop.index }}"
>
<a href="{{ product.url }}">{{ product.title }}</a>
</div>
{% endfor %}
</div>