# Template semantics xtemplate templates are Go [`html/template`](https://pkg.go.dev/html/template) programs with file- and define-based routing, a uniform per-request [dot context](dot-context.md), and a few extra behaviors. Loading and routing rules are detailed in [Instance loading](instance-loading.md). ## Go template syntax Actions use the configured delimiters (default `{{` `}}`). Conditionals, ranges, pipelines, variables, and `define` / `block` / `template` work as in the standard library. Prefer the official [text/template](https://pkg.go.dev/text/template) and [html/template](https://pkg.go.dev/html/template) docs for syntax; this page covers xtemplate-specific behavior. Output is context-aware HTML-escaped by default. See [Design](../explanation/design.md) for the safety posture and the `trust*` / `sanitizeHtml` funcs when you need to opt out carefully. ## Functions and dot context Two extension mechanisms: | Mechanism | Lifetime | Typical use | |---|---|---| | Template functions (`{{myFunc x}}`) | Set when the instance is built; no request state | Formatting, strings, pure computation | | Dot fields (`{{.DB.QueryRows ...}}`) | From dot providers, built per request | DB, FS, request/response, I/O | > [!note] > Dot fields are initialized on every request with access to the underlying `http.Request` and `http.ResponseWriter`, the request-scoped logger, and the server context. Prefer template functions for simple computational work; dot fields for network, database, and filesystem access. - [Template functions](functions.md) - [Dot context](dot-context.md) ## Loops and conditionals Standard `range`, `if`, `with`, and `else` apply. SQL helpers often return maps or iterators that work directly with `range`: ```html ``` ## Global template namespace All templates under the template root share one namespace after load. A name comes from either: - the file path relative to the root with a leading `/` (path template), or - an explicit `{{define "name"}}` (define template). Later definitions with the same name override earlier ones (logged at debug). That means any file can invoke `/shared/.head.html` or a define named `navbar` regardless of directory. ## Invoking templates ```html {{template "/shared/.head.html" .}} {{template "navbar" .}} ``` Pass `.` (or a narrowed value) so nested templates keep the fields they need. Re-rendering a path template after a mutation is a common pattern: ```html {{define "POST /contacts/{id}"}} {{$_ := .DB.Exec `UPDATE contacts SET name=? WHERE id=?` (.Req.FormValue "name") (.Req.PathValue "id")}} {{template "/contacts/{id}.html" .}} {{end}} ``` ## Path templates and routes A path template is associated with a `GET` route derived from its file path (extension stripped; `index.html` → directory). Hidden basenames are still parsed into the global namespace but are not given a file-based GET route (so partials like `/shared/.head.html` remain invocable). See [Instance loading](instance-loading.md). ## Define-based routes `{{define "METHOD /path/{param}"}}` registers a route. Supported methods: `GET`, `POST`, `PUT`, `PATCH`, `DELETE`, and the pseudo-method `SSE` (flushing handler, registered as `GET`). Path parameters use ServeMux syntax and are read with `.Req.PathValue`. ```html {{define "GET /contact/{id}"}} {{$contact := .DB.QueryRow `SELECT name, phone FROM contacts WHERE id=?` (.Req.PathValue "id")}}
Name: {{$contact.name}} Phone: {{$contact.phone}}
{{end}} {{define "DELETE /contact/{id}"}} {{$_ := .DB.Exec `DELETE FROM contacts WHERE id=?` (.Req.PathValue "id")}} {{.Resp.SetStatus 204}} {{end}} ``` ## Early return An early return stops template execution successfully (not as an error). Triggers include: - the `return` function: `{{return}}` - response helpers such as `.Resp.ReturnStatus` (keeps the template buffer as the body) - `.Resp.RespondWith` (replaces the buffer with an explicit status and body) - some `.Flush` helper execution paths when the request or server context is cancelled (`Sleep`, `WaitForServerStop`) so streams can stop cleanly Handlers treat the internal sentinel as normal completion (not a failure). Use `failf` when you want a real failure (discard buffer → generic 500): ```html {{if eq (.Req.FormValue "name") ""}}{{failf "name is required"}}{{end}} ``` ### Load a resource or respond 404 Define-templates cannot return values to the caller. Use request-scoped [`.Vars`](dot-context.md#request-scratch-in-vars) as out-params and [`.Resp.RespondWith`](dot-context.md#replace-the-response-with-respondwith) when the resource is missing so partial output is not sent: ```html {{define "require-list"}} {{- $id := .Req.PathValue "id"}} {{- $r := try .DB "QueryRow" `SELECT id, name FROM lists WHERE id=?` $id}} {{- if not $r.OK}} {{.Resp.RespondWith 404 (.X.Template "/shared/.404.html" .)}} {{- end}} {{- .Vars.Set "list" $r.Value -}} {{end}} {{define "GET /list/{id}"}} {{- template "require-list" .}} {{- $list := .Vars.Get "list"}}

{{index $list "name"}}

{{end}} ``` Load defines should avoid writing output; they set vars or call `RespondWith`. Prefer `.Vars.Get` / `.Vars.Has` over Sprig `get` (which returns `""` on missing keys). Contrast: | Helper | Buffer | Typical use | |---|---|---| | `ReturnStatus` | kept | status after normal render | | `RespondWith` | replaced | 404 page, 400 text, empty 303 | | `ServeContent` | replaced | file-like payload | | `failf` | discarded | real failures → 500 | ## Related - [Instance loading](instance-loading.md) - [Dot context](dot-context.md) - `.Vars`, `.Resp.RespondWith` - [Glossary](glossary.md) - path template, define template, early return, response replace