Variable Scopes

Variable Scopes

While it is possible to create templates without a good understanding of scope, it may be helpful to understand how liquid variables are scoped in order to create truly reusable template or integrate smoothly with code written by other developers.

What is scope?

Put as simply as possible, scope is the context in which any particular object is accessible.

Scopes may be nested similarly to how partial templates may be nested - with a root scope which may contain child scopes, each of which may also contain child scopes.

Any object on a parent scope is accessible in all child scopes unless the child scope has another object by the same name.

Object on a child scope, however, are no longer accessible on the parent scope once the child scope is complete.

Additionally, if an object exists by the same name on both a parent and a child scope, only the object on the child scope will be accessible until the child scope is complete.

How are child scopes created and completed?

Child scopes are created every time you open up a new block tag (eg: {% if %}, {% for %}, {% capture %}, etc...) or include a partial template. The child scope is completed when the block tag is closed (eg: {% endif %}, {% endfor %}, {% endcapture %}, etc...) or the partial template is finished rendering.

The liquid developer documentation should indicate which tags create child scopes (eg: "The case tag creates a new child scope." in the {% case %} documentation).

Why should I care?

On the most basic level, if you save objects on a child scope, they will not be available when the child scope is completed. This means that if you attempt to retrieve a value inside an {% if %} statement, it will be unavailable after the matching {% endif %} tag unless you save that value on a parent scope.

On a more advanced level, if you are creating reusable templates that you want to allow other people to use without messing up their templates while still using readable variable names, all you need to do is be careful to save your objects on the child scope and when your template is done rendering it will not have disturbed any of the parent template objects or variables. For example: you could use the variable "start" even if the same variable is used by a parent template, and when your template is done rendering the "start" object would be unchanged inside its scope.

OK. So how do I choose where to save an object?

Ahh. Now we get to the point of this discussion. There are three different ways to save objects on the scope which are designed to give you full control over where objects are saved without becoming overly complicated:

There are three ways that variables may be defined and saved to a scope:

  • var stores the variable on the current scope. Once the current scope is completed, the variable will no longer be accessible. This is the default functionality for everything except for the {% assign %} and {% set %} methods.
  • set updates the variable on the closest scope which it is already defined on. The the variable has not been defined on any scope, it will be stored on the root scope.
  • assign removes the variable from all scopes and stores it on the root scope.

When writing templates, the best practice is to utilize var and set wherever possible and reserve assign for cases where it is specifically necessary. By restricting your variables to the scopes where they are used, you make your code more friendly and compatible with code written by other developers who may wish to use the same variable names.

Note that there is currently no way to unset a variable from a scope other than using the assign method to define the variable at the root scope. Setting a variable to null does not remove it from the scope.