Syntax Reference
NML uses 4-space indentation to express nesting. No closing tags. No angle brackets.
Elements & Attributes
Every line is an element. Attributes chain with dot notation. Text content follows a pipe character.
div.class("container").id("main")
Boolean attributes (like disabled, checked) need no value.
Variable Interpolation
Use double curly braces to output context values. Values are HTML-escaped by default. Dot notation accesses nested properties.
p | Hello, {{ name }}!
If a variable is not found, the placeholder renders verbatim — no crash, no empty string.
Loops — @each
ul
| Condition | Result |
|---|---|
| Non-empty array | Renders children N times |
| Empty array [] | Renders nothing |
| Missing key | Renders nothing silently |
Nested loops work naturally — inner loops can access outer item variables
@each(categories as category)
Conditionals — @if
@if(user.isAdmin)
NML uses Pythonic truthiness — empty arrays, empty strings, 0, null, and undefined all evaluate to false
| Value | Truthy? |
|---|---|
| null / undefined / false | ✗ false |
| 0 / "" / [] / {} | ✗ false |
| any non-empty value | ✓ true |
Filter Pipeline
0.00
{{ html|raw }}
div.x-data("")
| Filter | Description |
|---|---|
| uppercase | Converts to UPPERCASE |
| lowercase | Converts to lowercase |
| trim | Strips leading/trailing whitespace |
| json | Serialises to JSON (raw, not HTML-escaped) |
| default("x") | Returns "x" if value is falsy |
| raw | Bypasses HTML escaping entirely |
Partials — @include
doctype.html
Partials inherit the full parent context automatically.
Partials can include other partials. Circular includes throw a parse error.
The readFile option accepts any async function — filesystem, R2, D1, or in-memory.