Skip to main content
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

dict

Creates a map from key-value pairs:
{{$user := dict "Name" "Alice" "Age" 30 "Admin" true}}
<p>{{$user.Name}} is {{$user.Age}} years old</p>

list

Creates a slice from values:
{{$fruits := list "apple" "banana" "cherry"}}
<ul>
{{range $fruits}}
    <li>{{.}}</li>
{{end}}
</ul>

String Functions

upper

Converts a string to uppercase:
<span class="status">{{upper .Data.Status}}</span>
<!-- "active" → "ACTIVE" -->

lower

Converts a string to lowercase:
<div class="type-{{lower .Data.Type}}">
<!-- "Featured" → "featured" -->

trim

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).

split

Splits a string into a slice:
{{$parts := split .Data.Tags ","}}
{{range $parts}}
    <span class="tag">{{trim .}}</span>
{{end}}

join

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

FunctionDescriptionExample
dictCreate a map{{dict "key" "value"}}
listCreate a slice{{list 1 2 3}}
upperUppercase string{{upper .Data.Name}}
lowerLowercase string{{lower .Data.Name}}
trimTrim whitespace{{trim .Data.Text}}
containsCheck substring{{if contains .Data.Text "hello"}}
replaceReplace substring{{replace .Data.Text "old" "new" -1}}
splitSplit string{{split .Data.Tags ","}}
joinJoin slice{{join .Data.Items ", "}}
hasPrefixCheck prefix{{if hasPrefix .Data.URL "https"}}
hasSuffixCheck suffix{{if hasSuffix .Data.File ".go"}}

Go’s Standard Functions

Go’s html/template package provides these built-in functions:

Comparison Functions

FunctionDescription
eqEqual
neNot equal
ltLess than
leLess than or equal
gtGreater than
geGreater 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

FunctionDescription
andLogical AND
orLogical OR
notLogical 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

FunctionDescription
lenLength of array, slice, map, or string
indexIndex into array, slice, or map
printFormat with default formatting
printfFormat with format string
printlnFormat with newline
htmlHTML escape
jsJavaScript escape
urlqueryURL 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

Date Formatting

"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>

Number Formatting

"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
}