Getting Started¶
Quill is a general-purpose template engine for Go. This page takes you from install to a first render, then covers loaders, passing Go data, and the render options you will reach for most. It assumes no prior Quill knowledge; the Guide covers the language itself.
Install¶
Add the library to a module:
Optionally install the command-line tool, which renders a template with JSON data, reports coverage, and compiles a template to Go:
Quill requires Go 1.23 or newer and depends on nothing outside the Go standard library.
Your first render¶
An Environment is the engine facade: you build one over a loader, then render
templates by name. The quickest loader is an in-memory map, via NewFromMap:
package main
import (
"context"
"fmt"
quill "github.com/avmnu-sng/quill-template-engine"
"github.com/avmnu-sng/quill-template-engine/pkg/runtime"
)
func main() {
env := quill.NewFromMap(map[string]string{
"greet.quill": `Hello {{ name | upper }}{{ "!" if loud }}`,
})
out, err := env.Render(context.Background(), "greet.quill", map[string]runtime.Value{
"name": runtime.Str("ada"),
"loud": runtime.Bool(true),
})
if err != nil {
panic(err)
}
fmt.Println(out) // Hello ADA!
}
{{ name | upper }} interpolates name through the upper filter, and
{{ "!" if loud }} is a postfix conditional that emits ! only when loud is
truthy. Both are covered in the Guide.
Every render and load method takes a context.Context as its first argument, so
a render honors cancellation and deadlines: pass a request's context and a caller
that goes away or a deadline that elapses aborts the render with the context's
error. Use context.Background() when you have no context to thread.
A template loads once and is memoized; subsequent renders of the same name reuse the parsed and type-checked module.
Loaders¶
A loader resolves a template name to its source. Loading templates from disk lets
@extends, @include, @import, and @from resolve other templates by name
under a root:
import "github.com/avmnu-sng/quill-template-engine/pkg/loader"
env := quill.New(loader.NewFilesystemLoader("templates"))
Loaders compose, so you can assemble exactly the resolution strategy you need:
loader.NewChainLoader(a, b, ...)tries several loaders in order and serves the first hit (the base-plus-override pattern).loader.NewPrefixLoader(routes)routes a name by its leading prefix to a sub-loader.loader.NewFSLoader(fsys, root...)serves anfs.FS, including anembed.FSbaked into the binary so a program ships its templates with no filesystem at runtime.loader.NewFuncLoader(fn)sources templates from a callback: a database, a config object, or any lookup the host already owns.
The composable loaders are documented in full in Extensions & Loaders.
Passing Go data¶
Render binds a map[string]runtime.Value built with the runtime
constructors (runtime.Str, runtime.Int, runtime.Bool, runtime.Arr, and
friends). To pass ordinary Go values instead, use RenderValues, which marshals
each binding through runtime.FromGo:
type User struct {
Name string `quill:"name"`
Admin bool `quill:"admin"`
Tags []string `quill:"tags"`
}
out, _ := env.RenderValues(context.Background(), "greet.quill", map[string]any{
"user": User{Name: "ada", Admin: true, Tags: []string{"x", "y"}},
"count": 3,
})
FromGo maps scalars, slices, maps (with a deterministic key order), and structs
(honoring a quill:"name" or json:"name" tag) to the value model, and passes
any existing runtime.Value through unchanged, so hand-built and native bindings
mix freely.
Streaming output¶
Render returns the result as a string. To stream to any io.Writer without
buffering the whole output, use RenderTo:
RenderStringTo is the string-keyed variant. Streaming is covered alongside the
sandbox in Escaping & Safety.
Render options¶
The Environment is configured with Option values passed to New /
NewFromMap. The ones you meet first:
quill.WithAutoescapeHTML(true): turn on HTML escaping globally. Off by default; see Escaping & Safety.quill.WithStrictVariables(false): switch from the strict-undefined default to lenient mode, where a missing read becomesNull. Strict by default; see Types.quill.WithCoverage(collector): attach a coverage collector; see Coverage.quill.WithExtensions(set)/quill.WithExtension(bundle): register custom filters, functions, and tests; see Extensions & Loaders.quill.WithTabWidth(n): the spaces one indent level expands to (default 4).
Editor support¶
The engine does not enforce a file extension (a template name is any string),
but the recommended convention for template files on disk is .quill. It sidesteps
a clash with CodeQL, whose .ql extension GitHub Linguist claims by default. A VS
Code extension with a Quill TextMate grammar lives under editors/vscode/ in the
repository; its README covers local installation.
Where to go next¶
- The Guide walks through the language: templates, expressions, control flow, and composition.
- Types introduces the gradual type system.
- Whitespace Control covers byte-exact output and a mapping
table for people coming from Jinja, Twig, or Go
text/template. - The Language Reference and Grammar are the exhaustive specification.