Open-source project
gobuffalo/plush avatar
gobuffalo/plush

Plush gives Go templates a scripting language and a compiled VM

The powerful template system that Go needs

1,024 stars58 forksGoMIT

At a glance

What is it?
Plush is the Go template system from the Buffalo project, where ERB-style tags let a template define variables and functions rather than only interpolate values. Recent versions added a compiled VM renderer alongside the interpreter, with static string-key map access optimised automatically, and a render budget that stops runaway recursion.
Who is it for?
Adopt Plush if you are building Go web applications that need more than value interpolation, since the ERB-style tags let a template hold logic and helpers that Go's own templates cannot express. Do not adopt it if you want a purely declarative template with no scripting, because the whole design is a scripting layer inside the template.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 44 days ago.
What is it written in?
Mainly Go, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 20, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Two tags, and the difference is one character

Plush allows dynamic code to be embedded inside templates, and the tag syntax follows the ERB convention. The `<%= %>` tag dynamically renders the inner content into the template, so `<p><%= "plush is great" %></p>` produces `<p>plush is great</p>`. The `<% %>` tag evaluates and executes the inner content without injecting it into the output, so the same expression inside `<% %>` produces `<p></p>`. That second tag is what makes the language a scripting language rather than an interpolation engine, because it is where you define variables and functions for later use in the same template, with `let` for bindings and `fn` for functions. The README is explicit that Go-style `var a = func() { ... }` is not Plush syntax, which is worth knowing if you arrive from Go templates and expect the same function literals.

Functions can recurse and close over template scope

The recursion support is documented with a working example. A function is defined with `let name = fn(...) { ... }` and called by name inside its own body, and this works in both the interpreter and the compiled VM renderer. A countdown function that returns done at zero and otherwise calls itself with a decremented argument renders done for an input of three. Recursive functions can also close over values from the surrounding template scope, so a function can mutate a `remaining` variable declared beside it. The README states the important rule plainly: recursive functions need a stopping condition, and without a base case the function will keep calling itself until the render fails or a configured render budget stops it. The repository has a `budget.go` and a `budget_config.go` at the top level, which is where that limit lives.

Numbers compare across Go types without ceremony

A feature worth knowing about is the numeric comparison rule. Numeric equality and ordering are safe across common Go numeric types, which lets backend values such as `int32`, `uint32`, `uint64`, `float32` and `float64` compare against template integer or float literals without type errors. So a template can write `item.Count == 0` against an `int32` field, `totalValue > 0.0` against a float, and `uintValue == 3.0` or `floatValue == 3` without a conversion on the Go side. The same mixed numeric comparison rules apply to struct fields, map values, indexed values, helper returns and method returns, which means a helper in a template file is not a second-class citizen when it comes to types.

Maps and arrays translate to Go types, and maps get optimised

Plush maps translate to the Go type `map[string]interface{}` and arrays to `[]interface{}`. A map literal in a template looks like JSON, with `let h = {key: "value", "a number": 1, bool: true}`, and access is bracket notation, `h["key"]`. Go maps passed through the render context use the same bracket syntax, including typed maps from backend code such as `map[string]string` and `map[string]uint32`, and the same holds for nested chains such as `records["primary"].Name`. The compiled VM renderer optimises static string-key map access automatically for those typed maps, and the README is precise about what it caches: the access plan and the typed map key, not the map value itself. That distinction is the difference between a cache that stays correct when the data changes and one that does not.

A third tag exists for trimming whitespace

The `<%- %>` tag renders an expression and removes contiguous whitespace immediately around the tag, so a `<pre>` block with a `<%- "Hello" %>` inside it renders without the surrounding blank lines. The rules are narrow on purpose: only spaces, tabs, carriage returns and newlines directly before the opening tag and directly after the closing tag are trimmed. The tag renders and escapes values exactly as `<%= %>` does, and the existing whitespace behaviour of `<%= %>` is unchanged. That is a small, well-specified addition, and it is the kind of detail that shows a template engine has been used on real HTML, where a stray newline inside a `<pre>` is visible.

Rendering from Go is a context and a call

The Go side of the library is small. You create a context with `plush.NewContext()`, set values on it, and call `plush.Render(html, ctx)`, which returns the rendered string and an error:

