Skip to main content

How to Create a Custom Section in Shopify (Online Store 2.0 Guide)

| Admin | ,

Learning how to create a custom section in Shopify gives you full control over your theme layout, content structure, and editor experience without relying on apps or repeated code edits.

In this guide, you’ll learn:

This guide walks you through the process step by step, from structure and implementation to real examples and troubleshooting.


Understanding Shopify Custom Sections

What a Shopify custom section is

A Shopify custom section is a reusable layout component built directly into your theme. It defines a larger area of a page, such as a product information area, a testimonial block, or a custom content layout.

Custom sections are created as .liquid files and saved in the theme’s /sections directory. Once created, they appear in the Theme Editor, where merchants can add them to pages and adjust their settings without editing code again.

Sections are different from blocks.

  • Sections are the main containers that structure a page
  • Blocks are smaller elements that live inside sections, such as text, images, or buttons

A section may contain blocks, but it does not have to. Some sections have fixed content and no blocks at all.

Important note: This article focuses on custom sections created in theme code

It does not cover Shopify’s Custom Liquid section, which is a pre-built editor feature used for quick, one-off Liquid or HTML inserts and is not intended for building reusable section components.

When you need a Shopify custom section

You should create a Shopify custom section when existing theme sections are not flexible enough for your needs. Common cases for using a custom section in Shopify include:

  • You need a custom layout that your theme does not support
  • You want reusable content that can be added to multiple pages
  • You need to connect dynamic data, such as metafields or product attributes
  • You want more control without relying on third-party apps

If your goal is only to insert a small piece of custom HTML or Liquid once, a Custom Liquid section may be enough. But if you want a scalable, editable, and reusable solution, a custom section is the better approach.


Basic Structure of a Shopify Section File

Core components of a Shopify section file

Every Shopify section file is built from four main components. Two are required, and two are optional.

ComponentPurposeRequired
Liquid markupRenders the HTML output and dynamic contentYes
{% schema %} blockDefines settings, blocks, and presets for the Theme EditorYes
<style>Section-specific CSS stylingNo
<script>Section-specific JavaScript logicNo

Below is how each component works.

1. Liquid markup (required)

Liquid markup defines the visual structure of the section. It is written using standard HTML combined with Liquid syntax.

This part of the file controls:

  • Where content appears on the page
  • How settings are displayed using section.settings
  • How repeatable content is rendered using loops and conditions

Liquid markup is where you output dynamic values, such as headings, images, or block content.

2. Schema block (required)

The {% schema %} block contains JSON configuration for the section. It tells Shopify how the section should behave inside the Theme Editor.

The schema defines:

  • The section name shown in the editor
  • Which settings are editable
  • Whether the section supports blocks
  • Presets that allow the section to be added from the editor

If the schema block is missing or invalid, the section will not appear in the Theme Editor.

3. Stylesheet (optional)

You can add section-specific CSS inside <style> tags. This is useful when styles should only apply to this section. But here are important limits:

  • Section-level custom CSS is limited to 500 characters
  • Theme-wide custom CSS is limited to 1,500 characters

For complex styling, it is better to move CSS into theme asset files.

4. JavaScript (optional)

JavaScript inside <script> tags can be used for interactive features, such as sliders or toggles. Scripts should be scoped carefully to avoid affecting other sections. You should avoid running heavy JavaScript on every page unless necessary.

Here is a basic section file structure example:

<!-- Liquid Markup -->

<div class="custom-section">

  <h2>{{ section.settings.heading }}</h2>

  {% for block in section.blocks %}

    <div {{ block.shopify_attributes }}>

      {{ block.settings.content }}

    </div>

  {% endfor %}

</div>

<!-- Stylesheet -->

<style>

  .custom-section {

    padding: 40px;

  }

</style>

<!-- JavaScript -->

<script>

  // Section-specific interaction

</script>

<!-- Schema -->

{% schema %}

{

  "name": "Custom Section",

  "settings": [

    {

      "type": "text",

      "id": "heading",

      "label": "Heading",

      "default": "Section title"

    }

  ],

  "blocks": [],

  "presets": [

    {

      "name": "Custom Section"

    }

  ]

}

{% endschema %}

How schema settings control the Theme Customizer

The schema block is the connection between your code and the Theme Editor. It defines what merchants can customize without editing code.

