 
        
         
        Templates are files that define a specific pattern and give room for automation, be it a simple text file or an HTML file for a webpage. If it provides space for customization, it is a template.
 
You can write programming logic to parse simple templates, but as the level of customization in your template increases, the programming logic you need to customize increases, making it less feasible to write template parsing logic.
Programming languages and web development frameworks support template parsing out of the box or provide libraries for template parsing.
In this article, we will explore features in the Go programming language that support template parsing. We can use these methods to parse any file extension, but we will use only text files in this article.
This guide will also only display Go code and the least amount of template (text) needed to understand the Go code.
The Replay is a weekly newsletter for dev and engineering leaders.
Delivered once a week, it's your curated guide to the most important conversations around frontend dev, emerging AI tools, and the state of modern software.
Before beginning our tutorial, you should have:
You can also clone the guide’s repository to access the complete template files or enter:
git clone https://github.com/Bamimore-Tomi/go-templates-guide.git
In this section, we’ll explore the features of the text/template package in Go.
ParseFiles in GoTo work with templates, you must parse them into your Go program.
The text/template standard library provides the functions needed to parse our program:
package main
import (
    "log"
    "os"
    "text/template"
)
// Prints out the template without passing any value using the text/template package
func main() {
    template, err := template.ParseFiles("template-01.txt")
    // Capture any error
    if err != nil {
        log.Fatalln(err)
    }
    // Print out the template to std
    template.Execute(os.Stdout, nil)
}
//OUTPUT
// Hi <no value>
// You are welcome to this tutorial
The program above prints a template file called template-01.txt. The template variable holds the content of the file. To print out the file to Stdout, we call the Execute method.
ParseGlobFor parsing multiple files at once, the ParseGlob function is useful:
package main
import (
    "log"
    "os"
    "text/template"
)
// Parse all the files in a certain directory
func main() {
    // This function takes a pattern. It can be a folder
    temp, err := template.ParseGlob("templates/*")
    if err != nil {
        log.Fatalln(err)
    }
    // Simply calling execute parses the first file in the directory
    err = temp.Execute(os.Stdout, nil)
    if err != nil {
        log.Fatalln(err)
    }
    // Or you can execute a particular template in the directory
    err = temp.ExecuteTemplate(os.Stdout, "template-03.txt", nil)
    if err != nil {
        log.Fatalln(err)
    }
    // Calling a template not in the directory will produce an error
    err = temp.ExecuteTemplate(os.Stdout, "template-04.txt", nil)
    if err != nil {
        log.Fatalln(err)
    }
}
With this code, we parsed all the files in the templates/ directory to our program. To execute any of the templates parsed, we call the ExecuteTemplate method on the result of ParseGlob.
Execute method in GoThe Execute method is where we parse data into the template(s). Here is the template-04.txt file:
Hello {{.}}
You are doing great. Keep learning.
Do not stop {{.}}
The {{.}} tells the text/template package where to place the data that is passed into the template. In this template, we want to set the data in two places: line 1 and line 4:
package main
import (
    "log"
    "os"
    "text/template"
)
// Declare type pointer to a template
var temp *template.Template
// Using the init function to make sure the template is only parsed once in the program
func init() {
    // template.Must takes the reponse of template.ParseFiles and does error checking
    temp = template.Must(template.ParseFiles("template-04.txt"))
}
func main() {
    // Execute myName into the template and print to Stdout
    myName := "Oluwatomisin"
    err := temp.Execute(os.Stdout, myName)
    if err != nil {
        log.Fatalln(err)
    }
}
// Hello Oluwatomisin
// You are doing great. Keep learning.
// Do not stop Oluwatomisin
Here, we use slightly different syntax to initialize the template. temp.Execute takes an io.writer and data interface{}, which is myName in this case.
You can also pass in more complicated data structures in your template; the only change to do this is how you access the structures inside the template file, which we will discuss in the next section.
We can also initialize variables right inside a template. Check the example in template-05.txt:
Hello {{.Name}}
{{$aTrait := .Trait}}
You are {{$aTrait}}
Notice a change in how we use data inside the template. template.Execute takes a data interface{} argument, which means we can execute a struct in a template:
package main
import (
    "log"
    "os"
    "text/template"
)
type PersonTrait struct {
    Name string
    Trait string
}
// Declare type pointer to a template
var temp *template.Template
// Using the init function to make sure the template is only parsed once in the program
func init() {
    // template.Must takes the reponse of template.ParseFiles and does error checking
    temp = template.Must(template.ParseFiles("template-05.txt"))
}
func main() {
    // Execute person into the template and print to Stdout
    person := PersonTrait{Name: "Oluwatomisin", Trait: "a great writer"}
    err := temp.Execute(os.Stdout, person)
    if err != nil {
        log.Fatalln(err)
    }
}
// Hello Oluwatomisin
// You are a great writer
In this example, we execute the PersonTrait struct in the template. This way, you can execute any data type inside a template.
The text/template package also allows you to run loops inside a template. In template-06.txt, we will list some cute pets:
Animals are cute; some cute animals are:
{{range .}}
{{.}}
{{end}}
In the program, we must execute a slice of cute pets in the template:
package main
import (
    "log"
    "os"
    "text/template"
)
// Declare type pointer to a template
var temp *template.Template
// Using the init function to make sure the template is only parsed once in the program
func init() {
    // template.Must takes the reponse of template.ParseFiles and does error checking
    temp = template.Must(template.ParseFiles("template-06.txt"))
}
func main() {
    // Execute cuteAnimals into the template and print to Stdout
    cuteAnimals := []string{"Dogs", "Cats", "Mice", "Fish"}
    err := temp.Execute(os.Stdout, cuteAnimals)
    if err != nil {
        log.Fatalln(err)
    }
}
// Animals are cute, some cute animals are:
// Dogs
// Cats
// Mice
// Fish
We can also loop through a map if needed:
Animals are cute, some cute animals are:
{{range $key, $val := .}}
{{$key}} , {{$val}}
{{end}}
Then, we can create and execute the map in the program:
package main
import (
    "log"
    "os"
    "text/template"
)
// Declare type pointer to a template
var temp *template.Template
// Using the init function to make sure the template is only parsed once in the program
func init() {
    // template.Must takes the response of template.ParseFiles and does error checking
    temp = template.Must(template.ParseFiles("template-06.txt"))
}
func main() {
    // Execute cuteAnimalsSpecies into the template and print to Stdout
    cuteAnimalsSpecies := map[string]string{
        "Dogs": "German Shepherd",
        "Cats": "Ragdoll",
        "Mice": "Deer Mouse",
        "Fish": "Goldfish",
    }
    err := temp.Execute(os.Stdout, cuteAnimalsSpecies)
    if err != nil {
        log.Fatalln(err)
    }
}
// Animals are cute, some cute animals are:
// Cats , Ragdoll
// Dogs , German Shepherd
// Fish , Goldfish
// Mice , Deer Mouse
To add more customization to our templates, we can use conditional statements. To use conditionals, we must call the comparison functions inside the template. In this example, we can check whether a random integer is less or greater than 200:
{{if (lt . 200)}}
Number {{.}} is less than 200
{{else}}
Number {{.}} is greater than 200
{{end}}
The (lt . 200) syntax is how we compare the random integer value using lt. Other operators include:
lt for the less-than operatorgt for the greater-than operatoreq for the equals-o operatorne for the not equals-to operatorle for the less-than-or-equal-to operatorge for the greater-than-or-equal-to operatorNow, we can generate the random value in our main program:
package main
import (
    "log"
    "math/rand"
    "os"
    "text/template"
    "time"
)
// Declare type pointer to a template
var temp *template.Template
// Using the init function to make sure the template is only parsed once in the program
func init() {
    // template.Must takes the reponse of template.ParseFiles and does error checking
    temp = template.Must(template.ParseFiles("template-06.txt"))
}
func main() {
    // Generate random number between 100 and 300
    rand.Seed(time.Now().UnixNano())
    min := 100
    max := 300
    // Execute myNumber into the template and print to Stdout
    myNumber := rand.Intn((max-min)+1) + min
    err := temp.Execute(os.Stdout, myNumber)
    if err != nil {
        log.Fatalln(err)
    }
}
// Number 141 is less than 200
The text/template package also provides a way to execute custom functions inside a template. A famous example is converting timestamps into other date formats:
Hi,
Time before formatting : {{.}}
Time After formatting : {{formatDate .}}
The template displays the time before and after parsing with the formatDate function. Observe the syntax that can call the custom function:
package main
import (
    "log"
    "os"
    "text/template"
    "time"
)
// Declare type pointer to a template
var temp *template.Template
// Using the init function to make sure the template is only parsed once in the program
func init() {
    // template.Must takes the reponse of template.ParseFiles and does error checking
    temp = template.Must(template.New("template-07.txt").Funcs(funcMap).ParseFiles("template-08.txt"))
}
// Custom function must have only 1 return value, or 1 return value and an error
func formatDate(timeStamp time.Time) string {
    //Define layout for formatting timestamp to string
    return timeStamp.Format("01-02-2006")
}
// Map name formatDate to formatDate function above
var funcMap = template.FuncMap{
    "formatDate": formatDate,
}
func main() {
    timeNow := time.Now()
    err := temp.Execute(os.Stdout, timeNow)
    if err != nil {
        log.Fatalln(err)
    }
}
// Hi,
// Time before formatting : 2021-10-04 18:01:59.6659258 +0100 WAT m=+0.004952101
// Time After formatting: 09-04-2021
The layout format must follow the timestamp format — in this case, Jan 2 15:04:05 2006 MST. Check the method’s documentation for more information.
Here, however, we use template.FuncMap to map a string to the custom function. The function will then be referenced using the string inside the template.
Whenever you see your favorite newsletter that is customized to you, chances are an email template and a templating processing package like text/template in Go made it look that way. The following program will customize a newsletter from the various techniques and methods in the text/template package.
package main
 