go
ctx := plush.NewContext()
ctx.Set("names", []string{"john", "paul", "george", "ringo"})

s, err := plush.Render(html, ctx)
if err != nil {
  log.Fatal(err)
}

fmt.Print(s)

The template that goes with it loops over the names with a `for (n) in names` construct and calls a `capitalize` helper, and the rendered output is a list with each name capitalised. Helper functions are registered on the context, and the `helpers/` directory and `helpers.go` hold the built-in set. There is also a `partial_helper.go` for including partial templates, and a `helper_scope.go` and `helper_context.go` for how helper lookups are scoped. The whole thing is a single Go module, `github.com/gobuffalo/plush/v5`, requiring Go 1.21, with a small dependency set: flect, gobuffalo/tags, testify, and golang.org/x/sync.

The compiled VM arrives with its own benchmark suite

The release history shows the recent structural work. v5.0.12 is titled as the Plush VM release, dated 2026-07-23, and v5.0.13 followed on 2026-07-30, with v5.0.11 from 2025-11-14 before them. The repository layout shows the compiler, the lexer and parser directories, an AST package, and a `vm/` tree referenced by the build tooling. The Makefile is mostly a benchmark harness, with separate targets for the strings builder output size planning benchmark, the VM output size estimator benchmark, and the heavy template render engine benchmark, each run with a fixed benchtime and count, plus a profiling target that writes CPU and memory profiles to a directory under `/tmp`. There is also an `OUTPUT_SIZE_ESTIMATOR.md` in the root, which suggests the size estimator is a documented feature rather than an internal detail, and a `render_diagnostics.go` and `render_mode.go` at the top level for switching between renderers.

Where Plush sits against Go's own templates

The comparison is worth making explicit, because Go's standard `text/template` and `html/template` are the obvious alternative and they are much stricter. The standard packages deliberately do not give a template the ability to define arbitrary functions or hold mutable state, because a template that can do arbitrary things is a template that can break the escaping guarantees of `html/template`. Plush makes the opposite trade, and the cost is that the escaping story is weaker, which is why the `<%= %>` tag exists separately from the plain output path and why the README mentions escaping behaviour explicitly for the trim tag. The other trade is compilation. The standard library compiles templates once and reuses them, while Plush's interpreter walks the template each time and the VM path exists to close that gap, and the benchmark targets in the Makefile are aimed squarely at that question. Plush is also developed under the gobuffalo organisation.

Editorial conclusion

Adopt Plush if you are building Go web applications that need more than value interpolation, since the ERB-style tags let a template hold logic and helpers that Go's own templates cannot express. Do not adopt it if you want a purely declarative template with no scripting, because the whole design is a scripting layer inside the template. Verify first which renderer you are using, since the interpreter and the compiled VM behave the same for templates but differ in how they optimise map access, and read the render budget documentation before you let a template call a recursive function without a base case.

Frequently asked questions

How do I install Plush for Go?

The README gives `go get -u github.com/gobuffalo/plush`. The module path for the current major version is `github.com/gobuffalo/plush/v5` and it requires Go 1.21.

What is the difference between the <%= %> and <% %> tags in Plush?

The `<%= %>` tag dynamically renders the inner content into the template, while the `<% %>` tag evaluates and executes the inner content without injecting it into the output. The second form is where you define variables and functions with `let` and `fn`.

Can Plush template functions be recursive?

Yes. Define the function with `let name = fn(...) { ... }` and call it by name inside its own body, in both the interpreter and the compiled VM renderer. The README warns that recursive functions need a stopping condition, since without a base case the render keeps going until it fails or a configured render budget stops it.

How do I render a Plush template from Go?

Create a context with `plush.NewContext()`, set values on it with `ctx.Set`, and call `plush.Render(html, ctx)`, which returns the rendered string and an error. The README's full example sets a slice of names and renders a loop over them.

What licence is Plush released under?

The project is released under the MIT licence, with the licence file in the root of the repository. The most recent releases are v5.0.13 and v5.0.12, the latter being the Plush VM release.

Official sources

  1. gobuffalo/plush on GitHub
  2. Issues
  3. License: MIT
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/gobuffalo-plush.svg)](https://hysenlabs.com/projects/gobuffalo-plush)

Was this useful?