Fetch Product Metafields

Hello,

*Note: when I say metafield I mean a metafield created in Admin>Settings>Metafields

I’ve been trying to show nutritional info for a selected product that is included in a pack. The goal is to select a flavor from the options rendered by liquid, fetch the metafield data of the given product and then display it without reloading the page.

I see that it’s not possible to retrieve metafield data for a product via the storefront API, not sure why because that would make this 1000% easier.

Here is the liquid for the dropdown:


Here is the js running the logic behind the dropdown:

window.addEventListener('DOMContentLoaded', function() {
	setActiveSelectContainer('#multi-flav-select', '.dropdown-item');
});

function setActiveSelectContainer(containerSelector, subItemSelector) {
  let container = document.querySelector(containerSelector);
  let subItems = container.querySelectorAll(subItemSelector);

  for (var i = 0; i < subItems.length; i++) {
    subItems[i].addEventListener("click", function() {
      var current = document.querySelectorAll(".dropdown-item.active");

      if(current.length > 0) {
        current[0].className = current[0].className.replace(" active", "");
      }

      this.className += " active";
      
      setFlavorInfo(this.innerText, this.dataset.handle);
    });
  } 
}

function setFlavorInfo(currentFlavor, productHandle) {
  let flvLabel = document.querySelector('#flavor-label');
  flvLabel.innerText = currentFlavor;
  
  console.log("Current Product: " + currentFlavor + " | Product Data Handle:" + productHandle);
  
  /* grab flavor info async then display it on successful response  */
  const shopURL = '{{ shop.url }}';
  const urlForID = shopURL + '/products/' + productHandle + '.json';
  
  fetch(urlForID)
  .then(response => response.json())
  .then(function(data) {
    console.log("	--Product ID: " + data.product.id);
    getMetaFields(data.product.id);
  })
  .catch(err => console.log(err));
}

function getMetaFields(productID) {
  const shopURL = '{{ shop.url }}';
  const urlForMeta = shopURL + '/admin/products/' + productID + '/metafields.json';
  
  fetch(urlForMeta)
  .then(response => response.json())
  .then(function(data) {
    console.log("	--Product Metafields: " + data);
  })
  .catch(err => console.log(err))
}

I seem to be getting a CORS error on the second fetch request and I’m now wondering this: “Do I really have to create a private app to simply fetch metafields?”

Or am I approaching this wrong and there is another way to do this without creating an app?

The exact errors are as follow:

-TypeError: NetworkError when attempting to fetch resource.

-Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at https://my-store.com/admin/products/3486492852299/metafields.json . (Reason: CORS header ‘Access-Control-Allow-Origin’ missing). Status code: 401.

Hey Dave!

Have you seen this documentation? https://shopify.dev/custom-storefronts/products/metafields

If so, was that not what you were looking for? While you’re not able to create/update/delete metafields from the Storefront API — you are able to get/read them, which seems like is what is needed for your use case.

Here is the specific reference for the Product Metafield object: https://shopify.dev/api/storefront/2021-10/objects/Product#field-product-metafield

With regards to the private app — I’m quite sure you will need a storefront access token in order to access the Storefront API. You can set this up right in the admin: https://help.shopify.com/en/manual/apps/custom-apps#enable-custom-app-development-from-the-shopify-admin

Hope this helps!

Hey Ben,

Thanks for the quick response, I did see that documentation, but seeing that it used graphQL I thought it was meant to be used from an app and not from the front-end. The js I’m showing is a snippet I’m rendering into a product page. I’m not well versed with graphQL either by the way.

If I could I’d like to just use the initial fetch request and display the info from there, but it doesn’t recognize ‘data.product.metafields’ when I try to use it, but does recognize ‘data.product.id’. You’re correct though I only want to display the metafields for reading and not for writing to.

Something like this:

function setFlavorInfo(currentFlavor, productHandle) {
  let flvLabel = document.querySelector('#flavor-label');
  flvLabel.innerText = currentFlavor;
  
  console.log("Current Product: " + currentFlavor + " | Product Data Handle: " + productHandle);
  
  const full_url = '/products/' + productHandle + '.json';
  
  fetch(full_url)
  .then(response => response.json())
  .then(function(data) {
    console.log("	--Product ID: " + data.product.id);
    console.log("	--Product Ingredients: " + data.product.metafields.nutrition.ingredients);
  })
  .catch(err => console.log(err));
}

*Edit: forgot to mention, it seems like I didn’t need to provide a token to get the product id above. I’m also working with the live site up in another browser so I’m not signed in at all.

Thanks again, I appreciate the help a ton!

Edit 2: Would an approach using graphQL and fetch such as this work?

https://www.netlify.com/blog/2020/12/21/send-graphql-queries-with-the-fetch-api-without-using-apollo-urql-or-other-graphql-clients/

1 Like

Yes, exactly! That Netlify outline is great. I think we have some of our own documentation around learning GraphQL — but that is a good source.

I understand it’s not the easiest learning curve, but it’s a very powerful tool when you can get the hang of it, and you minimize the size of the network request by only grabbing the fields you actually need.

Give it a shot and see where you get to. If you need more help, I’m happy to follow up with some examples when back at my computer.

Good luck!

1 Like