The schema controls four key areas.

Section identity: “name” determines the section title shown in the Theme Editor sidebar

Customization options

  • “settings” define section-level controls such as text, images, colors, and toggles
  • Each setting includes properties like type, id, label, and default

Content blocks

  • “blocks” define repeatable content inside the section
  • Blocks can be added, removed, and reordered in the editor
  • Each block type has its own settings

Default configuration

  • “presets” determine how the section appears in the Add section list
  • Presets also define what content loads when the section is first added

When a setting is defined in the schema, Shopify automatically generates the correct input in the Theme Editor. For example:

  • “type”: “text” creates a text input field
  • “type”: “color” creates a color picker

Theme settings apply across the store, while section and block settings apply only to the selected section.

Shopify section and block limits you should know

Shopify enforces limits to protect performance and editor stability.

Section limitsBlock limitsCSS limits
• Maximum 25 sections per template
• Sections can be added to most pages, except checkout and gift card pages
• Every section must include a valid schema block
• Maximum 1,250 blocks per template across all sections
• Up to 8 levels of nested blocks, for sections that support nesting
• The allowed block types and quantities are defined in the section schema
• 500 characters for section-specific CSS
• 1,500 characters for theme-wide custom CSS

These limits help keep Shopify themes fast, stable, and easy to manage while still allowing flexible customization.


How to Create a Custom Section in Shopify (Code Required)

Step 1: Open the theme code editor

Before you start, always work on a duplicate or unpublished theme to avoid breaking your live store.

On desktopOn mobile
• Go to Online Store → Themes
• Find the theme you want to edit
• Click the three-dot menu (•••)
• Select Edit code
• Open the Shopify app
• Tap Menu (☰)Online Store
• Tap Manage all themes
• Find your theme and tap •••
• Select Edit code

Once opened, you’ll see the theme’s file structure in the left sidebar. You’ll be working mainly inside the Sections folder.

Step 2: Create a new section file in the /sections directory

  1. In the code editor sidebar, open the Sections folder
  2. Click Add a new section (or the + icon)
  3. Enter a section name
  4. Click Create section

We recommend these naming best practices:

  • Use lowercase letters only
  • Separate words with hyphens (kebab-case)
  • Keep names descriptive but short

For example: custom-faq.liquid, testimonial-grid.liquid, hero-banner.liquid. Shopify will automatically create a new .liquid file then save it in sections/your-section-name.liquid and open the file for editing.

Step 3: Add basic Liquid markup for structure

Let’s start with a simple HTML structure. This defines how your section appears on the page.

<div class="custom-section-wrapper">

  <div class="container">

    <h2>{{ section.settings.heading }}</h2>

    <div class="content">

      {{ section.settings.text }}

    </div>

  </div>

</div>

Next, add the schema block at the bottom of the file. This makes the section editable in the Theme Editor.

{% schema %}

{

  "name": "Custom Section",

  "settings": [

    {

      "type": "text",

      "id": "heading",

      "label": "Section Heading",

      "default": "Welcome to Our Store"

    },

    {

      "type": "richtext",

      "id": "text",

      "label": "Section Text",

      "default": "<p>Add your content here.</p>"

    }

  ],

  "presets": [

    {

      "name": "Custom Section"

    }

  ]

}

{% endschema %}

When you reference {{ section.settings.heading }}, Shopify outputs the value of the setting whose id is “heading”. This follows a consistent pattern: section.settings.[setting_id], where the setting ID must exactly match what you defined in the schema.

The preset plays a different role. It does not control content, but visibility. A section must include a preset to appear in the Add section list in the Theme Editor. Without a preset, the section still exists in code, but merchants cannot add it through the editor.


How to Create Custom Section in Shopify​:​ Simple Custom Section Examples

The following examples show real-world custom sections that you can add to a Shopify theme and use immediately. They demonstrate how sections, schema settings, and blocks work together in practical scenarios.

To use either example on how to create a custom section in Shopify:

  1. Copy the complete code for the section
  2. Go to Online Store → Themes → Click the three-dot menu (•••) next to your theme → Edit code
  3. Add a new section in the Sections folder
  4. Paste the code and save
  5. Open the Theme Editor and add the section to a page

Both examples are production-ready and follow Shopify best practices for performance, editor usability, and maintainability.

