Django-syntax like template-engine for Go
Go Smarty HTML
Latest commit 22499f9 Aug 13, 2016 @flosch committed on GitHub Merge pull request #138 from thatguystone/implicit-exec
Implicit *ExecutionContext arg, take 2
Failed to load latest commit information.
docs Create Oct 7, 2014
template_tests Add split filter builtin. Aug 13, 2016
.gitignore Introducing template sandboxing (directories, banned filters/tags). Oct 2, 2014
.travis.yml Activating 1.4 and tip tests again for travis Dec 12, 2014
AUTHORS Added a AUTHORS file. Feel free to add yourself to the list if you've… Jul 3, 2014
LICENSE New README. Jun 28, 2014 Merge pull request #111 from digitalcrab/master Jul 17, 2016
context.go Some cleanup May 23, 2015
doc.go Update doc.go Aug 4, 2014
error.go Some cleanup May 23, 2015
filters.go further golint work/cleanup Mar 9, 2015
filters_builtin.go Add split filter builtin. Aug 13, 2016
helpers.go wordwrap-filter added. Jul 28, 2014
lexer.go Prevent content inside comments from being parsed in any way. Feb 19, 2016
nodes.go This change improves the execution speed by reducing the allocation c… Dec 12, 2014
nodes_html.go This change improves the execution speed by reducing the allocation c… Dec 12, 2014
nodes_wrapper.go This change improves the execution speed by reducing the allocation c… Dec 12, 2014
parser.go Prevent content inside comments from being parsed in any way. Feb 19, 2016
parser_document.go New Error return type. API-incompatible change. Fixes #47 Oct 2, 2014
parser_expression.go first golint run and respective fixes Mar 9, 2015
pongo2.go Some cleanup May 23, 2015
pongo2_template_test.go Add FromBytes Aug 13, 2016
pongo2_test.go Add missing package name to implicit ExecutionContext tests Aug 13, 2016
tags.go first golint run and respective fixes Mar 9, 2015
tags_autoescape.go further golint work/cleanup Mar 9, 2015
tags_block.go Add {{ block.Super }} support Feb 21, 2016
tags_comment.go Prevent content inside comments from being parsed in any way. Feb 19, 2016
tags_cycle.go first golint run and respective fixes Mar 9, 2015
tags_extends.go first golint run and respective fixes Mar 9, 2015
tags_filter.go first golint run and respective fixes Mar 9, 2015
tags_firstof.go further golint work/cleanup Mar 9, 2015
tags_for.go Fix ignoring error while processing for tag body Jul 23, 2015
tags_if.go tiny cleanup May 23, 2015
tags_ifchanged.go further golint work/cleanup Mar 9, 2015
tags_ifequal.go further golint work/cleanup Mar 9, 2015
tags_ifnotequal.go Proper tagging for endifnot Jun 12, 2015
tags_import.go Some cleanup May 23, 2015
tags_include.go further golint work/cleanup Mar 9, 2015
tags_lorem.go further golint work/cleanup Mar 9, 2015
tags_macro.go further golint work/cleanup Mar 9, 2015
tags_now.go further golint work/cleanup Mar 9, 2015
tags_set.go This change improves the execution speed by reducing the allocation c… Dec 12, 2014
tags_spaceless.go further golint work/cleanup Mar 9, 2015
tags_ssi.go further golint work/cleanup Mar 9, 2015
tags_templatetag.go further golint work/cleanup Mar 9, 2015
tags_widthratio.go further golint work/cleanup Mar 9, 2015
tags_with.go first golint run and respective fixes Mar 9, 2015
template.go first working version of a virtual file system. addresses #68 Jul 31, 2015
template_loader.go more comment improvements Jul 31, 2015
template_sets.go Add FromBytes Aug 13, 2016
value.go Expression does not work properly Mar 15, 2016
variable.go Revert "Merge pull request #137 from flosch/revert-84-implicit-exec" Aug 13, 2016


Join the chat at GoDoc Build Status Coverage Status gratipay Bountysource

pongo2 is the successor of pongo, a Django-syntax like templating-language.

Install/update using go get (no dependencies required by pongo2):

go get -u

