Hello! Welcome back to our journey into interactive Shopify theming.
In our last lesson, we established the fundamental purpose of the Shopify Ajax API. We learned that while Liquid builds the page on the server, the Ajax API is our essential toolkit for creating dynamic, client-side experiences in the browser. You understood that it provides specific endpoints for tasks like cart manipulation, allowing our JavaScript code to communicate with Shopify's backend without needing a full page reload.
Today, we transition from theory to practice. We'll take that conceptual understanding and apply it to one of the most common and crucial features in e-commerce. Our learning outcome is to implement 'add to cart' functionality without a page reload using the Fetch API.
Given your background in full-stack development, you're already comfortable with the underlying concepts: handling DOM events, making asynchronous HTTP requests with fetch, and processing JSON responses. We'll now apply these skills within the specific context of the Shopify ecosystem.
1. The Starting Point: A Standard Shopify Product Form
By default, when you use the standard Liquid {% form 'product', product %} tag, Shopify generates an HTML form similar to this:
<form method="post" action="/cart/add">
<input type="hidden" name="id" value="{{ product.selected_or_first_available_variant.id }}">
<input type="number" name="quantity" value="1" min="1">
<button type="submit">Add to Cart</button>
</form>
When the user clicks the "Add to Cart" button, the browser submits this form, leading to a request to /cart/add and a full page reload. Our goal is to intercept this process to create a smoother user experience.
2. Implementing Asynchronous "Add to Cart"
We'll build this feature step-by-step, following a clear and logical process. The video below provides an excellent walkthrough of this entire process using plain JavaScript, which we will use as our guide.
Shopify ajax add to cart tutorial (theme development)
This tutorial by 'Coding with Robby' is a clear, practical guide to implementing an Ajax 'add to cart' feature. We will follow the key steps from this video.
We will reference specific segments of this video as we go through each step. For now, know that this is our main practical reference for today's lesson.
Step 1: Intercept the Form Submission
Our first task is to prevent the browser's default form submission behavior. We'll use JavaScript to listen for the submit event on our product form and stop it in its tracks.
Shopify ajax add to cart tutorial (theme development)
Let's watch the first part of the tutorial to see how to select the form and prevent the default submission.
Watch from 01:29 to 03:20. Pay attention to how document.querySelectorAll is used to target all 'add to cart' forms on the page and how event.preventDefault() is used within the event listener.
As the video demonstrates, the core logic is:
- Select all relevant forms: Using an attribute selector like
form[action$="/cart/add"]is a robust way to find all "add to cart" forms, whether on a product page or a collection grid. - Loop and attach an event listener: We iterate through each form and listen for the
submitevent. - Prevent the default action: Inside the event handler, we call
event.preventDefault(). This gives us a "clean slate" to handle the submission with our own code.
Step 2: Send Form Data Using the Fetch API
Now that we've stopped the default submission, we need to manually send the product data to Shopify's Ajax API. The designated endpoint for this is /cart/add.js.
Let's see how to construct this request.
Shopify ajax add to cart tutorial (theme development)
The next segment of the video shows how to use the Fetch API to send the form data.
Watch from 03:20 to 04:38. Notice the use of fetch with the POST method and, crucially, how the body is created using new FormData(form). We'll discuss why this is the preferred method.
The key to this step is packaging the form's data. The Shopify Cart API documentation outlines two primary ways to do this.
Let's consult the official Shopify Cart API documentation. This is our source of truth for API endpoints and request formats.
Read the section for POST /{locale}/cart/add.js. Focus on the two 'Example request' blocks. The first shows building a JSON object manually, and the second uses the FormData constructor, which is what we just saw in the video.
As you've seen, you can either construct a JSON object or serialize the form with FormData. For theme development, using FormData is the superior approach. Why?
- Extensibility: It automatically includes all input fields within the form. If a Shopify App adds a hidden input for custom properties or other functionality, your code doesn't need to change. It will be included automatically.
- Simplicity: It's less code to write and maintain than manually grabbing each value and building a JSON object.
Your curiosity for "first principles" makes it worthwhile to hear another developer advocate for this approach.
22 - Shopify Ajax Add To Cart with Fetch API and Alpine.js
This clip from a 'Code Inspire' video offers an excellent explanation for why serializing the form with FormData is the professional standard for theme development.
Watch from 02:17 to 04:10. The presenter clearly explains the future-proofing benefits of serializing the form versus creating a custom object.
Step 3: Update the UI with the New Cart State
After our fetch request successfully adds the item, the cart has changed on Shopify's servers, but our user's browser doesn't know it yet. We need to provide immediate feedback. The most common updates are refreshing the cart item count and displaying a success message.
A robust pattern for this is to make a second API call to GET /cart.json. This retrieves the entire, up-to-date cart object, ensuring we have the correct total item count, regardless of what was just added.
Shopify ajax add to cart tutorial (theme development)
Let's return to our primary tutorial to see how to fetch the updated cart and update the UI.
Watch from 04:38 to 08:35. This section covers two key actions: first, fetching /cart.json to get the new item_count and updating the header display; second, displaying an 'Added to cart' message to the user for clear visual feedback.
This sequence—POSTing to add, then GETting to update—is a reliable pattern for dynamic cart interactions.
3. Putting It All Together: The Complete Script
Here is a complete JavaScript snippet that combines all the steps we've covered. You would typically place this in one of your theme's JavaScript asset files.
// Listen for the DOM to be fully loaded before running the script
document.addEventListener('DOMContentLoaded', function() {
// Select all forms with the action '/cart/add'
const addToCartForms = document.querySelectorAll('form[action$="/cart/add"]');
addToCartForms.forEach(form => {
form.addEventListener('submit', async (event) => {
// 1. Prevent the default form submission (page reload)
event.preventDefault();
// You can add a 'loading' state to your button here if you wish
// e.g., button.classList.add('loading');
try {
// 2. Send the form data to the Ajax API
await fetch('/cart/add.js', {
method: 'POST',
body: new FormData(form)
});
// 3. Fetch the updated cart data
const res = await fetch('/cart.json');
const cart = await res.json();
// 4. Update UI elements with the new cart state
// Update all elements with a specific class for the cart count
document.querySelectorAll('.cart-count').forEach(el => {
el.textContent = cart.item_count;
});
// Display a success message
// This could be toggling a class on an existing element...
const successMessage = form.querySelector('.added-to-cart-message');
if (successMessage) {
successMessage.classList.add('active');
// Optionally hide it after a few seconds
setTimeout(() => {
successMessage.classList.remove('active');
}, 3000);
}
} catch (error) {
console.error('Error adding to cart:', error);
// You would add user-facing error handling here
} finally {
// Remove the 'loading' state from your button
// e.g., button.classList.remove('loading');
}
});
});
});
Test your understanding!
A merchant reports that a newly installed "gift wrapping" app, which adds a checkbox to the product form, isn't working with your custom Ajax cart. The gift wrap option isn't being saved when a product is added. Based on what you've learned, what is the most likely cause of this issue in your JavaScript code?
- Your
fetchcall is missing theContent-Type: 'application/json'header. - Your JavaScript is manually building a JSON object with only the
idandquantity, instead of usingnew FormData(form). - Your code is missing an
awaitbefore thefetchcall to/cart/add.js. - You are not making a second
fetchcall to/cart.jsonafter adding the item.
Show answer
The most likely cause is #2.
If your code manually creates a JSON object with just the variant ID and quantity, it will be completely unaware of the new checkbox (<input type="checkbox" name="properties[Gift Wrap]">) added by the app. By switching to body: new FormData(form), your code will automatically serialize all fields in the form, including the app's new input, and send them to Shopify.
The other options are incorrect because:
- #1: The
Content-Typeheader is not needed when usingFormData, as the browser sets it correctly. - #3: A missing
awaitwould cause a race condition, but it wouldn't explain why specific form data is missing. - #4: Not fetching the updated cart would mean the UI doesn't refresh, but it wouldn't prevent the gift wrap data from being sent in the initial request.
Conclusion
Congratulations! You have just implemented one of the most fundamental features of a modern Shopify theme. By moving from a page reload to an asynchronous request, you've significantly improved the user experience.
Key Takeaways:
- Intercept and Prevent: The process begins by capturing the form's
submitevent and usingevent.preventDefault()to stop the browser's default behavior. - Serialize with
FormData: Usingnew FormData(formElement)is the most robust and future-proof way to gather all the necessary data to send to Shopify. - Use the Ajax API Endpoints: You make a
POSTrequest to/cart/add.jsto add an item, and aGETrequest to/cart.jsonto retrieve the updated cart state. - Update the UI: After a successful response, you must use JavaScript to update relevant parts of the page, like the cart item count, to reflect the new state.
In our next lesson, we will build directly on this foundation. Now that we can add items to the cart, we'll learn how to update cart quantities and remove items from a cart drawer using the Ajax API, further enhancing the dynamic capabilities of our theme.