go
51 lines · 6 steps
Auto-escaping HTML with Go templates
How html/template renders user data safely, neutralizing XSS without any manual escaping.
Explained by
highlit
1package main
2
3import (
4 "html/template"
5 "net/http"
6)
7
8type Comment struct {
9 Author string
10 Body string
11 HomeURL string
12}
13
14type pageData struct {
15 Title string
16 Comments []Comment
17}
18
19var commentPage = template.Must(template.New("comments").Parse(`
20<!DOCTYPE html>
21<html>
22<head><title>{{ .Title }}</title></head>
23<body>
24 <h1>{{ .Title }}</h1>
25 <ul>
26 {{ range .Comments }}
27 <li>
28 <a href="{{ .HomeURL }}">{{ .Author }}</a>
29 <p>{{ .Body }}</p>
30 </li>
31 {{ else }}
32 <li>No comments yet.</li>
33 {{ end }}
34 </ul>
35</body>
36</html>`))
37
38func commentsHandler(w http.ResponseWriter, r *http.Request) {
39 data := pageData{
40 Title: "Guestbook",
41 Comments: []Comment{
42 {Author: "Ada", Body: "<script>alert('xss')</script>", HomeURL: "https://ada.dev"},
43 {Author: "Grace & Co", Body: "Loved it!", HomeURL: "javascript:steal()"},
44 },
45 }
46
47 w.Header().Set("Content-Type", "text/html; charset=utf-8")
48 if err := commentPage.Execute(w, data); err != nil {
49 http.Error(w, "render failed", http.StatusInternalServerError)
50 }
51}
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1html/template escapes values based on where they appear, so the same data is treated differently in text, attributes, and URLs.
- 2template.Must panics at startup on a bad template, catching parse errors before any request is served.
- 3Dangerous input like script tags or javascript: URLs is neutralized automatically, no manual sanitizing required.
Related explainers
go
package streaming import ( "bufio"
Streaming NDJSON logs over HTTP in Go
http-streaming
channels
select
Advanced
10 steps
go
package api import ( "crypto/sha256"
ETag conditional requests in Gin
http-caching
etag
conditional-requests
Intermediate
6 steps
go
func (w *Watcher) resetDebounce(d time.Duration) { if !w.timer.Stop() { select { case <-w.timer.C:
Debouncing a stream of events in Go
debounce
timers
channels
Advanced
7 steps
java
public static Map<String, String> parseCookieHeader(String header) { Map<String, String> cookies = new LinkedHashMap<>(); if (header == null || header.isBlank()) { return cookies;
Parsing an HTTP Cookie header in Java
string-parsing
http
url-decoding
Intermediate
6 steps
go
package logging import ( "context"
Deduplicating log attributes in Go's slog
decorator-pattern
structured-logging
immutability
Intermediate
8 steps
go
package middleware import ( "net/http"
Per-plan export limits in Gin middleware
middleware
rate-limiting
authorization
Intermediate
7 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/auto-escaping-html-with-go-templates-explained-go-88ba/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.