tmpltool uses the MiniJinja template engine, which is compatible with Python's Jinja2. For complete documentation, visit: https://docs.rs/minijinja/
- Syntax Overview
- Variables
- Expressions & Operators
- Template Tags Reference — if, for, set, with, filter, raw, autoescape, do
- Whitespace Control
- Is Syntax (Validation & Checks)
- Comments
- Filters
- Template Includes
Templates contain three types of delimiters:
| Delimiter | Purpose | Example |
|---|---|---|
{{ ... }} |
Expressions (output) | {{ variable }} |
{% ... %} |
Statements (logic) | {% if condition %} |
{# ... #} |
Comments (ignored) | {# note #} |
{{ variable_name }}Note: Environment variables are NOT automatically available. Use the get_env() function to access them:
{{ get_env(name="MY_VAR", default="fallback") }}{{ "string" }} {# String #}
{{ 42 }} {# Integer #}
{{ 3.14 }} {# Float #}
{{ true }} {{ false }} {# Booleans #}
{{ none }} {# Null/None #}
{{ [1, 2, 3] }} {# Array/List #}
{{ {"key": "value"} }} {# Object/Map #}{{ 10 + 5 }} {# 15 - Addition #}
{{ 10 - 5 }} {# 5 - Subtraction #}
{{ 10 * 5 }} {# 50 - Multiplication #}
{{ 10 / 3 }} {# 3.33... - Division #}
{{ 10 // 3 }} {# 3 - Integer division #}
{{ 10 % 3 }} {# 1 - Modulo #}
{{ 2 ** 8 }} {# 256 - Power #}{{ a == b }} {# Equal #}
{{ a != b }} {# Not equal #}
{{ a < b }} {# Less than #}
{{ a <= b }} {# Less than or equal #}
{{ a > b }} {# Greater than #}
{{ a >= b }} {# Greater than or equal #}{{ a and b }} {# Logical AND #}
{{ a or b }} {# Logical OR #}
{{ not a }} {# Logical NOT #}{# String concatenation #}
{{ "Hello, " ~ name ~ "!" }}
{# Membership test #}
{{ "foo" in ["foo", "bar"] }} {# true #}
{{ "x" not in "abc" }} {# true #}
{# Ternary/conditional expression #}
{{ "yes" if condition else "no" }}
{# Default value (if undefined or none) #}
{{ value | default("fallback") }}
{# Attribute access #}
{{ object.property }}
{{ object["property"] }}
{# Array indexing #}
{{ array[0] }}
{{ array[-1] }} {# Last element #}
{# Slicing #}
{{ array[1:3] }} {# Elements 1 and 2 #}
{{ array[:2] }} {# First 2 elements #}
{{ array[2:] }} {# From index 2 to end #}Conditional statement execution with multiple branches.
{% if condition %}
content when true
{% elif other_condition %}
alternative content
{% else %}
fallback content
{% endif %}Important: get_env() cannot be used directly in {% if %} conditions. Use {% set %} to assign to a variable first:
{% set debug = get_env(name="DEBUG", default="false") %}
{% if debug == "true" %}
Debug mode enabled
{% endif %}Truthiness: Empty strings, 0, false, none, and empty collections are falsy. Everything else is truthy.
Loop over sequences (arrays, objects, ranges).
{% for item in items %}
{{ item }}
{% endfor %}Loop Variables:
| Variable | Description |
|---|---|
loop.index |
Current iteration (1-indexed) |
loop.index0 |
Current iteration (0-indexed) |
loop.first |
True if first iteration |
loop.last |
True if last iteration |
loop.length |
Total number of items |
loop.revindex |
Iterations remaining (1-indexed) |
loop.revindex0 |
Iterations remaining (0-indexed) |
loop.depth |
Nesting level (starts at 1) |
loop.depth0 |
Nesting level (starts at 0) |
Examples:
{# Basic loop with index #}
{% for user in users %}
{{ loop.index }}. {{ user.name }}
{% endfor %}
{# Alternating row styles #}
{% for row in rows %}
<tr class="{{ loop.cycle('odd', 'even') }}">
<td>{{ row }}</td>
</tr>
{% endfor %}
{# Loop with else (empty collection) #}
{% for item in items %}
{{ item }}
{% else %}
No items found.
{% endfor %}
{# Unpacking tuples/pairs #}
{% for key, value in object | items %}
{{ key }}: {{ value }}
{% endfor %}
{# Filtering during iteration #}
{% for user in users if user.active %}
{{ user.name }}
{% endfor %}
{# Range loop #}
{% for i in range(5) %}
Item {{ i }}
{% endfor %}Assign values to variables in the current scope.
{% set variable = value %}
{% set name = get_env(name="USER", default="guest") %}Unpacking:
{% set a, b = [1, 2] %}
{% set first, rest = items | first, items | slice(1) %}Block form (capture template output):
{% set navigation %}
<nav>
<a href="/">Home</a>
<a href="/about">About</a>
</nav>
{% endset %}
{# Use later #}
{{ navigation }}With filter:
{% set title | upper %}
page title
{% endset %}
{# title = "PAGE TITLE" #}Create a new inner scope for temporary variables.
{% with %}
{% set temp = "value" %}
{{ temp }} {# works here #}
{% endwith %}
{# temp is not accessible here #}Inline assignment:
{% with foo = 42, bar = "hello" %}
{{ foo }} - {{ bar }}
{% endwith %}Apply filters to entire blocks of content.
{% filter upper %}
this text will be uppercase
{% endfilter %}
{% filter indent(width=4) %}
line 1
line 2
{% endfilter %}Output literal template syntax without processing.
{% raw %}
This {{ will not }} be processed.
{% for item in seq %} is literal text.
{% endraw %}Useful when generating templates or documentation containing Jinja syntax.
Toggle HTML auto-escaping within template regions.
{% autoescape true %}
{{ user_input }} {# HTML entities escaped #}
{% endautoescape %}
{% autoescape false %}
{{ trusted_html }} {# Output as-is #}
{% endautoescape %}Execute expressions without outputting results (useful for side-effects).
{% do my_list.append(item) %}Control whitespace around template tags using -:
{# Remove whitespace before tag #}
{%- if true %}
{# Remove whitespace after tag #}
{% if true -%}
{# Remove whitespace on both sides #}
{%- if true -%}
{# Also works with expressions #}
{{- variable -}}Example:
<ul>
{%- for item in items %}
<li>{{ item }}</li>
{%- endfor %}
</ul>Without -, you'd get extra blank lines. With -, whitespace is trimmed.
The is syntax provides readable conditionals for validation and type checking. All is-functions support both function syntax and the more readable "is" syntax:
| Test | Function Equivalent | Description |
|---|---|---|
{% if x is email %} |
is_email(string=x) |
Valid email format |
{% if x is url %} |
is_url(string=x) |
Valid URL format |
{% if x is ip %} |
is_ip(string=x) |
Valid IPv4/IPv6 address |
{% if x is uuid %} |
is_uuid(string=x) |
Valid UUID format |
{% if y is leap_year %} |
is_leap_year(year=y) |
Year is a leap year |
{% if p is port_available %} |
is_port_available(port=p) |
Port is free to use |
{% if f is file %} |
is_file(path=f) |
Path is an existing file |
{% if d is dir %} |
is_dir(path=d) |
Path is a directory |
{% if s is symlink %} |
is_symlink(path=s) |
Path is a symbolic link |
Examples:
{# Validate user input #}
{% if user_email is email %}
Valid email: {{ user_email }}
{% else %}
Invalid email format
{% endif %}
{# Check filesystem #}
{% if "config.json" is file %}
{% set config = read_json_file(path="config.json") %}
{% endif %}
{# Port availability #}
{% if 8080 is port_available %}
port: 8080
{% elif 3000 is port_available %}
port: 3000
{% endif %}
{# Negation with "is not" #}
{% if user_input is not uuid %}
Warning: Invalid ID format
{% endif %}{# This is a comment - not rendered in output #}
{#
Multi-line comments
are also supported
#}{{ variable | filter_name }}
{{ variable | filter_name(arg=value) }}
Built-in MiniJinja filters:
upper,lower,title- Case conversiontrim,truncate- String operationsdate(format="%Y-%m-%d")- Date formattingsplit(pat=",")- Split string into arraylength- Get array/string length
String manipulation (function + filter syntax):
All string filters support both function and filter syntax:
slugify(string)/| slugify- Convert to URL-friendly slug (e.g., "Hello World" → "hello-world")indent(string, spaces=4)/| indent(spaces=4)- Indent text by N spaces (useful for YAML/configs)dedent(string)/| dedent- Remove common leading whitespacequote(string, style="double")/| quote(style="double")- Quote string (single/double/backtick)escape_quotes(string)/| escape_quotes- Escape quotes in stringto_snake_case(string)/| to_snake_case- Convert to snake_case (e.g., "HelloWorld" → "hello_world")to_camel_case(string)/| to_camel_case- Convert to camelCase (e.g., "hello_world" → "helloWorld")to_pascal_case(string)/| to_pascal_case- Convert to PascalCase (e.g., "hello_world" → "HelloWorld")to_kebab_case(string)/| to_kebab_case- Convert to kebab-case (e.g., "HelloWorld" → "hello-world")pad_left(string, length, char=" ")/| pad_left(length, char=" ")- Pad string on leftpad_right(string, length, char=" ")/| pad_right(length, char=" ")- Pad string on rightrepeat(string, count)/| repeat(count)- Repeat string N timesreverse(string)/| reverse- Reverse string
Formatting (function + filter syntax):
filesizeformat(bytes)/| filesizeformat- Format bytes (e.g., "1.5 KB")urlencode(string)/| urlencode- URL encoding (percent-encoding)
Examples:
{# Case conversion - both syntaxes work #}
{{ "hello_world" | to_camel_case }} {# Output: helloWorld #}
{{ to_camel_case(string="hello_world") }} {# Output: helloWorld #}
{{ "HelloWorld" | to_snake_case }} {# Output: hello_world #}
{{ to_snake_case(string="HelloWorld") }} {# Output: hello_world #}
{# Slugify #}
{{ "Hello World!" | slugify }} {# Output: hello-world #}
{{ slugify(string="Hello World!") }} {# Output: hello-world #}
{# Indentation for configs #}
{{ "host: localhost\nport: 8080" | indent(spaces=2) }}
{{ indent(string="host: localhost", spaces=4) }}
{# Padding for alignment #}
{{ "1" | pad_left(length=4, char="0") }} {# Output: 0001 #}
{{ pad_left(string="5", length=3, char="0") }} {# Output: 005 #}
{# Creating separators #}
{{ "=" | repeat(count=40) }} {# Output: ======================================== #}
{{ repeat(string="-", count=5) }} {# Output: ----- #}
{# Quoting #}
{{ "hello" | quote(style="single") }} {# Output: 'hello' #}
{{ quote(string="world", style="backtick") }} {# Output: `world` #}
{# Chaining filters #}
{{ "hello_world" | to_pascal_case | reverse }} {# Output: dlroWolleH #}
{# Formatting - both syntaxes work #}
{{ 1048576 | filesizeformat }} {# Output: 1 MB #}
{{ filesizeformat(bytes=1048576) }} {# Output: 1 MB #}
{{ "hello world" | urlencode }} {# Output: hello%20world #}
{{ urlencode(string="hello world") }} {# Output: hello%20world #}
Template includes allow you to modularize your templates by splitting them into reusable partials. This promotes code reuse and keeps templates maintainable.
{% include "./path/to/partial.tmpltool" %}Important: Always use relative paths starting with ./ for includes.
Include a template from the same directory:
templates/
├── main.tmpltool
└── header.tmpltool
main.tmpltool:
{% include "./header.tmpltool" %}
Main content hereheader.tmpltool:
=== Header ===Output:
=== Header ===
Main content here
Include templates from subdirectories:
templates/
├── main.tmpltool
└── partials/
├── header.tmpltool
└── footer.tmpltool
main.tmpltool:
{% include "./partials/header.tmpltool" %}
Main content
{% include "./partials/footer.tmpltool" %}Includes can be nested—an included template can include other templates:
templates/
├── main.tmpltool
├── layout.tmpltool
└── components/
└── nav.tmpltool
main.tmpltool:
{% include "./layout.tmpltool" %}layout.tmpltool:
<header>
{% include "./components/nav.tmpltool" %}
</header>
<main>Content</main>components/nav.tmpltool:
<nav>Navigation menu</nav>Output:
<header>
<nav>Navigation menu</nav>
</header>
<main>Content</main>Combine multiple partials to build complex configurations:
{% include "./partials/header.tmpltool" %}
{% include "./partials/database.tmpltool" %}
{% include "./partials/logging.tmpltool" %}
{% include "./partials/footer.tmpltool" %}Use conditionals to include templates based on environment variables:
{% set env = get_env(name="ENVIRONMENT", default="development") %}
{% if env == "production" %}
{% include "./configs/production.tmpltool" %}
{% elif env == "staging" %}
{% include "./configs/staging.tmpltool" %}
{% else %}
{% include "./configs/development.tmpltool" %}
{% endif %}Or toggle optional sections:
{% set enable_monitoring = get_env(name="ENABLE_MONITORING", default="false") %}
{% if enable_monitoring == "true" %}
{% include "./partials/monitoring.tmpltool" %}
{% endif %}Included templates have access to all variables defined in the parent template:
main.tmpltool:
{% set app_name = get_env(name="APP_NAME", default="myapp") %}
{% set port = get_env(name="PORT", default="8080") %}
{% include "./server-config.tmpltool" %}server-config.tmpltool:
server:
name: {{ app_name }}
port: {{ port }}Included templates can also use all tmpltool functions like get_env(), read_file(), etc.
By default, includes are restricted for security:
| Path Type | Default Mode | Trust Mode (--trust) |
|---|---|---|
Same directory (./file.tmpltool) |
✅ Allowed | ✅ Allowed |
Subdirectory (./sub/file.tmpltool) |
✅ Allowed | ✅ Allowed |
Parent directory (../file.tmpltool) |
❌ Blocked | ✅ Allowed |
Absolute path (/etc/file) |
❌ Blocked | ✅ Allowed |
Parent directory access blocked (default):
{# This will fail without --trust #}
{% include "../shared/common.tmpltool" %}Error:
Security: Parent directory (..) traversal is not allowed: ../shared/common.tmpltool.
Use --trust to bypass this restriction.
Enable with trust mode:
tmpltool --trust template.tmpltoolOrganize a Docker Compose template with reusable service partials:
templates/
├── docker-compose.tmpltool
└── services/
├── web.tmpltool
├── database.tmpltool
└── redis.tmpltool
docker-compose.tmpltool:
version: "3.8"
services:
{% include "./services/web.tmpltool" %}
{% set use_db = get_env(name="USE_DATABASE", default="true") %}
{% if use_db == "true" %}
{% include "./services/database.tmpltool" %}
{% endif %}
{% set use_cache = get_env(name="USE_CACHE", default="false") %}
{% if use_cache == "true" %}
{% include "./services/redis.tmpltool" %}
{% endif %}services/web.tmpltool:
web:
image: {{ get_env(name="WEB_IMAGE", default="nginx:latest") }}
ports:
- "{{ get_env(name="WEB_PORT", default="80") }}:80"services/database.tmpltool:
database:
image: postgres:15
environment:
POSTGRES_DB: {{ get_env(name="DB_NAME", default="app") }}
POSTGRES_USER: {{ get_env(name="DB_USER", default="postgres") }}Render:
WEB_PORT=8080 USE_CACHE=true tmpltool templates/docker-compose.tmpltoolSplit Kubernetes manifests into reusable components:
k8s/
├── deployment.tmpltool
└── components/
├── metadata.tmpltool
├── containers.tmpltool
└── resources.tmpltool
deployment.tmpltool:
apiVersion: apps/v1
kind: Deployment
{% include "./components/metadata.tmpltool" %}
spec:
replicas: {{ get_env(name="REPLICAS", default="3") }}
template:
spec:
containers:
{% include "./components/containers.tmpltool" %}components/metadata.tmpltool:
metadata:
name: {{ get_env(name="APP_NAME", default="myapp") }}
namespace: {{ get_env(name="NAMESPACE", default="default") }}
labels:
app: {{ get_env(name="APP_NAME", default="myapp") }}- Use
./prefix - Always start include paths with./for clarity - Organize partials - Keep partials in subdirectories like
partials/,components/, orincludes/ - Name clearly - Use descriptive names like
header.tmpltool,db-config.tmpltool - Keep partials focused - Each partial should do one thing well
- Document dependencies - Comment which variables a partial expects
- Avoid deep nesting - Limit include depth for maintainability (2-3 levels max)