Skip to content

Module 21: Troubleshooting

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.


{% raw %}
Liquid error: undefined variable 'contct_first_name'
{% endraw %}

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)

Typo: {% raw %}❌ {{ contct_first_name }}{% endraw %} {% raw %}✅ {{ contact_first_name }}{% endraw %}

Missing CRM field — add a default: {% raw %}❌ {{ health_score }}{% endraw %} {% raw %}✅ {{ health_score | default: "N/A" }}{% endraw %}

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

💡 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.


{% raw %}
Liquid error: 'if' tag was never closed
Liquid error: missing 'endfor'
{% endraw %}

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

Opens With Must Close With
{% raw %} {% if %}
{% raw %} {% unless %}
{% raw %} {% for %}
{% raw %} {% case %}
{% raw %} {% capture x %}
{% raw %} {% comment %}

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

{% raw %}
Top tier enterprise content.
{%- # end: tier check -%}
{% else %}
Standard content.
{%- # end: score check -%}
{% endraw %}

💡 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

Section titled “Error Category 3 — Type Mismatch in Comparisons”

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.

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

  • 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

Numeric comparisons — always convert first: {% raw %}❌ &#123;% if arr > 100000 %&#125;{% endraw %} {% raw %}✅ &#123;%- assign arr_val = arr | default: 0 | plus: 0 -%&#125;&#123;% if arr_val > 100000 %&#125;{% endraw %}

Snippet boolean comparisons — always compare strings: {% raw %}❌ &#123;% if IsEMEA == true %&#125;{% endraw %} {% raw %}✅ &#123;% if IsEMEA == "yes" %&#125; (with yes/no snippet design){% endraw %}

Case-sensitive string comparisons — normalize first: {% raw %}❌ &#123;% if tier == "enterprise" %&#125; (CRM stores “Enterprise”){% endraw %} {% raw %}✅ &#123;%- assign tier = product_tier | downcase -%&#125;&#123;% if tier == "enterprise" %&#125;{% endraw %}

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


{% raw %}
Liquid error: divided by 0
{% endraw %}

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

Always guard division with a zero check:

{% raw %}
{%- assign reserved = LicenseReserved | default: 0 | times: 1.0 -%}
{%- assign pct = used | divided_by: reserved | times: 100 | round: 0 -%}
Your utilization is {{ pct }}%.
{% else %}
No reserved capacity on file.
{% endraw %}

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


{% raw %}
Liquid error: nil is not a string
Liquid error: undefined method 'size' for nil
{% endraw %}

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

Add default before the filter that’s failing:

{% raw %}❌ {{ contact_account_name | cast_titlecase }}{% endraw %} {% raw %}✅ {{ contact_account_name | default: "your company" | cast_titlecase }}{% endraw %}

{% raw %}❌ {%- assign items = feature_list | split: "," -%}{% endraw %} {% raw %}✅ {%- assign items = feature_list | default: "" | split: "," -%}{% endraw %}

💡 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

Section titled “Error Category 6 — Whitespace Causing Silent Failures”

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

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

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

{% raw %}
DEBUG: [{{ IsEMEA }}] length={{ IsEMEA | size }}
{% endraw %}

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

Always strip before comparing:

{% raw %}
{%- assign region = IsEMEA | strip -%}
{% endraw %}

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


Error Category 7 — Integer vs. Decimal Division

Section titled “Error Category 7 — Integer vs. Decimal Division”

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

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

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

Ensure at least one operand is a decimal:

{% raw %}❌ &#123;%- assign pct = 45 | divided_by: 60 | times: 100 -%&#125; → 0%{% endraw %} {% raw %}✅ &#123;%- assign pct = 45 | divided_by: 60.0 | times: 100 -%&#125; → 75%{% endraw %}

Or convert with times: 1.0 in the setup block:

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

Error Category 8 — Date Parsing Failures

Section titled “Error Category 8 — Date Parsing Failures”
{% raw %}
Liquid error: invalid date
{% endraw %}

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

  • 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

Guard against empty dates:

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

Check for the sentinel value:

{% raw %}
{%- assign ts = some_date | date: "%s" -%}
{%- # Safe to use the date -%}
{% endraw %}

Error Category 9 — Unexpected Output from cast_titlecase

Section titled “Error Category 9 — Unexpected Output from cast_titlecase”

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

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

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

For specific known names, use replace after cast_titlecase:

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

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


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.
{% raw %}
{%- # 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 }}]
{% endraw %}

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


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
{% raw %} “divided by 0” error Zero denominator
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
{% raw %} &#123;% if IsEMEA %&#125; always true Testing string as boolean

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

{% raw %}
{% assign name = contact_first_name %}
{% assign score = health_score %}
{% assign company = contact_account_name | cast_titlecase %}
Hi {{ name }},
Welcome to {{ company | cast_apostrophe }} review.
Great score!
{% elsif score > 60 }
Good score.
Your renewal is on {{ renewal_date | date: "%B %d, %Y" }}.
EMEA content.
{% endraw %}
Click to reveal the answer

Bug 1: No default on contact_first_name — errors if nil. {% raw %}✅ &#123;% assign name = contact_first_name | default: "there" %&#125;{% endraw %}

Bug 2: health_score not converted to number — comparison unreliable. {% raw %}✅ &#123;% assign score = health_score | default: 0 | plus: 0 %&#125;{% endraw %}

Bug 3: No default before cast_titlecase — errors if nil. {% raw %}✅ &#123;% assign company = contact_account_name | default: "your company" | cast_titlecase %&#125;{% endraw %}

Bug 4: Missing % in closing elsif tag and missing endif. {% raw %}❌ &#123;% elsif score > 60 } → ✅ &#123;% elsif score > 60 %&#125;{% endraw %} {% raw %}Also missing &#123;% else %&#125; and &#123;% endif %&#125; for the score block.{% endraw %}

Bug 5: Comparing snippet to boolean true instead of string. {% raw %}✅ &#123;% if IsEMEA == "yes" %&#125; (assuming yes/no snippet design){% endraw %}

Also: renewal_date has no guard for nil — should check before formatting.

Fixed version:

{% raw %}
{%- 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.
Great score!
{% elsif score >= 60 %}
Good score.
{% else %}
Let's work on improving things.
Your renewal is on {{ renewal | date: "%B %d, %Y" }}.
EMEA content.
{% endraw %}

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: