← Back to guides
ExperienceLiquid BasicsLiquid · Published 2026-06-15 · 11 min read

Shopify Liquid Mistakes We Still See on Client Stores

Nine years of rescue work distilled — the Liquid mistakes Dhruv and Nishad still find on live client stores, why they break, and the fixes we apply on every audit.

Share with Shopify developers — useful guides spread faster in theme dev communities.

Rescue projects arrive with working homepages and broken collection templates — Liquid that compiled, shipped, and embarrassed someone during a sale. Nishad Kikani wrote this audit list after three consecutive agency handoffs in 2025 where the storefront looked fine until merchants opened the theme editor or PageSpeed flagged render-blocking loops. For the fix workflow, see debugging Liquid on production themes.

We still get hired to fix themes that "mostly work." The homepage looks fine. Then you open collection templates, check structured data, run PageSpeed, or watch a merchant edit a section and the cracks show. The mistakes below are not academic — we found every one on a real client store in the last eighteen months. Some came from junior developers, some from experienced freelancers who rushed a launch, and some from app snippets injected into layout/theme.liquid without understanding scope. If you recognize your own code here, no shame: fix it once, document it, and add a Theme Check rule so it does not return.

Mistake 1: Using the wrong object for the template context

Liquid does not throw friendly errors when you ask for product.title on a page that has no product object — you get blank output and confused merchants. We see cart templates using product instead of item in loops, blog templates assuming article exists on listing pages, and sections copied from product pages dropped onto the homepage where product is nil. The fix is defensive checks: {% if product %} wrappers where appropriate, and understanding which global objects Shopify exposes per template type. Shopify Liquid for Beginners documents object availability; we keep that page open during code review.

{%- comment -%} Wrong on cart page — item is the loop variable {%- endcomment -%}
{% for item in cart.items %}
  

{{ product.title }}

{% endfor %} {%- comment -%} Correct {%- endcomment -%} {% for item in cart.items %}

{{ item.product.title }}

{{ item.final_line_price | money }}

{% endfor %}

Mistake 2: For loops without limit on large collections

{% for product in collection.products %} without limit on a collection with 2,000 SKUs will slow the template and sometimes hit Shopify's rendering limits. Homepage sections that let merchants pick any collection without a cap are repeat offenders. We add {% for product in collection.products limit: section.settings.products_to_show %} and mirror the limit in schema as a range setting with max 24 unless there is pagination. Performance and UX align: shoppers rarely scroll past twelve tiles on mobile. See Theme Performance Tips for how this interacts with image loading.

Nested loops that accidentally square complexity

A related sin: nested for loops over all products inside all collections for "related items" widgets. We replace these with collection.filters, metafield references, or Search & Discovery app blocks unless there is a hard merchandising reason. One wholesale client had a snippet that looped every product in the shop to find matching tags — TTFB was four seconds. The fix was a metafield list of related product references editable per SKU.

Mistake 3: Money formatting without the money filter

{{ product.price }} outputs cents as a raw integer — 1999 instead of $19.99. Stores in non-USD currencies look broken. We still find themes that concatenate dollar signs in JavaScript and Liquid separately. Use {{ product.price | money }} or money_with_currency when needed. For compare-at pricing, pair product.compare_at_price with a conditional so you do not show strikethrough when compare_at is blank. Shopify Liquid Cheat Sheet money filters section is mandatory reading for anyone touching pricing markup.

{% if product.compare_at_price > product.price %}
  {{ product.compare_at_price | money }}
  {{ product.price | money }}
{% else %}
  {{ product.price | money }}
{% endif %}

Mistake 4: Images without image_url and width

Using {{ product.featured_image | img_url: 'master' }} or dumping full-resolution URLs in srcset slows mobile and hurts Core Web Vitals. Modern Shopify Liquid uses image_url with width (and height when needed). We define widths per breakpoint in section settings or hardcode srcset widths that match the layout — 400, 800, 1200 for a two-column hero. Missing lazy loading on below-fold images is still common; add loading="lazy" except for LCP candidates. Image Optimization Guide walks through srcset patterns we copy into client themes.

Mistake 5: Hardcoded URLs and product handles

href="/products/blue-widget" breaks when merchandising renames the handle to blue-widget-v2. Use {{ product.url }}, {{ collection.url }}, routes object for cart and account links, and url settings in schema for merchant-controlled CTAs. We grep client themes for href="/products/ during audits — the hit list becomes the fix list. For shop policies and pages, use {{ pages.about.url }} or section.settings.page.url via page picker settings.

Mistake 6: include instead of render

Legacy themes and Stack Overflow answers still use {% include 'snippet' %}. include merges parent scope, makes variable origin unclear, and is deprecated. render isolates scope and accepts parameters explicitly: {% render 'icon', name: 'cart', class: 'icon--sm' %}. When we migrate rescue themes, include-to-render is a scripted find-replace with manual verification — some snippets relied on leaked parent variables.

