RSS & Atom cheat sheet
A scannable RSS & Atom reference: 10 short snippets across 8 topics, each linking back to the lesson it came from.
At a glance
| Topic | What it covers | |
|---|---|---|
| The RSS 2.0 feed format | An RSS 2.0 feed is an XML document with a single <rss> root, version 2.0, containing exactly one <channel> | lesson |
| Generating a valid feed | Hand-written string templates are how feeds break. One headline containing an ampersand, an emoji, or a stray tag | lesson |
| Atom and how readers consume feeds | Atom is stricter and better specified; RSS 2.0 is simpler and better supported. Serving both is cheap — many sites | lesson |
| Choosing a feed format: RSS 2.0, Atom and JSON Feed | The audience decides the format: readers and podcasts need RSS, Atom is stricter and date-safe, and JSON Feed is | lesson |
| Podcast feeds and the Apple and Podcasting 2.0 tags | A podcast feed is an RSS feed with an enclosure, plus a set of namespaced tags that directories enforce strictly enough | lesson |
| Validating and troubleshooting feeds | A feed that a browser renders can still be rejected by readers. Validation in the build is the only way to know before | lesson |
| Feed security: sanitising third-party content | Fetching and rendering a third-party feed is running untrusted code against your own systems, and the risks are | lesson |
| Feeds at scale: pagination, archives and pipelines | A feed with a hundred thousand items is useless to a reader. Paged and archive feeds, generated incrementally, are what | lesson |
Quick snippets
The RSS 2.0 feed format
The details readers rely on
<item>
<title>Episode 12: Shipping small</title>
<enclosure url="https://cdn.example.com/ep12.mp3" length="48213004" type="audio/mpeg"/>
<category>engineering</category>
<author>[email protected] (Platform Team)</author>
<comments>https://example.com/posts/ep12#comments</comments>
</item>Full lesson: The RSS 2.0 feed format →
Generating a valid feed
Validate and serve correctly
# well-formedness, then structure against the RSS 2.0 schema
xmllint --noout feed.xml
curl -sSL https://www.rssboard.org/files/schema.rng -o rss.rng
xmllint --noout --relaxng rss.rng feed.xml
# check what the reader will actually see
curl -sSI https://example.com/feed.xml | grep -i content-type
curl -s https://example.com/feed.xml | xmllint --format - | head -30
Validate and serve correctly
HTTP/1.1 200 OK
Content-Type: application/rss+xml; charset=utf-8
Cache-Control: max-age=300
ETag: "feed-2026-09-18-01"
Last-Modified: Fri, 18 Sep 2026 09:00:00 GMT
Validate and serve correctly
<link rel="alternate" type="application/rss+xml"
title="Example Dev Blog" href="https://example.com/feed.xml"/>Full lesson: Generating a valid feed →
Atom and how readers consume feeds
How a reader actually polls
# emulate a reader: fetch, remember the validator, then revalidate
curl -sSI https://example.com/feed.xml | grep -iE 'etag|last-modified'
curl -sS -o /dev/null -w '%{http_code}\n' \
-H 'If-None-Match: "feed-2026-09-18-01"' \
https://example.com/feed.xml # expect 304
# aggregate feeds are frequently rate limited - honour Retry-After
curl -sSI https://example.com/feed.xml | grep -i retry-afterFull lesson: Atom and how readers consume feeds →
Choosing a feed format: RSS 2.0, Atom and JSON Feed
One item model, several serialisers
<!-- Atom benefit in practice: the date is unambiguous and required -->
<entry>
<id>https://example.com/releases/2026-09-platform-3-2</id>
<title>Platform 3.2</title>
<updated>2026-09-18T09:00:00+01:00</updated>
<author><name>Platform Team</name></author>
<link rel="alternate" href="https://example.com/releases/2026-09-platform-3-2"/>
<summary>Streaming transforms and faster cold start.</summary>
<content type="html"><p>Streaming transforms and faster cold start.</p></content>
</entry>Full lesson: Choosing a feed format: RSS 2.0, Atom and JSON Feed →
Podcast feeds and the Apple and Podcasting 2.0 tags
Identifiers that survive a re-host
# A podcast guid must not depend on anything you might change later.
BAD = "https://cdn.example.com/audio/ep41.mp3" # breaks if you move the CDN
BAD2 = "Episode 41: Cold starts" # breaks if you fix a typo in a title
GOOD = "urn:example:podcast:shipping-it:ep-41" # opaque, permanent, ours
# If the feed moves to a new host, the <podcast:guid> of the channel should
# stay put too, so directories recognise it as the same show.
CHANNEL_GUID = "9f2b1c4d-1111-2222-3333-abcdef012345"Full lesson: Podcast feeds and the Apple and Podcasting 2.0 tags →
Validating and troubleshooting feeds
The checks worth automating
# Validate in the build, so a broken feed never reaches subscribers
import subprocess, sys
def build_and_check(path: str) -> None:
xml = render_feed()
open(path, "w", encoding="utf-8").write(xml)
r = subprocess.run(["xmllint", "--noout", path], capture_output=True, text=True)
if r.returncode != 0:
print(r.stderr, file=sys.stderr)
raise SystemExit("feed is not well-formed XML")Full lesson: Validating and troubleshooting feeds →
Feed security: sanitising third-party content
Rendering safely
Content-Security-Policy: default-src 'none'; img-src https: data:; style-src 'unsafe-inline'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'Full lesson: Feed security: sanitising third-party content →
Feeds at scale: pagination, archives and pipelines
Generating and serving incrementally
GET /feed.xml HTTP/1.1
If-None-Match: "412:2026-09-18T09:00:00+00:00"
HTTP/1.1 304 Not Modified
ETag: "412:2026-09-18T09:00:00+00:00"
Cache-Control: public, max-age=300
Content-Type: application/rss+xml; charset=utf-8Full lesson: Feeds at scale: pagination, archives and pipelines →
FAQ
Is this RSS & Atom cheat sheet free to use?
Where do the examples come from?
How do I go deeper than a cheat sheet?
Related cheat sheets
XML XPath XSLT SOAP RESTful APIs
Last refreshed 2026-09-27.