Nunjucks & Liquid Tricks for Eleventy, Shopify, Jekyll etc.


First Things First

Built-in filters

Syntax highlighting in VS Code~editors

.njk

This is a modern fork of the original extension. It is specifically designed to solve the "11ty problem" where you mix Nunjucks and HTML.

Why it's better: It injects Nunjucks grammar directly into the standard HTML grammar. This means you get full Nunjucks highlighting and the editor still knows it's an HTML file, giving you better Emmet and CSS autocompletion.

No Config: It works out of the box without needing to manually map file associations in your settings.

.liquid

Auto-formatting in VS Code~editors

  1. 🧩 Install /prettier/prettier-vscode (if not yet)

.njk

  1. Install a compatible Prettier plugin for your project, for example:
npm i -D prettier-plugin-jinja-template
  1. It might be tricky to find a well-maintained Nunjucks plugin, but jinja-template works just fine with .njk via .html override:
{
  "plugins": ["prettier-plugin-jinja-template"],
  "overrides": [
    {
      "files": ["*.njk", "*.html"],
      "options": {
        "parser": "jinja-template"
      }
    }
  ]
}

PRO TIP: If you already use /anydigital/bricks, it’s even easier. You can simply:

ln -s ./node_modules/@anydigital/bricks/.prettierrc.json

.liquid

  1. 🧩 Install /docs/storefronts/themes/tools/shopify-liquid-vscode (same extension for both highlighting and formatting)
How to prevent unclosed html tags from breaking auto-formatting?

{% # prettier-ignore %} might not help with that, because the parser crashes on the "broken" HTML before it even reads the ignore command.

But there is a trick with "fake" {% if true %} condition:

{% if true %}<html>{% endif %}
<head>
  ...
</head>
{% if true %}<body>{% endif %}
  ...
{% if true %}</body><html>{% endif %}

This "fake conditional" trick is a clever way to bypass the Abstract Syntax Tree (AST) parsers used by formatters like Prettier. Since the formatter sees the tag wrapped in a logic block, it often treats the HTML as a string or a partial fragment rather than a structural error.


Tricks

Create array

{% capture _new_array %}
1
2
3
{% endcapture %}
{% assign _new_array = _new_array | strip | split: '\n' %}

Sort array by attribute

Per official .njk documentation:

sort(arr, reverse, caseSens, attr)
Sort arr with JavaScript's arr.sort function. If reverse is true, result will be reversed. Sort is case-insensitive by default, but setting caseSens to true makes it case-sensitive. If attr is passed, will compare attr from each item.

But you can actually do this trick:

{% for item in array | sort(attribute='weight') %}
  ...
{% endfor %}

Include and render .md file w/o its Front Matter (11ty-only)

{# first, get the raw content using `html` as plain-text engine #}
{% set _eval = "{% renderFile './YOUR_FILE.md', {}, 'html' %}" %}
{% set _raw_md = _eval | renderContent('njk') %}

{# then, remove the front matter using regex, and render using `md` #}
{{ _raw_md | replace(r/^---[\s\S]*?---/, '') | renderContent('md') | safe }}

PS: Beware: False Positives in .liquid

In Liquid templating, a "false positive" usually occurs when a value you expect to be falsy (like an empty string or zero) is actually treated as truthy by the Liquid engine.

Unlike languages like JavaScript or Python, Liquid has very specific rules for truthiness.

The "Everything is Truthy" Rule

In Liquid, only false and nil (null) are falsy. Every other value evaluates to true in a conditional statement.

Common Pitfalls:

Value Liquid Result Why?
"" (Empty String) Truthy An empty string is still a "present" object.
0 (Zero) Truthy Numbers are always truthy, regardless of value.
[] (Empty Array) Truthy An empty collection is not nil.

How to Avoid False Positives

To prevent these values from triggering your if statements, you should check for size or specific content rather than just the variable itself.

The Right Way vs. The Wrong Way:

{% if my_string %} This will always show if the string exists, even if empty. {% endif %}
{% if my_string != blank %} This only shows if there is actual text. {% endif %}
{% if my_array.size > 0 %} This ensures the list isn't empty. {% endif %}

The blank and empty Keywords

Liquid provides special keywords to handle these cases gracefully:

Note: These are often used with the unless tag or the != operator to ensure you are working with meaningful data.


Template Bricks (Components)

The package includes reusable Liquid templates in the bricks/ directory. These are useful for common web development patterns.

Base HTML Template (__html.liquid)

A unified base HTML template that provides the essential document structure with built-in support for modern web best practices.

Features:

Usage:

{% capture page_content %}
  <!-- Your page content -->
{% endcapture %}

{% render 'bricks/__html', site: site, title: title, content_for_header: content_for_header, body: page_content %}

Note: Google Tag Manager is automatically included in both <head> and <body> (via the _gtm.liquid template) when site.prod and site.gtm_id are set.

Variables:

A navigation template that renders a list of navigation links with proper accessibility attributes.

Parameters:

Usage:

{% render 'bricks/_nav', nav_pages: navPages, current_url: page.url %}

Example:

{% assign navPages = site.pages | where: "nav", true %}
{% render 'bricks/_nav', nav_pages: navPages, current_url: page.url %}

Output:

<nav>
  <a href="/">Home</a>
  <a href="/about" aria-current="page">About</a>
  <a href="/contact">Contact</a>
</nav>

Compatibility: Compatible with Eleventy Navigation plugin.

Google Tag Manager (_gtm.liquid)

A template for embedding Google Tag Manager scripts in your pages.

Parameters:

Note: This template is automatically included when using __html.liquid. You only need to manually render it if you're not using that base template.

Manual Usage:

In your base template's <head>:

{% render 'bricks/_gtm', site: site %}

In your base template's <body> (right after the opening tag):

{% render 'bricks/_gtm', site: site, for_body: true %}

Example (Manual Integration):

<!DOCTYPE html>
<html>
<head>
  {% render 'bricks/_gtm', site: site %}
</head>
<body>
  {% render 'bricks/_gtm', site: site, for_body: true %}
  <!-- Your content -->
</body>
</html>

Rendering Logic: The GTM script is only rendered when both site.prod is true and site.gtm_id is set. The template uses a capture block to strip whitespace from the output.