Shopify Webstore 2.0 product FAQ example

Cover image by cottonbro

Using features of Webstore 2.0 to implement a product page FAQ panel.

Shopify Webstore 2.0 implements a suite of new features, some of which you might be familiar with if you’ve worked with themes directly. This guide shows you how to set up individual FAQ sections in your product pages. You’ll also learn how your Shopify content authors can edit them using native features of Webstore 2.0, without the use of apps.

Getting metafields set up

You can now access and configure metafields from the Shopify menu (previously, you needed to use apps or tricky URLs). To set up metafields, within Shopify, click on Settings (bottom left) and from the new menu select Metafields. You should see this:

The Metafields menu in Shopify.

At the time of writing, metafields are only available on products and product variants. Click on Products:

The Metafield Product menu in Shopify.

Unfortunately, the whole concept of blocks (which we’ll cover later) doesn’t extend to metafields. This means we’ll need to know the maximum number of questions and answers each product will support in advance. While it’s possible to add more questions and answers later, this will lead to a slightly ugly interface for your Shopify users.

Add the metafields via the interface in the wrong order, so they appear in the correct order for your Shopify users. For example, if you want to allow up to 10 questions and answers, the first metafield you need to add is the answer to question 10:

The metafield form for answer 10 of the FAQs.

Namespaces are useful for organising groups of data into categories. I’ve used faq in the example above.

Notice that text fields come in two flavours: big and little. This becomes relevant much later when we’re attaching these dynamic data points to the customisation options in Shopify. The data type of the metafield needs to match what the Shopify template expects.

The lower half of the metafield form gives additional options. For example:

Valudation fields in Shopify metadata capture form.

You can add regular expressions to limit what input the user can provide. These don’t seem to be HTML5 pattern attributes, but are run server-side and if your Shopify users break them, they’ll get a message only after attempting to save:

Not especially useful for our purposes, but good to know.

Populating metafield content

Visit a product within Shopify, scroll to the bottom and you should see your new metafields appearing:

Metafield data being populated from within Shopify.

Note that the data we’re setting up here is specific to individual products. Much of the headline features of Shopify Webstore 2.0 refers to features which are applied globally to every instance of a particular content type. We’ll use this to our advantage a little later.

Populate one of your products with FAQ content. We’ll need some data to test our code later.

Creating an FAQ section

We could build logic at this point to check metafield data and include product-specific content. But if we did this, we wouldn’t be able to use some powerful features of Webstore 2.0.

In order to get the best out of the new features, we need to pull the section in using a particular kind of product template. Let’s look at that first.

The product template

If you’re migrating to Webstore 2.0 from a theme built for Webstore 1.0 (I don’t think anyone actually called it that), you’ll need to make some changes to your product template.

Webstore 2.0 is a lot more modular than before. Essentially, liquid files are being replaced with human-readable JSON files which can be dynamically updated by non-technical Shopify users using the Customise features of Shopify themes.

The file /templates/product.liquid should be renamed to something like product-backup.liquid. You cannot have product.liquid and product.json in the same directory as both perform the same task.

The simplest possible product.json would look like this:

{
  "name": "Default product template",
  "sections": {
    "main": {
      "type": "product-section"
    }
  },
  "order": [
    "main"
  ]
}

A more complicated example

Let’s assume your current product template has this structure (each of the nested boxes represent sections):

product.liquid

product-intro-section.liquid

product-body-section.liquid

product-footer-section.liquid

A product template in Shopify which imports three section elements

Shopify sections cannot be nested inside each other. This means you can’t simply take all of the markup inside the product.liquid and put it into one new section file.

Instead, the above structure would be represented in product.json like this:

{
  "name": "Default product template",
  "wrapper": "div.container",
  "sections": {
    "intro": {
      "type": "product-intro-section"
    },
    "body": {
      "type": "product-body-section"
    },
    "footer": {
      "type": "product-footer-section"
    }
  },
  "order": [
    "intro", "body", "footer"
  ]
}

Note that only one wrapper element can be added, which may restrict your markup.

Once this is done, your product page is ready to deal with dynamic sections. Congratulations!

Creating a dynamic section

Remember back at the start, when we created the data for the FAQs and assigned them to a product? It’s time to start dealing with that.

Inside the sections directory, create a new file called faq.liquid (or just look at the example we’ve created in the Mannequin repo).

Checking if the questions and answers exist

The file starts with ten almost identical if statements. Here's the first one:

{% if product.metafields.faq.question_1 != blank or product.metafields.faq.answer_1 != blank %}
  {% assign q1_exists = true %}
{% endif %}