Example 1: FAQ section using schema and blocks

This FAQ section is designed for content that changes often. You can add, remove, and reorder questions directly in the Theme Editor without editing code.

Each question-and-answer pair is created as a block, which makes the section flexible and scalable. The accordion behavior keeps the layout clean while still allowing unlimited FAQ items.

Below is the complete code for custom-faq.liquid.

<div class="faq-section" style="background-color: {{ section.settings.background_color }};">

  <div class="faq-container">

    {% if section.settings.heading != blank %}

      <h2 class="faq-heading">{{ section.settings.heading }}</h2>

    {% endif %}

    {% if section.settings.subheading != blank %}

      <div class="faq-subheading">{{ section.settings.subheading }}</div>

    {% endif %}

    <div class="faq-list">

      {% for block in section.blocks %}

        <div class="faq-item" {{ block.shopify_attributes }}>

          <button class="faq-question" type="button">

            {{ block.settings.question }}

            <span class="faq-icon">+</span>

          </button>

          <div class="faq-answer">

            {{ block.settings.answer }}

          </div>

        </div>

      {% endfor %}

    </div>

  </div>

</div>

<style>

  .faq-section {

    padding: 60px 20px;

  }

  .faq-container {

    max-width: 800px;

    margin: 0 auto;

  }

  .faq-heading {

    text-align: center;

    font-size: 2em;

    margin-bottom: 10px;

  }

  .faq-subheading {

    text-align: center;

    color: #666;

    margin-bottom: 40px;

  }

  .faq-item {

    margin-bottom: 15px;

    border: 1px solid #e0e0e0;

    border-radius: 8px;

    overflow: hidden;

  }

  .faq-question {

    width: 100%;

    padding: 20px;

    background: #fff;

    border: none;

    text-align: left;

    font-size: 1.1em;

    font-weight: 600;

    cursor: pointer;

    display: flex;

    justify-content: space-between;

    align-items: center;

  }

  .faq-question:hover {

    background: #f9f9f9;

  }

  .faq-icon {

    font-size: 1.5em;

    transition: transform 0.3s;

  }

  .faq-answer {

    padding: 0 20px;

    max-height: 0;

    overflow: hidden;

    transition: max-height 0.3s ease, padding 0.3s ease;

  }

  .faq-item.active .faq-answer {

    padding: 20px;

    max-height: 500px;

  }

  .faq-item.active .faq-icon {

    transform: rotate(45deg);

  }

</style>

<script>

  document.addEventListener('DOMContentLoaded', function() {

    const faqQuestions = document.querySelectorAll('.faq-question');

    faqQuestions.forEach(function(question) {

      question.addEventListener('click', function() {

        const faqItem = this.parentElement;

        const isActive = faqItem.classList.contains('active');

        // Close all FAQ items

        document.querySelectorAll('.faq-item').forEach(function(item) {

          item.classList.remove('active');

        });

        // Open clicked item if it wasn't active

        if (!isActive) {

          faqItem.classList.add('active');

        }

      });

    });

  });

</script>

{% schema %}

{

  "name": "FAQ Section",

  "settings": [

    {

      "type": "text",

      "id": "heading",

      "label": "Section Heading",

      "default": "Frequently Asked Questions"

    },

    {

      "type": "textarea",

      "id": "subheading",

      "label": "Subheading",

      "default": "Find answers to common questions below"

    },

    {

      "type": "color",

      "id": "background_color",

      "label": "Background Color",

      "default": "#ffffff"

    }

  ],

  "blocks": [

    {

      "type": "faq_item",

      "name": "FAQ Item",

      "settings": [

        {

          "type": "text",

          "id": "question",

          "label": "Question",

          "default": "What is your question?"

        },

        {

          "type": "richtext",

          "id": "answer",

          "label": "Answer",

          "default": "<p>Your answer goes here.</p>"

        }

      ]

    }

  ],

  "presets": [

    {

      "name": "FAQ",

      "blocks": [

        {

          "type": "faq_item",

          "settings": {

            "question": "What is your return policy?",

            "answer": "<p>We accept returns within 30 days of purchase with original receipt.</p>"

          }

        },

        {

          "type": "faq_item",

          "settings": {

            "question": "Do you ship internationally?",

            "answer": "<p>Yes, we ship to over 50 countries worldwide.</p>"

          }

        },

        {

          "type": "faq_item",

          "settings": {

            "question": "How long does shipping take?",

            "answer": "<p>Standard shipping takes 5-7 business days. Express shipping is 2-3 business days.</p>"

          }

        }

      ]

    }

  ]

}

