> ## Content Index
> Fetch the complete content index at: https://debugly.dev/llms.txt
> Use this file to discover other available public pages before exploring further.

# Liquid Error: Nothing Renders and There Is No Error Message
- URL: https://debugly.dev/shopify-liquid-error-silent-failure/
- Published: 2025-11-06T09:05:00.000Z
- Updated: 2026-10-17T05:59:07.000Z
- Description: Liquid fails quietly by design. Here is where the error actually goes and how to make your theme tell you what went wrong.
- Author: Rohit Bhadani
- Tags: Shopify, Error Autopsy, Debugging, Ecommerce

**Disclosure:** I spent four years building Shopify themes at Debutify, finishing as CDO.

## The short version

A section is blank, a value does not print, and the browser console is clean. Liquid errors do not throw, they render as nothing or as an HTML comment.

Look in the page source, not the console:

```bash
curl -s https://yourstore.com/products/example | grep -i "liquid error"

```

```html
<!-- Liquid error (sections/product-form line 42): divided by 0 -->

```

If that returns nothing, you do not have a Liquid error, you have a value that is empty or a condition that is false. Dump the object:

```liquid
<pre>{{ product | json }}</pre>

```

Tested against Shopify Online Store 2.0, 2026.

## Why Liquid fails silently

Liquid is a template language designed to run untrusted merchant input on Shopify's servers. Throwing on error would mean a merchant's typo takes down their storefront during a sale.

So the design choice is: **render what you can, skip what you cannot, never crash.** An undefined variable renders as an empty string. A filter applied to the wrong type usually returns nil. A syntax error in one tag produces an HTML comment and the rest of the page continues.

That is the right trade for a hosted platform and it makes debugging genuinely harder, because the failure produces no signal anywhere you normally look.

## The three ways Liquid fails

### 1\. A real Liquid error, which leaves a comment

```html
<!-- Liquid error (snippets/price line 8): divided by 0 -->
<!-- Liquid error (sections/header line 23): Unknown filter 'moneyy' -->

```

These appear in the HTML source at the point where the error happened. They are invisible in the rendered page and invisible in DevTools' Elements panel if you are not looking for comments.

View source, or curl and grep. This is the first thing to check and most people never do.

Common causes: a typo in a filter name, dividing by a value that is zero or nil, calling a filter on the wrong type, and malformed tag syntax.

### 2\. A value that is empty

No error, because nothing went wrong. The variable simply has no value.

```liquid
{{ product.metafields.custom.badge }}    {%- comment -%} renders nothing {%- endcomment -%}

```