Mistake 7: Missing block.shopify_attributes

Blocks without {{ block.shopify_attributes }} on their wrapper break theme editor selection. Merchants click a FAQ row and the whole section highlights. Developers forget this when converting static HTML to blocks because static HTML has no concept of Shopify's editor overlay. We add shopify_attributes to the outermost element per block row — never on an inner span. For section-level highlighting, section.shopify_attributes exists but is rarely needed on OS 2.0 sections. Read How to Build Dynamic Blocks for the full block loop pattern.

Mistake 8: Renaming schema setting ids after launch

Theme JSON stores merchant content keyed by setting id. Rename heading to headline in schema and every existing heading value disappears on deploy. We treat id changes as migrations: add new id, copy value in a one-time script or manual theme editor pass, deprecate old id, remove later. Shopify Schema Guide stresses id stability; we stress it in bold during developer onboarding. This mistake causes more client panic than any Liquid syntax error because it looks like data loss.

Mistake 9: Unescaped HTML in user-facing output

Merchants paste tables and spans into richtext settings. Output with {{ section.settings.body }} strips formatting; output with {{ section.settings.body | newline_to_br }} breaks on complex HTML. For richtext, output directly without escape filters unless you have a deliberate plain-text field. Conversely, never output plain text settings with | raw. XSS risk is lower in theme editor context but apps and metafields can surprise you. Know your setting type: text, richtext, inline_richtext behave differently.

Mistake 10: Logic duplication across sections

The same vendor badge logic copied into five section files drifts within months. One section checks metafield namespace custom.vendor; another still uses old namespace vendor_info. Extract shared logic to snippets or app proxies. For metafield access patterns, Metafields Beginner Tutorial is our reference. Duplication is not a Liquid syntax mistake — it is a maintenance mistake that manifests as inconsistent Liquid.

Our audit grep list

  1. grep for include ' — migrate to render
  2. grep for img_url — migrate to image_url
  3. grep for /products/ hardcoded paths
  4. grep for for product in collections — often wrong iterator
  5. Theme Check CLI on CI before merge
  6. Compare money output on CAD and USD test stores

How we fix these without halting the merchant's business

Rescue work runs on a duplicate theme, never live-first. We fix Liquid in branches, deploy to unpublished theme, run merchant UAT checklist, then schedule switch during low-traffic window. Critical fixes — broken cart, wrong prices — hotfix immediately with rollback plan. We document every Liquid change in client-facing release notes because merchants notice when compare-at pricing suddenly appears correctly. Prevention beats rescue: add Theme Check to GitHub Actions, require code review for sections/ and snippets/, and keep Cheat Sheet linked in your team wiki. Common Liquid Mistakes covers overlapping ground for beginners; this article is the production war stories layer.

Mistake 11: assign and capture inside loops

{% assign %} inside a for loop overwrites the same variable each iteration; merchants see only the last value outside the loop. capture is safer for building HTML strings but still confuses developers who expect loop-scoped variables like JavaScript. We use assign before loops when the value is global to the template, and keep loop variables inside the loop body only. When we need to collect items across loops, we use concat filter patterns or move logic to a snippet with explicit parameters. Liquid Loops Guide documents loop gotchas we still teach in onboarding week two.

Mistake 12: blank checks that hide real bugs

{% if product %} is correct; {% if product != empty %} is usually wrong for objects. Beginners use blanket blank checks that pass when product exists but fields are empty — then wonder why title does not show. We check the specific field: {% if product.title != blank %}. For images, test {% if product.featured_image %} before image_url. For metafields, namespace and key typos return nil silently — grep metafield references against the client's metafield definition export from Shopify admin.

{%- liquid
  assign featured = section.settings.collection
  if featured == blank
    assign featured = collections.all
  endif
-%}
{% for product in featured.products limit: section.settings.limit %}
  {% render 'card-product', card_product: product %}
{% endfor %}

Mistake 13: theme.liquid as a junk drawer

Every app wants a snippet in layout/theme.liquid. After six apps you have twelve script tags in head, duplicate jQuery, and a global CSS rule from a reviews widget. We audit theme.liquid on rescue projects before touching sections. Remove dead app code when the client uninstalls. Defer what you can. Merchants blame the theme developer when PageSpeed fails but the culprit is a heatmap script loaded synchronously in head. Document every theme.liquid injection in the handoff doc with owner and removal steps.

Teaching clients' in-house devs after rescue

When we hand a fixed theme to a client developer, we pair for one hour on the grep list and Theme Check setup. We show them how we found img_url, which sections use blocks, and why setting ids are frozen. The goal is they never need rescue again for the same mistake class. We point them to Liquid for Beginners for juniors and Cheat Sheet for daily reference. Rescue projects fail long-term when knowledge stays only in our Git history.

Mistake 14: pagination ignored on blog and collection templates

