O que é Template em Go?
Um template em Go é um mecanismo da biblioteca padrão para gerar saída textual a partir de dados estruturados. Go oferece dois pacotes de template: text/template para texto genérico e html/template para HTML com proteção automática contra XSS (Cross-Site Scripting). Ambos compartilham a mesma sintaxe e API, mas html/template adiciona escaping contextual que é essencial para aplicações web seguras.
O sistema de templates de Go usa uma abordagem baseada em ações (delimitadas por {{ }}) que são substituídas por valores durante a execução. Diferente de engines de template como Jinja2 (Python) ou ERB (Ruby), os templates de Go são deliberadamente simples — eles não permitem lógica de negócios complexa dentro do template, forçando uma separação limpa entre apresentação e lógica.
Essa filosofia se alinha com a abordagem geral de Go de simplicidade e clareza. Templates são usados em diversas situações: renderização de páginas web, geração de emails, criação de arquivos de configuração, geração de código e até na formatação de saída de ferramentas CLI. Para aplicações web modernas, consulte também o artigo sobre Go com HTMX e Templ para templates type-safe.
Template básico — text/template
O fluxo de trabalho com templates envolve três etapas: definir, parsear e executar.
package main
import (
"os"
"text/template"
)
func main() {
// 1. Definir o template
tmplStr := `Olá, {{.Nome}}!
Você tem {{.Idade}} anos e trabalha com {{.Linguagem}}.
{{if .Senior}}Nível: Sênior{{else}}Nível: Pleno{{end}}
`
// 2. Parsear
tmpl, err := template.New("saudacao").Parse(tmplStr)
if err != nil {
panic(err)
}
// 3. Executar com dados
dados := struct {
Nome string
Idade int
Linguagem string
Senior bool
}{
Nome: "Ana",
Idade: 28,
Linguagem: "Go",
Senior: true,
}
tmpl.Execute(os.Stdout, dados)
}
Saída:
Olá, Ana!
Você tem 28 anos e trabalha com Go.
Nível: Sênior
O ponto (.) no template refere-se ao dado atual no escopo. No nível mais externo, . é o objeto passado para Execute. Dentro de loops e blocos, . muda para o item atual — similar ao conceito de receiver em métodos Go.
html/template — templates seguros para web
Para aplicações web, sempre use html/template em vez de text/template. O pacote HTML faz escaping automático contextual:
package main
import (
"html/template"
"net/http"
)
func main() {
tmpl := template.Must(template.ParseFiles("templates/pagina.html"))
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
dados := struct {
Titulo string
Conteudo string
Comentario string
}{
Titulo: "Minha Página",
Conteudo: "Bem-vindo ao site!",
Comentario: `<script>alert("XSS")</script>`, // tentativa de ataque
}
tmpl.Execute(w, dados)
})
http.ListenAndServe(":8080", nil)
}
Com o template HTML:
<!DOCTYPE html>
<html lang="pt-br">
<head><title>{{.Titulo}}</title></head>
<body>
<h1>{{.Conteudo}}</h1>
<p>Comentário: {{.Comentario}}</p>
</body>
</html>
O html/template automaticamente escapa {{.Comentario}} para <script>alert("XSS")</script>, neutralizando o ataque XSS. Isso funciona em todos os contextos — HTML, atributos, JavaScript, CSS e URLs. Para mais sobre segurança em Go, consulte o tutorial de segurança.
Ações e controle de fluxo
Templates suportam condicionais, loops e controle de escopo:
const tmplStr = `
{{/* Comentário — não aparece na saída */}}
{{/* Condicional */}}
{{if .Premium}}
<div class="premium">Conta Premium</div>
{{else if .Trial}}
<div class="trial">Período de teste — {{.DiasRestantes}} dias</div>
{{else}}
<div class="free">Conta gratuita</div>
{{end}}
{{/* Loop com range */}}
<ul>
{{range .Produtos}}
<li>{{.Nome}} — R$ {{printf "%.2f" .Preco}}</li>
{{else}}
<li>Nenhum produto encontrado</li>
{{end}}
</ul>
{{/* Range com índice */}}
{{range $i, $p := .Produtos}}
<div>{{$i}}. {{$p.Nome}}</div>
{{end}}
{{/* Variáveis */}}
{{$total := len .Produtos}}
<p>Total: {{$total}} produtos</p>
{{/* With — muda o escopo do ponto */}}
{{with .Autor}}
<p>Por {{.Nome}} ({{.Email}})</p>
{{end}}
`
O range itera sobre slices, arrays e maps. O bloco else dentro do range é executado quando a coleção está vazia. O with muda o escopo de . para o valor especificado — útil para acessar campos aninhados sem repetição.
Funções de template
Templates têm funções built-in e permitem funções customizadas:
// Funções built-in principais
const builtins = `
{{len .Items}} {{/* comprimento */}}
{{index .Items 0}} {{/* acesso por índice */}}
{{printf "%.2f" .Preco}} {{/* formatação */}}
{{and .A .B}} {{/* AND lógico */}}
{{or .A .B}} {{/* OR lógico */}}
{{not .Ativo}} {{/* negação */}}
{{eq .Status "ativo"}} {{/* igualdade */}}
{{lt .Idade 18}} {{/* menor que */}}
{{call .Funcao .Arg}} {{/* chamada de função */}}
`
// Funções customizadas com FuncMap
func main() {
funcMap := template.FuncMap{
"maiusculo": strings.ToUpper,
"monetario": func(valor float64) string {
return fmt.Sprintf("R$ %.2f", valor)
},
"dataFormatada": func(t time.Time) string {
return t.Format("02/01/2006")
},
"truncar": func(s string, max int) string {
if len(s) <= max {
return s
}
return s[:max] + "..."
},
"safeHTML": func(s string) template.HTML {
return template.HTML(s) // CUIDADO: desativa escaping
},
}
tmpl := template.Must(
template.New("pagina").Funcs(funcMap).Parse(`
<h1>{{maiusculo .Titulo}}</h1>
<p>Preço: {{monetario .Preco}}</p>
<p>Data: {{dataFormatada .Data}}</p>
<p>Resumo: {{truncar .Descricao 100}}</p>
`),
)
tmpl.Execute(os.Stdout, dados)
}
O FuncMap deve ser registrado antes do Parse. A função safeHTML merece atenção especial: ela desativa o escaping automático, portanto só deve ser usada com conteúdo que você controla completamente — nunca com input do usuário.
Pipelines
Pipelines permitem encadear operações com o operador |, similar ao pipe do Unix:
const tmplStr = `
{{/* Pipeline simples */}}
{{.Nome | maiusculo}}
{{/* Pipeline com múltiplos estágios */}}
{{.Descricao | truncar 50 | maiusculo}}
{{/* Pipeline com condicional */}}
{{if .Produtos | len | lt 0}}
<p>Sem produtos</p>
{{end}}
{{/* Pipeline com printf */}}
{{"Olá, %s! Você tem %d itens." | printf .Nome .Total}}
`
Pipelines são a forma idiomática de compor transformações em templates, mantendo a legibilidade. O resultado de cada estágio é passado como último argumento do próximo.
Herança de templates — block/define/template
Templates podem ser compostos usando define, block e template:
// base.html — template base
const baseHTML = `
<!DOCTYPE html>
<html lang="pt-br">
<head>
<meta charset="UTF-8">
<title>{{block "titulo" .}}Site Padrão{{end}}</title>
{{block "head_extra" .}}{{end}}
</head>
<body>
<nav>{{template "nav" .}}</nav>
<main>{{block "conteudo" .}}Conteúdo padrão{{end}}</main>
<footer>{{template "footer" .}}</footer>
</body>
</html>
`
// nav.html
const navHTML = `
{{define "nav"}}
<ul>
<li><a href="/">Home</a></li>
<li><a href="/sobre">Sobre</a></li>
<li><a href="/contato">Contato</a></li>
</ul>
{{end}}
`
// pagina.html — sobrescreve blocos da base
const paginaHTML = `
{{define "titulo"}}{{.Titulo}} | Meu Site{{end}}
{{define "conteudo"}}
<article>
<h1>{{.Titulo}}</h1>
<div>{{.Corpo}}</div>
</article>
{{end}}
`
func main() {
tmpl := template.Must(
template.New("base").Parse(baseHTML),
)
template.Must(tmpl.Parse(navHTML))
template.Must(tmpl.Parse(paginaHTML))
dados := struct {
Titulo string
Corpo template.HTML
}{
Titulo: "Artigo sobre Go",
Corpo: "<p>Go é uma linguagem incrível para backend.</p>",
}
tmpl.ExecuteTemplate(os.Stdout, "base", dados)
}
A diferença entre block e template: block define um bloco com conteúdo padrão que pode ser sobrescrito, enquanto template inclui um template nomeado sem fallback. Esse sistema é a base de layouts em aplicações web Go, equivalente à herança de templates do Django ou Blade (Laravel).
Templates com arquivos
Em produção, templates ficam em arquivos separados:
func main() {
// Parsear todos os templates de uma vez
tmpl := template.Must(template.New("").Funcs(funcMap).ParseGlob("templates/*.html"))
// Ou parsear arquivos específicos
tmpl2 := template.Must(template.ParseFiles(
"templates/base.html",
"templates/nav.html",
"templates/footer.html",
"templates/home.html",
))
mux := http.NewServeMux()
mux.HandleFunc("GET /", func(w http.ResponseWriter, r *http.Request) {
tmpl.ExecuteTemplate(w, "base.html", dados)
})
http.ListenAndServe(":8080", mux)
}
Embedding templates (Go 1.16+)
Com embed, templates são compilados junto com o binário:
//go:embed templates/*.html
var templateFS embed.FS
func main() {
tmpl := template.Must(
template.New("").Funcs(funcMap).ParseFS(templateFS, "templates/*.html"),
)
// Agora o binário contém todos os templates — zero dependências de arquivo
mux := http.NewServeMux()
mux.HandleFunc("GET /", func(w http.ResponseWriter, r *http.Request) {
tmpl.ExecuteTemplate(w, "home.html", dadosHome())
})
http.ListenAndServe(":8080", mux)
}
Isso é ideal para deploy em containers Docker — um único binário sem diretório de templates externo.
Templates e handlers HTTP
O padrão completo para usar templates com handlers HTTP:
type App struct {
templates *template.Template
db *sql.DB
}
func NewApp(db *sql.DB) *App {
funcMap := template.FuncMap{
"monetario": formatarMoeda,
"dataRelativa": dataRelativa,
}
tmpl := template.Must(
template.New("").Funcs(funcMap).ParseFS(templateFS, "templates/*.html"),
)
return &App{templates: tmpl, db: db}
}
func (app *App) renderizar(w http.ResponseWriter, tmplName string, dados interface{}) {
w.Header().Set("Content-Type", "text/html; charset=utf-8")
if err := app.templates.ExecuteTemplate(w, tmplName, dados); err != nil {
slog.Error("erro ao renderizar template", "template", tmplName, "err", err)
http.Error(w, "Erro interno", http.StatusInternalServerError)
}
}
func (app *App) homeHandler(w http.ResponseWriter, r *http.Request) {
produtos, err := app.listarProdutos(r.Context())
if err != nil {
http.Error(w, "Erro interno", http.StatusInternalServerError)
return
}
app.renderizar(w, "home.html", map[string]interface{}{
"Titulo": "Bem-vindo",
"Produtos": produtos,
})
}
O método renderizar centraliza o tratamento de erros de template, evitando duplicação em cada handler. Para organização avançada, consulte o tutorial de Clean Architecture.
Segurança em templates HTML
O html/template faz escaping automático baseado no contexto:
// SEGURO — html/template faz escaping automático
tmpl := template.Must(template.New("").Parse(`
<p>{{.Texto}}</p> {{/* Escapa HTML */}}
<a href="{{.URL}}">Link</a> {{/* Escapa URL */}}
<div style="{{.Estilo}}"> {{/* Escapa CSS */}}
<script>var x = {{.JSON}}</script> {{/* Escapa JS */}}
`))
// Tipos especiais para conteúdo confiável
type DadosPagina struct {
HTMLConfiavel template.HTML // NÃO será escapado
URLConfiavel template.URL // NÃO será escapado
CSSConfiavel template.CSS // NÃO será escapado
JSConfiavel template.JS // NÃO será escapado
}
Use os tipos template.HTML, template.URL, template.CSS e template.JS apenas com conteúdo que você controla. Nunca converta input do usuário para esses tipos — isso desativaria a proteção contra XSS.
Alternativas modernas: Templ
Para projetos que precisam de type safety em templates, considere o Templ — uma engine que gera código Go a partir de templates, oferecendo verificação em tempo de compilação:
// componente.templ — compilado para Go
templ Pagina(titulo string, items []Item) {
<!DOCTYPE html>
<html lang="pt-br">
<head><title>{titulo}</title></head>
<body>
<h1>{titulo}</h1>
for _, item := range items {
<div>{item.Nome}</div>
}
</body>
</html>
}
Perguntas Frequentes
Qual a diferença entre text/template e html/template?
A sintaxe e API são idênticas. A diferença fundamental é que html/template adiciona escaping automático contextual: conteúdo inserido em HTML é escapado como HTML, em URLs como URL, em CSS como CSS e em JavaScript como JS. Isso previne ataques XSS automaticamente. Para qualquer aplicação web, sempre use html/template. Use text/template apenas para geração de texto não-HTML (configs, emails texto puro, código).
Como fazer herança de templates em Go?
Use {{block "nome" .}}conteúdo padrão{{end}} no template base para definir blocos que podem ser sobrescritos. Nos templates filhos, use {{define "nome"}}novo conteúdo{{end}} para substituir o bloco. Combine múltiplos templates com ParseFiles ou ParseGlob, e renderize com ExecuteTemplate. Esse padrão é equivalente à herança de templates do Django ou Blade.
Templates são lentos em Go?
Templates parseados são rápidos — o parsing acontece uma vez (no startup) e a execução usa o template compilado. Um benchmark típico mostra renderização de templates HTML complexos em microsegundos. A recomendação é parsear templates uma vez com template.Must e reutilizar. Nunca parsear dentro de handlers HTTP — isso criaria overhead desnecessário por requisição.
Quando usar Templ em vez de html/template?
Use html/template para projetos simples a médios onde a flexibilidade de templates dinâmicos é vantajosa. Use Templ quando precisar de: verificação de tipos em tempo de compilação, autocompletar na IDE, componentes reutilizáveis com parâmetros tipados e detecção de erros de template antes do deploy. Templ é especialmente útil em projetos grandes com equipes que preferem segurança de tipos.