I am using Accentuate Custom Fields and Cart.js with a Shopify project. I have added some custom fields to my products via ACF. These fields are present on the product object via product.metafields.accentuate.[key]
.
I am using Cart.js to customize my cart experience and I was hoping to be able to access some of the values from ACF in my cart.
I tried accessing this custom field by adding following to my cart layout (every item is the name for a product in my JS loop): item.metafields.accentuate.product_benefits_content
However, I am getting the following error: Uncaught TypeError: Cannot read property 'accentuate' of undefined
I also tried: item.product.metafields.accentuate.product_benefits_content
per a response I found on Stack Overflow.
This gives a slightly different error: Uncaught TypeError: Cannot read property 'metafields' of undefined
What do I need to do to access these custom fields in my cart?
Here is a screen shot of the product object in my console:
2
Answers
You are confusing more than a couple of issues. When you speak of item.metafields, that would be something in Liquid. Not Javascript. If you wanted to use the contents of metafields in your cart, you would likely want to render the metafields from Liquid to some JS based data structure.
The way I would do it (been good for 10+ years now) is during your Liquid render phase in the template, store the item’s metafields of interest in something keyed to the variant ID (or product ID if that works for you). Then in JS world, once DOM has rendered, run a function to loop through your ID’s and show/hide your extra data as needed. Really has nothing to do with how you create metafields or the use of cartJS, but instead is more generic. If you’re good, you can hack in your event listeners in cartJS to show/hide your extra data, say in a tooltip or whatever.
On Shopify, metafields are semi-protected and aren’t part of the normal product output. You can test this for yourself with a simple script on a product page:
Note that regardless of whether or not the product has metafields, there will be no metafield output in the above.
Similarly, if you load the product’s associated JSON object from either of the frontend product endpoints, metafields are omitted. To verify, we can test by grabbing the JSON output from both of Shopify’s frontend endpoints:
Again, we note that no matter which way we try to get the info there’s just no metafield data being served with our request.
This brings us to an initially-depressing realization: Metafields can only be accessed through Liquid
So what can we do?
Metafields are semi-protected: You have to know that a namespace exists and access it in Liquid. In part, I think, this is to provide a layer of protection for apps that rely on metafields to do whatever they do. (It wouldn’t be good if a potential customer who knows how Shopify works could open a window that shows some app’s metadata about qualifying for a better price, for example)
One way or another, we’ll need to create new variables that store the metadata that we need and then use that variable in your JS loop.
We have several options at our disposal, and which one you go with is up to you. Here are two ideas:
Idea 1: Creating global variables on the page
This is generally the easiest, but creating a lot of global variables can be considered sloppy. Also, Liquid is only rendered when the page loads, so if your site is extremely dynamic you may find situations where customer actions can result in you not having all the metadata loaded.
To do this, we’ll create an object in Javascript that uses a unique identifier for each product on the page that we might care about – the most obvious choices are either the product’s ID or the product’s handle, since both of those are guaranteed to be unique.
Since we’re only saving metadata for the app/feature in question and not saving every metafield that might possibly exist on the product, let’s create a snippet named
save-accentuate-metadata.liquid
to hold the logic:Now we’ll just need to include it everywhere that there’s a product object – product pages, collection product-loops, etc. We’re expecting a variable named
product
in the snippet, so let’s be clear about that in our snippet call:If there’s a chance our code needs to be able to run on the search page, that include will need to reference whatever the variable is called on the search page (often
result
):And finally, we want to make sure that we save all the metafield data for the items currently in the cart. We would place this call somewhere global, like in layout/theme.liquid (or a snippet that we include inside theme.liquid)
Now that we’ve saved all this info, we can access it in whatever script we need so long as we have a product handle.
Idea 2: Creating our own custom endpoint
This takes a little bit more work, but it gives you your own endpoint that you can hit to get the data you want any time you need. This lets you be a bit more responsive to fancy features or apps running on your site that can manipulate the cart or load products that weren’t on the page initially (such as upsell-style apps). Note that going this route will mean that you need to make an asynchronous AJAX call to get the data, which may introduce a lag while the data is loading.
To make a custom endpoint, we first make a new template for the kind of object that we want to get data from. Let’s make two: one for the cart, which will give all of the data for our cart-items, and one other for a single product.
Let’s start by creating a template called
cart.accentuate.json.liquid
with the following:And just in case we want to grab just one product, let’s make a template for
product.accentuate.json.liquid
. Since we’re grabbing a specific product, we probably don’t need anything other than the metadata:Now we can access either of these from any arbitrary piece of code using AJAX calls by specifying a
view
in the querystring that matches our template suffix. For example:Note that when we create a custom endpoint this way, Shopify will serve its response as though it were a regular HTML page. This can cause jQuery’s AJAX functions to behave weirdly if we try to specify that we’re expecting a JSON response. Discovering all the unexpected behaviours is left as an exercise to the reader, but the takeaway is that you want to GET your page as though it were a regular HTML page, then use
JSON.parse
to turn the retrieved text into a proper JSON object.I hope this helps you to finish the feature that you’re working on!
Disclaimer: All of the above code is free-handed and hasn’t been tested for typos. If anything doesn’t work immediately, please comment, edit and/or check for any obvious bad code-grammar