{% paginate collection.products by 24 %} is not optional on large catalogs. We see collection templates that loop all products and wonder why Google crawls timeout. Blog templates without paginate tags load every article on /blogs/news. Fix is mechanical but often missed on custom themes cloned from landing-page HTML. Always close paginate tags and preserve query string filters inside paginate blocks. Test page 2 URLs in Search Console after launch.

Mistake 15: selling_plan and subscription blind spots

Subscription apps expose selling_plan_allocation on line items and variant selling_plan_groups on products. Themes that ignore subscriptions show wrong prices or hide subscribe options. When a client uses Recharge or Shopify Subscriptions, we read their snippet docs and test add-to-cart with a subscription SKU before launch. Liquid mistakes here are business logic mistakes — merchants lose revenue, not just styling.

Building a Liquid review culture on client teams

Every sections/ PR gets a two-person review: one developer checks schema and CSS scope, another runs the theme editor click path. Reviewers keep Cheat Sheet open for filter syntax. We comment on PRs with screenshot of block selection working. After three months most Liquid mistakes in this article stop appearing — not because developers memorized Shopify docs, but because review catches patterns before merge. Rescue projects taught us prevention is cheaper than emergency calls.

Quick reference: mistake → fix

  • Wrong object in loop → use item, article, block correctly
  • Raw price output → money filter
  • img_url master → image_url with width
  • Hardcoded /products/ → product.url or url settings
  • include → render with explicit params
  • Missing shopify_attributes → add to block wrapper
  • Renamed setting id → migration, not rename
  • Unbounded loops → limit or paginate

Keep this list in your theme repo CONTRIBUTING.md. When a rescue client asks what went wrong, the grep list and this table are the first deliverable before we quote fix hours. Prevention through review and Theme Check beats heroics every time — and it keeps merchants trusting the theme editor instead of fearing it.

Liquid is forgiving until a merchant depends on your template at checkout time. Then it is very specific.

Frequently asked questions

Why does my Liquid work in the editor but break on the live storefront?

Usually a context mismatch — you are previewing a product template with a sample product that has variants, while the live product has none. Or you used product instead of item inside a collection loop. Always test with edge-case products: single variant, sold out, no image, and long titles.

Is it safe to use JavaScript to render product prices?

Use Liquid for initial price HTML for SEO and no-JS shoppers. JavaScript can update prices after variant changes, but duplicating money formatting logic in JS and Liquid causes mismatch bugs. Prefer the theme's existing variant picker patterns.

Should I use include or render for snippets?

Always render in modern themes. include is deprecated, leaks parent scope, and makes debugging harder. render passes variables explicitly, which is clearer for the next developer.

How do I debug Liquid without console.log?

Temporarily output variables with {{ variable | json }} in HTML comments, use {% liquid %} blocks for readability, and enable the Shopify theme inspector. Theme Check catches syntax errors before deploy.

Why do collection loops show duplicate products?

Often the collection sort is manual and products appear in multiple child collections you are iterating. Less commonly, nested loops reuse the same variable name product, shadowing the outer scope.

Topic cluster

Liquid Basics

Objects, tags, filters, loops, and beginner Liquid workflows.

Convert HTML to Shopify Liquid

Paste HTML & generate Liquid with schema, blocks, and scoped CSS. No signup required.

Share

Share this guide

Found this guide useful? Share it with other Shopify developers on LinkedIn, X, or Reddit.

Authors

About the authors

Practical Shopify section workflows from the developers who build and maintain this tool.

Dhruv Goyani, Shopify Developer at HTML to Liquid Converter

Dhruv Goyani

3 years web design + 3 years Shopify development experience

Dhruv combines web design and Shopify theme development — converting Figma and static HTML into merchant-editable OS 2.0 sections for client stores. He focuses on schema structure, section presets, and clean handoffs between design and Liquid.

Follow Dhruv on LinkedIn
Nishad Kikani, Lead Shopify Developer at HTML to Liquid Converter

Nishad Kikani

2 years web design + 6 years Shopify development experience

Nishad has six years of Shopify theme engineering — Dawn migrations, JSON templates, performance tuning, and complex block-based sections. He leads client delivery, code review, and the production standards behind every export from HTML to Liquid Converter.

Follow Nishad on LinkedIn

Meet our Shopify developers · Our development experience

Editorial review

Reviewed by the HTML to Liquid Converter team

Dhruv Goyani, Shopify Developer at HTML to Liquid Converter

Dhruv Goyani

3 years web design + 3 years Shopify development experience

LinkedIn profile →
Nishad Kikani, Lead Shopify Developer at HTML to Liquid Converter

Nishad Kikani

2 years web design + 6 years Shopify development experience

LinkedIn profile →

Content is reviewed by the HTML to Liquid Converter Shopify development team before publication. Technical accuracy is validated against current Shopify Online Store 2.0 conventions and active client theme work.

Last updated:

Questions or corrections? Contact us.