Template Feature Cheat Sheet
Keep this page beside a layout when you need to recall a marker, expression, filter, or statement.
The examples use the default delimiters and a bird nesting record with names such as nest and eggs.
The template language topic explains how these pieces work together; the
rendering reference gives the exact value and error rules.
Markers and Whitespace
Ordinary text is copied to the result. The three marker pairs have distinct jobs:
Purpose |
Example |
Result |
|---|---|---|
Insert a scalar value |
|
The species name |
Control rendering |
|
|
Leave a source comment |
|
Nothing |
Add - just inside a marker to strip adjacent ASCII whitespace, as in {{- nest.species -}} or {%- endif -%}.
Without a whitespace marker, source spaces and line breaks remain in the result.
An environment can choose different delimiter pairs; see Configuring the Render Environment.
Expressions at a Glance
An expression may appear inside {{ ... }}, in a condition, on the right of set, or after in in a loop.
Names read the active context, and dots read members of a map.
Missing names resolve to null.
Form |
Compact example |
|---|---|
Name and map member |
|
Literals |
|
Collections |
|
Grouping and signs |
|
Arithmetic |
|
Text concatenation |
|
Comparison |
|
Membership |
|
Value test |
|
Boolean logic |
|
Filter chain |
|
String literals accept \\, \', \", \b, \f, \n, \r, \t, and \uXXXX escapes.
Lists and maps can be nested and may have one trailing comma.
Filters take at most two positional arguments after their input value.
They run from left to right.
From tightest to loosest, binding is: members and collections, filters, unary + and -, * and /, +
and - and ~, comparisons and tests, not, and, then or.
Use parentheses when that order is not immediately clear.
and and or stop early and return an operand; comparisons and not return a Boolean.
Combine comparisons with and rather than chaining them.
Available is Tests
Use value is test or value is not test.
The tests are none (also null ), true, false, boolean, integer, float, number, text
(also string ), list (also sequence ), map (also mapping ), iterable, scalar, even, and
odd.
For example, eggs is odd and nest is mapping ask different questions about the same record.
Built-In Filters
A pipe sends the expression on its left to a filter.
For example, {{ nest.species | trim | capitalize }} first removes surrounding whitespace and then changes the
initial letter.
These examples show every built-in filter and its optional arguments; Template Language Syntax gives a longer
example of filters in a rendered layout.
Text
Filter |
Example |
|---|---|
|
|
|
|
|
|
|
|
|
|
Collections
Filter |
Example |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
first, last, min, and max return null for an empty collection.
batch splits a list into lists of the given positive size.
The last list is shorter unless a non-null fill value is supplied to pad it; an empty input list produces an empty
list.
A list or map cannot be inserted directly; select a scalar, join suitable entries, or use a loop.
Numbers
Filter |
Example |
|---|---|
|
|
|
|
|
|
|
|
The round mode is 'common' (the default), 'ceil', or 'floor'.
Defaults and JSON
Filter |
Example |
|---|---|
|
|
|
|
default replaces null; its second argument also replaces other false values when true.
tojson accepts an indentation from zero through 16 and returns ordinary text.
The safe modifier in the example prevents a second escaping pass when the JSON is inserted directly.
Statements and Layout Structure
Statements control which text is written and where values are available.
The examples below can be read as small layout fragments; Sharing Layouts with Inheritance develops the shared-layout
workflow, and Loading Render Layouts explains how names such as parts/nest.txt are loaded.
Feature |
Compact form |
|---|---|
Conditional |
|
Local assignment |
|
List loop |
|
Map loop |
|
Include |
|
Optional include |
|
Isolated include |
|
Inheritance |
|
Named block |
|
Parent block content |
|
An include uses the current context by default; with context states that choice explicitly.
ignore missing and one context modifier can be combined in either order.
Include and extends names are fixed string literals, not expressions.
An extending layout puts its output inside blocks; see Sharing Layouts with Inheritance for the exact outer-layout rules.
Inside a list loop, loop.index starts at one and loop.index0 starts at zero.
loop.revindex and loop.revindex0 count from the end; loop.first, loop.last, and loop.length describe
the current iteration.
A loop’s else runs when its collection is empty.
Each iteration has its own local scope, and set never changes the caller’s context.
Within a block, super() renders the next parent implementation and super.super() reaches one further level.
Output Escaping
The layout name selects automatic escaping: .html, .xml, .elcl, .md, and .json have built-in
formats; other suffixes write scalars without automatic escaping.
For an individual direct insertion, put an output modifier last:
Modifier |
Example |
|---|---|
|
|
|
|
Explicit format |
|
escape without an argument uses the format selected by the suffix, or HTML if no format was selected.
safe and escape apply only to a direct output expression, not to assignments or conditions.
Keep the intended output format in mind when using safe; it inserts the value without automatic escaping.
See Configuring the Render Environment for suffix mappings and custom escaping options.
A Working Layout
This compiled demo brings together map lookup, arithmetic, tests, filters, conditions, and a loop. Its context supplies the data, while the layout decides how to present it.
text/RenderLayouts/FeatureCheatSheet.cpp
/// Render a bird nesting record from a compact layout.
///
/// A map supplies named fields, a list supplies observations, and the layout combines expressions, tests, filters,
/// conditions, local assignments, and iteration without changing the caller's context.
/// @notest{Demo function verified by the documentation executable.}
void featureCheatSheet() {
using el::render::Value;
using el::render::ValueList;
using el::render::ValueMap;
const auto directory = el::Path::systemTempDirectoryOrThrow().operations().createTempDirectoryOrThrow();
(directory->path() / "nest.txt"_el)
.content()
.writeTextOrThrow(
"Pesä: {{ nest.species | capitalize }}\n"
"{% set next_count = nest.eggs + 1 %}Munia: {{ nest.eggs }}; seuraava: {{ next_count }}\n"
"{% if nest.eggs is odd %}Pariton määrä\n{% endif %}"
"{% for sighting in sightings %}{{ loop.index }}. {{ sighting | trim }}\n{% endfor %}"
"Merkitsijä: {{ keeper | default('tuntematon') }}\n"_el);
// Prepare the record and its observations for one render call.
auto nest = ValueMap{};
nest.set("species"_el, Value{"kirjosieppo"_el}).set("eggs"_el, Value{3});
const auto sightings = ValueList{Value{" aamulla "_el}, Value{"illalla"_el}};
const auto context = el::render::Context{}.set("nest"_el, nest).set("sightings"_el, sightings);
const auto environment = el::render::Environment::create();
environment->addLayoutLoader(el::render::FileSystemLoader::create(directory->path()));
el::io::print(environment->render("nest.txt"_el, context));
}
Output
Pesä: Kirjosieppo
Munia: 3; seuraava: 4
Pariton määrä
1. aamulla
2. illalla
Merkitsijä: tuntematon
Complete demo sources
text/RenderLayouts/FeatureCheatSheet.cpp
// Copyright (c) 2026 Tobias Erbsland - https://erbsland.dev
// SPDX-License-Identifier: Apache-2.0
#include <DemoCommon.hpp>
#include <erbsland/conf/ValueList.hpp>
#include <erbsland/List.hpp>
#include <erbsland/Path.hpp>
#include <erbsland/PathContent.hpp>
#include <erbsland/PathOperations.hpp>
#include <erbsland/render/Context.hpp>
#include <erbsland/render/Environment.hpp>
#include <erbsland/render/FileSystemLoader.hpp>
#include <erbsland/render/Value.hpp>
#include <erbsland/StandardStreams.hpp>
#include <erbsland/TempDirectory.hpp>
namespace demo {
/// Render a bird nesting record from a compact layout.
///
/// A map supplies named fields, a list supplies observations, and the layout combines expressions, tests, filters,
/// conditions, local assignments, and iteration without changing the caller's context.
/// @notest{Demo function verified by the documentation executable.}
void featureCheatSheet() {
using el::render::Value;
using el::render::ValueList;
using el::render::ValueMap;
const auto directory = el::Path::systemTempDirectoryOrThrow().operations().createTempDirectoryOrThrow();
(directory->path() / "nest.txt"_el)
.content()
.writeTextOrThrow(
"Pesä: {{ nest.species | capitalize }}\n"
"{% set next_count = nest.eggs + 1 %}Munia: {{ nest.eggs }}; seuraava: {{ next_count }}\n"
"{% if nest.eggs is odd %}Pariton määrä\n{% endif %}"
"{% for sighting in sightings %}{{ loop.index }}. {{ sighting | trim }}\n{% endfor %}"
"Merkitsijä: {{ keeper | default('tuntematon') }}\n"_el);
// Prepare the record and its observations for one render call.
auto nest = ValueMap{};
nest.set("species"_el, Value{"kirjosieppo"_el}).set("eggs"_el, Value{3});
const auto sightings = ValueList{Value{" aamulla "_el}, Value{"illalla"_el}};
const auto context = el::render::Context{}.set("nest"_el, nest).set("sightings"_el, sightings);
const auto environment = el::render::Environment::create();
environment->addLayoutLoader(el::render::FileSystemLoader::create(directory->path()));
el::io::print(environment->render("nest.txt"_el, context));
}
}