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