Skip to main content
Back to Blog
15 Common Shopify Liquid & Theme Development Mistakes (and How to Fix Them)
Tutorial

15 Common Shopify Liquid & Theme Development Mistakes (and How to Fix Them)

The mistakes that quietly break Shopify themes — deprecated tags and filters, missing nil-safety, hardcoded URLs, image CLS, invalid schema — each with the wrong code, the right code, and the Theme Check rule that catches it.

10 min read

Most Shopify theme bugs aren't dramatic — they're small, repeated habits that pass a quick visual check but cause broken layouts, slow pages, or settings that silently do nothing. The good news: nearly all of them are caught by Theme Check, Shopify's official linter, and are easy to fix once you know the pattern.

Here are 15 of the most common ones, each with the wrong code, the right code, and why it matters. To verify the correct syntax as you go, keep the Liquid Cheatsheet open.

1. Using {% include %} instead of {% render %}

include is deprecated. It shares the parent scope, which leads to subtle variable-leak bugs. render runs in an isolated scope — safer and faster.

{# ✗ #}
{% include 'product-card' %}

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

With render you pass exactly what the snippet needs. Caught by: ConvertIncludeToRender.

2. Deprecated image filter img_url

img_url is deprecated in favor of image_url, which requires an explicit width (and lets you build responsive images).

{# ✗ #}
<img src="{{ product.featured_image | img_url: '500x' }}">

{# ✓ #}
{{ product.featured_image | image_url: width: 500 | image_tag }}

Caught by: DeprecatedFilter (auto-correctable with --auto-correct).

3. Deprecated color filter hex_to_rgba

{# ✗ #}
color: {{ settings.color_name | hex_to_rgba: 0.5 }};

{# ✓ #}
color: {{ settings.color_name | color_modify: 'alpha', 0.5 }};

Use color_modify and color_to_rgb for color manipulation. Caught by: DeprecatedFilter.

4. Hardcoding URLs instead of the routes object

Hardcoded paths break on stores with locale or currency subpaths (Shopify Markets).

{# ✗ #}
<a href="/cart">Cart</a>
<a href="/collections/all">Shop</a>

{# ✓ #}
<a href="{{ routes.cart_url }}">Cart</a>
<a href="{{ routes.all_products_collection_url }}">Shop</a>

Caught by: HardcodedRoutes.

5. Missing width and height on images

Images without dimensions cause layout shift (CLS), which hurts Core Web Vitals and SEO.

{# ✗ #}
<img src="{{ image | image_url: width: 800 }}">

{# ✓ — image_tag adds width/height automatically #}
{{ image | image_url: width: 800 | image_tag }}

Caught by: ImgWidthAndHeight.

6. Not using responsive srcset

Serving one large image to every device wastes bandwidth and slows LCP. image_tag builds a responsive srcset for you:

{{ image | image_url: width: 1500 | image_tag:
   widths: '400, 800, 1200, 1500',
   sizes: '(min-width: 750px) 50vw, 100vw' }}

7. Outputting metafields without .value

A metafield is an object. Output it directly and you won't get the data.

{# ✗ #}
{{ product.metafields.custom.subtitle }}

{# ✓ #}
{{ product.metafields.custom.subtitle.value }}

See the full breakdown in Metafields in Liquid.

8. Invalid or duplicated {% schema %}

A section may contain exactly one {% schema %}, it must be valid JSON, and it can't contain comments, trailing commas, or Liquid. A single trailing comma stops the section from saving.

{
  "name": "Hero",
  "settings": [
    { "type": "text", "id": "title", "label": "Title" }
  ]
}

Caught by: ValidSchema / LiquidHTMLSyntaxError. Avoid it entirely by building schema with the Schema Builder.

9. Statically rendering sections that should be in a JSON template

Statically rendered sections ({% section 'name' %}) can't be reordered or removed by merchants, and a single instance is shared everywhere it appears. For anything a merchant should control, reference the section from a JSON template instead.

{# ✗ in a .liquid template for editable content #}
{% section 'featured-product' %}

Use static rendering only for genuinely fixed, non-editable placements.

10. Forgetting nil-safety on resource objects

Resource settings and objects can be empty — a merchant may not have selected anything, or the resource was deleted.

{# ✗ — errors / empty markup when nothing is selected #}
<h2>{{ section.settings.featured_product.title }}</h2>

{# ✓ #}
{% assign p = section.settings.featured_product %}
{% if p != blank %}
  <h2>{{ p.title }}</h2>
{% endif %}

11. Heavy work inside loops

Filtering or sorting a large array on every iteration is slow. Do the work once, before the loop.

{# ✗ #}
{% for product in collection.products %}
  {% assign sale = collection.products | where: 'available' %}
{% endfor %}

{# ✓ #}
{% assign available = collection.products | where: 'available' %}
{% for product in available %}
  ...
{% endfor %}

12. Cluttering the editor instead of using visible_if

Showing every advanced setting at once overwhelms merchants. Reveal options only when relevant:

{
  "type": "range", "id": "slides", "label": "Slides", "min": 1, "max": 5, "step": 1, "default": 2,
  "visible_if": "{{ section.settings.layout == 'slider' }}"
}

13. Not running Theme Check

Every issue above is caught automatically. Run it locally:

shopify theme check
shopify theme check --auto-correct

It's the single highest-leverage habit for theme quality — wire it into your editor and CI.

14. Hardcoded strings instead of locale files

Hardcoded text can't be translated and breaks multi-language stores.

{# ✗ #}
<button>Add to cart</button>

{# ✓ #}
<button>{{ 'products.product.add_to_cart' | t }}</button>

Store the strings in locales/en.default.json and translate per locale.

15. Using the removed @global block type

Older themes used @global to accept app blocks. It was replaced by @app.

{# ✗ #}
"blocks": [{ "type": "@global" }]

{# ✓ #}
"blocks": [{ "type": "@app" }]

See Theme Blocks in Shopify for the current block model.

The pattern behind all of these

Almost every mistake here comes down to one of three things: using a deprecated API, assuming data is always present, or hardcoding what Shopify gives you a dynamic source for. Internalize those three instincts — prefer current filters/tags, guard with != blank, and use routes/t/settings instead of literals — and your themes will be more robust, faster, and translation-ready.

Next steps

Run Theme Check on your theme today and fix what it flags. When you're building new sections, start them clean with the Schema Builder, generate correct markup from HTML with the converter, and double-check syntax against the Liquid Cheatsheet.

Found this helpful?

Share it with your network!

Ready to Convert HTML to Liquid?

Try our free HTML to Liquid converter and build your Shopify themes faster.

Try HTML2Liquid Now