jelonyx
§ / Shopify · Error fixes · Fix 04

Shopify custom Liquid
not working.

You drop Liquid into a Custom Liquid block, hit save, and the section renders empty. No error, no warning: Shopify swallows the failure and serves a blank. Almost always a forbidden tag, a syntax slip, or a variable that doesn't exist in the current template. Five ranked causes below, a debug overlay that prints the active template and variable scope, and a safe-Liquid wrapper that fails loud in the customizer and quiet on the storefront.

SeverityBlock-rendering
Time to fix~10–20 minutes
Theme accessRequired
Most common causeForbidden tag
Affects:Dawn 12+HorizonAll OS 2.0 themesCustom Liquid blocks

Symptoms

  • Block renders empty: the Custom Liquid section shows nothing on the page, no error, no whitespace placeholder.
  • Works in the customizer, blank on the storefront: the preview shows the block correctly but the published store serves an empty space.
  • Half the block renders, the rest is missing:everything up to the syntax error renders; everything after is cut off silently.
  • Variable output prints literal Liquid: the page shows {{ product.title }} as plain text instead of the product name. Liquid was never parsed.
  • Block disappeared after save: was rendering fine, you edited it, hit save, and now the whole section is gone from the live store.

Liquid debug overlay

Drop this snippet into your theme and {% render %} it from inside the Custom Liquid block you're debugging. In the customizer it shows automatically; on the live storefront it stays hidden until you append ?debug=liquid to the URL. The pinned overlay prints the current template, page type, template suffix, and which top-level variables (product, collection, page, cart, article) are actually defined in this scope.

snippets/jlx-liquid-debug.liquid
Liquid
{% comment %}
  Jelonyx · Custom Liquid debug overlay
  Drop into snippets/jlx-liquid-debug.liquid and {% render %} it from inside
  the Custom Liquid block (or from any section) you're debugging.
  Visible only when ?debug=liquid is in the URL or in the theme customizer.
  Source: jelonyx.com/shopify/fix/custom-liquid-not-working
{% endcomment %}

{%- assign jlx_show = false -%}
{%- if request.design_mode -%}{%- assign jlx_show = true -%}{%- endif -%}

<pre id="jlx-liquid-debug" hidden style="
  position: fixed; bottom: 12px; right: 12px; max-width: 420px; max-height: 60vh;
  overflow: auto; padding: 14px 16px; z-index: 99999;
  background: #101218; color: #B8D67A; border: 1px solid #2A2D38;
  font: 12px/1.5 ui-monospace, Menlo, monospace; white-space: pre-wrap;
">
CUSTOM LIQUID : render context
template:        {{ template | default: '(none)' }}
template.suffix: {{ template.suffix | default: '(none)' }}
page_type:       {{ request.page_type | default: '(none)' }}
design_mode:     {{ request.design_mode }}
path:            {{ request.path }}

VARIABLE SCOPE (truthy in this template?)
product:    {% if product %}yes : {{ product.handle }}{% else %}no{% endif %}
collection: {% if collection %}yes : {{ collection.handle }}{% else %}no{% endif %}
page:       {% if page %}yes : {{ page.handle }}{% else %}no{% endif %}
cart:       {% if cart %}yes : {{ cart.item_count }} items{% else %}no{% endif %}
article:    {% if article %}yes : {{ article.handle }}{% else %}no{% endif %}
blog:       {% if blog %}yes : {{ blog.handle }}{% else %}no{% endif %}
customer:   {% if customer %}yes : id {{ customer.id }}{% else %}no (logged out){% endif %}

