Filesystems
A registry holds an fs.FS, and every template lookup is a read against
that filesystem. This single design choice drives all the conveniences
below — and it means templator never touches your host filesystem directly.
The path inside the FS
Section titled “The path inside the FS”Get("components/header") reads
<templates-path>/components/header.html inside the registry’s fs.FS.
The default path is templates; override it with WithTemplatesPath:
reg, _ := templator.NewRegistry[PageData]( fsys, templator.WithTemplatesPath[PageData]("views"),)Keep in mind that embed.FS roots are the package directory, and
os.DirFS(dir) roots are dir — the same templates/home.html file is
addressed as "templates/home.html" from one and "home.html" from the
other. templator always prepends the templates path, and an empty
WithTemplatesPath is ignored, so there is no root-relative mode:
whatever fs.FS you hand the registry must contain a directory literally
named templates — or the value of WithTemplatesPath — directly under
its root. If your templates live at web/templates/ inside the
filesystem you have, bracket the parent, not the templates directory:
sub, _ := fs.Sub(fsys, "web") // parent of the templates dirreg, _ := templator.NewRegistry[HomeData](sub)
home, _ := reg.Get("home") // resolves to web/templates/home.html
// fs.Sub(fsys, "web/templates") would NOT work here: the registry// would look for web/templates/templates/home.html and fail with an fs error.The three filesystems you will actually use
Section titled “The three filesystems you will actually use”Disk — os.DirFS
Section titled “Disk — os.DirFS”The zero-config setup from the Quickstart. Templates are separate files you can edit and reload by rebuilding the registry:
fs := os.DirFS(".")reg, _ := templator.NewRegistry[HomeData](fs)Good for servers deployed alongside their templates and for local development.
The binary — embed.FS
Section titled “The binary — embed.FS”Templates shipped inside a single binary. Deploy one file; nothing to forget:
import "embed"
//go:embed templatesvar templateFS embed.FS
reg, _ := templator.NewRegistry[HomeData](templateFS)Embedding the directory (as above) includes its files recursively. The
embed FS is rooted at the declaring package, so the file lands at
templates/home.html — which is exactly the registry’s default path, so
no option is needed. You only need WithTemplatesPath when the embedded
root is not called templates, for example:
//go:embed htmlvar templateFS embed.FS
reg, _ := templator.NewRegistry[HomeData]( templateFS, templator.WithTemplatesPath[HomeData]("html"),)Memory — fstest.MapFS
Section titled “Memory — fstest.MapFS”The test harness. Templates are declared as data, so tests define exactly what each case needs — no fixture directories, no I/O:
fsys := fstest.MapFS{ "templates/home.html": &fstest.MapFile{ Data: []byte(`<h1>{{.Title}}</h1>`), },}reg, _ := templator.NewRegistry[HomeData](fsys)Full worked tests are in Test templates.
What the fs.FS boundary buys you
Section titled “What the fs.FS boundary buys you”- Testability without fixtures. Site templates, malformed templates, and nested-layout cases are interchangeable data, not files on disk.
- Test data fidelity.
fstest.MapFSerrors match what real filesystems return, so your error-path tests behave like production. - No hidden state. The registry reads nothing outside the filesystem you hand it. It keeps no goroutines, watchers, or caches beyond the parsed handlers, so lifecycles are simple: keep the registry for the life of the app, drop it to force a re-parse.
- Modularity. Any
fs.FS-shaped thing works: a sub-FS (fs.Sub), a read-only overlay, or your own wrapper.