This is by far the most common case and it has three usual causes: the namespace or key is wrong, the metafield has no value for this product, or [you are outputting the object instead of .value](https://debugly.dev/shopify-metafield-rendering-escaped-json/).

### 3\. A condition that is false

The section renders and the branch you expected did not run.

```liquid
{%- if product.metafields.custom.features -%}

```

A metafield object exists even when its value is empty, so this is truthy and the branch runs with nothing inside it. Conversely:

```liquid
{%- if collection.products.size > 50 -%}

```

An unpaginated product loop caps at 50, so this is never true no matter how large the collection is.

## The diagnostic sequence

**1\. View source and grep for Liquid error.** Ten seconds, and it either gives you the answer or rules out a whole category.

**2\. Dump the object.**

```liquid
<pre style="max-height:400px;overflow:auto">{{ product | json }}</pre>

```

Works on `product`, `collection`, `cart`, `customer`, `section.settings`, `block.settings`, `template`, and `shop`. Seeing the actual structure resolves most "why is this empty" questions immediately.

`section.settings` is the one I reach for most. A setting id in the schema that does not match the id being read in the template is a silent failure and completely invisible until you dump it.

**3\. Print to the console instead of the page** when the dump breaks your layout:

```liquid
<script>console.log("settings", {{ section.settings | json }});</script>

```

**4\. Check the type.**

```liquid
{{ my_var.class }}          {%- comment -%} not available in Shopify Liquid {%- endcomment -%}
{{ my_var | json }}         {%- comment -%} use this instead {%- endcomment -%}

```

`| json` tells you whether you have a string, a number, an array, or an object. A surprising number of Liquid bugs are a value being a string when you assumed a number, because `"10" > 5` does not do what you want.

**5\. Gate the debug output** so you can leave it in:

```liquid
{%- if request.design_mode -%}
  <pre>{{ product | json }}</pre>
{%- endif -%}

```

`request.design_mode` is true only in the theme editor.

## The specific traps

### Whitespace and nil in comparisons

```liquid
{%- if product.metafields.custom.note != blank -%}

```

`blank` covers nil, empty string, and empty array. `empty` is narrower. Comparing against `""` misses nil. Use `!= blank` for content checks, which is almost always what you want.

### Filters that return nil

```liquid
{{ product.price | money_without_currency | times: 2 }}

```

`money_without_currency` returns a formatted string like `1,299.00`. `times` on a string with a comma produces nil, and the whole expression renders as empty.

Order matters: do arithmetic on the raw integer, format last.

```liquid
{{ product.price | times: 2 | money }}

```

Shopify prices are integers in the shop's smallest currency unit, so `1299` is 12.99\. Formatting first and calculating second is a very common mistake.

### The 50 item loop cap

```liquid
{%- for product in collection.products -%}

```

Caps at 50 with no warning. Your 200 product collection shows 50 and looks fine, and nobody notices for months.

```liquid
{%- paginate collection.products by 24 -%}
  {%- for product in collection.products -%}
    ...
  {%- endfor -%}
  {{ paginate | default_pagination }}
{%- endpaginate -%}

```

The `paginate` tag must wrap the loop, not sit beside it.

### all\_products lookup limits

```liquid
{%- assign p = all_products[handle] -%}

```

There is a per template cap on these lookups. Past it, they silently return nothing, so the last few items in a hardcoded list just do not render. [Use a collection instead](https://debugly.dev/shopify-liquid-n-plus-one-performance/), which is one fetch and merchant editable.

### assign scope in loops and snippets

```liquid
{%- assign total = 0 -%}
{%- for item in cart.items -%}
  {%- assign total = total | plus: item.line_price -%}
{%- endfor -%}
{{ total }}    {%- comment -%} works {%- endcomment -%}

```

`assign` inside a `for` does persist after the loop in Shopify Liquid. But variables assigned inside a `{% render %}` do **not** leak back to the caller, which is different from the deprecated `{% include %}`. Code migrated from `include` to `render` breaks silently here, and it is one of the harder ones to spot because the snippet works fine in isolation.

Pass values in and return them through the rendered output, or restructure.

## Making failures visible

The theme cannot throw, and you can make it complain.

```liquid
{%- comment -%} snippets/require.liquid {%- endcomment -%}
{%- unless value != blank -%}
  {%- if request.design_mode -%}
    <div style="background:#fee;border:2px solid #c00;padding:8px;font:12px monospace">
      Missing required value: {{ label }}
    </div>
  {%- endif -%}
{%- endunless -%}

```

```liquid
{%- render 'require', value: product.metafields.custom.spec, label: 'product.metafields.custom.spec' -%}

```

A merchant editing in the theme editor sees a red box saying exactly what is missing. Customers see nothing. That is the closest thing Liquid has to an assertion, and it turns a silent empty region into [an error message somebody can act on](https://debugly.dev/error-messages-are-a-user-interface/).

## Prevention

**Keep the theme in git.** `shopify theme pull` plus version control means every change is a diff and you can bisect a regression instead of guessing.

**Use `shopify theme dev`** rather than the browser code editor. Hot reload against real data changes the iteration speed enough to change how you debug.

**Check `Server-Timing` after changes** so a performance regression is caught immediately:

```bash
curl -sI https://yourstore.com/collections/all | grep -i server-timing

```

**Test with a realistic catalogue.** A development store with 12 products will never show you the 50 item cap or the reference resolution cost. Ask for the merchant's catalogue size and generate something comparable.

**Grep for `Liquid error` in CI** if you have a staging store. A simple curl over your main templates, failing the build if the string appears, catches the loud category automatically.

---

Published on [debugly.dev](https://debugly.dev/) by [Rohit Bhadani](https://debugly.dev/about/) | Founder of [Krova Cloud](https://krova.cloud/?ref=debugly.dev)