Generating an Atom feed using Go
See Atom feeds for a summary of the Atom specification.
See notes with the “rss” tag for other implementations.
This implementation uses the atom package from the golang.org/x/tools module.
Setup
Create an initial main.go:
package main
import (
"encoding/xml"
"fmt"
"golang.org/x/tools/blog/atom"
)
func main() {
feed := atom.Feed{}
out, _ := xml.Marshal(feed)
fmt.Println(string(out))
}
Fetch the dependency and vendor it. Then check that everything is set up:
go mod init test
go mod tidy
go mod vendor
go run .
Creating a feed
In main.go, set the desired metadata on the Feed:
updated := parseDate("2025-11-02")
feed := atom.Feed{
Title: "My Blog",
ID: "tag:example.com,2025-11:blog",
Link: []atom.Link{
{Rel: "self", Href: "https://www.example.com/feed.atom"},
{Rel: "alternate", Href: "https://www.example.com/"},
},
Updated: atom.Time(updated),
Author: &atom.Person{
Name: "AN Author",
URI: "https://www.example.com/about.html",
},
}
This makes use of a helper function:
func parseDate(date string) time.Time {
t, err := time.Parse("2006-01-02", date)
if err != nil {
log.Fatal(err)
}
return t.Add(12 * time.Hour)
}
Note that the following metadata cannot be set using this library, unlike with FeedGenerator:
- subtitle
- language
- copyright
- categories
Neither library supports adding the following:
- icon
- logo
- generator
- base URL for resolving relative links in entry contents
Adding entries
For each post, after parsing the metadata and reading in the body,
create an Entry and append it to the feed:
body := `<h1>New year, new blog</h1>
<p>Let me tell you all about my blogging setup!</p>`
entry := &atom.Entry{
Title: "New year, new blog",
ID: makeTagUri("www.example.com", "/first-post/", "2025-01-01"),
Link: []atom.Link{
{Rel: "alternate", Href: "https://www.example.com/first-post/"},
},
Published: atom.Time(parseDate("2025-01-01")),
Updated: atom.Time(parseDate("2025-11-02")),
Summary: &atom.Text{Body: "My blogging setup", Type: "html"},
Content: &atom.Text{Body: body, Type: "html"},
}
feed.Entry = append(feed.Entry, entry)
This makes use of another helper function:
func makeTagUri(hostname, path, date string) string {
return fmt.Sprintf("tag:%s,%s:%s", hostname, date, path)
}
Note that the following metadata cannot be set using this library, unlike with FeedGenerator:
- categories
Writing to disk
In order to add the XML header to the output, as well as some indentation for readability, let’s use another helper function:
func main() {
// ...
writeXML(os.Stdout, &feed)
}
func writeXML(w io.Writer, feed *atom.Feed) {
_, err := w.Write([]byte(xml.Header))
if err != nil {
log.Fatal("write xml header: ", err)
}
enc := xml.NewEncoder(w)
enc.Indent("", " ")
err = enc.Encode(feed)
if err != nil {
log.Fatal("xml encode: ", err)
}
}
The resulting output looks like this:
<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>My Blog</title>
<id>tag:example.com,2025-11:blog</id>
<link rel="self" href="https://www.example.com/feed.atom"></link>
<link rel="alternate" href="https://www.example.com/"></link>
<updated>2025-11-02T12:00:00+00:00</updated>
<author>
<name>AN Author</name>
<uri>https://www.example.com/about.html</uri>
</author>
<entry>
<title>New year, new blog</title>
<id>tag:www.example.com,2025-01-01:/first-post/</id>
<link rel="alternate" href="https://www.example.com/first-post/"></link>
<published>2025-01-01T12:00:00+00:00</published>
<updated>2025-11-02T12:00:00+00:00</updated>
<summary type="html">My blogging setup</summary>
<content type="html"><h1>New year, new blog</h1>
<p>Let me tell you all about my blogging setup!</p></content>
</entry>
</feed>