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.