What You’ll Learn in This Module

Even experienced Cast users run into errors. This module is a practical troubleshooting guide — organized as a reference you can come back to when something goes wrong. It covers the most frequent error types, their root causes, and the exact fix for each.


How Errors Work in Cast’s Strict Mode

Cast runs Liquid in strict mode, which means errors are surfaced immediately rather than silently producing blank or wrong output. While this is stricter than standard Shopify Liquid, it’s ultimately a feature, not a bug — it catches mistakes before they reach customers.

When Cast encounters an error, it typically:

  1. Stops rendering the affected narration
  2. Shows an error message indicating what went wrong
  3. Identifies the approximate location of the problem

The key to troubleshooting is understanding the category of error and knowing the standard fix.


Error Category 1 — Undefined Variable

What it looks like


Liquid error: undefined variable 'contct_first_name'

Root cause

You referenced a variable that doesn’t exist. This is almost always one of three things:

  • A typo in the variable name
  • A missing field in the CRM data that wasn’t given a default
  • A snippet name that doesn’t match exactly (names are case-sensitive)

Fixes

Typo:{{ contct_first_name }}{{ contact_first_name }}

Missing CRM field — add a default:{{ health_score }}{{ health_score | default: "N/A" }}

Snippet name mismatch:{{ isEMEA }} (wrong case) ✅ {{ IsEMEA }} (matches the snippet name exactly)

💡 Prevention: Use the setup block pattern from Module 15. By assigning all variables with defaults at the top, you catch missing fields before they cause errors deeper in the narration.


Error Category 2 — Unclosed Tag

What it looks like


Liquid error: 'if' tag was never closed
Liquid error: missing 'endfor'

Root cause

Every opening tag needs a matching closing tag. The most common unclosed pairs:

Opens With Must Close With
{% if %} {% endif %}
{% unless %} {% endunless %}
{% for %} {% endfor %}
{% case %} {% endcase %}
{% capture x %} {% endcapture %}
{% comment %} {% endcomment %}

Fixes

Count your opening and closing tags. For complex narrations, add a comment next to each closing tag:


{% if score >= 80 %}
  {% if tier == "Enterprise" %}
    Top tier enterprise content.
  {% endif %}  {%- # end: tier check -%}
{% else %}
  Standard content.
{% endif %}  {%- # end: score check -%}

💡 Prevention: Write the closing tag immediately after writing the opening tag, then fill in the content between them.


Error Category 3 — Type Mismatch in Comparisons

What it looks like

This one is sneaky — it often doesn’t produce a visible error, but produces wrong results. The narration renders, but the wrong branch is chosen.

Root cause

Comparing a string to a number, or using > / < on unconverted string values.

Symptoms

  • A customer with ARR of $150,000 is told “your account is below the threshold” when the threshold is $100,000
  • A health score of 85 triggers the “needs improvement” branch
  • An if block that should match never triggers

Fixes

Numeric comparisons — always convert first:{% if arr > 100000 %}{%- assign arr_val = arr | default: 0 | plus: 0 -%}{% if arr_val > 100000 %}

Snippet boolean comparisons — always compare strings:{% if IsEMEA == true %}{% if IsEMEA == "yes" %} (with yes/no snippet design)

Case-sensitive string comparisons — normalize first:{% if tier == "enterprise" %} (CRM stores “Enterprise”) ✅ {%- assign tier = product_tier | downcase -%}{% if tier == "enterprise" %}

💡 Prevention: Every numeric field gets | plus: 0 in the setup block. Every snippet result gets | strip. Every string comparison uses normalized casing.


Error Category 4 — Division by Zero

What it looks like


Liquid error: divided by 0

Root cause

Dividing by a variable that is zero or was never set. Common in percentage calculations where the denominator comes from CRM data.

Fixes

Always guard division with a zero check:


{%- assign reserved = LicenseReserved | default: 0 | times: 1.0 -%}

{% if reserved > 0 %}
  {%- assign pct = used | divided_by: reserved | times: 100 | round: 0 -%}
  Your utilization is {{ pct }}%.
{% else %}
  No reserved capacity on file.
{% endif %}

💡 Prevention: Before any divided_by, check that the denominator is greater than zero.


Error Category 5 — Filter on Nil Value

What it looks like


Liquid error: nil is not a string
Liquid error: undefined method 'size' for nil

Root cause

Applying a filter (like size, split, upcase, cast_titlecase) to a variable that is nil.

Fixes

Add default before the filter that’s failing:

{{ contact_account_name | cast_titlecase }}{{ contact_account_name | default: "your company" | cast_titlecase }}

{%- assign items = feature_list | split: "," -%}{%- assign items = feature_list | default: "" | split: "," -%}

💡 Prevention: In your setup block, every variable that might be nil gets a default before any other filter is applied.


Error Category 6 — Whitespace Causing Silent Failures

What it looks like

No visible error — but conditions that should match don’t, and content that should appear is missing.

Root cause

Snippet output or CRM fields contain invisible leading/trailing whitespace. "yes" and " yes " are different strings.

Diagnosis

Temporarily display the raw value with brackets to make whitespace visible:


DEBUG: [{{ IsEMEA }}] length={{ IsEMEA | size }}

If you see [ yes ] with spaces, or a length longer than expected, whitespace is the culprit.

Fixes

Always strip before comparing:


{%- assign region = IsEMEA | strip -%}
{% if region == "yes" %}

💡 Prevention: Make | strip automatic when assigning any snippet result.


Error Category 7 — Integer vs. Decimal Division

What it looks like

No error — but percentages come out as 0% when they should be 75%, or results are unexpectedly rounded.

Root cause

Integer division drops the decimal part. 45 / 60 produces 0, not 0.75.

Diagnosis

If a percentage or ratio calculation returns 0 (or a suspiciously round number), check whether you’re using integer or decimal division.

Fixes

Ensure at least one operand is a decimal:

{%- assign pct = 45 | divided_by: 60 | times: 100 -%} → 0% ✅ {%- assign pct = 45 | divided_by: 60.0 | times: 100 -%} → 75%

Or convert with times: 1.0 in the setup block:


{%- assign used = LicenseUsed | default: 0 | times: 1.0 -%}
{%- assign reserved = LicenseReserved | default: 0 | times: 1.0 -%}


Error Category 8 — Date Parsing Failures

What it looks like


Liquid error: invalid date

Or: a date displays as January 01, 1900 or shows raw timestamp numbers.

Root cause

  • The date field is empty or contains non-date text
  • The date format from the CRM isn’t recognized by Liquid’s parser
  • The sentinel value -2208988800 (blank date) is being formatted

Fixes

Guard against empty dates:


{%- assign renewal = renewal_date | default: "" -%}
{% if renewal != "" %}
  Renews on {{ renewal | date: "%B %d, %Y" }}.
{% endif %}

Check for the sentinel value:


{%- assign ts = some_date | date: "%s" -%}
{% if ts != "-2208988800" and ts != "" %}
  {%- # Safe to use the date -%}
{% endif %}


Error Category 9 — Unexpected Output from cast_titlecase

What it looks like

Company name displays with incorrect casing — usually non-English particles like “van,” “de,” or “la” being capitalized when they should be lowercase.

Root cause

cast_titlecase handles English particles (of, the, and) but capitalizes non-English particles.

Diagnosis

Check the input value. If it contains van, der, de, la, le, or similar non-English particles, this is a known limitation.

Workaround

For specific known names, use replace after cast_titlecase:


{{ contact_account_name | cast_titlecase | replace: "Van Der", "van der" }}

This is a manual fix for specific cases — not a general solution.


Debugging Checklist

When something isn’t working, run through this checklist:

  1. Is the variable name spelled correctly? Check for typos, case sensitivity.
  2. Does the variable have a value? Add default if it might be nil.
  3. Is the data type correct? Convert strings to numbers before math/comparisons.
  4. Are all tags closed? Count if/endif, for/endfor, etc.
  5. Is there hidden whitespace? Use | strip on snippet results.
  6. Is division safe? Check for zero denominators.
  7. Are strings being compared to strings? Quote the comparison values.
  8. Is the date valid? Check for blank, nil, and sentinel values.
  9. Is the output what you expect? Use temporary debug output to inspect values.

Debug output pattern


{%- # TEMPORARY — remove before publishing -%}
DEBUG score: [{{ health_score }}] type-check: [{{ health_score | plus: 0 }}]
DEBUG region: [{{ IsEMEA }}] stripped: [{{ IsEMEA | strip }}]
DEBUG arr: [{{ arr }}] numeric: [{{ arr | plus: 0 }}]

The square brackets make whitespace visible. The | plus: 0 test confirms whether a value converts to a number successfully.


Quick Fix Reference Table

Symptom Likely Cause Fix
“undefined variable” error Typo or missing default Check spelling; add \| default:
“‘if’ tag was never closed” Missing endif Add matching closing tag
Wrong branch chosen in if String vs. number comparison Convert with \| plus: 0 before comparing
Percentage shows 0% Integer division Use \| times: 1.0 on operands
“divided by 0” error Zero denominator Wrap in {% if x > 0 %}
Snippet condition never matches Whitespace in output Add \| strip before comparison
“nil is not a string” Filter on nil value Add \| default: before other filters
Date shows 1900 or garbage Invalid/empty date field Guard with blank check before formatting
Company name cased wrong Non-English particles Known limitation; use replace workaround
{% if IsEMEA %} always true Testing string as boolean Compare with == "yes" instead

Try It Yourself

Exercise: The following narration has five bugs. Find and fix all of them.


{% assign name = contact_first_name %}
{% assign score = health_score %}
{% assign company = contact_account_name | cast_titlecase %}

Hi {{ name }},

Welcome to {{ company | cast_apostrophe }} review.

{% if score > 80 %}
  Great score!
{% elsif score > 60 }
  Good score.

Your renewal is on {{ renewal_date | date: "%B %d, %Y" }}.
{% elsif score > 60 }
  Good score.

Your renewal is on {{ renewal_date | date: "%B %d, %Y" }}.
{% if IsEMEA == true %}
  EMEA content.
{% endif %}

Click to reveal the answer **Bug 1:** No `default` on `contact_first_name` — errors if nil. ✅ `{% assign name = contact_first_name | default: "there" %}` **Bug 2:** `health_score` not converted to number — comparison unreliable. ✅ `{% assign score = health_score | default: 0 | plus: 0 %}` **Bug 3:** No `default` before `cast_titlecase` — errors if nil. ✅ `{% assign company = contact_account_name | default: "your company" | cast_titlecase %}` **Bug 4:** Missing `%` in closing `elsif` tag and missing `endif`. ❌ `{% elsif score > 60 }` → ✅ `{% elsif score > 60 }` → ✅ `{% elsif score > 60 %}` Also missing `{% else %}` and `{% endif %}` for the score block. **Bug 5:** Comparing snippet to boolean `true` instead of string. ✅ `{% if IsEMEA == "yes" %}` (assuming yes/no snippet design) **Also:** `renewal_date` has no guard for nil — should check before formatting. **Fixed version:** ```liquid {%- assign name = contact_first_name | default: "there" -%} {%- assign score = health_score | default: 0 | plus: 0 -%} {%- assign company = contact_account_name | default: "your company" | cast_titlecase -%} {%- assign renewal = renewal_date | default: "" -%} {%- assign region = IsEMEA | default: "" | strip -%} Hi {{ name }}, Welcome to {{ company | cast_apostrophe }} review. {% if score >= 80 %} Great score! {% elsif score >= 60 %} Good score. {% else %} Let's work on improving things. {% endif %} {% if renewal != "" %} Your renewal is on {{ renewal | date: "%B %d, %Y" }}. {% endif %} {% if region == "yes" %} EMEA content. {% endif %} ```

What’s Next

In Module 22, you’ll get the Quick Reference Card — a compact, single-page reference with every filter, tag, and pattern you need for daily Cast Liquid work.


📖 Official documentation:

  • Overview: https://school.cast.app/liquid.html
  • Tags/Blocks: https://school.cast.app/liquid/liquid-blocks.html
  • Standard Filters: https://school.cast.app/liquid/liquid-filters.html
  • Custom Cast Filters: https://school.cast.app/liquid/custom-cast-filters.html

This site uses Just the Docs, a documentation theme for Jekyll.