A Go implementation of the Jinja2 template engine with comprehensive support for standard features including variables, loops, conditionals, filters, tests, and template inclusion.
Comparison with Python's Jinja2 v3.1 template engine:
| Feature | Python Jinja2 | This Implementation | Notes |
|---|---|---|---|
| Core Syntax | |||
Variable substitution {{ var }} |
âś… | âś… | Full support |
Comments {# comment #} |
âś… | âś… | Full support |
| Control Structures | |||
{% for %} loops |
âś… | âś… | With tuple unpacking |
{% if %} conditionals |
âś… | âś… | With elif/else support |
{% set %} variables |
âś… | âś… | Full support |
{% include %} |
âś… | âś… | Full support |
{% extends %} / {% block %} |
✅ | ❌ | Not implemented |
{% macro %} |
✅ | ❌ | Not implemented |
{% import %} |
✅ | ❌ | Not implemented |
| Expressions | |||
| Arithmetic operators | âś… | âś… | +, -, *, /, % |
| Comparison operators | âś… | âś… | ==, !=, <, <=, >, >= |
| Logical operators | âś… | âś… | and, or, not |
in operator |
âś… | âś… | Full support |
Attribute access . |
âś… | âś… | Full support |
Index access [] |
âś… | âś… | Full support |
| Filters | 60 | 43 | See details |
| Chained filters | âś… | âś… | Full support |
| Custom filters | âś… | âś… | Full support |
| Tests | 31 | 16 | See details |
Negated tests is not |
âś… | âś… | Full support |
| Custom tests | âś… | âś… | Full support |
| Advanced Features | |||
| Tuple unpacking in loops | âś… | âś… | {% for key, value in items %} |
| Whitespace control | âś… | âś… | Full support (- modifiers) |
| Custom delimiters | âś… | âś… | Full support |
| Template inheritance | ✅ | ❌ | Planned for future |
| Macros | ✅ | ❌ | Planned for future |
| Auto-escaping | ✅ | ❌ | Manual escaping only |
| Line statements | ✅ | ❌ | Not implemented |
| Loaders | |||
| FileSystemLoader | âś… | âś… | Full support |
| StringLoader | âś… | âś… | Full support |
| PackageLoader | ✅ | ❌ | Not needed in Go |
Production Ready:
- Core template rendering with 100% Jinja2 behavioral compatibility
- Variables and expressions
- Control flow (for, if, elif, else, set)
- Tuple unpacking in for loops
- 43 built-in filters (72% of Jinja2)
- 16 built-in tests (52% of Jinja2)
- Template inclusion
- Whitespace control
- Custom delimiters
- Custom filters/tests
Not Implemented:
- Template inheritance (extends/block)
- Macros and imports
- Auto-escaping
- Line statements
This implementation focuses on the most commonly used Jinja2 features, providing clean, efficient, and fully compatible template rendering for Go applications.
go get github.com/newsamples/jinja2package main
import (
"fmt"
"github.com/newsamples/jinja2"
)
func main() {
template := "Hello {{ name }}!"
context := map[string]interface{}{
"name": "World",
}
result, err := jinja2.RenderString(template, context)
if err != nil {
panic(err)
}
fmt.Println(result)
}{{ variable }}
{{ user.name }}
{{ items[0] }}{% for item in items %}
{{ item }}
{% endfor %}{% for key, value in dict | dictsort %}
{{ key }}: {{ value }}
{% endfor %}
{% for x, y, z in coordinates %}
Point: ({{ x }}, {{ y }}, {{ z }})
{% endfor %}{% if condition %}
yes
{% elif other_condition %}
maybe
{% else %}
no
{% endif %}{% set x = 10 %}
{{ x }}Filters transform values using the pipe operator:
{{ name | upper }}
{{ items | join(', ') }}
{{ value | default('N/A') }}
{{ users | map('attribute', 'name') | join(', ') }}
{{ numbers | select('even') | join(',') }}43 built-in filters available (full comparison):
String (13): upper, lower, capitalize, title, trim, replace, escape, forceescape, safe, string, striptags, truncate, wordwrap
List/Sequence (9): join, first, last, length, reverse, list, sort, unique, slice, batch
Numeric (3): abs, int, float
Formatting (4): format, center, indent, wordcount
Filtering/Selection (5): map, select, reject, selectattr, rejectattr
Aggregation (4): sum, min, max, groupby
Data Structure (4): dictsort, items, attr, tojson
Other (1): default
Tests check conditions using the is keyword:
{% if value is defined %}
{{ value }}
{% endif %}
{% if num is odd %}
odd number
{% endif %}
{% if name is string %}
string value
{% endif %}16 built-in tests available (full comparison):
Existence/Boolean (6): defined, undefined, none, boolean, true, false
Type (5): number, string, mapping, sequence, iterable
Numeric (3): odd, even, divisibleby
String (2): upper, lower
{{ 2 + 3 }}
{{ 10 - 5 }}
{{ 4 * 3 }}
{{ 10 / 2 }}
{{ 10 % 3 }}{{ x == y }}
{{ x != y }}
{{ x < y }}
{{ x <= y }}
{{ x > y }}
{{ x >= y }}{{ true and false }}
{{ true or false }}
{{ not false }}
{{ 'a' in items }}Control whitespace with the - modifier:
{%- if true -%}
No whitespace before or after
{%- endif -%}Syntax:
{{-/-}}- Strip whitespace around variable tags{%-/-%}- Strip whitespace around block tags{#-/-#}- Strip whitespace around comments
Example:
<ul>
{%- for item in items %}
<li>{{ item }}</li>
{%- endfor %}
</ul>env := jinja2.NewEnvironment(nil)
env.AddFilter("double", func(value interface{}, args []interface{}) (interface{}, error) {
if num, ok := value.(int64); ok {
return num * 2, nil
}
return value, nil
})
tmpl, _ := env.FromString("{{ num | double }}")
result, _ := tmpl.Render(map[string]interface{}{"num": int64(5)})
// Output: "10"env := jinja2.NewEnvironment(nil)
env.AddTest("positive", func(value interface{}, args []interface{}) (bool, error) {
if num, ok := value.(int64); ok {
return num > 0, nil
}
return false, nil
})
tmpl, _ := env.FromString("{% if num is positive %}yes{% endif %}")
result, _ := tmpl.Render(map[string]interface{}{"num": int64(5)})
// Output: "yes"env := jinja2.NewEnvironment(nil)
// Change delimiters to avoid conflicts
env.SetVariableDelimiters("[[", "]]")
env.SetBlockDelimiters("[%", "%]")
env.SetCommentDelimiters("[*", "*]")
tmpl, _ := env.FromString("[% if true %]Hello [[ name ]][% endif %]")
result, _ := tmpl.Render(map[string]interface{}{"name": "World"})
// Output: "Hello World"loader := jinja2.NewFileSystemLoader("./templates")
env := jinja2.NewEnvironment(loader)
tmpl, err := env.GetTemplate("base.html")
result, err := tmpl.Render(context)loader := jinja2.NewStringLoader(map[string]string{
"base.html": "Hello {{ name }}",
})
env := jinja2.NewEnvironment(loader)
tmpl, err := env.GetTemplate("base.html"){% include "header.html" %}
<main>
Content here
</main>
{% include "footer.html" %}The library works with Go types:
int64andintfor integersfloat64andfloat32for floating-point numbersstringfor stringsboolfor booleans (rendered asTrue/False)[]interface{}for arraysmap[string]interface{}for objects
- Numbers: The implementation supports int, int64, float32, float64 for numeric operations and tests
- Booleans: Rendered as capitalized
True/Falsematching Jinja2 behavior - Sequences: Strings are treated as sequences, consistent with Python Jinja2
- Floats in Tests: Tests like
odd,even, anddivisiblebyaccept whole-number floats (e.g.,3.0is odd)
{% for key, value in dict | dictsort %}
{{ key }} = {{ value }}
{% endfor %}
{% for key, value in dict | items %}
{{ key }}: {{ value }}
{% endfor %}{# Get names of users with even age #}
{{ users | selectattr('age', 'even') | map('attribute', 'name') | join(', ') }}
{# Sum prices of active items #}
Total: {{ items | selectattr('active') | sum('price') }}
{# Group items by category #}
{% for group in items | groupby('category') %}
<h2>{{ group.grouper }}</h2>
{% for item in group.list %}
<li>{{ item.name }}</li>
{% endfor %}
{% endfor %}{# Truncate and wrap text #}
{{ long_text | truncate(100) | wordwrap(40) }}
{# Center and indent code #}
<pre>{{ code | indent(4, true) }}</pre>
{# Strip HTML tags #}
{{ html_content | striptags }}For detailed filter and test documentation, see:
- Filters and Tests Comparison - Complete list with examples
MIT