A simple, expressive and safe Shopify / Github Pages compatible template engine in pure JavaScript.
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
LiquidJS provides 21 built-in tags that offer control flow, template composition, variable manipulation, and content processing capabilities. Tags use the {% %} syntax and can contain complex logic and nested structures.
Tags are organized into the following functional categories:
Tags for implementing conditional logic and iteration.
/**
* Conditional execution based on expression truthiness
* Supports elsif and else branches
*/
{% if condition %}
<!-- content when true -->
{% elsif other_condition %}
<!-- content for elsif -->
{% else %}
<!-- fallback content -->
{% endif %}
/**
* Inverse conditional - executes when condition is false
*/
{% unless condition %}
<!-- content when false -->
{% endunless %}Usage Examples:
{% if user.active %}
<p>Welcome back, {{ user.name }}!</p>
{% elsif user.pending %}
<p>Your account is pending approval.</p>
{% else %}
<p>Please log in to continue.</p>
{% endif %}
{% unless product.sold_out %}
<button>Add to Cart</button>
{% endunless %}
<!-- Complex conditions -->
{% if user.age >= 18 and user.verified %}
<p>Full access granted</p>
{% endif %}/**
* Iterate over arrays, objects, and ranges
* Supports modifiers: offset, limit, reversed
* Provides forloop object with iteration details
*/
{% for variable in collection %}
<!-- iteration content -->
{% else %}
<!-- content when collection is empty -->
{% endfor %}
/**
* For loop modifiers
*/
{% for item in items limit: 5 offset: 2 reversed %}
{{ item }}
{% endfor %}
/**
* Forloop object properties:
* - first: true if first iteration
* - last: true if last iteration
* - index: 1-based iteration count
* - index0: 0-based iteration count
* - rindex: reverse index (countdown)
* - rindex0: reverse index starting from 0
* - length: total iterations
*/Usage Examples:
<!-- Basic iteration -->
{% for product in products %}
<div class="product">
<h3>{{ product.name }}</h3>
<p>${{ product.price }}</p>
</div>
{% else %}
<p>No products available</p>
{% endfor %}
<!-- With forloop object -->
{% for item in items %}
<div class="item {% if forloop.first %}first{% endif %}">
{{ forloop.index }}. {{ item.title }}
</div>
{% endfor %}
<!-- Range iteration -->
{% for i in (1..5) %}
<span>{{ i }}</span>
{% endfor %}
<!-- Object iteration -->
{% for key_value in user %}
<p>{{ key_value[0] }}: {{ key_value[1] }}</p>
{% endfor %}
<!-- With modifiers -->
{% for post in posts limit: 10 offset: 5 reversed %}
<article>{{ post.title }}</article>
{% endfor %}/**
* Multi-branch conditional similar to switch/case
* Supports multiple values per when clause
*/
{% case variable %}
{% when value1 %}
<!-- content for value1 -->
{% when value2, value3 %}
<!-- content for value2 or value3 -->
{% else %}
<!-- default content -->
{% endcase %}Usage Examples:
{% case user.role %}
{% when 'admin' %}
<p>Administrator Dashboard</p>
{% when 'editor', 'author' %}
<p>Content Management</p>
{% when 'subscriber' %}
<p>Read-only Access</p>
{% else %}
<p>Guest Access</p>
{% endcase %}
{% case product.status %}
{% when 'available' %}
<span class="status-available">In Stock</span>
{% when 'backordered' %}
<span class="status-backordered">Back Ordered</span>
{% when 'discontinued' %}
<span class="status-discontinued">No Longer Available</span>
{% endcase %}/**
* Special iteration for creating HTML tables
* Automatically handles table rows and provides tablerowloop object
*/
{% tablerow variable in collection cols: number %}
<!-- cell content -->
{% endtablerow %}
/**
* Tablerowloop object properties:
* - col: current column number (1-based)
* - col0: current column number (0-based)
* - col_first: true if first column
* - col_last: true if last column
* - first: true if first row
* - last: true if last row
* - index: iteration index (1-based)
* - index0: iteration index (0-based)
* - length: total iterations
* - row: current row number (1-based)
*/Usage Examples:
<table>
{% tablerow product in products cols: 3 %}
<td>
{{ product.name }}
{% if tablerowloop.col_last %}</tr><tr>{% endif %}
</td>
{% endtablerow %}
</table>
<!-- With styling -->
{% tablerow item in items cols: 4 %}
<td class="{% if tablerowloop.col_first %}first{% endif %}">
Row {{ tablerowloop.row }}, Col {{ tablerowloop.col }}
</td>
{% endtablerow %}/**
* Break out of current loop
*/
{% break %}
/**
* Skip to next iteration
*/
{% continue %}Usage Examples:
{% for product in products %}
{% if product.hidden %}
{% continue %}
{% endif %}
{% if product.featured %}
<div class="featured">{{ product.name }}</div>
{% break %}
{% endif %}
<div>{{ product.name }}</div>
{% endfor %}Tags for managing variables and their values.
/**
* Assign value to variable
*/
{% assign variable = value %}
/**
* Increment variable (creates if not exists, starting at 0)
*/
{% increment variable %}
/**
* Decrement variable (creates if not exists, starting at -1)
*/
{% decrement variable %}Usage Examples:
{% assign user_name = user.first_name | append: ' ' | append: user.last_name %}
<p>Hello, {{ user_name }}!</p>
{% assign total_price = 0 %}
{% for item in cart.items %}
{% assign total_price = total_price | plus: item.price %}
{% endfor %}
<p>Total: ${{ total_price }}</p>
<!-- Counter variables -->
{% increment my_counter %} <!-- Output: 0 -->
{% increment my_counter %} <!-- Output: 1 -->
{% increment my_counter %} <!-- Output: 2 -->
{% decrement my_counter %} <!-- Output: -1 -->
{% decrement my_counter %} <!-- Output: -2 -->/**
* Capture rendered content into a variable
*/
{% capture variable %}
<!-- content to capture -->
{% endcapture %}Usage Examples:
{% capture user_info %}
<div class="user">
<h3>{{ user.name }}</h3>
<p>{{ user.bio | truncatewords: 20 }}</p>
{% if user.avatar %}
<img src="{{ user.avatar }}" alt="{{ user.name }}">
{% endif %}
</div>
{% endcapture %}
<!-- Use captured content multiple times -->
<div class="sidebar">{{ user_info }}</div>
<div class="main">{{ user_info }}</div>
<!-- Capture complex expressions -->
{% capture product_list %}
{% for product in products %}
{{ product.name }}{% unless forloop.last %}, {% endunless %}
{% endfor %}
{% endcapture %}
<p>Products: {{ product_list }}</p>/**
* Cycle through values on each call
* Useful for alternating classes or values
*/
{% cycle 'value1', 'value2', 'value3' %}
/**
* Named cycles for multiple independent cycles
*/
{% cycle 'group1': 'odd', 'even' %}Usage Examples:
<!-- Alternating row classes -->
{% for item in items %}
<tr class="{% cycle 'odd', 'even' %}">
<td>{{ item.name }}</td>
</tr>
{% endfor %}
<!-- Multiple independent cycles -->
{% for product in products %}
<div class="{% cycle 'colors': 'red', 'blue', 'green' %} {% cycle 'sizes': 'small', 'large' %}">
{{ product.name }}
</div>
{% endfor %}Tags for including and composing templates from multiple sources.
/**
* Include partial template
* Variables are shared with parent template
*/
{% include 'template_name' %}
{% include template_variable %}
/**
* Include with additional variables
*/
{% include 'template_name', variable: value %}
/**
* Jekyll-style include (when jekyllInclude option is true)
*/
{% include 'template_name' variable=value %}Usage Examples:
<!-- Basic include -->
{% include 'header' %}
<main>{{ content }}</main>
{% include 'footer' %}
<!-- Dynamic include -->
{% assign sidebar_template = 'sidebar-' | append: user.role %}
{% include sidebar_template %}
<!-- Include with variables -->
{% include 'product-card', product: featured_product, featured: true %}
<!-- Jekyll-style -->
{% include 'alert.html' type='warning' message='Please update your profile' %}/**
* Render template with isolated scope
* Only explicitly passed variables are available
*/
{% render 'template_name' %}
{% render 'template_name' with object %}
{% render 'template_name' with object as alias %}
{% render 'template_name' for array %}Usage Examples:
<!-- Basic render -->
{% render 'button' %}
<!-- Render with data -->
{% render 'product-card' with product %}
<!-- Render with alias -->
{% render 'user-info' with current_user as user %}
<!-- Render for each item in array -->
{% render 'list-item' for menu_items %}
<!-- Render with additional variables -->
{% render 'modal', title: 'Confirm Action', type: 'warning' %}/**
* Wrap current template in layout
* Content is available as {{ content }} in layout
*/
{% layout 'layout_name' %}Usage Examples:
<!-- In page template -->
{% layout 'base' %}
<h1>{{ page.title }}</h1>
<p>{{ page.content }}</p>
<!-- In base.liquid layout -->
<!DOCTYPE html>
<html>
<head><title>{{ page.title }}</title></head>
<body>
<header>{% include 'header' %}</header>
<main>{{ content }}</main>
<footer>{% include 'footer' %}</footer>
</body>
</html>/**
* Define content blocks for layouts
* Blocks can have default content
*/
{% block block_name %}
<!-- default content -->
{% endblock %}Usage Examples:
<!-- In layout template -->
<head>
<title>{% block title %}Default Title{% endblock %}</title>
{% block head %}{% endblock %}
</head>
<body>
{% block content %}
<p>Default content</p>
{% endblock %}
</body>
<!-- In page template using layout -->
{% layout 'base' %}
{% block title %}Custom Page Title{% endblock %}
{% block head %}<link rel="stylesheet" href="custom.css">{% endblock %}
{% block content %}
<h1>Custom page content</h1>
{% endblock %}Tags for processing and manipulating content.
/**
* Output content without Liquid processing
* Useful for code examples or conflicting syntax
*/
{% raw %}
<!-- unprocessed content -->
{% endraw %}Usage Examples:
{% raw %}
<script>
var template = "Hello {{ name }}!";
// This won't be processed by Liquid
</script>
{% endraw %}
<!-- Display Liquid syntax in documentation -->
{% raw %}
Use {% for item in items %} to iterate over arrays.
{% endraw %}/**
* Output expression (alternative to {{ }} syntax)
*/
{% echo expression %}Usage Examples:
{% echo user.name %}
<!-- Equivalent to {{ user.name }} -->
{% echo products | map: 'name' | join: ', ' %}
<!-- Useful when {{ }} conflicts with other syntax -->/**
* Multi-line comments (not rendered)
*/
{% comment %}
<!-- comment content -->
{% endcomment %}
/**
* Inline comments (single line)
*/
{% # This is an inline comment %}Usage Examples:
{% comment %}
This template handles user profiles
Variables available: user, preferences, settings
{% endcomment %}
{% for product in products %}
{% # Skip discontinued products %}
{% unless product.discontinued %}
{{ product.name }}
{% endunless %}
{% endfor %}/**
* Execute multiple Liquid statements without output
* Useful for complex logic blocks
*/
{% liquid
statement1
statement2
statement3
%}Usage Examples:
{% liquid
assign total = 0
for item in cart.items
assign total = total | plus: item.price
endfor
if total > 100
assign discount = 0.1
else
assign discount = 0
endif
assign final_total = total | times: discount | minus: total | abs
%}
<p>Subtotal: ${{ total }}</p>
<p>Discount: ${{ final_total }}</p>
<p>Total: ${{ total | minus: final_total }}</p><!-- Basic tag -->
{% tag_name %}
<!-- Tag with arguments -->
{% tag_name argument1 argument2 %}
<!-- Tag with named parameters -->
{% tag_name parameter1: value1, parameter2: value2 %}
<!-- Block tags -->
{% tag_name %}
content
{% endtag_name %}<!-- Variables assigned in tags are available in parent scope -->
{% assign global_var = 'available everywhere' %}
<!-- Loop variables are scoped to the loop -->
{% for item in items %}
{{ item }} <!-- 'item' only available here -->
{% endfor %}
<!-- Capture creates variables in parent scope -->
{% capture my_content %}Content{% endcapture %}
{{ my_content }} <!-- Available here --><!-- Normal whitespace -->
{% if condition %}
content
{% endif %}
<!-- Trim left whitespace -->
{%- if condition -%}
content
{%- endif -%}
<!-- Trim right whitespace -->
{% if condition -%}
content
{% endif -%}<!-- Tags can be nested -->
{% if user %}
{% for post in user.posts %}
{% if post.published %}
{% capture post_info %}
{{ post.title | upcase }}
{% endcapture %}
<h3>{{ post_info }}</h3>
{% endif %}
{% endfor %}
{% endif %}Most tags respect the global strictVariables and strictFilters options:
// Strict mode - throws on undefined
const engine = new Liquid({ strictVariables: true });
// Lenient mode - continues with undefined
const engine = new Liquid({ strictVariables: false });