Shopify Recommended Products AJAX API and Sections
Tags: shopify, javascript, intersectionobserver, liquid
Published: 2021-08-10 15:44:06

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](https://shopify.dev/api/ajax/reference/product-recommendations)  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](https://shopify.dev/api/liquid/objects/product) 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](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API). 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!