Custom implementations: Track events without the Coveo app for Shopify
Custom implementations: Track events without the Coveo app for Shopify
usage analytics events are crucial for analyzing your storefront performance and power Coveo Machine Learning (Coveo ML) models. This page explains how to track commerce events if you’re not using the Coveo AI Search and Discovery app. If you’re using the app, see Track Shopify events using the Coveo AI Search & Discovery app.
Types of events
There are four types of commerce events:
|
Note
Under the Coveo Event Protocol (EP), the Commerce API logs search events server-side automatically for you. |
Headless
The appropriate Headless controllers log click, cart, and product view events.
-
To log click and product view events, see Displaying products.
-
To log cart events, see Managing the cart.
Headless can’t log purchase events in your store.
To log purchase events, implement an app web pixel that uses Coveo Relay.
Whenever your store logs a
checkout_completed
event, the pixel triggers a corresponding purchase event, as in the following implementation example.
-
Create a web pixel extension
and configure it as follows:
# shopify.extension.toml name = "coveo-analytics-web-pixel" type = "web_pixel_extension" runtime_context = "strict" [customer_privacy] analytics = true
marketing = false preferences = false sale_of_data = "disabled" [settings] type = "object" [settings.fields.accessToken]
name = "Access token" description = "The access token used to push events to Coveo" type = "single_line_text_field" validations = [{ name = "min", value = "1" }]
Set the analytics
setting to
true
because the app web pixel will log analytics events.When activating your web pixel , you’ll pass an API key or search token that grants the privileges to push UA data to the target Coveo organization. See Authenticate commerce requests.
-
Install the required dependencies, namely @coveo/relay
and @coveo/relay-event-types
.
// package.json { // ... "dependencies": { // ... "@coveo/relay": "^1.1.0", "@coveo/relay-event-types": "^13.1.3" } }
-
Create a helper file to build the target Coveo Relay events.
// src/ecEventsBuilders.ts import { CurrencyCodeISO4217, ProductQuantity } from "@coveo/relay-event-types"; import { Checkout, CheckoutLineItem } from "@shopify/web-pixels-extension"; import { Ec } from "@coveo/relay-event-types"; export const ecPurchaseEvent = (checkout: Checkout): Ec.Purchase => ({
currency: checkout.currencyCode?.toUpperCase() as CurrencyCodeISO4217, products: checkout.lineItems.map(lineItemObject), transaction: { id: checkout.token ?? "", revenue: checkout.totalPrice?.amount ?? 0, }, }); const lineItemObject = (lineItem: CheckoutLineItem): ProductQuantity => ({ quantity: lineItem.quantity, product: { productId: lineItem.id ?? "", name: lineItem.title ?? "", price: lineItemPrice(lineItem), }, }); const lineItemPrice = (lineItem: CheckoutLineItem): number => (lineItem.finalLinePrice?.amount ?? 0) / lineItem.quantity;
Build the purchase event. -
Create a Relay environment helper file for client ID and context management. Outside of Shopify web pixels, Relay can manage the client ID and context automatically, but the restrictive nature of web pixels requires a custom implementation.
// relayEnvironment.ts import type { CustomEnvironment } from "@coveo/relay"; import { ExtensionApi } from "@shopify/web-pixels-extension"; import { v5 } from "uuid"; const uuidNamespace = "c2db3701-991e-424d-b117-03e30d43b651";
const clientIdFromShopifyCookie = (shopifyCookie: string) => v5(shopifyCookie, uuidNamespace); const isCoveoClientIdCookie = (key: string) => key === "visitorId" || key === "clientId"; const coveoCookiePrefix = "coveo_"; export const webPixelRelayEnvironment = ( { init, browser }: ExtensionApi, shopifyCookie: string, ): CustomEnvironment => ({ generateUUID: () => clientIdFromShopifyCookie(shopifyCookie), getLocation: () => init.context.document.location.href, getReferrer: () => init.context.document.referrer, getUserAgent: () => init.context.navigator.userAgent, send: (url, token, event) =>
fetch(`${url}?access_token=${token}`, { method: "POST", headers: { "Content-Type": "application/json", }, keepalive: true, body: JSON.stringify([event]), }), storage: {
getItem: (key) => (isCoveoClientIdCookie(key) ? clientIdFromShopifyCookie(shopifyCookie) : null), removeItem: (key) => { if (!isCoveoClientIdCookie(key)) { browser.cookie.set(`${coveoCookiePrefix}${key}`, `expires=${new Date(0).toUTCString()}`); } }, setItem: (key: string, data: string) => { if (key !== "visitorId" && key !== "clientId") { browser.cookie.set(`${coveoCookiePrefix}${key}`, data); } }, }, });
Make sure to use this same UUID namespace everywhere, so that the client ID is generated in a way consistent with the Coveo Event Protocol. Implement the Relay send
function using the access token created in this file.Implement the Relay storage
functions to manage the client ID and context, working around the restrictive nature of web pixels. -
Populate the
index.ts
file to register the web pixel extension.// src/index.ts import { ExtensionApi, register } from "@shopify/web-pixels-extension"; import { createRelay, Relay } from "@coveo/relay"; import { ecPurchaseEvent } from "./ecEventBuilder.js"; import { webPixelRelayEnvironment } from "./relayEnvironment.js"; import { name as webPixelPackageName, version as webPixelPackageVersion } from "../package.json"; register((extensionAPI: ExtensionApi) => { const { analytics, browser, settings } = extensionAPI; const relayInstance: Promise<Relay> = (async () => createRelay({ token: settings.accessToken, trackingId: "market_17962041446",
url: "https://my-org.analytics.org.coveo.com/rest/organizations/my-org/events/v1",
environment: webPixelRelayEnvironment(extensionAPI, await browser.cookie.get("_shopify_y")),
source: [`${webPixelPackageName}@${webPixelPackageVersion}`], }))(); analytics.subscribe("checkout_completed", async (event) => (await relayInstance).emit("ec.purchase", ecPurchaseEvent(event.data.checkout)), ); });
In a real implementation, you would most likely retrieve the tracking ID programmatically, depending on the market. The destination URL for sending Relay requests. It follows this format: https://<ORG_ID>.analytics.org.coveo.com/rest/organizations/<ORG_ID>/events/v1
, where you replace<ORG_ID>
with your organization ID.Use the webPixelRelayEnvironment
function to manage the client ID and context. -
When activating your web pixel
, pass an API key or a search token that grants the privileges to push UA data to the target Coveo organization. See Authenticate commerce requests.
mutation { webPixelCreate(webPixel: { settings: "{\"accessToken\":\"xx602de46b-0902-442b-87e1-8e53f4f0c578\"}" }) { userErrors { code field message } webPixel { settings id } } }
Atomic
Atomic logs click events automatically.
We recommend creating an app web pixel with Coveo Relay to track cart, product, and purchase events, when the following events are logged from your store, respectively:
The following implementation example explains how to do so.
-
Create a web pixel extension
and configure it as follows:
# shopify.extension.toml name = "coveo-analytics-web-pixel" type = "web_pixel_extension" runtime_context = "strict" [customer_privacy] analytics = true
marketing = false preferences = false sale_of_data = "disabled" [settings] type = "object" [settings.fields.accessToken]
name = "Access token" description = "The access token used to push events to Coveo" type = "single_line_text_field" validations = [{ name = "min", value = "1" }]
Set the analytics
setting to
true
because the app web pixel will log analytics events.When activating your web pixel , you’ll pass an API key or search token that grants the privileges to push UA data to the target Coveo organization. See Authenticate commerce requests.
-
Install the required dependencies, namely @coveo/relay
and @coveo/relay-event-types
.
// package.json { // ... "dependencies": { // ... "@coveo/relay": "^1.1.0", "@coveo/relay-event-types": "^13.1.3" } }
-
Create a helper file to build the target Coveo Relay events.
// src/ecEventsBuilders.ts import { CurrencyCodeISO4217, Product, ProductQuantity } from "@coveo/relay-event-types"; import { CartLine, ProductVariant, Checkout, CheckoutLineItem } from "@shopify/web-pixels-extension"; import { Ec } from "@coveo/relay-event-types"; export const ecProductViewEvent = (product: ProductVariant): Ec.ProductView => ({
currency: product.price.currencyCode.toUpperCase() as CurrencyCodeISO4217, product: productObject(product), }); export const ecAddToCartEvent = (cartLine: CartLine): Ec.CartAction => ({
action: "add", currency: cartLine?.cost.totalAmount.currencyCode.toUpperCase() as CurrencyCodeISO4217, quantity: cartLine?.quantity, product: productObject(cartLine.merchandise), }); export const ecRemoveFromCartEvent = (cartLine: CartLine): Ec.CartAction => ({
action: "remove", currency: cartLine.cost.totalAmount.currencyCode.toUpperCase() as CurrencyCodeISO4217, quantity: cartLine.quantity, product: productObject(cartLine.merchandise), }); const productObject = (product: ProductVariant): Product => ({ productId: product.id || "", name: product.title || "", price: product.price.amount || 0, }); export const ecPurchaseEvent = (checkout: Checkout): Ec.Purchase => ({
currency: checkout.currencyCode?.toUpperCase() as CurrencyCodeISO4217, products: checkout.lineItems.map(lineItemObject), transaction: { id: checkout.token ?? "", revenue: checkout.totalPrice?.amount ?? 0, }, }); const lineItemObject = (lineItem: CheckoutLineItem): ProductQuantity => ({ quantity: lineItem.quantity, product: { productId: lineItem.id ?? "", name: lineItem.title ?? "", price: lineItemPrice(lineItem), }, }); const lineItemPrice = (lineItem: CheckoutLineItem): number => (lineItem.finalLinePrice?.amount ?? 0) / lineItem.quantity;
Build the product view event. Build the add to cart event. Build the remove from cart event. Build the purchase event. -
Create a Relay environment helper file for client ID and context management. Outside of Shopify web pixels, Relay can manage the client ID and context automatically, but the restrictive nature of web pixels requires a custom implementation.
// relayEnvironment.ts import type { CustomEnvironment } from "@coveo/relay"; import { ExtensionApi } from "@shopify/web-pixels-extension"; import { v5 } from "uuid"; const uuidNamespace = "c2db3701-991e-424d-b117-03e30d43b651";
const clientIdFromShopifyCookie = (shopifyCookie: string) => v5(shopifyCookie, uuidNamespace); const isCoveoClientIdCookie = (key: string) => key === "visitorId" || key === "clientId"; const coveoCookiePrefix = "coveo_"; export const webPixelRelayEnvironment = ( { init, browser }: ExtensionApi, shopifyCookie: string, ): CustomEnvironment => ({ generateUUID: () => clientIdFromShopifyCookie(shopifyCookie), getLocation: () => init.context.document.location.href, getReferrer: () => init.context.document.referrer, getUserAgent: () => init.context.navigator.userAgent, send: (url, token, event) =>
fetch(`${url}?access_token=${token}`, { method: "POST", headers: { "Content-Type": "application/json", }, keepalive: true, body: JSON.stringify([event]), }), storage: {
getItem: (key) => (isCoveoClientIdCookie(key) ? clientIdFromShopifyCookie(shopifyCookie) : null), removeItem: (key) => { if (!isCoveoClientIdCookie(key)) { browser.cookie.set(`${coveoCookiePrefix}${key}`, `expires=${new Date(0).toUTCString()}`); } }, setItem: (key: string, data: string) => { if (key !== "visitorId" && key !== "clientId") { browser.cookie.set(`${coveoCookiePrefix}${key}`, data); } }, }, });
Make sure to use this same UUID namespace everywhere, so that the client ID is generated in a way consistent with the Coveo Event Protocol. Implement the Relay send
function using the access token created in this file.Implement the Relay storage
functions to manage the client ID and context, working around the restrictive nature of web pixels. -
Populate the
index.ts
file to register the web pixel extension.// src/index.ts import { ExtensionApi, register } from "@shopify/web-pixels-extension"; import { createRelay, Relay } from "@coveo/relay"; import { ecAddToCartEvent, ecProductViewEvent, ecPurchaseEvent, ecRemoveFromCartEvent } from "./ecEventBuilder.js"; import { webPixelRelayEnvironment } from "./relayEnvironment.js"; import { name as webPixelPackageName, version as webPixelPackageVersion } from "../package.json"; register((extensionAPI: ExtensionApi) => { const { analytics, browser, settings } = extensionAPI; const relayInstance: Promise<Relay> = (async () => createRelay({ token: settings.accessToken, trackingId: "market_17962041446",
url: "https://my-org.analytics.org.coveo.com/rest/organizations/my-org/events/v1",
environment: webPixelRelayEnvironment(extensionAPI, await browser.cookie.get("_shopify_y")),
source: [`${webPixelPackageName}@${webPixelPackageVersion}`], }))(); analytics.subscribe("checkout_completed", async (event) => (await relayInstance).emit("ec.purchase", ecPurchaseEvent(event.data.checkout)), ); analytics.subscribe("product_viewed", async (event) => (await relayInstance).emit("ec.productView", ecProductViewEvent(event.data.productVariant)), ); analytics.subscribe("product_added_to_cart", async (event) => { if (!event.data.cartLine) { return; } (await relayInstance).emit("ec.cartAction", ecAddToCartEvent(event.data.cartLine)); }); analytics.subscribe("product_removed_from_cart", async (event) => { if (!event.data.cartLine) { return; } (await relayInstance).emit("ec.cartAction", ecRemoveFromCartEvent(event.data.cartLine)); }); });
In a real implementation, you would most likely programmatically retrieve the tracking ID depending on the market. The destination URL for sending Relay requests. It follows this format: https://<ORG_ID>.analytics.org.coveo.com/rest/organizations/<ORG_ID>/events/v1
, where you replace<ORG_ID>
with your organization ID.Use the webPixelRelayEnvironment
function to manage the client ID and context. -
When activating your web pixel
, pass an API key or a search token that grants the privileges to push UA data to the target Coveo organization. See Authenticate commerce requests.
mutation { webPixelCreate(webPixel: { settings: "{\"accessToken\":\"xx602de46b-0902-442b-87e1-8e53f4f0c578\"}" }) { userErrors { code field message } webPixel { settings id } } }