Files
2025-11-30 08:48:52 +08:00

10 KiB

Themes Reference

Guide for developing Shopify themes with Liquid templating.

Liquid Templating

Syntax Basics

Objects (Output):

{{ product.title }}
{{ product.price | money }}
{{ customer.email }}

Tags (Logic):

{% if product.available %}
  <button>Add to Cart</button>
{% else %}
  <p>Sold Out</p>
{% endif %}

{% for product in collection.products %}
  {{ product.title }}
{% endfor %}

{% case product.type %}
  {% when 'Clothing' %}
    <span>Apparel</span>
  {% when 'Shoes' %}
    <span>Footwear</span>
  {% else %}
    <span>Other</span>
{% endcase %}

Filters (Transform):

{{ product.title | upcase }}
{{ product.price | money }}
{{ product.description | strip_html | truncate: 100 }}
{{ product.image | img_url: 'medium' }}
{{ 'now' | date: '%B %d, %Y' }}

Common Objects

Product:

{{ product.id }}
{{ product.title }}
{{ product.handle }}
{{ product.description }}
{{ product.price }}
{{ product.compare_at_price }}
{{ product.available }}
{{ product.type }}
{{ product.vendor }}
{{ product.tags }}
{{ product.images }}
{{ product.variants }}
{{ product.featured_image }}
{{ product.url }}

Collection:

{{ collection.title }}
{{ collection.handle }}
{{ collection.description }}
{{ collection.products }}
{{ collection.products_count }}
{{ collection.image }}
{{ collection.url }}

Cart:

{{ cart.item_count }}
{{ cart.total_price }}
{{ cart.items }}
{{ cart.note }}
{{ cart.attributes }}

Customer:

{{ customer.email }}
{{ customer.first_name }}
{{ customer.last_name }}
{{ customer.orders_count }}
{{ customer.total_spent }}
{{ customer.addresses }}
{{ customer.default_address }}

Shop:

{{ shop.name }}
{{ shop.email }}
{{ shop.domain }}
{{ shop.currency }}
{{ shop.money_format }}
{{ shop.enabled_payment_types }}

Common Filters

String:

  • upcase, downcase, capitalize
  • strip_html, strip_newlines
  • truncate: 100, truncatewords: 20
  • replace: 'old', 'new'

Number:

  • money - Format currency
  • round, ceil, floor
  • times, divided_by, plus, minus

Array:

  • join: ', '
  • first, last
  • size
  • map: 'property'
  • where: 'property', 'value'

URL:

  • img_url: 'size' - Image URL
  • url_for_type, url_for_vendor
  • link_to, link_to_type

Date:

  • date: '%B %d, %Y'

Theme Architecture

Directory Structure

theme/
├── assets/              # CSS, JS, images
├── config/              # Theme settings
│   ├── settings_schema.json
│   └── settings_data.json
├── layout/              # Base templates
│   └── theme.liquid
├── locales/             # Translations
│   └── en.default.json
├── sections/            # Reusable blocks
│   ├── header.liquid
│   ├── footer.liquid
│   └── product-grid.liquid
├── snippets/            # Small components
│   ├── product-card.liquid
│   └── icon.liquid
└── templates/           # Page templates
    ├── index.json
    ├── product.json
    ├── collection.json
    └── cart.liquid

Layout

Base template wrapping all pages (layout/theme.liquid):

<!DOCTYPE html>
<html lang="{{ request.locale.iso_code }}">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <title>{{ page_title }}</title>

  {{ content_for_header }}

  <link rel="stylesheet" href="{{ 'theme.css' | asset_url }}">
</head>
<body>
  {% section 'header' %}

  <main>
    {{ content_for_layout }}
  </main>

  {% section 'footer' %}

  <script src="{{ 'theme.js' | asset_url }}"></script>
</body>
</html>

Templates

Page-specific structures (templates/product.json):

{
  "sections": {
    "main": {
      "type": "product-template",
      "settings": {
        "show_vendor": true,
        "show_quantity_selector": true
      }
    },
    "recommendations": {
      "type": "product-recommendations"
    }
  },
  "order": ["main", "recommendations"]
}

Legacy format (templates/product.liquid):

<div class="product">
  <div class="product-images">
    <img src="{{ product.featured_image | img_url: 'large' }}" alt="{{ product.title }}">
  </div>

  <div class="product-details">
    <h1>{{ product.title }}</h1>
    <p class="price">{{ product.price | money }}</p>

    {% form 'product', product %}
      <select name="id">
        {% for variant in product.variants %}
          <option value="{{ variant.id }}">{{ variant.title }} - {{ variant.price | money }}</option>
        {% endfor %}
      </select>

      <button type="submit">Add to Cart</button>
    {% endform %}
  </div>
</div>

Sections

Reusable content blocks (sections/product-grid.liquid):