{% endschema %}

This section works well for help pages, product pages, and landing pages where information needs to stay editable and structured.

Example 2: Testimonial section with images

This testimonial section is designed for social proof. It displays customer reviews in a responsive grid layout and supports optional photos, star ratings, and author details.

Each testimonial is a block, which allows merchants to manage reviews visually in the Theme Editor. Below is the complete code for custom-testimonials.liquid.

<div class="testimonials-section" style="background-color: {{ section.settings.bg_color }};">

  <div class="testimonials-container">

    {% if section.settings.title != blank %}

      <h2 class="testimonials-title">{{ section.settings.title }}</h2>

    {% endif %}

    {% if section.settings.subtitle != blank %}

      <p class="testimonials-subtitle">{{ section.settings.subtitle }}</p>

    {% endif %}

    <div class="testimonials-grid" style="grid-template-columns: repeat({{ section.settings.columns }}, 1fr);">

      {% for block in section.blocks %}

        <div class="testimonial-card" {{ block.shopify_attributes }}>

          {% if block.settings.image != blank %}

            <div class="testimonial-image">

              <img 

                src="{{ block.settings.image | img_url: '150x150', crop: 'center' }}" 

                alt="{{ block.settings.author }}"

                loading="lazy"

              >

            </div>

          {% endif %}

          <div class="testimonial-content">

            {% if block.settings.rating > 0 %}

              <div class="testimonial-rating">

                {% for i in (1..5) %}

                  {% if i <= block.settings.rating %}

                    <span class="star filled">★</span>

                  {% else %}

                    <span class="star">☆</span>

                  {% endif %}

                {% endfor %}

              </div>

            {% endif %}

            <blockquote class="testimonial-quote">

              "{{ block.settings.quote }}"

            </blockquote>

            <div class="testimonial-author">

              <strong>{{ block.settings.author }}</strong>

              {% if block.settings.position != blank %}

                <span class="author-position">{{ block.settings.position }}</span>

              {% endif %}

            </div>

          </div>

        </div>

      {% endfor %}

    </div>

  </div>

</div>

<style>

  .testimonials-section {

    padding: 80px 20px;

  }

  .testimonials-container {

    max-width: 1200px;

    margin: 0 auto;

  }

  .testimonials-title {

    text-align: center;

    font-size: 2.5em;

    margin-bottom: 10px;

    color: #333;

  }

  .testimonials-subtitle {

    text-align: center;

    font-size: 1.2em;

    color: #666;

    margin-bottom: 50px;

  }

  .testimonials-grid {

    display: grid;

    gap: 30px;

    margin-top: 40px;

  }

  @media (max-width: 768px) {

    .testimonials-grid {

      grid-template-columns: 1fr !important;

    }

  }

  .testimonial-card {

    background: #fff;

    padding: 30px;

    border-radius: 12px;

    box-shadow: 0 4px 6px rgba(0,0,0,0.1);

    text-align: center;

    transition: transform 0.3s, box-shadow 0.3s;

  }

  .testimonial-card:hover {

    transform: translateY(-5px);

    box-shadow: 0 8px 12px rgba(0,0,0,0.15);

  }

  .testimonial-image {

    margin-bottom: 20px;

  }

  .testimonial-image img {

    width: 80px;

    height: 80px;

    border-radius: 50%;

    object-fit: cover;

    border: 3px solid #f0f0f0;

  }

  .testimonial-rating {

    margin-bottom: 15px;

    font-size: 1.2em;

  }

  .star {

    color: #ddd;

  }

  .star.filled {

    color: #ffc107;

  }

  .testimonial-quote {

    font-size: 1.1em;

    line-height: 1.6;

    color: #555;

    margin: 20px 0;

    font-style: italic;

  }

  .testimonial-author {

    margin-top: 20px;

  }

  .testimonial-author strong {

    display: block;

    font-size: 1.1em;

    color: #333;

    margin-bottom: 5px;

  }

  .author-position {

    display: block;

    font-size: 0.9em;

    color: #888;

  }