import (
   "fmt"
   "os"
   "text/template"
   "time"
)
 
// Declare type pointer to a template
var temp *template.Template
 
// Using the init function to make sure the template is only parsed once in the program
func init() {
   // template.Must takes the reponse of template.ParseFiles and does error checking
   temp = template.Must(template.New("template-08.txt").Funcs(funcMap).ParseFiles("template-08.txt"))
}
 
// Custom function must have only 1 return value, or 1 return value and an error
func formatDate(timeStamp time.Time) string {
   //Define layout for formatting timestamp to string
   // return timeStamp.Format("01-02-2006")
   return timeStamp.Format("Mon, 02 Jan 2006")
 
}
 
// Map name formatDate to formatDate function above
var funcMap = template.FuncMap{
   "formatDate": formatDate,
}
 
type customer struct {
   Username             string
   Subscriptions        []string
   LastLoginDays        int
   SubscriptionDeadline time.Time
}
 
func check(e error) {
   if e != nil {
       panic(e)
   }
}
 
func main() {
   customers := []customer{
       {
           Username:             "wisman",
           Subscriptions:        []string{"Kikat", "Lollipop", "Marshmallow", "Oreo", "Jelly Bean"},
           LastLoginDays:        10,
           SubscriptionDeadline: time.Now(),
       },
       {
           Username:             "berry blings",
           Subscriptions:        []string{"Honeycomb", "Gingerbread", "Nougat", "Froyo", "Donut"},
           LastLoginDays:        5,
           SubscriptionDeadline: time.Date(2023, 3, 15, 5, 5, 5, 3, time.UTC),
       },
       {
           Username:             "savy",
           Subscriptions:        []string{"Honeycomb", "Gingerbread", "Nougat", "Froyo", "Donut"},
           LastLoginDays:        5,
           SubscriptionDeadline: time.Date(2023, 6, 15, 5, 5, 5, 3, time.UTC),
       },
   }
   for _, user := range customers {
       // Create a new file for each customer
       file, err := os.Create(fmt.Sprintf("output/%v.txt", user.Username))
       check(err)
       // Execute the template for each user into their respective files
       err = temp.Execute(file, user)
       check(err)
   }
}
The program executes an email template using the customers’ data.
Hi {{.Username}},
We notice you last logged in {{.LastLoginDays}} days ago.
Your current subscriptions:
{{range $subscription := .Subscriptions}}{{$subscription}}
{{end}}
expires on {{formatDate .SubscriptionDeadline}}.
After running the program, it outputs a file for each customer in the array of customers:

