Throwing this up here in hopes it is easier to find than some of the random examples I found after lots of looking.
Anyway, Shopify's AJAX unauthenticated API is pretty limited in scope but its recommended products suggestions are certainly more robust than whatever clever looping and coding that I can do within a liquid template.
Also in the one fairly thorough example I found they don't completely explain how sections work with this. So i'll try to do that here.
When you create a url to fetch recommendations from you can add 3 variables: the id of the product (required), the limit of items to pull, and the section id that will render the results.
var productId="12345"; //{ in a template it would be product.id }
var sectionId="product-recommendations";
var limit="4";
var url="/recommendations/products?section_id="+sectionId+"&limit="+limit+"&product_id="+productId;
If you only do the product id you will get up to 10 product results in json format. And if javascript only is your thing then you're all set. For me having a simple liquid section to format the data is easier. So in our section named "product-recommendations" we have some liquid and HTML. The recommendations object (recommendations.products) is a set of products and we will loop through it to show all the items. Of course the result could be empty so we will check for that too.
<div class="product-recommendations">
{%- if recommendations.products_count > 0 -%}
<h2>You may also like</h2>
{%- for product in recommendations.products -%}
<div>Item url: {{ product.url }}</div>
<div>Image url: {{ product.featured_image | img_url: '300x300' }}"</div>
<div>Item name: {{ product.title }}</div>
<div>Item price: {{ product.price | money}}</div>
{%- endfor -%}
{%- endif -%}
</div>
Pretty much anything in the normal product object is available so mark it up however you need for your shop. So using javascript fetch and assuming you have a container div on your page with the id "product-recs" you would have this (using the url we built earlier)
fetch(url).then(function (response) {
return response.text();
}).then(function (html) {
document.getElementById("product-recs").innerHTML=html;
}).catch(function (err) {
console.warn('Something went wrong.', err);
});
And that is pretty much it! You could add this to any product page, or the cart page (use the product id of the first or last item in the cart), or even the accounts area (maybe get the customer favorite item id and use that etc).
BUT, there is one more thing i'll add here, and that is a way to save some initial load time on the pages you implement this. As our code stands it will load the items as part of the page load, but most usually recommendations are found at the bottom of the page below the fold, and out of sight. So lets load them when they enter the viewport. For this we will use javascript's IntersectionObserver. A very nice and clean way to trigger our fetch (rather than listening to scroll events).
var productId="12345"; //{ in a template it would be product.id }
var sectionId="product-recommendations";
var limit="4";
var url="/recommendations/products?section_id="+sectionId+"&limit="+limit+"&product_id="+productId;
var intersectionObserver = new IntersectionObserver(function(entries) {
// If intersectionRatio is 0, the target is out of view
// and we do not need to do anything.
if (entries[0].intersectionRatio <= 0) return;
fetch(url).then(function (response) {
//once loaded we can stop observing
intersectionObserver.unobserve(document.getElementById("product-recs"));
return response.text();
}).then(function (html) {
document.getElementById("product-recs").innerHTML=html;
}).catch(function (err) {
console.warn('Something went wrong.', err);
});
console.log('Loaded new items');
});
// start observing
intersectionObserver.observe(document.getElementById("product-recs"));
Last thing: assuming you make a section named "product-recommendations" with the HTML markup you need, here's how to add this to your theme.
Add a div with the id "product-recommendations"
where you want suggestions to show up
in your product.liquid template.
Add this script to your theme footer
{% if request.page_type == "product" %}
<script>
if(document.getElementById("product-recommendations")){
var intersectionObserver = new IntersectionObserver(function(entries) {
if (entries[0].intersectionRatio <= 0) return;
var requestUrl = "/recommendations/products?section_id=product-recommendations&limit=4&product_id="+{{ product.id }};
fetch(requestUrl).then(function (response) {
intersectionObserver.unobserve(document.getElementById("product-recommendations"));
return response.text();
}).then(function (html) {
document.getElementById("product-recommendations").innerHTML=html;
}).catch(function (err) {
console.warn('Something went wrong.', err);
});
});
intersectionObserver.observe(document.getElementById("product-recommendationss"));
}
</script>
{% endif %}
I'm using this on a project and it is working well. Hope this is helpful, happy coding!
Top comments (3)
Good write-up, and the IntersectionObserver approach is underused โ lazy-loading recommendations on scroll is far cleaner than tying it to scroll event listeners.
This pattern works well for product recommendations, and the same logic applies if you want to add "related articles" to Shopify blog posts. The built-in
blogs.blog.articlesobject lets you loop through posts and filter by tag or author, but there's no native "recommended posts" API equivalent the way there is for products.I actually built Better Related Blog Posts (apps.shopify.com/better-related-blog-posts) to solve exactly this gap โ it injects a related posts section at the end of blog articles using tag-based matching. The idea came from doing the manual Liquid version and realizing that any store with a blog was missing something WordPress users take for granted.
Also worth noting for completion: if you find the recommended products are loading slowly because users click through before the intersection triggers, Prefetch (apps.shopify.com/prefetch) handles preloading destination pages on hover, which pairs nicely with the lazy-load approach you've described here.
(Disclosure: I built both apps.) Thanks for this reference โ this is genuinely one of the cleaner explanations of the sections + AJAX combo I've seen.
๐ Download Seers Cookie Consent Banner for your Shopify store today!
Make compliance easy and build trust with your customers.
Great post.
I learned a lot from it.
Iโm a full-stack & Shopify developer and enjoy thoughtful technical discussions.
Would love to connect and exchange ideas sometime. Thanks for sharing!