A type-safe HTML template rendering engine for Go.
- Problem Statement
- What Templator Solves
- Features
- Installation
- Quick Start
- How It All Works Together
- Usage Examples
- Template Generation
- Configuration
- Development Requirements
- Contributing
- License
Go's built-in html/template package is great, but it has one big weakness: no type safety.
tmpl := template.Must(template.ParseFiles("home.html"))
// this compiles fine...
err := tmpl.Execute(w, struct {
WrongField string
MissingStuff int
}{})You only find out at runtime that your data does not match what the template expects: missing fields, typos, wrong types, and so on.
Templator wraps html/template and gives you compile-time guarantees with generics.
You define your data once:
type HomeData struct {
Title string
Content string
}Then template execution is type-checked against the template by the compiler.
You can get a handler in two equivalent ways:
- Simple and direct:
reg.Get("home") - IDE-friendly codegen:
tpl.GetHome()
Both return *Handler[T] with the same behavior. The difference is only developer experience.
- Compile-time type safety via generics
- Optional field validation (catches mismatches when loading templates)
- Concurrent-safe template management with
fs.FSsupport - Custom template functions
- Context cancellation and deadline propagation
- Minimal overhead on top of
html/template - Small API with optional code generation helpers
go get github.com/alesr/templator@latestOptional generator binary:
go install github.com/alesr/templator/cmd/generate@latestpackage main
import (
"context"
"log"
"os"
"github.com/alesr/templator"
)
type HomeData struct {
Title string
Content string
}
func main() {
fs := os.DirFS(".")
reg, err := templator.NewRegistry[HomeData](fs)
if err != nil {
log.Fatal(err)
}
home, err := reg.Get("home")
if err != nil {
log.Fatal(err)
}
err = home.Execute(context.Background(), os.Stdout, HomeData{
Title: "Welcome",
Content: "Hello, World!",
})
if err != nil {
log.Fatal(err)
}
}- Define a data struct (this is your type parameter
T). - Create a
Registry[T]for your filesystem. - Get a
Handler[T]either withreg.Get("name")or generated accessors liketpl.GetName(). - Execute with
handler.Execute(ctx, writer, data). - Optionally enable field validation, custom functions, or a different templates path.
Everything is still powered by the standard html/template package.
type HomeData struct{ Title, Content string }
type AboutData struct{ Company string; Year int }
homeReg, _ := templator.NewRegistry[HomeData](fs)
aboutReg, _ := templator.NewRegistry[AboutData](fs)
home, _ := homeReg.Get("home")
about, _ := aboutReg.Get("about")
home.Execute(ctx, w, HomeData{...}) // ok
home.Execute(ctx, w, AboutData{...}) // trying to pass about data to the home tmpl is caught by the compilerfuncMap := template.FuncMap{
"upper": strings.ToUpper,
}
reg, _ := templator.NewRegistry[PageData](
fs,
templator.WithTemplateFuncs[PageData](funcMap),
)Use in templates as usual: {{.Title | upper}}
// Embedded FS
//go:embed templates/*
var embedFS embed.FS
reg, _ := templator.NewRegistry[HomeData](embedFS)
// os dir
reg, _ = templator.NewRegistry[HomeData](os.DirFS("templates"))
// in-memory for tests
fsys := fstest.MapFS{ /* ... */ }
reg, _ = templator.NewRegistry[HomeData](fsys)type ArticleData struct {
Title string
Content string
}
reg, _ := templator.NewRegistry[ArticleData](
fs,
templator.WithFieldValidation(ArticleData{}),
)If a template references {{.Author}} but Author does not exist in ArticleData, Get(...) returns a validation error.
Want tpl.GetHome() instead of string lookup? Use the generator.
go run github.com/alesr/templator/cmd/generate \
-package main \
-templates ./templates \
-out ./templator_accessors_gen.goYou can also wire it into go generate:
//go:generate go run github.com/alesr/templator/cmd/generate -package main -templates ./templates -out ./templator_accessors_gen.goThen use the generated wrapper:
reg, _ := templator.NewRegistry[HomeData](fs)
tpl := NewTemplateAccessors(reg)
home, _ := tpl.GetHome()
header, _ := tpl.GetComponentsHeader()components/header.html maps to GetComponentsHeader.
For full generator docs (flags, behavior, and examples), see cmd/generate/README.md.
reg, err := templator.NewRegistry[HomeData](
fs,
templator.WithTemplatesPath[HomeData]("views"),
templator.WithTemplateFuncs[HomeData](funcMap),
templator.WithFieldValidation(HomeData{}),
)- Go 1.24 or higher
Contributions are welcome.
MIT