Skip to content

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.

Found a problem on this page? Report it