<div class="product-grid">
  {% for product in section.settings.collection.products %}
    <div class="product-card">
      <a href="{{ product.url }}">
        <img src="{{ product.featured_image | img_url: 'medium' }}" alt="{{ product.title }}">
        <h3>{{ product.title }}</h3>
        <p>{{ product.price | money }}</p>
      </a>
    </div>
  {% endfor %}
</div>

{% schema %}
{
  "name": "Product Grid",
  "settings": [
    {
      "type": "collection",
      "id": "collection",
      "label": "Collection"
    },
    {
      "type": "range",
      "id": "products_per_row",
      "min": 2,
      "max": 5,
      "step": 1,
      "default": 4,
      "label": "Products per row"
    }
  ],
  "presets": [
    {
      "name": "Product Grid"
    }
  ]
}
{% endschema %}

Snippets

Small reusable components (snippets/product-card.liquid):

<div class="product-card">
  <a href="{{ product.url }}">
    {% if product.featured_image %}
      <img src="{{ product.featured_image | img_url: 'medium' }}" alt="{{ product.title }}">
    {% endif %}
    <h3>{{ product.title }}</h3>
    <p class="price">{{ product.price | money }}</p>
    {% if product.compare_at_price > product.price %}
      <p class="sale-price">{{ product.compare_at_price | money }}</p>
    {% endif %}
  </a>
</div>

Include snippet:

{% render 'product-card', product: product %}

Development Workflow

Setup

# Initialize new theme
shopify theme init

# Choose Dawn (reference theme) or blank

Local Development

# Start local server
shopify theme dev

# Preview at http://localhost:9292
# Changes auto-sync to development theme

Pull Theme

# Pull live theme
shopify theme pull --live

# Pull specific theme
shopify theme pull --theme=123456789

# Pull only templates
shopify theme pull --only=templates

Push Theme

# Push to development theme
shopify theme push --development

# Create new unpublished theme
shopify theme push --unpublished

# Push specific files
shopify theme push --only=sections,snippets

Theme Check

Lint theme code:

shopify theme check
shopify theme check --auto-correct

Common Patterns

Product Form with Variants

{% form 'product', product %}
  {% unless product.has_only_default_variant %}
    {% for option in product.options_with_values %}
      <div class="product-option">
        <label>{{ option.name }}</label>
        <select name="options[{{ option.name }}]">
          {% for value in option.values %}
            <option value="{{ value }}">{{ value }}</option>
          {% endfor %}
        </select>
      </div>
    {% endfor %}
  {% endunless %}

  <input type="hidden" name="id" value="{{ product.selected_or_first_available_variant.id }}">
  <input type="number" name="quantity" value="1" min="1">

  <button type="submit" {% unless product.available %}disabled{% endunless %}>
    {% if product.available %}Add to Cart{% else %}Sold Out{% endif %}
  </button>
{% endform %}

Pagination

{% paginate collection.products by 12 %}
  {% for product in collection.products %}
    {% render 'product-card', product: product %}
  {% endfor %}

  {% if paginate.pages > 1 %}
    <div class="pagination">
      {% if paginate.previous %}
        <a href="{{ paginate.previous.url }}">Previous</a>
      {% endif %}

      {% for part in paginate.parts %}
        {% if part.is_link %}
          <a href="{{ part.url }}">{{ part.title }}</a>
        {% else %}
          <span class="current">{{ part.title }}</span>
        {% endif %}
      {% endfor %}

      {% if paginate.next %}
        <a href="{{ paginate.next.url }}">Next</a>
      {% endif %}
    </div>
  {% endif %}
{% endpaginate %}

Cart AJAX

// Add to cart
fetch('/cart/add.js', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    id: variantId,
    quantity: 1
  })
})
.then(res => res.json())
.then(item => console.log('Added:', item));

// Get cart
fetch('/cart.js')
  .then(res => res.json())
  .then(cart => console.log('Cart:', cart));

// Update cart
fetch('/cart/change.js', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    id: lineItemKey,
    quantity: 2
  })
})
.then(res => res.json());

Metafields in Themes

Access custom data:

{{ product.metafields.custom.care_instructions }}
{{ product.metafields.custom.material.value }}

{% if product.metafields.custom.featured %}
  <span class="badge">Featured</span>
{% endif %}

Best Practices

Performance:

  • Optimize images (use appropriate sizes)
  • Minimize Liquid logic complexity
  • Use lazy loading for images
  • Defer non-critical JavaScript

Accessibility:

  • Use semantic HTML
  • Include alt text for images
  • Support keyboard navigation
  • Ensure sufficient color contrast

SEO:

  • Use descriptive page titles
  • Include meta descriptions
  • Structure content with headings
  • Implement schema markup

Code Quality:

  • Follow Shopify theme guidelines
  • Use consistent naming conventions
  • Comment complex logic
  • Keep sections focused and reusable

Resources