Reference

Nunchucks templating reference focused on implementation details and parity work.

Historical syntax inspiration: mozilla.github.io/nunjucks/templating.html

Nunchucks syntax preview

{% import "macros/ui.njk" as ui %}
{% set active = users | selectattr("active") %}
{% call ui.panel("success") %}
  {{ active | length }} active users
{% endcall %}

Pick A Language

Select runtime docs examples for the same template concepts.

env := nunchucks.Configure(nunchucks.ConfigOptions{Path: "views"})
out, err := env.Render("app.njk", map[string]any{"user": map[string]any{"name": "sam"}})
if err != nil { panic(err) }
fmt.Println(out)

Directory

Each item links to a detailed section with examples.

Core Topics

User-Defined Templates Warning

Treat templates as executable input. Do not render untrusted template source without a sandbox boundary.

go run ./cmd/nunchucks render -views ./views -template app.njk -data '{\"safe\":true}'

File Extensions

Use .njk for templates and reserve .html / target formats for rendered output.

views/\n  layout.njk\n  app.njk\n  partials/user-card.njk

Syntax Highlighting

This docs site uses highlight.js with language-django for template syntax. The repo also ships a custom VS Code extension in vscode-nunchucks and workspace config in .vscode/settings.json for .njk highlighting.

Marketplace extension: BishopCoTech.nunchucks-syntax (search for nunchucks syntax in VS Code extensions).

{% if user.isAdmin %}{{ \"ok\" | upper }}{% endif %}

VS Code Setup (requested style)

1) Install the local extension folder vscode-nunchucks in VS Code. 2) Use workspace settings in .vscode/settings.json. This repo maps .njk to nunchucks-template and maps .html/.yaml/.yml to HTML syntax for your preferred color model (HTML tags orange, attributes blue).

{
  "files.associations": {
    "*.html": "html",
    "*.yaml": "html",
    "*.yml": "html",
    "*.njk": "nunchucks-template"
  },
  "editor.tokenColorCustomizations": {
    "textMateRules": [
      {
        "scope": ["entity.name.tag", "entity.name.tag.html"],
        "settings": { "foreground": "#ce9178" }
      },
      {
        "scope": ["entity.other.attribute-name", "entity.other.attribute-name.html"],
        "settings": { "foreground": "#4FC1FF" }
      }
    ]
  },
  "editor.showUnused": true
}

Unused gray underline requires diagnostics from a language server/linter. VS Code can style unnecessary code, but true \"unused\" detection in .njk depends on extension support.

Variables

Variables resolve from context and local scope. Dot-path lookups are supported.

Hello {{ user.name | default("guest", true) }}

Filters

Filters transform values and chain left-to-right.

{{ title | trim | lower | capitalize }}
{% filter upper %}hello {{ user.name }}{% endfilter %}

Template Inheritance

Use extends + block for layouts and super() to include parent output.

{% extends "layout.njk" %}
{% block body %}
  {{ super() }}
  

Page body

{% endblock %}

super

super() inserts parent block output so child templates can append rather than replace.

{% block header %}\n  {{ super() }}\n  Subheader\n{% endblock %}

Keyword Arguments

Macros and callable expressions support named arguments.

{% macro badge(text, kind="info") %}[{{ kind }}] {{ text }}{% endmacro %}
{{ badge("API", kind="success") }}

Comments

Comments do not render output and are useful for in-template notes.

{# rendered output ignores this #}\nVisible value

Whitespace Control

Whitespace in templates impacts generated output, especially config files.

{% if enabled %}\nvalue: {{ value }}\n{% endif %}

Autoescaping

Use escape, forceescape, and safe intentionally when rendering HTML.

{{ html | escape }}\n{{ trustedHtml | safe }}

Tags

if

{% if user %}Hi {{ user.name }}{% else %}Hi guest{% endif %}

for

{% for item in items %}
  {{ loop.index0 }}:{{ item }}
{% endfor %}

macro + call

{% macro panel(kind="info") %}
{{ caller() }}
{% endmacro %} {% call panel("success") %}inside{% endcall %}

include + import

{% import "macros/ui.njk" as ui %}
{% include "partials/user-card.njk" with context %}
{% include "partials/optional.njk" ignore missing %}

raw + verbatim + filter block

{% raw %}{{ untouched }}{% endraw %}
{% verbatim %}{% if x %}y{% endif %}{% endverbatim %}
{% filter upper %}hello {{ user.name }}{% endfilter %}

Expressions

Math

{{ 1 + 2 * 3 }}
{{ (1 + 2) * 3 }}

Comparisons + is/in

{{ 2 < 3 < 4 }}
{{ role is "admin" }}
{{ 2 in nums }}
{{ 5 not in nums }}

Logic + if expression

{{ isAuth and isAdmin }}
{{ "yes" if user.isAdmin else "no" }}

Function Calls

{{ range(1, 8, 2) | join(",") }}

Global Functions

range([start], stop, [step])

{{ range(5) | join(",") }}
{{ range(1, 8, 2) | join(",") }}
{{ range(5, 0, -2) | join(",") }}

<todo still: cycler and joiner docs when implemented>

Builtin Filters

Real code examples for built-in filters (grouped by purpose).

Strings + Formatting

{{ " nunchucks " | trim | capitalize }}
{{ "nunchucks" | upper }}
{{ "NUNCHUCKS" | lower }}
{{ "hello world" | title }}
{{ "hello world" | center(20) }}
{{ "long text for docs" | truncate(10, false, "...") }}
{{ "a\nb" | nl2br }}
{{ "hi" | striptags }}

Collections

{{ nums | first }}
{{ nums | last }}
{{ nums | length }}
{{ nums | join("-") }}
{{ nums | reverse | join(",") }}
{{ nums | batch(2) | length }}
{{ nums | slice(3) | length }}
{{ users | sort(false, false, "name") | first | dump }}
{{ users | groupby("role", "none") | length }}

Selection Filters

{{ nums | select("odd") | length }}
{{ nums | reject("odd") | length }}
{{ users | selectattr("active") | length }}
{{ users | rejectattr("active") | length }}
{{ users | selectattr("age", "divisibleby", 2) | length }}

Numbers + Types

{{ -4 | abs }}
{{ "3.14" | float }}
{{ "42" | int }}
{{ nums | sum }}
{{ 3.14159 | round(2) }}
{{ value | string }}

Escaping + URLs + Debug

{{ html | escape }}
{{ html | forceescape }}
{{ trusted | safe }}
{{ "a b" | urlencode }}
{{ textWithUrl | urlize }}
{{ body | wordcount }}
{{ data | dictsort("value", false, true) | first | first }}
{{ state | dump }}

<todo still: publish per-filter parity status table and edge-case notes>