Templates are parsed once at startup. The same template engine is used for all output formats. When a custom
HTML template is set via --html-template, the --template-name and --rotation-mode flags are ignored.
Error pages uses the standard Go text/template package (with HTML output treated as text to
preserve full control over markup). If you have never written a Go template before, do not worry - it is genuinely one
of the simplest templating languages around. The full mental model fits in a few minutes.
Key concepts:
{{ }}- action delimiters; everything outside them is emitted verbatim.{{ .Field }}- output a field from the data object (.is "current value").{{ if .Cond }} ... {{ else }} ... {{ end }}- conditional.{{ range .Slice }} {{ . }} {{ end }}- iteration (.becomes each element).{{ .Value | funcName }}- pipeline; passes the value on the left as the last argument to the function on the right.{{ /* comment */ }}- comment (not emitted).
Documentation:
text/templatepackage - the complete, authoritative reference.- Go Templates Guide - syntax explained with examples (recommended for beginners).
Warning
{{ and }} are reserved as Go template delimiters - any literal occurrence causes a parse error. This is
common in JSDoc type annotations (/** @param {{ id: number }} ❌ */), CSS, etc. To work around this, you can simply
add a single space between the braces: /** @param { { id: number } } ✅ */.
All templates receive a data object with the following fields:
| Field | Type | Description |
|---|---|---|
.StatusCode |
uint16 |
HTTP status code (e.g. 404) |
.Message |
string |
Short status text (e.g. Not Found) |
.Description |
string |
Longer description (e.g. The server can not find the requested page) |
.OriginalURI |
string |
Request URI that caused the error * |
.Namespace |
string |
Kubernetes namespace of the backend service * |
.IngressName |
string |
Name of the Ingress resource * |
.ServiceName |
string |
Name of the backend service * |
.ServicePort |
string |
Port of the backend service * |
.RequestID |
string |
Unique request ID * |
.ForwardedFor |
string |
Original client IP(s) from X-Forwarded-For * |
.Host |
string |
Request Host header * |
.HomepageURL |
string |
Homepage URL set via --homepage-url (empty if not configured) |
.Links |
[]Link |
Extra links set via --add-link (empty slice if not configured) |
.Config.ShowRequestDetails |
bool |
Whether --show-details is enabled |
.Config.L10nDisabled |
bool |
Whether --disable-l10n is set |
*- Requires--show-details
Each element of .Links has the following sub-fields:
| Sub-field | Type | Description |
|---|---|---|
.Label |
string |
Link text |
.URL |
string |
Target URL |
Example usage in a custom template:
{{ if .Links }}
<nav>
{{ range .Links }}<a href="{{ .URL }}">{{ .Label }}</a>{{ end }}
</nav>
{{ end }}In addition to the fields above, templates also have access to a set of built-in functions (see below), which are
pipeline-friendly (needle before haystack): {{ .Message | default "Unknown" | upper }}.
| Function | Description | Example |
|---|---|---|
now |
Current time (time.Time) |
{{ now.Format "2006-01-02" }} / {{ now.Unix }} |
hostname |
Server hostname | {{ hostname }} |
version |
Application version string | {{ version }} |
env "KEY" |
Env var value (sensitive keys masked with ***) |
{{ env "STAGE" }} |
toJson / toJSON |
JSON-encode a value | {{ .Message | toJson }} |
toInt / int |
Convert to integer | {{ .StatusCode | int }} |
toString / str |
Convert to string | {{ .StatusCode | str }} |
escape |
HTML-escape | {{ .OriginalURI | escape }} |
urlEncode |
URL-encode | {{ .OriginalURI | urlEncode }} |
trim |
Strip leading/trailing whitespace | {{ .Message | trim }} |
trimPrefix |
Remove prefix | {{ .Message | trimPrefix "Error: " }} |
trimSuffix / trimPostfix |
Remove suffix | {{ .Message | trimSuffix "!" }} |
trimAll |
Strip specific characters | {{ ".test." | trimAll "." }} |
lower / upper |
Change case | {{ .Message | upper }} |
replace |
Replace all occurrences | {{ .Message | replace " " "_" }} |
contains |
Substring check | {{ .Message | contains "test" }}...{{ end }} |
hasPrefix / hasSuffix |
Prefix/suffix check | {{ .Message | hasPrefix "test" }}...{{ end }} |
split |
Split string by separator | {{ split ";" "a;b;c" }} |
join |
Join slice with separator | {{ split ";" "a;b;c" | join ", " }} |
fields |
Split string by whitespace | {{ fields "foo bar baz" | join "-" }} |
substr |
Substring by rune index and length | {{ "Hello, World!" | substr 7 5 }} |
truncate |
Truncate with ... appended |
{{ .Description | truncate 80 }} |
repeat |
Repeat string N times | {{ "Ha" | repeat 3 }} |
quote / squote |
Wrap in double/single quotes | {{ .Message | quote }} |
count |
Count substring occurrences | {{ "test" | count "t" }} |
default |
Fallback value for empty input | {{ .OriginalURI | default "N/A" }} |
coalesce |
First non-empty value from a list | {{ coalesce .Message .Description "error" }} |
ternary |
Inline conditional | {{ .Config.ShowRequestDetails | ternary "shown" "hidden" }} |
isEmpty / isNotEmpty |
Emptiness check | {{ if isNotEmpty .Description }}...{{ end }} |
l10nScript |
Inline the localization JS script | <script>{{ l10nScript }}</script> |
Note
env masks values whose key (split by _) contains PASSWORD, SECRET, KEY, TOKEN, PASS, PWD,
or CRED (case-insensitive). Those calls return a string of * characters instead of the actual value.
HTML pages support automatic client-side localization in 15+ languages, templates that want l10n must:
- Add
data-l10nattributes to elements whose text content should be translated - Include
<script>{{ l10nScript }}</script>in the HTML
The browser detects the visitor's preferred language via navigator.languages and translates all [data-l10n]
elements in-place - no server round-trip required. Localization can be disabled globally with --disable-l10n
(or via env DISABLE_L10N=true).
You can find more details on handling localization in templates in the Localization documentation.