We have seen a range of features in the text/template package with usage examples. All the features implemented here are the same for the html/template package. Outputs generated from html/template are safe from cross-site scripting by providing auto-escaping and context-sensitive sanitization of inputs.
When using templates in the web context where the output is HTML, use the html/template package.
            Install LogRocket via npm or script tag. LogRocket.init() must be called client-side, not
            server-side
            
$ npm i --save logrocket 
// Code:
import LogRocket from 'logrocket'; 
LogRocket.init('app/id');
                    
                    
// Add to your HTML:
<script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script>
<script>window.LogRocket && window.LogRocket.init('app/id');</script>
                    
                    
line-clamp to trim lines of textMaster the CSS line-clamp property. Learn how to truncate text lines, ensure cross-browser compatibility, and avoid hidden UX pitfalls when designing modern web layouts.

Discover seven custom React Hooks that will simplify your web development process and make you a faster, better, more efficient developer.

Promise.all still relevant in 2025?In 2025, async JavaScript looks very different. With tools like Promise.any, Promise.allSettled, and Array.fromAsync, many developers wonder if Promise.all is still worth it. The short answer is yes — but only if you know when and why to use it.

Discover what’s new in The Replay, LogRocket’s newsletter for dev and engineering leaders, in the October 29th issue.
Hey there, want to help make our blog better?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up now 
         
        