Liquid Tutorial

Liquid + JavaScript

Source: Stacking Context

Bridging Server and Client

Liquid renders the initial HTML state. JavaScript handles interactivity: variant changes, cart drawers, predictive search. Pass data from Liquid to JS via JSON script tags — never embed unescaped user content in inline scripts.

Safe JSON embedding pattern
<script type="application/json" id="CartJson">
  {{ cart | json }}
</script>

<script type="application/json" id="ProductJson-{{ product.id }}">
  {{ product | json }}
</script>

<script src="{{ 'product-form.js' | asset_url }}" defer></script>
JAVASCRIPT
// assets/product-form.js
const product = JSON.parse(
  document.getElementById('ProductJson-{{ product.id }}')?.textContent || '{}'
);

const form = document.querySelector('#product-form');
const priceEl = document.querySelector('.product__price span:last-child');

form?.addEventListener('change', (event) => {
  const variantId = Number(form.querySelector('[name="id"]').value);
  const variant = product.variants.find((v) => v.id === variantId);
  if (variant && priceEl) {
    priceEl.textContent = formatMoney(variant.price, window.Shopify?.currency?.active);
  }
});

function formatMoney(cents, currency) {
  return new Intl.NumberFormat(undefined, {
    style: 'currency',
    currency: currency || 'USD',
  }).format(cents / 100);
}

Section Rendering API

Fetch updated HTML for a section without full page reload — powers AJAX cart drawers and dynamic filters.

JAVASCRIPT
const response = await fetch(
  `/?section_id=${sectionId}&variant=${variantId}`
);
const html = await response.text();
document.getElementById(`shopify-section-${sectionId}`).innerHTML = html;
  • Cart AJAX API: POST /cart/add.js, /cart/change.js, /cart/update.js
  • Predictive search: GET /search/suggest.json?q=query
  • Never trust client-side prices at checkout — server validates on order creation
Tip: Use @shopify/theme-cart or reference Dawn's cart-notification.js for battle-tested cart patterns.