Shopify Tips

Using comma-separated metafield values with Shopify filters

← Blog

I was recently tasked with creating a filtered product comparison page for a client’s website and thought I’d take advantage of Shopify 2.0’s new filters option.

Update: As of 6th April, Shopify has added the “list” metafield option. More details in their changelog.

There is an option to add product filters to your site via the store > navigation > menus page for those who haven’t noticed yet.

The Shopify docs indicate that metafield values containing comma-separated values will be treated as individual terms, not as a whole string. For example, Red,Yellow would be treated as Red and Yellow. Here’s a reference:

“A single metafield value, or a comma-separated list of metafield values.

For example, canada or canada,usa.

Note: A comma-separated list of metafield values is a list of individual metafield values, not a single metafield value that contains a comma-separated list.”

But, despite what it said in the Shopify docs, I couldn’t get comma-separated filter values to work in this way.

So, I had to develop a workaround, and here it is.

For this example, let us assume we have the following data (this matches the Shopify object filter.values):

[
  {
    "label": "Red",
    "value": "Red",
    "param_name": "filter.p.m.custom.colour"
  },
  {
    "label": "Blue",
    "value": "Blue",
    "param_name": "filter.p.m.custom.colour"
  },
  {
    "label": "Green",
    "value": "Green",
    "param_name": "filter.p.m.custom.colour"
  },
  {
    "label": "Red,Yellow",
    "value": "Red,Yellow",
    "param_name": "filter.p.m.custom.colour"
  },
  {
    "label": "Black,Yellow,Green",
    "value": "Black,Yellow,Green",
    "param_name": "filter.p.m.custom.colour"
  }
]

1. Split the metafield values

The first task was to split the values into individual terms to make them more usable for the user. We don’t want values repeated, so a little check has been included within the loop.

<fieldset>
  <legend>Colour</legend>
  <ul class="filters-example">
    {% for filter_value in filter.values %}

      {% comment %}
      Split value if there's a comma.
      {% endcomment %}

      {% assign split_filter_values = filter_value.label | split: ',' %}
      {% for value in split_filter_values %}

        {% comment %}
        This is part of the check to make sure values are not repeated.
        The value is appended to check_value at the end of each loop iteration.
        {% endcomment %}

        {% assign list_check_value = check_value | split: ',' %}
        {% unless list_check_value contains value %}
          <li>
            <label>
              <input
                class="js--filter js--keyword"
                type="checkbox"
                name="{{ filter_value.param_name }}"
                value="{{ value }}">
              {{ value | strip }}
            </label>
          </li>
        {% endunless %}

        {% comment %}
        Add the value to a list so we can check if exists
        within the next loop iteration.
        {% endcomment %}

        {%- capture check_value -%}
          {{- check_value -}}{%- unless check_value == "" -%},{%- endunless -%}
          {{- value | strip -}}
        {%- endcapture -%}
      {% endfor %}
    {% endfor %}
  </ul>
</fieldset>

Which would output the following, so values such as Black,Green,Yellow would appear as separate items.

Colour

By doing this, though, it would mean the filters would not pick up values like Red,Yellow. To fix this, I added these options as hidden checkboxes.

2. Add the comma-separated values as hidden checkboxes

For this article, I’ve greyed out the hidden options. You would hide these with CSS display: none; for production. Below is the above code updated with the hidden checkboxes included.

<fieldset>
  <legend>Colour</legend>
  <ul class="filters-example">
    {% for filter_value in filter.values %}
      {% assign split_filter_values = filter_value.label | split: ',' %}
      {% for value in split_filter_values %}
        {% assign list_check_value = check_value | split: ',' %}
        {% unless list_check_value contains value %}
          <li>
            <label>
              <input
                class="js--filter js--keyword"
                type="checkbox"
                name="{{ filter_value.param_name }}"
                value="{{ value }}">
              {{ value | strip }}
            </label>
          </li>
        {% endunless %}
        {%- capture check_value -%}
          {{- check_value -}}{%- unless check_value == "" -%},{%- endunless -%}
          {{- value | strip -}}
        {%- endcapture -%}
      {% endfor %}

      {% comment %}
      If there's a comma in the value, hide this option.
      {% endcomment %}

      {% if filter_value.value contains "," %}
        <li class="filter-hidden">
          <input
            class="js--filter js--list"
            type="checkbox"
            name="{{ filter_value.param_name }}"
            value="{{ filter_value.value }}">
          &nbsp;{{- filter_value.value -}} &nbsp;[hidden]
        </li>
      {% endif %}
    {% endfor %}
  </ul>
</fieldset>

Which would result in the following example that contains all of our filter values. At this point, they don’t do anything, but it gives us something to work with.

Colour
  •  Red,Yellow [hidden]
  •  Black,Yellow,Green [hidden]

3. Javascript to control the checkboxes

This is the clever part – a little bit of JavaScript to control the checkboxes, which checks if the selected value exists in any of the checkboxes.

document.querySelectorAll(".js--keyword").forEach((filter) => {
  filter.addEventListener("click", (e) => {
    let $this = e.target;
    let filterParamName = $this.name;
    let filterParamValue = $this.value;
    let arrayOfCheckedItems = [];

    // Create an array of checked items.
    document.querySelectorAll('input[name="' + filterParamName + '"].js--keyword').forEach((opt) => {
      if (opt.checked == true) {
        arrayOfCheckedItems.push(opt.value);
      }
    });

    // Loop through all the hidden fields and if any keyword from the
    // arrayOfCheckedItems exists in the hidden field value, check the
    // field.
    let hiddenFields = document.querySelectorAll('input[name="' + filterParamName + '"].js--list');
    hiddenFields.forEach((opt) => {
      if (arrayOfCheckedItems.some((k) => opt.value.includes(k))) {
        opt.checked = true;
      } else {
        opt.checked = false;
      }
    });
  });
});

Applying this to the code we generated above would give us checkboxes that automatically select the chosen values across the field set. Give it a try below!

Colour
  •  Red,Yellow [hidden]
  •  Black,Yellow,Green [hidden]

4. What next?

This is just a starting point, but you could add some ajax to apply the filters whenever a checkbox is selected or add a form with the get method to submit the filters.

Subscribe to my newsletter

Sign up to get my latest blog articles direct to your inbox.