Shopify provides a standard Ajax (Asynchronous JavaScript and XML) based API to perform different actions on the frontend. It is commonly known as Shopify Ajax API. Besides managing Cart, it also offers functionality to display related products and product search autocomplete. For this article we would be mainly focusing on the Cart API and Shopify Cart Events.
Shopify Cart API
The Cart API interacts with a cart during a customer’s session. You can read more details at Shopify Docs. It explains how to use the Cart API to update cart line items, add cart attributes and notes etc.
The documentation does a good job of explaining how to manipulate Cart in different scenarios. But it does not mention anything about Cart Events related to Cart updates. Like Product added, Cart Updated or other similar changes to that. The reason is because there are no standard events that the end user can subscribe to. However, there are certain use cases where these events can prove to be beneficial. For example, if you want to customize your theme to perform some action based on contents of the cart or if you are developing some Shopify App that needs to do some action based on some changes in the cart. In some cases, a theme may fire events related to Cart changes, but it is impossible to rely on those in case you are building an app as different stores will be using different themes.
Shopify Cart API Endpoints
In case we want to reliably determine the Shopify Cart changes regardless of theme, we can listen to network calls and dispatch events that match different conditions. To do that, we need to have a look at endpoints supported by Shopify Ajax Cart and what the mean.
From these 5 endpoints, we are interested in only 4 of these except the GET Cart as we can safely assume that it does not modify the contents of the Cart and simply returns the current state of Cart. So, the idea is to listen to any network calls made to these 4 endpoints and fire related Events. There are 2 ways in which a Shopify frontend may communicate with Shopify Ajax API.
- XMLHttpRequest
- Fetch API
Now we just need to listen to all networks requests made using XMLHttpRequest or Fetch API. To do so, we will look at each of these in a bit more detail to find out the relevant functions that we may use.
Listen to Network Requests using XMLHttpRequest
XMLHttpRequest
provides an open method that initializes a newly-created request, or re-initializes an existing one. Besides this, XMLHttpRequest
also provides a load event that is fired when an XMLHttpRequest
transaction completes successfully. Using this information, we can patch the open
function to add load
event listener for each request. To do so, we can do
function XHROverride() {
if (!window.XMLHttpRequest) return;
const originalOpen = window.XMLHttpRequest.prototype.open;
window.XMLHttpRequest.prototype.open = function () {
const url = arguments[1];
this.addEventListener("load", function () {
if (isShopifyCartURL(url)) {
dispatchEvent(url, this.response);
}
});
return originalOpen.apply(this, arguments);
};
}
The above function when executed, will save the original implementation in variable and override the original open
function with new implementation. In the new implementation, it would add a event handler for load
event and if it matches our conditions ( to be discussed later ), it calls another local function called dispatchEvent
. After that it just calls the real implementation of open
function that we saved in a variabler earlier.
Listen to Network Requests using Fetch API
To listen to all requests made using Fetch API, we can do something similar to above code where we will patch the original implementation of fetch
function to call our custom code.
function fetchOverride() {
if (!window.fetch || typeof window.fetch !== "function") return;
const originalFetch = window.fetch;
window.fetch = function () {
const response = originalFetch.apply(this, arguments);
if (isShopifyCartURL(arguments[0])) {
response.then((res) => {
res
.clone()
.json()
.then((data) => dispatchEvent(res.url, data));
});
}
return response;
};
}
The above function when executed will save the original implementation in a variable and override the original fetch
function with new implementation. In the new implementation, the original implementation is called and then trigger our custom function when Promise resolves based on our conditions.
Trigger Shopify Cart Events
Now that we are able to listen to all network calls being made regardless of the fact that they are made using XmlHttprequest
or Fetch API, we can discuss the conditions and URL matching logic that we skipped in the above parts. We have 2 different functions for that namely
- isShopifyCartURL
- dispatchEvent
Now we are listening to all network requests but we only care about Shopify Cart requests. So we can create and use a helper function to check if the URL in question is related to Shopify Cart or not.
const ShopifyCartURLs = [
"/cart/add",
"/cart/update",
"/cart/change",
"/cart/clear",
"/cart/add.js",
"/cart/update.js",
"/cart/change.js",
"/cart/clear.js",
];
function isShopifyCartURL(url) {
if (!url) return false;
const path = url.split("/").pop();
return ShopifyCartURLs.includes(`/cart/${path}`);
}
Once, we have identified that the URL is of Shopify Cart, we have another helper function to dispatch the relevant event with following implementation. Have a look at complete implementation for dispatchEvent that fires different events for add, update, change and clear.
function dispatchEvent(url, detail) {
window.dispatchEvent(new CustomEvent(CartEvents.mutate, {
detail
}));
}
Shopify Cart Events Script
The above code is available as complete script on Github, that fires 5 events related to Shopify Cart. To use this, you can download the package from Release page or copy the code from Github (index.min.js) and load it on your Shopify website. Ideally before loading your custom JavaScript. In case of standalone Shopify app, you can ship it as part of your custom JavaScript file.
window.addEventListener('SCE:mutate', (event) => {
// event.detail will be Shopify Cart object
})
window.addEventListener('SCE:add', (event) => {
// event.detail will be Shopify Cart object
})
window.addEventListener('SCE:update', (event) => {
// event.detail will be Shopify Cart object
})
window.addEventListener('SCE:change', (event) => {
// event.detail will be Shopify Cart object
})
window.addEventListener('SCE:clear', (event) => {
// event.detail will be Shopify Cart object
})
Final Word
The above code uses a technique commonly knows as Monkey Patching.
Monkey patching is a technique used to dynamically update the behavior of a piece of code at run-time.
Wikipedia
Even though it is useful, we need to use it with caution. It may not work as intended in some cases or may break something else. However, this is the most consistent way as of yet to have Shopify Cart Events specifically for Shopify Apps.