Skip to content

Liquid tags

The available tags, the layout, block and include rules, what is rejected, and the stateful-tag warning inside sections.

Updated 2 Sept 20262 min read
On this page

Dukkan's engine runs LiquidJS with its standard tags and hardened options. This page collects what you need to know about tags without repeating the general Liquid documentation.

Hardened options#

Option Effect
strict filters an unknown filter is a render error
own properties only only what an object actually owns resolves; no constructor, no prototype chain
JavaScript truthiness "", 0, null and false are all falsy; use != blank and size > 0 deliberately
output escaping every {{ }} is escaped except the output of safe filters
no relative references include only by full path inside the bundle
limits see Limits

layout and block#

templates/category.liquidLiquid
{% layout 'layout/theme.liquid' %}
{% block content %}
  <h1>{{ category.name }}</h1>
{% endblock %}
  • {% layout %} must be the first tag in the template, with the full path.
  • The layout declares blocks with {% block NAME %}…{% endblock %} and their default content inside; the template fills them by the same name.
  • A layout may declare more than one block (head_extra, content); whatever the template does not fill keeps the layout's default.
  • There is no variable for the template's content inside the layout; {% block %} is the only path.

include#

Liquid
{% include 'snippets/product-card.liquid' %}
{% include 'snippets/pager.liquid', pager_base: category.url %}
{% assign icon = 'cart' %}{% include 'snippets/icons.liquid' %}
  • The path is full from the bundle root with the extension; a missing file fails the render with template not found.
  • The snippet shares the variable scope with its includer (include semantics, not render), and accepts named arguments that become variables.
  • Including assets/theme.css inside <style> is the supported way to ship CSS.

Control flow and loops#

if/elsif/else/unless, case/when, for with forloop, break/continue, limit/offset/reversed, tablerow, assign, capture, comment, liquid, echo: all available as in LiquidJS.

Liquid
{% for product in products limit: 8 %}
  {% if forloop.first %}<ul>{% endif %}
  <li>{{ product.name }}</li>
  {% if forloop.last %}</ul>{% endif %}
{% endfor %}

increment, decrement and cycle#

Warning

These tags keep state across calls. Inside section bodies every body renders twice (capture, then emit), so their counters and cycles drift. Never use them in snippets/section-body.liquid or anything it includes. Use forloop.index and assign instead.

raw (the tag)#

{% raw %}…{% endraw %} is the standard tag for emitting text that contains Liquid braces literally; it is allowed, because it is escaped like any text. Do not confuse it with the neutralized | raw filter.

Rejected at push#

Any non-JSON-LD <script>, inline event handlers, javascript:, data:text/html, <iframe>, the | raw filter, and section wrapper markers. The list with its strings is in Theme validation.