</style>

{% schema %}

{

  "name": "Testimonials",

  "settings": [

    {

      "type": "text",

      "id": "title",

      "label": "Section Title",

      "default": "What Our Customers Say"

    },

    {

      "type": "text",

      "id": "subtitle",

      "label": "Subtitle",

      "default": "Don't just take our word for it"

    },

    {

      "type": "color",

      "id": "bg_color",

      "label": "Background Color",

      "default": "#f9f9f9"

    },

    {

      "type": "range",

      "id": "columns",

      "label": "Columns (Desktop)",

      "min": 1,

      "max": 4,

      "step": 1,

      "default": 3

    }

  ],

  "blocks": [

    {

      "type": "testimonial",

      "name": "Testimonial",

      "settings": [

        {

          "type": "image_picker",

          "id": "image",

          "label": "Customer Photo"

        },

        {

          "type": "textarea",

          "id": "quote",

          "label": "Testimonial Quote",

          "default": "This product exceeded my expectations!"

        },

        {

          "type": "text",

          "id": "author",

          "label": "Customer Name",

          "default": "Jane Doe"

        },

        {

          "type": "text",

          "id": "position",

          "label": "Position/Title (Optional)",

          "default": "Verified Buyer"

        },

        {

          "type": "range",

          "id": "rating",

          "label": "Star Rating",

          "min": 0,

          "max": 5,

          "step": 1,

          "default": 5

        }

      ]

    }

  ],

  "presets": [

    {

      "name": "Testimonials",

      "blocks": [

        {

          "type": "testimonial",

          "settings": {

            "quote": "Amazing quality and fast shipping! Highly recommend.",

            "author": "Sarah Johnson",

            "position": "Verified Buyer",

            "rating": 5

          }

        },

        {

          "type": "testimonial",

          "settings": {

            "quote": "Best purchase I've made this year. Worth every penny!",

            "author": "Michael Chen",

            "position": "Repeat Customer",

            "rating": 5

          }

        },

        {

          "type": "testimonial",

          "settings": {

            "quote": "Excellent customer service and beautiful products.",

            "author": "Emily Rodriguez",

            "position": "Verified Buyer",

            "rating": 5

          }

        }

      ]

    }

  ]

}

{% endschema %}

This section adapts automatically:

  • The number of columns can be adjusted on desktop
  • The layout collapses to one column on mobile
  • Images are optimized and lazy-loaded
  • Ratings and author details are optional

It works well on homepages, product pages, and trust-focused landing pages.


Troubleshooting Common Custom Shopify Section Issues

1. Schema and preset errors

Most section issues start in the {% schema %} block. If the schema is invalid, Shopify cannot load the section in the Theme Editor.

The most common issue is invalid JSON syntax. This includes missing commas, extra commas at the end of arrays, incorrect quotation marks, or mismatched brackets. Even a small typo can prevent the section from appearing.

For example, this schema is invalid because it is missing a comma:

{

  "name": "My Section"

  "settings": []

}

The corrected version includes the required comma:

{

  "name": "My Section",

  "settings": []

}

Another frequent issue is a missing preset. A section without a preset exists in code, but it will not appear in the Add section list in the Theme Editor.

Every section must include a preset like this:

"presets": [

  { "name": "My Section" }

]

Schema problems can also occur when setting types do not match their intended use. For example, image fields must use “image_picker”, and rich text content should use “richtext”. Misspelled or unsupported setting types will cause settings to behave incorrectly or not save at all.

If you are unsure, validate your schema using an online JSON validator before testing in Shopify.

2. Section not appearing or not loading

If your schema is valid but the section still does not appear, check the file itself. The section file must:

  • Be saved in the /sections directory
  • Use a .liquid file extension
  • Follow Shopify’s naming rules

For example, my-section.liquid is valid, while My Section.liquid or my-section is not.

Another common cause is syntax errors in Liquid or HTML. Unclosed {% if %} statements, missing {% endif %}, or unclosed HTML tags can prevent the section from loading completely.

In some cases, the issue is not the section itself, but the theme. Older (vintage) themes or missing JSON templates can limit where sections can be used. Sections cannot be added to checkout or gift card pages, and some advanced features require Online Store 2.0 themes.

