How to Add an FAQ Section on Shopify

Add an FAQ section on Shopify with a collapsible accordion — built from section blocks so merchants add and reorder questions, plus valid FAQ schema.

4 min readUpdated

Part of our guide to Shopify Custom Sections: The Complete Guide.

An FAQ section answers the questions that otherwise land in your support inbox, and the best version lets a merchant add, edit, and reorder questions from the theme editor without touching code. This guide builds exactly that: a collapsible FAQ accordion using section blocks, so each question-and-answer pair is an item a merchant manages in the editor — plus the correct way to add FAQ structured data for search.

This is part of the Shopify custom sections guide. If a .liquid section file and its {% schema %} are new to you, start with How to create a custom section.

Why blocks, not settings

You could add ten text settings for ten questions, but that caps the section at ten and forces a fixed order. Blocks solve both problems: a block is a repeatable item inside a section that a merchant can add, remove, and drag to reorder. Each FAQ row becomes one block with a question and an answer field. Blocks are covered in depth in Section schema and blocks; here we use them for a concrete FAQ.

Step 1 — Build the section with FAQ blocks

Create sections/faq.liquid. The markup loops over section.blocks and renders each as a native <details>/<summary> disclosure — which gives you a working accordion with zero JavaScript and full keyboard and screen-reader support out of the box.

<section class="faq">
  {% if section.settings.heading != blank %}
    <h2 class="faq__heading">{{ section.settings.heading }}</h2>
  {% endif %}
  <div class="faq__list">
    {% for block in section.blocks %}
      <details class="faq__item" {{ block.shopify_attributes }}>
        <summary class="faq__question">{{ block.settings.question }}</summary>
        <div class="faq__answer">{{ block.settings.answer }}</div>
      </details>
    {% endfor %}
  </div>
</section>

{{ block.shopify_attributes }} is important: it lets the theme editor highlight and select the exact block a merchant clicks on, so editing feels native. Never omit it on a block's root element.

Step 2 — Define the schema and blocks

Add the schema at the bottom of the same file. The blocks array declares a faq_item block type with its two settings; max_blocks caps the count if you want to, and the presets entry can seed a couple of starter questions so the section is not empty the moment it is added.

{% schema %}
{
  "name": "FAQ",
  "tag": "section",
  "settings": [
    {
      "type": "text",
      "id": "heading",
      "label": "Heading",
      "default": "Frequently asked questions"
    }
  ],
  "blocks": [
    {
      "type": "faq_item",
      "name": "Question",
      "settings": [
        {
          "type": "text",
          "id": "question",
          "label": "Question",
          "default": "How long does shipping take?"
        },
        {
          "type": "richtext",
          "id": "answer",
          "label": "Answer",
          "default": "<p>Orders ship within 2 business days.</p>"
        }
      ]
    }
  ],
  "max_blocks": 25,
  "presets": [
    {
      "name": "FAQ",
      "blocks": [
        { "type": "faq_item" },
        { "type": "faq_item" }
      ]
    }
  ]
}
{% endschema %}

Two notes:

  • The answer uses richtext, so merchants can bold text and add links inside an answer without HTML.
  • The block type (faq_item) is what the presets.blocks entries reference — the strings must match.

Step 3 — Style the accordion

The <details> element already opens and closes on click. A little CSS makes it look intentional and rotates a caret when open:

{% stylesheet %}
.faq { max-width: 48rem; margin: 0 auto; padding: 2rem 1.5rem; }
.faq__item { border-bottom: 1px solid #e5e5e5; }
.faq__question {
  cursor: pointer;
  list-style: none;
  padding: 1rem 0;
  font-weight: 600;
  display: flex;
  justify-content: space-between;
}
.faq__question::after { content: "+"; }
.faq__item[open] .faq__question::after { content: "–"; }
.faq__answer { padding: 0 0 1rem; }
{% endstylesheet %}

list-style: none on the summary hides the browser's default triangle so your +/ marker is the only indicator.

Step 4 — Add FAQ structured data (carefully)

FAQ structured data can make your questions eligible for rich results in search. Add FAQPage JSON-LD that mirrors the visible questions and answers — Google requires the marked-up content to match what shoppers actually see on the page. Loop the same blocks into the schema so the two never drift apart:

<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "FAQPage",
  "mainEntity": [
    {%- for block in section.blocks -%}
      {
        "@type": "Question",
        "name": {{ block.settings.question | json }},
        "acceptedAnswer": {
          "@type": "Answer",
          "text": {{ block.settings.answer | strip_html | json }}
        }
      }{%- unless forloop.last -%},{%- endunless -%}
    {%- endfor -%}
  ]
}
</script>

The | json filter safely escapes quotes and newlines for you — building JSON by hand is where most invalid-schema errors come from. Note that Google now limits FAQ rich results to authoritative government and health sites for most queries, so treat the schema as correct-but-optional rather than a guaranteed snippet. For general storefront layout choices, see the features overview.

Keep the FAQ section update-safe

Built as faq.liquid, this section lives in your theme files. On a theme update or theme switch, Shopify publishes a fresh copy of those files and your faq.liquid is not carried over — and because block content is stored per template against a section that no longer exists, the questions a merchant typed can be lost too. The full explanation is in Update your theme without losing customizations.

If your FAQ is a permanent fixture, rendering it from an app via a Theme App Extension means a theme update or swap never removes it. SectionGuard takes this approach — the FAQ renders from the app with the same block-based editing merchants expect, so it survives every update.