Creating custom Shopify sections has become essential for modern e-commerce businesses that want to offer unique, branded shopping experiences. Whether you're building a theme for clients or customizing your own store, understanding Shopify's section architecture is crucial. This comprehensive guide walks you through everything you need to know about designing, building, and deploying custom sections that customers love.
Understanding Shopify Sections Architecture
Shopify sections represent a paradigm shift in theme development. Rather than forcing merchants to understand code, sections provide a visual, intuitive interface for customizing pages. Each section consists of three core components: the Liquid template, the JSON schema, and optional CSS/JavaScript.
Why Sections Matter
The rise of Shopify sections transformed how merchants interact with their themes. Before sections, customizing a store required developer expertise. Now, any merchant can:
- Add or remove content blocks without touching code
- Configure colors, fonts, and spacing through visual editors
- Reorder content elements on the fly
- A/B test different layouts and designs
This democratization of customization has made themes more valuable and merchants happier. For developers, it means creating components that are both powerful and intuitive.
The Three-Layer Architecture
Every Shopify section consists of three integrated layers:
- The Liquid Template - The HTML and dynamic content rendering layer
- The JSON Schema - The configuration and customization interface
- CSS & JavaScript - Styling and interactivity (optional but recommended)
These layers work together to create sections that are flexible, user-friendly, and visually stunning. Understanding each layer's role is essential before writing any code.
Creating Section Schema Files
The JSON schema is the heart of a Shopify section. It defines what options merchants can configure, how they appear in the Theme Customizer, and what default values to use.
Schema Structure and Organization
A well-organized schema makes sections intuitive. Here's the fundamental structure:
{
"name": "Custom Section Name",
"settings": [
{
"type": "text",
"id": "heading_text",
"label": "Section Heading",
"default": "Welcome to Our Store"
}
],
"blocks": [
{
"type": "feature_block",
"name": "Feature",
"settings": []
}
],
"presets": [
{
"name": "Custom Section",
"blocks": [
{ "type": "feature_block" }
]
}
]
}
Setting Types Explained
Shopify provides numerous setting types, each with specific use cases:
Text and Selection:
text- Simple text inputtextarea- Multi-line textselect- Dropdown with predefined optionsrichtext- Rich text editor with formatting
Visual Selections:
image_picker- Choose from media librarycolor- Color pickerfont_picker- Shopify's available fonts
Product and Collection Selections:
product- Single product pickercollection- Single collection pickerproduct_list- Multiple product selector
Media and URLs:
url- URL input fieldvideo_url- Video URL inputfile_picker- File selection
Structural:
header- Section divider headerparagraph- Display text (read-only)checkbox- Boolean toggle
Building a Practical Schema Example
Let's create a schema for a "Featured Products Grid" section that demonstrates real-world complexity:
{
"name": "Featured Products Grid",
"settings": [
{
"type": "text",
"id": "section_heading",
"label": "Section Heading",
"default": "Featured Products"
},
{
"type": "select",
"id": "columns",
"label": "Number of Columns",
"options": [
{ "value": "2", "label": "2 Columns" },
{ "value": "3", "label": "3 Columns" },
{ "value": "4", "label": "4 Columns" }
],
"default": "3"
},
{
"type": "color",
"id": "bg_color",
"label": "Background Color",
"default": "#ffffff"
},
{
"type": "range",
"id": "padding_top",
"label": "Top Padding (px)",
"min": 0,
"max": 100,
"step": 10,
"unit": "px",
"default": 40
},
{
"type": "checkbox",
"id": "show_price",
"label": "Show Product Price",
"default": true
}
],
"blocks": [
{
"type": "product_item",
"name": "Product",
"settings": [
{
"type": "product",
"id": "featured_product",
"label": "Select Product"
},
{
"type": "text",
"id": "custom_label",
"label": "Custom Label (optional)",
"default": ""
}
]
}
],
"presets": [
{
"name": "Featured Products Grid",
"blocks": [
{ "type": "product_item" },
{ "type": "product_item" },
{ "type": "product_item" }
]
}
]
}
This schema demonstrates key principles: hierarchical organization, sensible defaults, and merchant-friendly naming. Notice how we use settings for global section configuration and blocks for repeatable content.
Section Settings and Blocks in Practice
Understanding how to implement settings and blocks in your Liquid template is where theory becomes reality.
Accessing Settings in Your Liquid Template
Once you've defined settings in your JSON schema, accessing them in Liquid is straightforward:
<div class="featured-products-section" style="background-color: {{ section.settings.bg_color }}; padding-top: {{ section.settings.padding_top }}px;">
<h2 class="section-heading">{{ section.settings.section_heading }}</h2>
<div class="products-grid products-grid--{{ section.settings.columns }}-columns">
{% for block in section.blocks %}
{% if block.type == 'product_item' %}
<div class="product-card">
{% assign product = block.settings.featured_product %}
{% if product %}
<div class="product-image">
{% if product.featured_image %}
<img src="{{ product.featured_image | image_url: width: 300 }}" alt="{{ product.title }}">
{% endif %}
</div>
<div class="product-info">
<h3 class="product-title">{{ product.title }}</h3>
{% if block.settings.custom_label != blank %}
<span class="custom-label">{{ block.settings.custom_label }}</span>
{% endif %}
{% if section.settings.show_price %}
<p class="product-price">{{ product.price | money }}</p>
{% endif %}
<a href="{{ product.url }}" class="button">View Product</a>
</div>
{% endif %}
</div>
{% endif %}
{% endfor %}
</div>
</div>
Responsive Design with Section Settings
Modern sections must work across all devices. Use section settings to allow merchants to control responsive behavior:
{
"type": "select",
"id": "mobile_columns",
"label": "Mobile Columns",
"options": [
{ "value": "1", "label": "1 Column" },
{ "value": "2", "label": "2 Columns" }
],
"default": "1"
}
Then in CSS, use these settings for responsive layouts:
<style>
@media (max-width: 768px) {
.products-grid--{{ section.settings.mobile_columns }}-columns {
grid-template-columns: repeat({{ section.settings.mobile_columns }}, 1fr);
}
}
</style>
Block-Level Customization Best Practices
When building blocks, think about what merchants might customize for each instance:
"blocks": [
{
"type": "testimonial",
"name": "Testimonial",
"settings": [
{
"type": "image_picker",
"id": "customer_image",
"label": "Customer Photo"
},
{
"type": "text",
"id": "customer_name",
"label": "Customer Name",
"default": "Jane Doe"
},
{
"type": "textarea",
"id": "testimonial_text",
"label": "Testimonial Text",
"default": "This product exceeded my expectations."
},
{
"type": "range",
"id": "rating",
"label": "Star Rating",
"min": 1,
"max": 5,
"default": 5
}
]
}
]
Dynamic Content Integration
The true power of Shopify sections emerges when you integrate dynamic content from your product catalog, collections, and custom data.
Pulling Product Data into Sections
Product-related sections require careful handling of product objects and their nested properties. Here's a robust example:
{% assign product = block.settings.product %}
{% if product %}
<!-- Product Meta Fields -->
{% if product.metafields.custom.featured_badge %}
<span class="badge">{{ product.metafields.custom.featured_badge }}</span>
{% endif %}
<!-- Variant Information -->
{% if product.variants.size > 0 %}
<select class="variant-selector">
{% for variant in product.variants %}
<option value="{{ variant.id }}" {% if variant.available == false %}disabled{% endif %}>
{{ variant.title }} - {{ variant.price | money }}
{% if variant.available == false %}(Out of Stock){% endif %}
</option>
{% endfor %}
</select>
{% endif %}
<!-- Availability Status -->
<p class="availability">
{% if product.available %}
<span class="in-stock">In Stock</span>
{% else %}
<span class="out-of-stock">Out of Stock</span>
{% endif %}
</p>
{% endif %}
Collection-Based Dynamic Sections
Some sections pull multiple products from a collection. Use collection pickers in your schema:
{
"type": "collection",
"id": "featured_collection",
"label": "Select Collection"
}
Then iterate through products in your Liquid template:
{% assign collection = section.settings.featured_collection %}
{% if collection %}
<h2>{{ collection.title }}</h2>
<div class="products-grid">
{% for product in collection.products limit: 8 %}
<div class="product-card">
<a href="{{ product.url }}">
<img src="{{ product.featured_image | image_url: width: 300 }}" alt="{{ product.title }}">
<h3>{{ product.title }}</h3>
<p class="price">{{ product.price | money }}</p>
</a>
</div>
{% endfor %}
</div>
<a href="{{ collection.url }}" class="view-all-link">View All Products</a>
{% endif %}
Using Metafields for Custom Data
Metafields allow you to attach custom data to products, collections, and other resources. Access them in sections like this:
<!-- Product metafield examples -->
{% assign sustainability_info = product.metafields.sustainability.certification %}
{% assign custom_description = product.metafields.custom.short_description %}
{% assign designer_name = product.metafields.brand.designer %}
<div class="product-details">
{% if sustainability_info %}
<p>Certification: {{ sustainability_info }}</p>
{% endif %}
{% if custom_description %}
<p>{{ custom_description }}</p>
{% endif %}
{% if designer_name %}
<span>By {{ designer_name }}</span>
{% endif %}
</div>
Best Practices for Reusable Sections
Creating sections that work across different stores and use cases requires discipline and foresight. Here are proven strategies that separate professional sections from amateur ones.
Section Documentation and Naming
Always include descriptive comments in your schema explaining non-obvious settings:
{
"type": "select",
"id": "image_position",
"label": "Image Position",
"info": "Determines if the image appears on the left or right side. Text will flow on the opposite side.",
"options": [
{ "value": "left", "label": "Left" },
{ "value": "right", "label": "Right" }
],
"default": "left"
}
The info field provides helpful context to merchants configuring your section, reducing support burden.
Building Flexible, Context-Agnostic Sections
The best sections work on any page type without custom configuration. Achieve this by:
- Avoiding page-specific assumptions - Don't assume you're on a product page or homepage
- Providing manual input options - Include text/image pickers alongside auto-filled options
- Using sensible defaults - Section should look good immediately after adding it
- Making everything optional - Merchants should be able to use your section partially
Example of a flexible section:
{
"name": "Hero with Optional CTA",
"settings": [
{
"type": "textarea",
"id": "hero_text",
"label": "Hero Text",
"default": "Welcome to our store"
},
{
"type": "image_picker",
"id": "background_image",
"label": "Background Image"
},
{
"type": "text",
"id": "button_text",
"label": "Button Text (leave blank to hide)",
"default": "Shop Now"
},
{
"type": "url",
"id": "button_link",
"label": "Button Link",
"default": "/collections/all"
}
]
}
CSS and JavaScript Best Practices
Encapsulate your section's styles to prevent conflicts with other sections:
{% stylesheet %}
#shopify-section-{{ section.id }} .featured-section {
background-color: {{ section.settings.bg_color }};
}
#shopify-section-{{ section.id }} .featured-section__heading {
font-size: {{ section.settings.heading_size }}px;
color: {{ section.settings.heading_color }};
}
{% endstylesheet %}
Use section.id to namespace your styles, preventing CSS conflicts when the same section appears multiple times on a page.
For JavaScript, similarly namespace your selectors:
{% javascript %}
document.addEventListener('shopify:section:load', function(e) {
const sectionId = e.detail.sectionId;
const section = document.getElementById('shopify-section-' + sectionId);
// Your JavaScript logic here
const buttons = section.querySelectorAll('.cta-button');
buttons.forEach(button => {
button.addEventListener('click', () => {
// Handle click
});
});
});
{% endjavascript %}
Performance-First Development
When building sections that will be used thousands of times across different stores, performance matters tremendously.
Liquid Performance Tips:
- Minimize loops and nested loops
- Avoid expensive filters like
wherein loops - Use
limitto restrict iterations - Cache computed values in variables
- Use
breakto exit loops early when possible
Image Optimization:
<!-- Optimize images by using appropriate widths and formats -->
<img
src="{{ image | image_url: width: 400 }}"
srcset="{{ image | image_url: width: 400 }} 400w,
{{ image | image_url: width: 800 }} 800w"
sizes="(max-width: 768px) 100vw, 50vw"
alt="{{ alt_text }}"
loading="lazy"
>
Asset Bundling:
<!-- Load CSS only when section is present -->
{{ 'featured-section.css' | asset_url | stylesheet_tag }}
<!-- Load JS only when needed -->
{% if section.settings.enable_slider %}
{{ 'featured-section-slider.js' | asset_url | script_tag }}
{% endif %}
Advanced Integration: E-commerce Optimization
When you're building sections for Shopify stores, integrating directly with the e-commerce platform creates powerful opportunities.
Leveraging Shopify Analytics
Sections can be enhanced with analytics data to show trending products or top-performing collections:
{% for product in collection.products %}
{% if product.variants.first.inventory_quantity > 0 %}
<!-- Show stock levels based on real-time data -->
<span class="stock-indicator">
Only {{ product.variants.first.inventory_quantity }} left
</span>
{% endif %}
{% endfor %}
Subscription and Payment Integration
Modern sections can integrate subscription products and alternative payment methods:
{
"type": "checkbox",
"id": "show_subscription_option",
"label": "Show Subscription Option",
"default": false
}
Then use the Shopify API to detect and display subscription-eligible products within your section.
Cart Integration Best Practices
Sections that add items to the cart should follow accessibility and UX standards:
<form method="post" action="/cart/add" class="product-form">
<input type="hidden" name="id" value="{{ product.variants.first.id }}">
<select name="quantity" aria-label="Quantity">
{% for i in (1..10) %}
<option value="{{ i }}">{{ i }}</option>
{% endfor %}
</select>
<button type="submit" class="button button--primary" aria-label="Add {{ product.title }} to cart">
Add to Cart
</button>
</form>
Implementation Strategy and Deployment
Taking custom sections from development to production requires careful planning and testing.
Testing Custom Sections
Before deploying, test comprehensively:
- Visual Testing - Check on desktop, tablet, and mobile
- Schema Testing - Verify all settings work as expected
- Performance Testing - Use Lighthouse to check load times
- Edge Case Testing - Test with long product names, missing images, empty states
- Browser Testing - Ensure compatibility across modern browsers
Submitting to Shopify Theme Store
If you've built a reusable section, consider submitting it to the Shopify Theme Store. This requires:
- Clean, well-commented code
- Comprehensive documentation
- No hardcoded content
- Accessibility compliance
- Performance optimization
Maintaining Sections Post-Launch
Keep sections updated by:
- Monitoring theme updates and Shopify deprecations
- Gathering merchant feedback
- Optimizing based on performance metrics
- Adding new features based on merchant requests
- Maintaining backward compatibility when possible
Troubleshooting Common Section Issues
Section Not Appearing in Theme Customizer
Ensure your section file is properly named (section-name.liquid) and located in the sections directory. Verify the JSON schema is valid by checking for syntax errors.
Settings Not Saving
Clear your browser cache and try again. If issues persist, check for console errors and validate your schema against Shopify's schema specification.
Performance Degradation with Multiple Sections
Use browser DevTools to profile. Often, the issue is unnecessary Liquid rendering or unoptimized images. Implement caching where possible and consider moving logic to JavaScript.
Mobile Responsiveness Issues
Test with actual mobile devices, not just browser emulation. Use flexbox and CSS grid for responsive layouts, and test your section at various viewport widths.
Next Steps: Mastering Shopify Development
Custom sections represent just one aspect of Shopify development. To build a complete understanding, explore:
- Advanced Shopify API integration for custom storefronts
- Theme app extensions for expanded functionality
- Liquid templating best practices
- Performance optimization techniques
Building high-quality custom sections positions you as a skilled Shopify developer and creates immense value for merchants. Start with simple sections, master the fundamentals, and progressively build more complex, feature-rich components.
Key Takeaways
- Shopify sections democratize theme customization through intuitive visual editors
- Well-structured JSON schemas are the foundation of usable sections
- Settings control global section behavior; blocks provide repeatable elements
- Dynamic content integration transforms static sections into powerful tools
- Reusable sections require careful planning, documentation, and testing
- Performance and accessibility should be built in from the start
Want to optimize your entire Shopify store for maximum conversions? Get your free e-commerce audit to identify performance improvements and conversion opportunities, or schedule a consultation with our e-commerce specialists to discuss a custom development strategy.