Finally, remember that Shopify limits each template to 25 sections. If this limit is reached, you must remove or consolidate sections before adding new ones.

3. Settings and blocks not rendering correctly

When a section loads but content does not appear as expected, the issue is usually a mismatch between schema settings and Liquid code.

Setting IDs must match exactly. If your schema defines an ID as “heading_text”, your Liquid code must reference:

{{ section.settings.heading_text }}

Blocks can also fail to render if they are not looped correctly. Every block-based section must include a loop over section.blocks, and each block container should include {{ block.shopify_attributes }} to enable drag-and-drop functionality in the editor.

Missing default values can also cause empty output, especially when a section is first added. Adding sensible defaults in the schema improves usability and prevents confusion.

If blocks do not behave as expected, double-check that block types in Liquid match the block types defined in the schema.

4. Liquid, CSS, and JavaScript errors

Liquid errors often come from unclosed tags or incorrect variable paths. For example, section settings must always be accessed through section.settings, not settings alone.

CSS issues usually appear when styles are not scoped properly. If styles are not wrapped in a unique parent class, they may affect other sections or break after theme updates. Keep all selectors scoped to your section’s main wrapper.

Shopify also enforces CSS limits. If styles stop applying, you may have exceeded the 500-character limit for section-level CSS. In that case, move styles to a theme stylesheet in the Assets folder.

JavaScript problems often occur when scripts run before the DOM is ready or conflict with existing theme scripts. To avoid this, always wrap your code in a DOMContentLoaded event and avoid using global variables. Do not assume jQuery is available unless you explicitly check for it.

5. Dynamic data, performance, and template compatibility

Dynamic sources and metafields only work in themes that support JSON templates. If dynamic sources are missing, confirm that your theme uses Online Store 2.0 architecture.

Metafield types must also match the setting type. For example, image metafields require an image_picker setting, and text metafields require a text or richtext setting.

Performance issues usually come from too many blocks, deeply nested loops, large images, or heavy JavaScript. You should use image size filters, lazy loading, and loop limits to keep sections fast.

Template compatibility issues can appear when a section assumes certain objects exist. For example, product is not available on all page types. Always check for object availability before using it in Liquid.

If a problem is difficult to diagnose:

  • Check the browser console for JavaScript errors
  • Validate your schema JSON
  • Test changes in a duplicated theme
  • Clear your browser cache after updates

If a section requires behavior beyond what your theme supports, consider editing the theme code further or working with a Shopify Partner.


FAQs About Shopify Custom Sections

Where do I save my custom section file in Shopify?

Custom section files must be saved in the /sections directory of your theme. Each section is a .liquid file, for example custom-faq.liquid. If the file is saved anywhere else, Shopify will not recognize it as a section.

How do I make my section appear in the Shopify theme editor?

Your section must include a valid {% schema %} block with a preset defined. The preset is what makes the section appear in the Add section list inside the Theme Editor. Without it, the section exists in code but cannot be added visually.

How do I reference settings in my Liquid code?

All section settings follow the same pattern: {{ section.settings.setting_id }}. The setting_id must exactly match the id defined in the schema. If the IDs do not match, the setting will not render on the page.

How do I make blocks draggable in the Shopify theme editor?

To enable drag-and-drop behavior for blocks, you must add: {{ block.shopify_attributes }} to the main container element of each block. Without this attribute, blocks will render on the page but cannot be reordered in the Theme Editor.


Final Words

Custom Shopify sections are not just a development technique. They are a way to keep your theme clean, flexible, and easy to manage over time.

By building sections with proper schema settings and presets, you make it possible for merchants to update content safely without touching code. This reduces theme bloat, lowers maintenance cost, and improves long-term performance.

If you are planning to scale your Shopify store or customize beyond what your theme allows, custom sections are a solid foundation to start with.

And now, let’s build what’s next!

At LitOS, we help brands grow smarter on Shopify with better technology, practical strategy, and hands-on support that delivers real results. From migration to long-term growth, we make the process seamless and scalable.

Contact Us

Let’s create something great

We have a reasonable rating system that fits every budget. If you’re just starting out, we can help you create your digital brand, work out a strategy for you and help you grow. And if you have a strong brand – we can help you grow it to be even stronger. Contact us. We would love to meet you.