Unreleased documentation. Choose your installed release in
the version menu. Features described here may be absent from that release.
Template syntax
Learn the syntax with small examples you can edit in your browser. These
examples produce text and don't connect to a cluster. For applying a template to
your installation, follow Write your first template.
Start with a small edit. In this template, {% var … %} defines a value and
{{ … }} writes it into the output:
{% var maxConnections = 1000 %}
global
maxconn {{ maxConnections }}
Change maxConnections from 1000 to 2000. The output updates to maxconn 2000. This is a configuration fragment; later examples generate a complete file.
Templates combine literal text with expressions, loops, and conditions.
{{ expression }} writes a value; {% statement %} runs a statement.
The syntax follows the Scriggo template language.
Control structures
The loop emits a backend for each environment. The condition gives production
more connection slots:
{% var environments = []string{"production", "staging"} %}
{% for _, environment := range environments %}
backend {{ environment }}
{% if environment == "production" %}
fullconn 2000
{% else %}
fullconn 500
{% end %}
{% end %}
Add another environment to the list and find its backend in the output.
{# … #} adds a template comment that doesn't appear in the output.
Avoid reserved words such as type, range, and default as variable names;
use names such as resourceType, items, and defaultPort instead.
Helper functions
HAPTIC provides helpers for resource and configuration templates: nil-safe navigation (dig, fallback, toSlice), string and map utilities, deduplication (first_seen), sorting (sort_by), and version gates (semver_gte). The Template Reference lists every function with its calling styles and an example each.
Try the helpers in a template and inspect the text they produce:
{# Every helper from the Template Reference is available here. Edit freely. #}
{%- var envs = []any{"prod", "dev", "staging"} %}
{%- var sorted = envs | sort_by([]string{"$"}) %}
{%- for _, e := range sorted %}
backend {{ e }}
server app {{ toLower(tostring(e)) }}.svc:80
{%- end %}
sort_by([]string{"$.weight:desc", "$.name"}) sorts by weight descending,
then by name when weights match. Try that two-key sort:
List the backends heaviest-first, breaking ties by name. Fix the sorted line with sort_by and check the output order.
{# Challenge: list the backends heaviest-first, ties broken by name.
sort_by(items, criteria) sorts a []any by criteria like "$.field:desc". #}
{%- var backends = []any{
map[string]any{"name": "web", "weight": 10},
map[string]any{"name": "api", "weight": 30},
map[string]any{"name": "cache", "weight": 30},
} %}
{#- TODO: sort by weight (desc), then name (asc). Fix the next line. -#}
{%- var sorted = backends %}
{%- for _, be := range sorted %}
server {{ be["name"] }} weight {{ be["weight"] }}
{%- end %}
Solution
$.weight:desc sorts by weight descending; $.name breaks ties alphabetically.
{%- var backends = []any{
map[string]any{"name": "web", "weight": 10},
map[string]any{"name": "api", "weight": 30},
map[string]any{"name": "cache", "weight": 30},
} %}
{%- var sorted = backends | sort_by([]string{"$.weight:desc", "$.name"}) %}
{%- for _, be := range sorted %}
server {{ be["name"] }} weight {{ be["weight"] }}
{%- end %}
first_seen("backend", serviceName) returns true for the first occurrence
of a service name in that group and false for later occurrences. Use it to emit
one backend when several routes share a service:
Several routes share a service; emit one backend line per unique service instead of one per route.
{%- var routes = []any{
map[string]any{"host": "a.example.com", "service": "api"},
map[string]any{"host": "b.example.com", "service": "api"},
map[string]any{"host": "c.example.com", "service": "web"},
} -%}
{% for _, r := range routes -%}
{%- var svc = r | dig("service") | fallback("") -%}
{#- TODO: a service can back many hosts — emit each backend only once -#}
backend {{ svc }}
{% end -%}
Peek at the solution
Gate the emit on first_seen("backend", svc) — it returns true only the first time it sees each service key, so the repeat is skipped.
{%- var routes = []any{
map[string]any{"host": "a.example.com", "service": "api"},
map[string]any{"host": "b.example.com", "service": "api"},
map[string]any{"host": "c.example.com", "service": "web"},
} -%}
{% for _, r := range routes -%}
{%- var svc = r | dig("service") | fallback("") -%}
{% if first_seen("backend", svc) -%}
backend {{ svc }}
{% end -%}
{% end -%}
Mutable variables
Accumulate values across nested loops with append, then emit the collected result. This flattens every endpoint address into one numbered server list:
{# Collect every address across nested loops, then emit them with a
running index. #}
{%- var addresses = []any{} %}
{%- var slices = []any{
map[string]any{"endpoints": []any{
map[string]any{"addresses": []any{"10.0.0.1"}},
map[string]any{"addresses": []any{"10.0.0.2"}},
}},
map[string]any{"endpoints": []any{
map[string]any{"addresses": []any{"10.0.0.3"}},
}},
} %}
{%- for _, es := range slices %}
{%- for _, ep := range es | dig("endpoints") | toSlice() %}
{%- for _, addr := range ep | dig("addresses") | toSlice() %}
{%- addresses = append(addresses, addr) %}
{%- end %}
{%- end %}
{%- end %}
{%- for i, addr := range addresses %}
server srv{{ i + 1 }} {{ addr }}:80
{%- end %}
Whitespace control
Add - inside a tag to trim adjacent whitespace: {%- strips whitespace before the tag, -%} strips whitespace after it.
{%- for _, item := range items %} {# Strip before #}
{% for _, item := range items -%} {# Strip after #}
{%- for _, item := range items -%} {# Strip both #}
The stripped loop below renders one clean line per environment. Delete a dash and re-run to see the blank lines it was removing:
{# `{%-` strips the newline before the tag and `-%}` strips the one after,
so this loop renders tight lines instead of a gap-filled block. #}
{%- var envs = []any{"prod", "staging", "dev"} %}
{%- for _, env := range envs %}
server {{ env }}.svc:80
{%- end %}
See the function reference for all available helpers.