This sets a series of variables which allow us to show or hide both individual question and answer sets or even the FAQ as a whole.

Once we’ve done this for the ten pairs of metafields, we work out if there’s anything to display at all:

{% if q1_exists or q2_exists or q3_exists or q4_exists or q5_exists or q6_exists or q7_exists or q8_exists or q9_exists or q10_exists %}
  {% assign faq_exists = true %}
{% else %}
  {% assign faq_exists = false %}
{% endif %}

This variable is used to decide if the FAQ panel (and its heading) will display or not. It's used like this:

{% if faq_exists %}
  <section aria-label="{{ section.settings.title }}">
    <h2>{{ section.settings.title }}>/h2>
    {% comment %} Questions and answers go here {% endcomment %}
  </section>
{% endif %}

Adding checks for site-wide content

We’ll go into more detail on this subject in the future, but we also need a bit of code here which checks for any content which doesn’t come from metafields:

{% for qanda in section.blocks %}
  {% if qanda.settings.title != '' %}
    {% render "faq-panel", question: qanda.settings.title, answer: qanda.settings.answer %}
  {% endif %}
{% endfor %}

This code loops through the content blocks for the current page and finds any which don’t match the metafield pattern.

Avoiding repeating markup

Individual sets of questions and answers might need to be wrapped in specific markup. To avoid repeating that markup multiple times, let’s outsource it to a snippet (or a poor man’s function, as I like to think of it):

<!-- faq-panel.liquid -->
<details>
    <summary>{{ question }}</summary>
    {{ answer }}
</details>
<!-- end faq-panel.liquid -->

Now we can call this snippet ten times and pass it the correct data from within faq.liquid. Here’s how that looks for the data relating to question 1:

{% if q1_exists %}
  {% render "faq-panel", question: product.metafields.faq.question_1, answer: product.metafields.faq.answer_1 %}
{% endif %}

Section schema

So far, this is a pretty standard section file. But it’s the schema where the advanced functionality starts to show up.

{% schema %}
{
  "name": "FAQs",
  "settings": [
    {
      "type": "text",
      "id": "title",
      "label": "Heading",
      "default": "Frequently Asked Questions",
      "info": "The heading which appears above the frequently asked question panel."
    }
  ],
  "blocks": [{
    "name": "Q&A block",
    "type": "qanda",
    "settings": [
      {
        "type": "text",
        "id": "question",
        "label": "Question"
      },
      {
        "type": "richtext",
        "id": "answer",
        "label": "Answer"
      }
    ]
  }],
  "presets": [
      { "name": "Frequently Asked Questions" }
    ]
}
{% endschema %}

This has some similarity to the schema for the product template, but with added features.

Data scope

It’s worth pausing here to talk about data scope. The metafields we’ve used here are scoped to the individual element - in our case, the product page.

Changes made to pages when customising them in Shopify, however, will occur across every page of that type in the site.

This means that if you rearrange your sections to be in a particular order on one product page, that same order will be repeated on every other page which uses the same template.

The same goes for data - if we added content to the FAQ section within customisation, that information would show up on every page of our site.

However, Shopify allows us to use a hybrid of the two, by using dynamic sources. Within the customisation settings of the product page, add the FAQ section and click on the first block:

Animation showing how to select a dynamic source to feed content to an FAQ template.

This links the metafield values we added at the start to the FAQ section, allowing them to be displayed directly to the customer.

Now continue the process above for the other nine questions. But you don’t have to stop there: if you add in content which does not come from dynamic content, you could have questions and answers which appeared against every single product on your site, saving you from adding boilerplate questions such as Can I return this product? to every single product.

Note that by default, your product-wide questions will only appear if there are any other metafield questions added to your product. If you want the product-wide questions to appear even when there are no product specific questions, you should make a small change to the logic of faq.liquid:

{% if q1_exists or q2_exists or q3_exists or q4_exists or q5_exists or q6_exists or q7_exists or q8_exists or q9_exists or q10_exists %}
  {% assign faq_exists = true %}
{% else %}
  {% assign faq_exists = false %}
{% endif %}

Assign faq_exists to true, to ensure that the default questions appear for every product on the site.

Next steps

Your products should now display frequently asked question panels, if the content for them exists. Congratulations!

Metafields can do so much more, but the process will involve most of the steps above. For example, you could add a product chooser metafield and allow your content authors to fill out a related products panel. Or make it even smarter, so it finds related products based on similar tags, but these will be used only if your authors fail to specify alternatives.

Get in touch to find out more     We turn casual browsers into committed buyers