Dedicated to the Hydrogen framework, headless commerce, and building custom storefronts using the Storefront API.
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:
<form>
<div class="dropdown mt-4 mb-0">
<label for="nutrition-dropdown">Nutrition Info</label>
<button class="btn btn-lg bg-white btn-block text-left pl-1" id="nutrition-dropdown" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<span id="flavor-label">Select a Flavor</span><span class="text-muted float-right"><i class="fas fa-chevron-up"></i></span>
</button>
<div id="multi-flav-select" class="dropdown-menu w-100" aria-labelledby="nutrition-dropdown">
{%- for flavor in flavors.included_items -%}
<a class="dropdown-item pl-1" href="#" data-handle="{{ flavor.flavor_product_handle }}">{{ flavor.flavor_name }}</a>
{%- endfor -%}
</div>
</div>
</form>
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.
Solved! Go to the solution
This is an accepted solution.
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-a...
Hope this helps!
This is an accepted solution.
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!
This is an accepted solution.
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-a...
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?
This is an accepted solution.
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!