skip to Main Content

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:

The product object

2

Answers


  1. 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.

    Login or Signup to reply.
  2. 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:

    <script>
      var product_info_from_liquid = {{ product | json }}
      console.log('Product info from Liquid:',product_info_from_liquid)
    </script>
    

    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:

    <script>
      //Grabbing the data for the 'sleepy-head' product from the .js endpoint
      jQuery.ajax({
        url:'/products/sleepy-head.js',
        dataType:'json',
        success:function(product){
          console.log('The ".js" results:', product)
        }
    
      //Grabbing the data for the 'sleepy-head' product from the .json endpoint
      //I don't know why Shopify serves different at the .js and .json addresses, but apparently they do
      jQuery.ajax({
        url:'/products/sleepy-head.json',
        dataType:'json',
        success:function(product){
          console.log('The ".json" results:', product)
        }
    
      })
    
    </script>
    

    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:

    <script>
      // Initialize our global object only if it doesn't exist yet
      window.AccentuateLookup = window.AccentuateLookup || {}
      AccentuateLookup[{{ product.handle | json }}] = {{ product.metafields.accentuate | json }} 
    </script>
    

    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:

     {% include 'save-accentuate-metadata', product: product %}
    

    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):

     {% if result.object_type == 'product' %}
       {% include 'save-accentuate-metadata', product: result %}
     {% endif %}
    

    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)

    {% for item in cart.items %}
      {% include 'save-accentuate-metadata', product: item.product %}
    {% endfor %}
    

    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.

    // Meanwhile, in some other script on the page:
    if(typeof AccentuateLookup === 'undefined'){
      console.error('No data initialized!')
    }
    var accentuate_data = AccentuateLookup[item.handle]
    console.log('Our records show the following for' + item.title, accentuate_data)
    

    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:

    {% layout none %} {% comment %} That tells Shopify to ignore all the layout code/HTML, freeing us to create a simple JSON object {% endcomment %}
    {
      {% for item in cart.items %}
        {% comment %}Output the JSON data, and append a comma unless we're on the last one{% endcomment %}
        {{ item.handle | json }}: {{ item.product.metafields.accentuate | json }}{% unless forloop.last %},{% endunless %}
      {% endfor %}
    }
    

    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:

    {% layout none %}
    {{ product.metafields.accentuate | json }}
    

    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:

    jQuery.ajax({
      url:'/cart?view=accentuate.json',
      success:function(unparsed_json){
        var cart_data = JSON.parse(unparsed_json)
        console.log('This just in from cart.accentuate.json:', cart_data)
      }
    })
    
    var handle = 'sleepy-head'
    jQuery.ajax({
      url:'/products/' + handle + '?view=accentuate.json',
      success:function(unparsed_json){
        var cart_data = JSON.parse(unparsed_json)
        console.log('This just in from product.accentuate.json:', cart_data)
      }
    })
    

    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

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search