Documentation Index
Fetch the complete documentation index at: https://docs.go-mizu.dev/llms.txt
Use this file to discover all available pages before exploring further.
The view engine provides built-in template functions that make your templates more expressive. This guide covers all available functions and how to add your own.
Built-in Functions
These functions are available in all templates.
Data Structure Functions
Creates a map from key-value pairs:
{{$user := dict "Name" "Alice" "Age" 30 "Admin" true}}
<p>{{$user.Name}} is {{$user.Age}} years old</p>
Creates a slice from values:
{{$fruits := list "apple" "banana" "cherry"}}
<ul>
{{range $fruits}}
<li>{{.}}</li>
{{end}}
</ul>
String Functions
Converts a string to uppercase:
<span class="status">{{upper .Data.Status}}</span>
<!-- "active" → "ACTIVE" -->
Converts a string to lowercase:
<div class="type-{{lower .Data.Type}}">
<!-- "Featured" → "featured" -->
Removes leading and trailing whitespace:
<p>{{trim .Data.Description}}</p>
contains
Checks if a string contains a substring:
{{if contains .Data.Email "@company.com"}}
<span class="internal">Internal User</span>
{{end}}
replace
Replaces occurrences of a substring:
{{replace .Data.Content "\n" "<br>" -1}}
<!-- Replace all newlines with <br> -->
The third argument is the maximum number of replacements (-1 means all).
Splits a string into a slice:
{{$parts := split .Data.Tags ","}}
{{range $parts}}
<span class="tag">{{trim .}}</span>
{{end}}
Joins a slice into a string:
<meta name="keywords" content="{{join .Data.Keywords ", "}}">
hasPrefix
Checks if a string starts with a prefix:
{{if hasPrefix .Data.URL "https"}}
<span class="secure">Secure</span>
{{end}}
hasSuffix
Checks if a string ends with a suffix:
{{if hasSuffix .Data.Filename ".pdf"}}
<span class="file-icon">PDF</span>
{{end}}
Summary Table
| Function | Description | Example |
|---|
dict | Create a map | {{dict "key" "value"}} |
list | Create a slice | {{list 1 2 3}} |
upper | Uppercase string | {{upper .Data.Name}} |
lower | Lowercase string | {{lower .Data.Name}} |
trim | Trim whitespace | {{trim .Data.Text}} |
contains | Check substring | {{if contains .Data.Text "hello"}} |
replace | Replace substring | {{replace .Data.Text "old" "new" -1}} |
split | Split string | {{split .Data.Tags ","}} |
join | Join slice | {{join .Data.Items ", "}} |
hasPrefix | Check prefix | {{if hasPrefix .Data.URL "https"}} |
hasSuffix | Check suffix | {{if hasSuffix .Data.File ".go"}} |
Go’s Standard Functions
Go’s html/template package provides these built-in functions:
Comparison Functions
| Function | Description |
|---|
eq | Equal |
ne | Not equal |
lt | Less than |
le | Less than or equal |
gt | Greater than |
ge | Greater than or equal |
{{if eq .Data.Status "active"}}Active{{end}}
{{if gt .Data.Count 10}}Many items{{end}}
{{if le .Data.Stock 5}}Low stock{{end}}
Logic Functions
| Function | Description |
|---|
and | Logical AND |
or | Logical OR |
not | Logical NOT |
{{if and .Data.LoggedIn .Data.IsAdmin}}Admin controls{{end}}
{{if or .Data.Error .Data.Warning}}Alert{{end}}
{{if not .Data.Disabled}}Enabled{{end}}
Other Standard Functions
| Function | Description |
|---|
len | Length of array, slice, map, or string |
index | Index into array, slice, or map |
print | Format with default formatting |
printf | Format with format string |
println | Format with newline |
html | HTML escape |
js | JavaScript escape |
urlquery | URL query escape |
<p>{{len .Data.Items}} items</p>
<p>First: {{index .Data.Items 0}}</p>
<p>{{printf "%.2f" .Data.Price}}</p>
Adding Custom Functions
Add your own functions via the Funcs option:
import (
"html/template"
"strings"
"time"
"github.com/go-mizu/mizu/view"
)
engine := view.New(view.Config{
Dir: "./views",
Funcs: template.FuncMap{
// Simple function
"greet": func(name string) string {
return "Hello, " + name + "!"
},
// Function with multiple arguments
"pluralize": func(count int, singular, plural string) string {
if count == 1 {
return singular
}
return plural
},
// Date formatting
"formatDate": func(t time.Time, layout string) string {
return t.Format(layout)
},
// Word count
"wordCount": func(s string) int {
return len(strings.Fields(s))
},
// Truncate with ellipsis
"truncate": func(s string, length int) string {
if len(s) <= length {
return s
}
return s[:length] + "..."
},
},
})
Using Custom Functions
<h1>{{greet .Data.Name}}</h1>
<p>{{.Data.Count}} {{pluralize .Data.Count "item" "items"}}</p>
<p>Published: {{formatDate .Data.CreatedAt "January 2, 2006"}}</p>
<p>{{wordCount .Data.Content}} words</p>
<p>{{truncate .Data.Description 100}}</p>
Common Custom Functions
"formatDate": func(t time.Time, layout string) string {
return t.Format(layout)
},
"timeAgo": func(t time.Time) string {
duration := time.Since(t)
switch {
case duration < time.Minute:
return "just now"
case duration < time.Hour:
return fmt.Sprintf("%d minutes ago", int(duration.Minutes()))
case duration < 24*time.Hour:
return fmt.Sprintf("%d hours ago", int(duration.Hours()))
default:
return fmt.Sprintf("%d days ago", int(duration.Hours()/24))
}
},
<time>{{formatDate .Data.CreatedAt "Jan 2, 2006"}}</time>
<span>{{timeAgo .Data.CreatedAt}}</span>
"formatNumber": func(n int) string {
// Simple thousands separator
s := strconv.Itoa(n)
if len(s) <= 3 {
return s
}
var result []byte
for i, c := range s {
if i > 0 && (len(s)-i)%3 == 0 {
result = append(result, ',')
}
result = append(result, byte(c))
}
return string(result)
},
"formatCurrency": func(amount float64) string {
return fmt.Sprintf("$%.2f", amount)
},
<p>{{formatNumber .Data.Views}} views</p>
<p>Price: {{formatCurrency .Data.Price}}</p>
URL Helpers
"urlEncode": url.QueryEscape,
"activeClass": func(currentPath, linkPath string) string {
if currentPath == linkPath {
return "active"
}
return ""
},
<a href="/search?q={{urlEncode .Data.Query}}">Search</a>
<a href="/" class="{{activeClass .Data.CurrentPath "/"}}">Home</a>
Safe HTML (Use Carefully!)
"safeHTML": func(s string) template.HTML {
return template.HTML(s)
},
{{/* Only use with trusted content! */}}
{{safeHTML .Data.TrustedContent}}
Default Values
"default": func(defaultVal, val interface{}) interface{} {
if val == nil || val == "" || val == 0 {
return defaultVal
}
return val
},
<img src="{{default "/default-avatar.png" .Data.Avatar}}">
Best Practices
1. Keep Functions Pure
Functions should not have side effects:
// Good: pure function
"double": func(n int) int { return n * 2 },
// Bad: has side effects
"logAndReturn": func(s string) string {
log.Println(s) // Side effect!
return s
},
2. Handle Edge Cases
"truncate": func(s string, length int) string {
if s == "" || length <= 0 {
return ""
}
if len(s) <= length {
return s
}
return s[:length] + "..."
},
3. Use Pipelines for Readability
<!-- Good: pipeline style -->
<p>{{.Data.Bio | truncate 200 | upper}}</p>
<!-- Also fine: nested calls -->
<p>{{upper (truncate .Data.Bio 200)}}</p>
4. Document Your Functions
Funcs: template.FuncMap{
// formatPhone formats a phone number as (XXX) XXX-XXXX
// Example: "1234567890" → "(123) 456-7890"
"formatPhone": formatPhone,
// pluralize returns singular if count is 1, plural otherwise
// Example: pluralize 1 "item" "items" → "item"
"pluralize": pluralize,
}
5. Test Custom Functions
func TestTruncate(t *testing.T) {
tests := []struct {
input string
length int
expected string
}{
{"hello", 10, "hello"},
{"hello world", 5, "hello..."},
{"", 5, ""},
}
for _, tt := range tests {
result := truncate(tt.input, tt.length)
if result != tt.expected {
t.Errorf("truncate(%q, %d) = %q, want %q",
tt.input, tt.length, result, tt.expected)
}
}
}
6. Be Careful with safeHTML
Only use safeHTML with content you completely trust:
<!-- Safe: your own content -->
{{safeHTML .Data.TrustedContent}}
<!-- Dangerous: user content - let Go escape it -->
{{.Data.UserComment}} <!-- Auto-escaped -->
Complete Example
package main
import (
"fmt"
"html/template"
"strings"
"time"
"github.com/go-mizu/mizu"
"github.com/go-mizu/mizu/view"
)
func main() {
engine := view.New(view.Config{
Dir: "./views",
DefaultLayout: "default",
Funcs: template.FuncMap{
// String helpers
"truncate": truncate,
"slugify": slugify,
// Date helpers
"formatDate": formatDate,
"timeAgo": timeAgo,
// Number helpers
"formatNumber": formatNumber,
"formatCurrency": formatCurrency,
// Logic helpers
"default": defaultValue,
"pluralize": pluralize,
},
})
app := mizu.New()
app.Use(engine.Middleware())
// ... routes
app.Listen(":8080")
}
func truncate(s string, length int) string {
if len(s) <= length {
return s
}
return s[:length] + "..."
}
func slugify(s string) string {
return strings.ToLower(strings.ReplaceAll(s, " ", "-"))
}
func formatDate(t time.Time, layout string) string {
return t.Format(layout)
}
func timeAgo(t time.Time) string {
d := time.Since(t)
switch {
case d < time.Minute:
return "just now"
case d < time.Hour:
return fmt.Sprintf("%dm ago", int(d.Minutes()))
case d < 24*time.Hour:
return fmt.Sprintf("%dh ago", int(d.Hours()))
default:
return fmt.Sprintf("%dd ago", int(d.Hours()/24))
}
}
func formatNumber(n int) string {
return fmt.Sprintf("%d", n) // Simplified
}
func formatCurrency(amount float64) string {
return fmt.Sprintf("$%.2f", amount)
}
func defaultValue(defaultVal, val interface{}) interface{} {
if val == nil || val == "" {
return defaultVal
}
return val
}
func pluralize(count int, singular, plural string) string {
if count == 1 {
return singular
}
return plural
}