Please use the issue tracker if you're encountering any problems with pongo2 or if you need help with implementing tags or filters (create a ticket!). If possible, please use playground to create a short test case on what's wrong and include the link to the snippet in your issue.

New: Try pongo2 out in the pongo2 playground.

First impression of a template

<html><head><title>Our admins and users</title></head>
{# This is a short example to give you a quick overview of pongo2's syntax. #}

{% macro user_details(user, is_admin=false) %}
    <div class="user_item">
        <!-- Let's indicate a user's good karma -->
        <h2 {% if (user.karma >= 40) || (user.karma > calc_avg_karma(userlist)+5) %}
            class="karma-good"{% endif %}>

            <!-- This will call user.String() automatically if available: -->
            {{ user }}

        <!-- Will print a human-readable time duration like "3 weeks ago" -->
        <p>This user registered {{ user.register_date|naturaltime }}.</p>

        <!-- Let's allow the users to write down their biography using markdown;
             we will only show the first 15 words as a preview -->
        <p>The user's biography:</p>
        <p>{{ user.biography|markdown|truncatewords_html:15 }}
            <a href="/user/{{ }}/">read more</a></p>

        {% if is_admin %}<p>This user is an admin!</p>{% endif %}
{% endmacro %}

    <!-- Make use of the macro defined above to avoid repetitive HTML code
         since we want to use the same code for admins AND members -->

    <h1>Our admins</h1>
    {% for admin in adminlist %}
        {{ user_details(admin, true) }}
    {% endfor %}

    <h1>Our members</h1>
    {% for user in userlist %}
        {{ user_details(user) }}
    {% endfor %}

Development status

Latest stable release: v3.0 (go get -u / v3-branch) [read the announcement]

Current development: v4 (master-branch)

Note: With the release of pongo v4 the branch v2 will be deprecated.

Deprecated versions (not supported anymore): v1

Topic Status
Django version compatibility: 1.7
Missing (planned) filters: none (hints)
Missing (planned) tags: none (hints)

Please also have a look on the caveats and on the official add-ons.

Features (and new in pongo2)

Recent API changes within pongo2

If you're using the master-branch of pongo2, you might be interested in this section. Since pongo2 is still in development (even though there is a first stable release!), there could be (backwards-incompatible) API changes over time. To keep track of these and therefore make it painless for you to adapt your codebase, I'll list them here.

  • Function signature for tag execution changed: not taking a bytes.Buffer anymore; instead Execute()-functions are now taking a TemplateWriter interface.
  • Function signature for tag and filter parsing/execution changed (error return type changed to *Error).
  • INodeEvaluator has been removed and got replaced by IEvaluator. You can change your existing tags/filters by simply replacing the interface.
  • Two new helper functions: RenderTemplateFile() and RenderTemplateString().
  • Template.ExecuteRW() is now Template.ExecuteWriter()
  • Template.Execute*() functions do now take a pongo2.Context directly (no pointer anymore).

How you can help

  • Write filters / tags (see tutorial) by forking pongo2 and sending pull requests
  • Write/improve code tests (use the following command to see what tests are missing: go test -v -cover -covermode=count -coverprofile=cover.out && go tool cover -html=cover.out or have a look on
  • Write/improve template tests (see the template_tests/ directory)
  • Write middleware, libraries and websites using pongo2. :-)


For a documentation on how the templating language works you can head over to the Django documentation. pongo2 aims to be compatible with it.

You can access pongo2's API documentation on godoc.

Blog post series



  • date / time: The date and time filter are taking the Golang specific time- and date-format (not Django's one) currently. Take a look on the format here.
  • stringformat: stringformat does not take Python's string format syntax as a parameter, instead it takes Go's. Essentially {{ 3.14|stringformat:"pi is %.2f" }} is fmt.Sprintf("pi is %.2f", 3.14).
  • escape / force_escape: Unlike Django's behaviour, the escape-filter is applied immediately. Therefore there is no need for a force_escape-filter yet.


  • for: All the forloop fields (like forloop.counter) are written with a capital letter at the beginning. For example, the counter can be accessed by forloop.Counter and the parentloop by forloop.Parentloop.
  • now: takes Go's time format (see date and time-filter).


  • not in-operator: You can check whether a map/struct/string contains a key/field/substring by using the in-operator (or the negation of it): {% if key in map %}Key is in map{% else %}Key not in map{% endif %} or {% if !(key in map) %}Key is NOT in map{% else %}Key is in map{% endif %}.

Add-ons, libraries and helpers


  • ponginae - A web-framework for Go (using pongo2).
  • pongo2-tools - Official tools and helpers for pongo2
  • pongo2-addons - Official additional filters/tags for pongo2 (for example a markdown-filter). They are in their own repository because they're relying on 3rd-party-libraries.


Please add your project to this list and send me a pull request when you've developed something nice for pongo2.

API-usage examples

Please see the documentation for a full list of provided API methods.

A tiny example (template string)

// Compile the template first (i. e. creating the AST)
tpl, err := pongo2.FromString("Hello {{ name|capfirst }}!")
if err != nil {
// Now you can render the template with the given 
// pongo2.Context how often you want to.
out, err := tpl.Execute(pongo2.Context{"name": "florian"})
if err != nil {
fmt.Println(out) // Output: Hello Florian!

Example server-usage (template file)

package main

import (

// Pre-compiling the templates at application startup using the
// little Must()-helper function (Must() will panic if FromFile()
// or FromString() will return with an error - that's it).
// It's faster to pre-compile it anywhere at startup and only
// execute the template later.
var tplExample = pongo2.Must(pongo2.FromFile("example.html"))

func examplePage(w http.ResponseWriter, r *http.Request) {
    // Execute the template per HTTP request
    err := tplExample.ExecuteWriter(pongo2.Context{"query": r.FormValue("query")}, w)
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)

func main() {
    http.HandleFunc("/", examplePage)
    http.ListenAndServe(":8080", nil)


The benchmarks have been run on the my machine (Intel(R) Core(TM) i7-2600 CPU @ 3.40GHz) using the command:

go test -bench . -cpu 1,2,4,8

All benchmarks are compiling (depends on the benchmark) and executing the template_tests/complex.tpl template.

The results are:

BenchmarkExecuteComplexWithSandboxActive                50000             60450 ns/op
BenchmarkExecuteComplexWithSandboxActive-2              50000             56998 ns/op
BenchmarkExecuteComplexWithSandboxActive-4              50000             60343 ns/op
BenchmarkExecuteComplexWithSandboxActive-8              50000             64229 ns/op
BenchmarkCompileAndExecuteComplexWithSandboxActive      10000            164410 ns/op
BenchmarkCompileAndExecuteComplexWithSandboxActive-2    10000            156682 ns/op
BenchmarkCompileAndExecuteComplexWithSandboxActive-4    10000            164821 ns/op
BenchmarkCompileAndExecuteComplexWithSandboxActive-8    10000            171806 ns/op
BenchmarkParallelExecuteComplexWithSandboxActive        50000             60428 ns/op
BenchmarkParallelExecuteComplexWithSandboxActive-2      50000             31887 ns/op
BenchmarkParallelExecuteComplexWithSandboxActive-4     100000             22810 ns/op
BenchmarkParallelExecuteComplexWithSandboxActive-8     100000             18820 ns/op
BenchmarkExecuteComplexWithoutSandbox                   50000             56942 ns/op
BenchmarkExecuteComplexWithoutSandbox-2                 50000             56168 ns/op
BenchmarkExecuteComplexWithoutSandbox-4                 50000             57838 ns/op
BenchmarkExecuteComplexWithoutSandbox-8                 50000             60539 ns/op
BenchmarkCompileAndExecuteComplexWithoutSandbox         10000            162086 ns/op
BenchmarkCompileAndExecuteComplexWithoutSandbox-2       10000            159771 ns/op
BenchmarkCompileAndExecuteComplexWithoutSandbox-4       10000            163826 ns/op
BenchmarkCompileAndExecuteComplexWithoutSandbox-8       10000            169062 ns/op
BenchmarkParallelExecuteComplexWithoutSandbox           50000             57152 ns/op
BenchmarkParallelExecuteComplexWithoutSandbox-2         50000             30276 ns/op
BenchmarkParallelExecuteComplexWithoutSandbox-4        100000             22065 ns/op
BenchmarkParallelExecuteComplexWithoutSandbox-8        100000             18034 ns/op

Benchmarked on October 2nd 2014.