OUTPUT LENGTH (this snippet's siblings)
section.id:      {{ section.id | default: '(not in section)' }}
block.id:        {{ block.id | default: '(not in block)' }}
</pre>

<script>
  (function () {
    var el = document.getElementById('jlx-liquid-debug');
    if (!el) return;
    var qs = location.search || '';
    var inCustomizer = window.Shopify && window.Shopify.designMode;
    if (qs.indexOf('debug=liquid') !== -1 || inCustomizer) {
      el.hidden = false;
    }
  })();
</script>

Render from inside the Custom Liquid block:

{% render 'jlx-liquid-debug' %}
  • Overlay never appears → the snippet itself didn't render, which means a syntax error earlier in the block aborted parsing (cause #2). Strip the block back to just the render call and add lines until it disappears again.
  • Overlay shows but your variable says "no" → you're referencing a variable that doesn't exist in this template (cause #3). Move the block to the right template, or guard with {% if product %}.
  • Overlay shows in customizer, not on storefront → cache delay (cause #4). Hard refresh and wait ~30s after Save.
  • Overlay missing on storefront with ?debug=liquid → a forbidden tag stripped your whole block (cause #1) or it was pasted into a section schema default (cause #5).

Most likely causes, ranked

  1. 01
    Forbidden tag inside the Custom Liquid block.

    Shopify silently strips a small set of tags that aren't allowed inside Custom Liquid blocks: {% layout %}, {% schema %}, {% form %} outside product/cart context, dynamic {% include %} with a variable name, and any inline <script> that references Shopify.theme before it's loaded. The block renders empty with no warning. Frequency: most common.

    Fix: remove the forbidden tag. For schema settings use a proper section file, not a Custom Liquid block. Replace {% include %} with {% render %}. Move scripts that depend on Shopify.theme into DOMContentLoaded or a theme.liquid asset.

  2. 02
    Liquid syntax error swallowed silently.

    An unclosed {% if %}, missing {% endcomment %}, unbalanced {% for %}, or a stray {% with no closing tag will abort the parser. Shopify swallows the error and renders nothing, instead of throwing or printing a message, so the block looks deleted.

    Fix: append ?_fd=0&pb=0 to the storefront URL. That disables Shopify's preview bar and asset pipeline, so some syntax errors surface more visibly. Cut the block in half until the empty render flips to rendered, then drill into that half. The {% liquid %} tag with one statement per line makes mismatched blocks easier to spot.

  3. 03
    Variable doesn't exist in this template.

    Referencing {{ product.title }} on a page template, or {{ cart.item_count }} outside cart pages, returns blank. Liquid renders nothing, there's no error, and the rest of the block downstream of the empty value can also collapse if it depended on that variable. The template and request.page_type variables tell you which context you're in.

    Fix: use the debug overlay above to confirm which variables are truthy in the current template. Guard with {% if product %} before any product.* reference. Move the block to the right template, or split it into two blocks (one per template) and add each from the customizer separately.

  4. 04
    Theme customizer cache delay.

    The customizer renders an unminified preview that bypasses the storefront cache. The published theme runs through Shopify's asset CDN with cache invalidation that can lag ~30 seconds after Save. So the block renders correctly in the customizer but the live URL still serves the stale empty version.

    Fix: hard refresh the storefront with Ctrl+Shift+R (or Cmd+Shift+R) and wait ~30 seconds after Save. If still blank after a minute, the cause is structural (1, 2, 3, or 5), not cache.

  5. 05
    Liquid pasted into a section schema default.

    Liquid hard-coded into the "default" field of a {% schema %} setting's JSON gets double-escaped when Shopify writes it back to the section file. Quotes and braces inside the default value mangle on round-trip, breaking the rendered output even though the source looks correct.

    Fix: never paste Liquid into a schema default. Instead, expose a plain text or HTML setting and reference {{ section.settings.foo }} in the body of the section. Alternatively, for a per-page snippet, add a Custom Liquid block from the theme inspector and put the Liquid in the block's body, where it's parsed not stringified.

The safe-Liquid wrapper

A small wrapper that captures your Liquid output and checks whether it actually rendered anything. If empty, it shows a visible warning in the customizer (so you notice immediately) and silently renders nothing on the storefront (so customers never see a debug message).

snippets/jlx-safe-liquid.liquid
Liquid
{% comment %}
  Jelonyx · Safe Custom Liquid wrapper
  Wraps user-supplied Liquid so an empty render fails LOUD in the customizer
  (visible warning) and silent on the storefront (renders nothing).
  Use:
    {% capture content %}
      ...your liquid...
    {% endcapture %}
    {% render 'jlx-safe-liquid', content: content %}
  Source: jelonyx.com/shopify/fix/custom-liquid-not-working
{% endcomment %}

{%- assign output = content | strip -%}

{%- if output != blank -%}
  {{ content }}
{%- else -%}
  {%- if request.design_mode -%}
    <div style="
      padding: 12px 14px; margin: 8px 0;
      background: #2a1a1a; color: #ff9f9f; border: 1px dashed #ff6b6b;
      font: 12px/1.5 ui-monospace, Menlo, monospace;
    ">
      Custom Liquid block is empty: check the syntax or variable scope.
      <br />
      template: {{ template }} · page_type: {{ request.page_type }}
      <br />
      Visible to editors only. Hidden on the live storefront.
    </div>
  {%- endif -%}
{%- endif -%}

Use it inside any Custom Liquid block:

{% capture content %}
  {% if product %}
    <p>Ships free over ${{ shop.money_format }}50</p>
  {% endif %}
{% endcapture %}
{% render 'jlx-safe-liquid', content: content %}

When to call a developer

  • You need an inline <script> that talks to the cart or variant API: script ordering, race conditions with Shopify.theme, and Section Rendering API rebinds need a developer, not a Custom Liquid block.
  • The block needs its own settings exposed in the customizer: that's a section file with a {% schema %}, not a Custom Liquid block. Different fix.
  • You're seeing inconsistent output across pages you can't isolate: multi-template, multi-locale, or app-injected Liquid often interacts in ways that need theme access plus session inspection.
  • Headless storefront (Hydrogen, custom React): Custom Liquid doesn't exist in headless. The equivalent is a section query through the Storefront API, which is a completely different code path.
No-App Shopify Fix Sprint

Need this fixed today?

Custom Liquid usually powers the conversion-critical bits: announcement text, conditional banners, custom CTAs, third- party integrations. When the block silently renders empty, the page reads incomplete and the conversion flow breaks. We find the forbidden tag or scope mismatch, install the safe wrapper, and verify across every template and locale.