I use Echo in this post, but you could also use plain net/http, or any of the other awesome web frameworks for Go, see a list here.

The Go standard library provides a html/template package, for dynamically rendering HTML, it is built on top of text/template

Go templates are normal HTML files, with “Actions” (data evaluations or control structures), these “Actions” are delimited via {{and}}.

A template is executed via applying a data structure to it, the data structure is referenced as a dot . in the template.

Parsing Templates

Templates can be defined as string literals in a Go file, but it’s easier for organizing your templates and utilizing HTML support in your editor, to write your templates as separate files.

This templates, _ := template.ParseGlob("templates/*.html"), parses all .html files in the templates directory.

Writing Templates

This is an example template:

{{ template "head" . }}

{{ link "/foo" "Foo"}}
<p class="bg-gray-100">{{.test}}</p>
<p>{{toString .slice }}</p>

{{ template "foot" }}

But what does this do exactly?

{{ template "head" . }} embeds the head template from elsewhere and passes all template data to it (indicated by the dot).

{{ link "/foo" "Foo"}} calls the function link with the parameters "/foo" and "Foo", in plain Go this would be link("/foo", "Foo").

{{.test}} outputs the value of .test.

{{ toString .slice }} calls the function toString with the parameter .slice.

{{ template "foot" }} embeds the foot template from elsewhere, no data is passed (notice the missing dot).

Passing data to a template (rendering)

func root(c echo.Context) error {
	return c.Render(200, "index.html", map[string]interface{}{
		"title": "Root",
		"test":  "Hello, world!",
		"slice": []int{1, 2, 3},

A template is executed via supplying data for “dot” (.). In Echo this is done with c.Render inside a handler function.

You pass a HTTP status code (200), the name of the template to execute (index.html), and the data (in this case a map literal).

To access the data from the template you can use “dot”, an element of the map can be accessed via .key, for structs it’s similarly .field, methods of a data type can be called via .Method.

Registering Functions

It is nice to execute functions right in the template, for control flow e.g. “Is a user logged in?”, or to render something.

A function can be added to a template as follows:

t := template.New("new-template")
	"email": func() string {
		return "example@example.com"
	"anotherFunc": anotherFunc,

In your template you can then call the email function to show the email of the currently logged in user.

<p>Your email address: {{ email }}</p>

Defining templates

You can define reusable blocks of HTML via wrapping the code in a “define” block, see the following example:

{{ define "p"}}
{{ end }}

This can then be used in another template/ file via:

{{ template "p" "Some content"}}

And will output:

<p>Some content</p>

A more real world use case would be the following, which defines a “head” and a “foot” template:

# templates/base.html
{{ define "head" }}
<!DOCTYPE html>
<html lang="en">

    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="/dist/main.css">
    <script src="/dist/main.js" defer></script>

    <div class="container mx-auto">
        {{ end }}

        {{ define "foot" }}

{{ end }}

These templates were already used in the example template from the beginning:

# templates/index.html
{{ template "head" . }}

{{ link "/foo" "Foo"}}
<p class="bg-gray-100">{{.test}}</p>
<p>{{toString .slice }}</p>

{{ template "foot" }}

There is more

Go templates also support additional functionality not covered in this post, notably:

  • if/ else blocks for conditional rendering
  • range for looping over a slice, map, array or channel
  • with only rendering if a value exists

See https://pkg.go.dev/text/template, for more information.

Sample Project

I wrote a quick sample project which also includes additional functionality, it can be found here.

The sample project also uses:

  • Turbo, part of Hotwire a new approach from Basecamp for writing modern web applications without much JavaScript. I will maybe do a separate post about Turbo in the Future.
  • tailwindcss, makes HTML look nice.
  • webpack, for packing JS and CSS into single files, with minimization enabled, setup to extract CSS to a separate file.
  • Air, for hot reloading Go code and templates on change.