App Block Storefront Events
Retail Cloud Connect™ App Blocks emit browser events for storefront integrations that need to react to search, collection, product-card, or conversational UI activity.
Use these events for small client-side integrations such as analytics, theme UI updates, custom success messages, debugging, or coordinating another script with the rendered search grid. They are integration hooks; they should not replace Retail Cloud Connect cart, search, or chat logic.
Browser Events
Search Result Events
retail-connect:products:load:start, retail-connect:products:load:success, and retail-connect:products:load:failure describe the Search Results and Product Grid result loading lifecycle.
The load-start event detail includes the request context:
The load-success event includes the same request context plus result metadata:
The load-failure event includes the request context plus error, normalized to an object with at least message when possible.
Search Grid Ready
retail-connect:search-grid:ready is the public hook for scripts that need to run after the search or collection product grid has rendered. Listen on document.
{%- if request.page_type == 'search' or request.page_type == 'collection' -%}
<script>
document.addEventListener('retail-connect:search-grid:ready', (event) => {
const { pageType, resultCount, totalResultCount, gridElement } = event.detail;
console.log('Retail Cloud Connect grid ready', {
pageType,
resultCount,
totalResultCount,
gridElement,
});
});
</script>
{%- endif -%}
The event detail includes:
Use this event instead of observing private product-grid selectors.
Product Card Add-to-Cart Events
retail-connect:add-to-cart:success and retail-connect:add-to-cart:failure fire when a product card includes an add-to-cart component, such as ADD_TO_BASKET or ADD_TO_BASKET_MODAL.
The event payload is available on event.detail:
When the success event fires, the App Block has already posted to Shopify's Ajax cart/add.js endpoint. Theme code should not add the item again.
Product cards can appear on several storefront templates, so install add-to-cart listeners globally unless the store deliberately limits them to known page types.
<script>
window.addEventListener('retail-connect:add-to-cart:success', (event) => {
const response = event.detail?.response;
const items = Array.isArray(response?.items)
? response.items
: response
? [response]
: [];
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
event: 'retail_connect_add_to_cart',
source: 'retail_connect_product_card',
ecommerce: {
items: items.map((item) => ({
item_id: item.product_id,
item_variant: item.variant_id || item.id,
item_name: item.product_title || item.title,
quantity: item.quantity || 1,
})),
},
});
});
</script>
Common uses include analytics, opening a theme cart drawer, updating a mini-cart badge, or showing a small success message.
Conversational Engagement Event
retail-connect:conversation-engagement fires when the conversational embed records a positive shopping engagement signal.
Current signal types are:
The event detail includes:
The conversational embed also publishes the same sanitized payload to Shopify analytics as rcc:conversation_engagement when window.Shopify.analytics.publish is available, and posts it to the storefront-agent engagement endpoint.
Search State and Runtime Controls
Each mounted Search Results or Product Grid App Block registers a reactive entry in window.RCCSearchStores. The entry becomes available when rcc-search-store:registered fires on window; its detail.storeKey identifies the entry.
search(query) starts a fresh search in the mounted block. It trims the query; clears shopper-selected filters; restores configured custom-filter and sort defaults; resets pagination, conversation, and autocomplete state; and refreshes the results. It preserves every configured default filter, including an active collection constraint.
For example, a theme can keep its search form on the current page when a Search App Block is mounted. Check signals.collectionId before taking over the form so a collection-scoped Product Grid continues to submit normally.
document
.querySelector('#your-search-form')
?.addEventListener('submit', (event) => {
const search = Object.values(window.RCCSearchStores ?? {})[0];
const query = new FormData(event.currentTarget).get('q');
if (
!search ||
search.signals.collectionId.value ||
typeof query !== 'string' ||
!query.trim()
) {
return;
}
event.preventDefault();
search.search(query);
});
After the registration event fires, this two-line example changes the rendered collection and clears shopper-selected filters in one request.
const search = Object.values(window.RCCSearchStores)[0];
search.setCollection('123456789', { clearFilters: true });
setFilters(filters) replaces shopper-selected filters, resets pagination to page 1, updates the URL query parameters, and refreshes the results. Each filter uses { field, value: string[] }. It does not remove hidden default filters or the active collection constraint; pass an empty array to clear shopper-selected filters. Use setCollection rather than passing the reserved attributes.rcc_collection_id or attributes.rcc_tags fields.
setCollection(collectionId, options?) replaces the active collection constraint, clears collection tags from the previous page, resets pagination to page 1, and refreshes the results. Pass a Shopify numeric collection ID as a string or number. Pass null to render catalog-wide results.
Set clearFilters: true to clear shopper-selected facet filters as part of the same state transition, producing one result request for the final collection and filter state. This clears only signals.userFilters. It preserves non-collection signals.defaultFilters, request-level window.Nimstrata.search.defaultFilters, and signals.appliedCustomFilters.
Changing the collection clears the original collection title and description because metadata for the new collection is not available in the current page; it does not navigate or change the storefront URL path.
The root .rcc-search element exposes its key as data-search-store after registration. This can identify a particular block when a page contains more than one.
The entry's signals object contains read-only Preact signals. Read current state from .value, or call .subscribe(callback) and retain the returned unsubscribe function.
Use search, setFilters, and setCollection to make changes rather than assigning to a signal's .value or calling the registry entry's underlying store.
Search App Blocks dispatch these lifecycle events for the registry:
Best Practices
- Add custom listeners in
snippets/nimstrata.liquid; see nimstrata.liquid Setup. - Scope listeners to the page types that render the relevant App Blocks.
- Keep listeners idempotent so duplicate snippets do not send duplicate analytics events or open multiple UI layers.
- Treat event payloads as browser-visible data. Do not add customer identifiers, storefront tokens, or secrets to forwarded analytics payloads.
- Read
event.detaildefensively because Shopify Ajax and result response shapes can vary by request. - Use failure events for diagnostics or carefully worded user messaging, but do not expose raw technical errors to shoppers.