Shopify/liquid · warning · Liquid::UndefinedVariable
undefined variable #
Error message
undefined variable #{key} What it means
VariableLookup#evaluate raises Liquid::UndefinedVariable, "undefined variable #{key}", only when the context's strict_variables option is enabled and a lookup key resolves to nothing (and is not a supported keyword). Without strict_variables the same situation silently returns nil.
Solutions
- Provide the variable in the assigns Hash or registers before rendering.
- Fix the template to reference an existing key (or use {{ key | default: '' }}).
- Guard with {% if key %} or use the default filter so the lookup is only evaluated when present.
- If nil is a legitimate value, disable strict_variables for that render.
Example fix
// before
template.render({}, strict_variables: true) # template: {{ user_name }}
// after
template.render({ 'user_name' => 'bob' }, strict_variables: true) Defensive patterns
Strategy: try-catch
Validate before calling
required_keys.each { |k| raise "missing template variable #{k}" unless assigns.key?(k) } Type guard
def defined_in_context?(key, assigns) assigns.key?(key) || assigns.key?(key.to_s) || assigns.key?(key.to_sym) end
Try / catch
begin
template.render(assigns, strict_variables: true)
rescue Liquid::UndefinedVariable => e
logger.warn("Template referenced #{e.message.sub('undefined variable ', '')}")
template.render(assigns)
end Prevention
- Keep a contract list of required template variables and assert before render
- Use {% if var %} or | default: filters in templates
- Enable strict_variables only in CI/test runs to surface missing keys early
When it happens
Trigger: Rendering with context strict_variables: true (often paired with strict_filters) while a template references {{ missing_key }} or {{ obj.missing_attr }} that no assign/register provides.
Common situations: Teams enabling strict_variables in CI to catch template typos; renamed variables in Ruby code not updated in templates; partials rendered with different assigns than expected.
Related errors
- undefined variable #
- undefined method #
- e.message (re-raised as Liquid::ZeroDivisionError from…
- e.message (re-raised as Liquid::ZeroDivisionError from…
- e.message (re-raised as Liquid::FloatDomainError from…
AI-assisted analysis of Shopify/liquid@807d45a6b3 (2026-09-08).
Data as JSON: /api/errors/b319a90dd7a70b19.
Report an issue: GitHub.
Appendix: source
Thrown at lib/liquid/variable_lookup.rb:83
object = res.to_liquid
# Some special cases. If the part wasn't in square brackets and
# no key with the same name was found we interpret following calls
# as commands and call them on the current object
elsif lookup_command?(i) && object.respond_to?(key)
object = object.send(key).to_liquid
# Handle string first/last like ActiveSupport does (returns first/last character)
# ActiveSupport returns "" for empty strings, not nil
elsif lookup_command?(i) && object.is_a?(String) && (key == "first" || key == "last")
object = key == "first" ? (object[0] || "") : (object[-1] || "")
# No key was present with the desired value and it wasn't one of the directly supported
# keywords either. The only thing we got left is to return nil or
# raise an exception if `strict_variables` option is set to true
else
return nil unless context.strict_variables
raise Liquid::UndefinedVariable, "undefined variable #{key}"
end
# If we are dealing with a drop here we have to
object.context = context if object.respond_to?(:context=)
end
object
end
def ==(other)
self.class == other.class && state == other.state
end
protected
def state
[@name, @lookups, @command_flags]
endView on GitHub (pinned to 807d45a6b3)