Important:
Before you start installing the Convert Shopify integration, make sure you read the Shopify Get Started guide so you choose the right integration.
This Article Will Help You:
- Step 1: Find your Convert Experiences Account-Project IDs.
- Step 2: Add the Convert Experiences Script to your Shopify Theme
- Step 3: Setup your Shopify Customer Events tracking
- Step 4: Copy code in your Global Project JS section
- Step 5: Add Convert Tracking Code on Checkout Pages on Shopify Plus
- Step 6: Add the checkout page domain to your Project's Active Websites
- Step 7: Forward tracking cookies to the checkout domain
- Step 8: Verify your Shopify Setup
- GA4 Event import for Shopify Sites
Step 1: Find your Convert Experiences Account-Project IDs.
In Convert Experiences, retrieve the project ID number from the place highlighted in the image below.
Step 2: Add the Convert Experiences Script to your Shopify Theme
To integrate the two platforms, please go to Sales Channels > Online Store > Themes > More Actions > Edit Code
Select the theme.liquid file and paste the tracking code just before the first "{% if %}" section, as shown in the screenshot below:
In the area indicated above, paste the code shown below. Make sure you replace the XXXXXXX with your own account ID and the YYYYYY number with your own project ID, which was mentioned earlier in this article.
<!-- begin Convert Experiences code--><script type="text/javascript">var _conv_page_type = "{{ request.page_type }}";var _conv_category_id = "{{ collection.id }}";var _conv_category_name = "{{ collection.title }}";var _conv_product_sku = "{{ product.selected_variant.sku }}";var _conv_product_name = "{{ product.title }}";var _conv_product_price = "{{ product.price_min | money_without_currency }}";var _conv_customer_id = "{{ customer.id }}";var _conv_custom_v1 = "{{ product.tags.first }}";var _conv_custom_v2 = "{{ collection.current_type }}";var _conv_custom_v3 = "{{ cart.item_count }}";var _conv_custom_v4 = "{{ cart.total_price | money_without_currency }}";</script><script type="text/javascript" src="//cdn-4.convertexperiments.com/v1/js/XXXXXXXX-YYYYYYYYY.js"></script><!-- end Convert Experiences code -->
Don't forget to save your changes in Shopify.
Step 3: Setup your Shopify Customer Events tracking
- Create two Code (JavaScript) triggered goals. Name one Purchase Shopify Customer Event and another one Added to the Cart Shopify Customer Event. Have their Ids handy to insert them on the script you will add to the Shopify Customer Events section.
-
On your Shopify Admin interface, go to Settings > Customer Events. Create a Custom one. Name it Convert Tracking.
Insert the code below, edit it, save it, and connect it.
Shopify Customer Event Code
Add the following code to your Shopify Admin > Settings > Customer Events > New Pixel section. Make sure you replace the goal ids with the ones you just created. If you have the legacy tracking code (then none Beta one), please find the code here.
const DEBUG = true; // Set to false to disable debug logs
const ENABLE_PROPERTY_FILTERING = false; // Set to false to disable property filtering
const purchase_goalid = '100136097';
const addToCart_goalid = '100134910';
const checkoutStarted_goalid = '100132287';
// Configuration object for filtering criteria
const filterCriteria = {
enabled: false, // Enable or disable criteria checking
checkExistence: ['sku'], // List of properties that must exist
matchValue: {
'sku': '23026961-pink-united-states-l-diameter-7-5cm' // Exact string values to match
},
checkValue: false // Enable or disable value matching
};
function isValidJSON(data) {
try {
JSON.parse(data);
} catch (e) {
return false;
}
return true;
}
function debugLog(message, ...optionalParams) {
if (DEBUG) {
console.log('Convert Shopify Integration:', message, ...optionalParams);
}
}
// Helper function to search for a property name anywhere in the object
function findProperty(obj, propertyName) {
if (obj === undefined || obj === null) {
return undefined;
}
if (obj.hasOwnProperty(propertyName)) {
return obj[propertyName];
}
for (const key in obj) {
if (obj.hasOwnProperty(key) && typeof obj[key] === 'object') {
const result = findProperty(obj[key], propertyName);
if (result !== undefined) {
return result;
}
}
}
return undefined;
}
function checkCriteria(purchase_event, criteria) {
if (!criteria.enabled) {
debugLog('Criteria checking is disabled.');
return true; // Bypass criteria checking
}
let allCriteriaMet = true; // Variable to track if all criteria are met
// Check for the existence of properties
if (criteria.checkExistence) {
for (const propertyName of criteria.checkExistence) {
const value = findProperty(purchase_event, propertyName);
debugLog(`Checking existence of property: ${propertyName}, Found value: ${value}`);
if (value === undefined) {
debugLog(`Property ${propertyName} does not exist.`);
allCriteriaMet = false;
}
}
}
// Check for matching value patterns if checkValue is enabled
if (criteria.checkValue) {
for (const [propertyName, targetValue] of Object.entries(criteria.matchValue)) {
const value = findProperty(purchase_event, propertyName);
debugLog(`Checking match for property: ${propertyName}, Target value: ${targetValue}, Found value: ${value}`);
if (value === undefined || value !== targetValue) {
debugLog(`Property ${propertyName} does not match value ${targetValue}. Value: ${value}`);
allCriteriaMet = false;
}
}
}
return allCriteriaMet;
}
async function postTransaction(convert_attributes_str, purchase_event, purchase_goalid) {
debugLog("Starting postTransaction function.");
try {
var convert_attributes = JSON.parse(convert_attributes_str);
if (convert_attributes && purchase_event) {
// Apply the filtering criteria if enabled
var purchase_event_str = JSON.stringify(purchase_event);
debugLog(`Purchase Event: ${purchase_event_str}`);
if (ENABLE_PROPERTY_FILTERING && !checkCriteria(purchase_event, filterCriteria)) {
debugLog("Transaction filtered out based on criteria:", filterCriteria);
return;
}
debugLog("Building POST data for transaction.");
let transactionAmount = parseFloat(purchase_event.data.checkout.totalPrice.amount);
debugLog(`Transaction amount: ${transactionAmount}, Min order value: ${convert_attributes.min_order_value}, Max order value: ${convert_attributes.max_order_value}`);
if (transactionAmount >= convert_attributes.min_order_value && transactionAmount <= convert_attributes.max_order_value) {
if (convert_attributes.conversion_rate && convert_attributes.conversion_rate !== 1) {
transactionAmount *= convert_attributes.conversion_rate;
debugLog(`Transaction amount adjusted by conversion rate (${convert_attributes.conversion_rate}): ${transactionAmount}`);
}
const transactionId = purchase_event.data.checkout.order.id;
const post = {
'cid': convert_attributes.cid,
'pid': convert_attributes.pid,
'seg': convert_attributes.defaultSegments,
's': 'shopify',
'vid': convert_attributes.vid,
'tid': transactionId,
'ev': [{
'evt': 'tr',
'goals': [purchase_goalid],
'exps': convert_attributes.exps,
'vars': convert_attributes.vars,
'r': transactionAmount,
'prc': purchase_event.data.checkout.lineItems.length
}]
};
let data = JSON.stringify(post);
// Verify and fix JSON if necessary
if (!isValidJSON(data)) {
data = JSON.stringify(JSON.parse(data));
}
const beaconUrl = `https://${convert_attributes.pid}.metrics.convertexperiments.com/track`;
try {
const response = await fetch(beaconUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: data
});
if (!response.ok) {
throw new Error('Network response was not ok');
}
const result = await response.json();
debugLog("fetch result:", result);
debugLog("transactionID: " + transactionId);
debugLog("purchase_event: " + JSON.stringify(purchase_event.data));
} catch (fetchError) {
console.error('Error in fetch request:', fetchError);
}
} else {
debugLog("Transaction amount filtered out. Amount:", transactionAmount);
}
} else {
console.error("Invalid or missing convert_attributes or purchase_event.");
}
} catch (parseError) {
console.error('Error parsing JSON in postTransaction:', parseError);
}
}
async function postConversion(convert_attributes_str, goalid) {
debugLog('Starting postConversion function with goal id:', goalid);
try {
var convert_attributes = JSON.parse(convert_attributes_str);
if (convert_attributes) {
debugLog("Building POST data for goal hit.");
const post = {
'cid': convert_attributes.cid,
'pid': convert_attributes.pid,
'seg': convert_attributes.defaultSegments,
's': 'shopify',
'vid': convert_attributes.vid,
'ev': [{
'evt': 'hitGoal',
'goals': [goalid],
'exps': convert_attributes.exps,
'vars': convert_attributes.vars
}]
};
let data = JSON.stringify(post);
// Verify and fix JSON if necessary
if (!isValidJSON(data)) {
data = JSON.stringify(JSON.parse(data));
}
const beaconUrl = `https://${convert_attributes.pid}.metrics.convertexperiments.com/track`;
try {
const response = await fetch(beaconUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: data
});
if (!response.ok) {
throw new Error('Network response was not ok');
}
const result = await response.json();
debugLog("fetch result:", result);
} catch (fetchError) {
console.error('Error in fetch request:', fetchError);
}
} else {
console.error("Invalid or missing convert_attributes.");
}
} catch (parseError) {
console.error('Error parsing JSON in postConversion:', parseError);
}
}
analytics.subscribe("checkout_completed", async (event) => {
debugLog("Event received for checkout_completed.");
try {
let result = await browser.localStorage.getItem('convert_attributes');
if (!result) {
result = findProperty(event.data.checkout, 'custom_attributes');
result = JSON.stringify(result);
}
await postConversion(result, purchase_goalid);
await postTransaction(result, event, purchase_goalid);
} catch (error) {
console.error('Error in checkout_completed promise chain:', error);
}
});
analytics.subscribe("product_added_to_cart", async (event) => {
debugLog("Event received for product_added_to_cart.");
try {
let result = await browser.localStorage.getItem('convert_attributes');
if (!result) {
result = findProperty(event.data.checkout, 'custom_attributes');
result = JSON.stringify(result);
}
await postConversion(result, addToCart_goalid);
} catch (error) {
console.error('Error retrieving convert_attributes for product_added_to_cart:', error);
}
});
analytics.subscribe("checkout_started", async (event) => {
debugLog("Event received for checkout_started.");
try {
let result = await browser.localStorage.getItem('convert_attributes');
if (!result) {
result = findProperty(event.data.checkout, 'custom_attributes');
result = JSON.stringify(result);
}
await postConversion(result, checkoutStarted_goalid);
} catch (error) {
console.error('Error retrieving convert_attributes for checkout_started:', error);
}
});
Step 4 Copy code into your Global Project JS code
Add the following code to your Convert App > Your Project > Configuration > Project Global JS section. If you have the legacy tracking code (then none Beta one), please find the code here.
console.log('URL:' + location.href);
let enableCurrencyFunctionality = false;
// Ensuring _conv_q is initialized
window._conv_q = window._conv_q || [];
window._conv_q.push({
what: 'addListener',
params: {
event: 'snippet.experiences_evaluated',
handler: function() {
let session_cookie = convert.getCookie('_conv_s');
if (!session_cookie) {
console.error('Session cookie not found.');
return;
}
let session_id = session_cookie.substring(
session_cookie.indexOf('sh:') + 3,
session_cookie.indexOf('*')
);
let exp_list = [];
let variation_list = [];
// Function to process experiences from currentData and historicalData
function processExperiences(sourceData, allData, isHistorical = false) {
for (let expID in sourceData) {
// Retrieve the type from main data structure to decide exclusion
let type = allData.experiences[expID]?.type;
if (type === "deploy") {
console.log('Skipping deploy type experiment:', expID);
continue; // Skip processing if type is "deploy"
}
let experience = sourceData[expID];
let variation = experience.variation || {};
let varID = variation.id || experience.variationId;
if (varID && !exp_list.includes(expID)) {
exp_list.push(expID);
variation_list.push(varID);
console.log(
'Adding experiment:',
expID,
'with variation:',
varID,
'from',
isHistorical ? 'historical data' : 'current data'
);
}
}
}
// Process current and historical data
if (convert.currentData && convert.currentData.experiences) {
processExperiences(convert.currentData.experiences, convert.data);
}
if (convert.historicalData && convert.historicalData.experiences) {
processExperiences(convert.historicalData.experiences, convert.data, true);
}
// Convert segments to the first format
function alignSegmentsToFirstFormat(segFromSecondFormat) {
const alignedSeg = {
browser: segFromSecondFormat.browser,
devices: segFromSecondFormat.devices,
source: segFromSecondFormat.source,
campaign: segFromSecondFormat.campaign,
ctry: segFromSecondFormat.country || "",
cust: Array.isArray(segFromSecondFormat.customSegments) ? segFromSecondFormat.customSegments : [],
};
// Adjust the 'new' flag based on 'visitorType'
// Since 'visitorType' of "returning" implies the visitor is not new, we map accordingly
alignedSeg.new =
segFromSecondFormat.visitorType === "new"
? 1
: segFromSecondFormat.visitorType === "returning"
? 0
: undefined;
return alignedSeg;
}
let convert_attributes = {
cid: convert.data.account_id,
pid: convert.data.project.id,
vid: session_id,
goals: JSON.stringify(convert.currentData.goals || {}),
vars: variation_list,
exps: exp_list,
defaultSegments: alignSegmentsToFirstFormat(convert.getDefaultSegments()),
conversionRate: 1, // Default value, modify as necessary
presentmentCurrency: "USD", // Default currency, modify as necessary
currencySymbol: "$", // Default symbol, modify as necessary
max_order_value: convert.data.project.settings.max_order_value,
min_order_value: convert.data.project.settings.min_order_value,
};
if (enableCurrencyFunctionality && typeof Shopify !== 'undefined' && Shopify.currency && typeof Currency !== 'undefined') {
let baseCurrency = Shopify.currency.active;
let presentmentCurrency = Shopify.currency.current;
let conversionRate = Currency.convert(1, baseCurrency, presentmentCurrency);
if (!isNaN(conversionRate) && conversionRate !== 0) {
convert_attributes.conversionRate = conversionRate;
convert_attributes.presentmentCurrency = presentmentCurrency;
convert_attributes.currencySymbol = Currency.symbol;
} else {
console.error('Invalid conversion rate. Not adding currency information.');
}
}
localStorage.setItem('convert_attributes', JSON.stringify(convert_attributes));
console.log('convert_attributes initialized:', convert_attributes);
}
}
});
Step 5 Add the tracking code to the checkout pages
This only works if you have access to your checkout liquid template. New Shopify setup does not allow this, so skip it if you do not have a checkout liquid template.
Stop here if you are using Shopify Checkout Extensibility and have completed all the previous steps. Your setup is complete.
If you use Shopify Plus , then you also have the possibility to add the Convert Experiences script in checkout pages (pages after /cart but before thank_you/purchase_confirmation). This is not possible for non-Shopify Plus subscriptions as Shopify does not allow third party scripts to be running on checkout pages.
Edit the checkout.liquid file and place your Convert Tracking Code to appear on /checkout/ pages.
Just go to your Project Configuration to get your Convert Tracking Code ("basic snippet") and paste it into the template as shown below.
Step 6: Add the checkout page domain to your Project's Active Websites
This only applies to non-Shopify Plus users and if your checkout domain is not the same as the one in the catalog pages. If you are on Shopify Plus stop here!
On Shopify, the checkout pages are either presented under a central Shopify subdomain (checkout.shopify.com) or your own domain, depending on the type of Shopify account you have. In either situation, you need to add that domain to your Project's Configuration:
Step 7: Forward tracking cookies to the checkout domain
This only applies to non-Shopify Plus users and if your checkout domain is not the same as the one in the catalog pages. If you are on Shopify Plus stop here!
If you are not using your own domain or if you are using a totally different domain than your main domain for the checkout pages, you will need to manually forward the tracking cookies so that experiments fired on the shop pages can be connected later on to conversion. To do that, on the cart page where the visitor redirects from your main domain, you need to read the convert tracking cookies and forward them to the checkout domain, as explained below.
Go to Online Store > Themes > Current Theme > Actions > Edit Code as explained in step 2). Go to "Sections" and select the "cart-template.liquid". Then, add the following JavaScript just before the <form> tag:
<script type="text/javascript">
function _conv_copy_cookies(form) {
try {
var _conv_v = encodeURIComponent(convert.getCookie("_conv_v"));
var _conv_s = encodeURIComponent(convert.getCookie("_conv_s"));
form.action = '/cart?_conv_v=' + _conv_v + '&_conv_s=' + _conv_s;
} catch (e) {}
return true;
}
</script>
Next, add the following inside the <form> tag:
onsubmit="return _conv_copy_cookies(this)"
After this, you should be done. Now, the Convert Experiences cookies should be forwarded to the checkout URL as two GET variables: _conv_v and _conv_s;
Note: If you use a subdomain of your main domain for checkout, then you probably do not have to complete this step. Cookies will be saved under the root domain, making them available on the shop and checkout pages.
Step 8: Verify your Shopify Setup
You should verify your installation using the following guide: Shopify Setup Verification Guide
GA4 Event import for Shopify Sites
Although it might seem logical to import your GA4 events to track purchases on your Shopify store, this is not possible. This is because the Convert tracking code needs to be installed on the Order Status page. However, this is not possible due to the new Shopify Checkout Extensibility. You will have to set up Customer events, such as in step 6 to be able to track revenue on Shopify using this installation setup.