RSS Feed Generator
Newsletter operators migrating off Substack, podcasters who need an Apple-compatible enclosure feed, and static-site developers who want a hand-written feed.xml without a build plugin all assemble RSS channels by copying snippets from forum posts and hoping the dates parse. This generator walks you through channel metadata, per-item fields, guid rules, and podcast enclosures, then validates the result against the RSS 2.0 grammar before you download the file. Every warning names the exact parser behavior that would drop the item.
Items
—
How the Generator Builds and Validates the Feed
Channel fields and items are held in a small in-memory model. On every keystroke the tool re-serializes the model into XML text, escaping ampersands, angle brackets, and quotes in titles and descriptions so a post containing raw HTML cannot break the document. Dates typed into pubDate are parsed with the JavaScript Date parser and re-emitted in RFC 822 syntax — the only date format the RSS 2.0 specification accepts. Guids default to the item permalink with isPermaLink="true"; when you supply a custom guid the attribute flips to false so readers key on your stable identifier instead of the URL.
Enclosures are validated on three axes that trip up most hand-written feeds: the url must be absolute http(s), length must be a non-negative integer (Apple rejects decimal or missing values), and type must be a known MIME string. When the type is blank the tool guesses from the file extension — .mp3 becomes audio/mpeg, .m4a becomes audio/mp4 — and leaves the field empty with a warning if the extension is unknown. A separate warning pass checks channel-level requirements (title, link, description, absolute URLs) and item-level rules (non-empty title/link/description, parseable pubDate, enclosure completeness) so the output is ready for Feed Validator or Apple Podcasts Connect before you ship it.
Where the feed lives
Serve the file at a stable path such as /feed.xml and reference it from your HTML with a link rel="alternate" type="application/rss+xml" tag. CDNs cache static XML aggressively, so either set a short Cache-Control on the feed path or purge on publish. Apple Podcasts and Feedly poll the URL directly; never rotate the path without a 301 redirect.
Escaping and CDATA
The generator escapes all special characters rather than wrapping descriptions in CDATA. CDATA sections break the moment a description contains the characters ]]> and many mobile readers parse them inconsistently. Plain escaping is always safe; if your CMS already emits CDATA, strip it before pasting content into the fields.
Before and after: fixing a podcast feed rejected by Apple
<item>
<title>Episode 12</title>
<link>/episodes/12</link>
<guid isPermaLink="true">/episodes/12</guid>
<pubDate>2026-01-05T12:00:00Z</pubDate>
<enclosure url="https://cdn.example.com/ep12.mp3"
type="audio/mpeg" />
</item> <item>
<title>Episode 12</title>
<link>https://podcast.example.com/episodes/12</link>
<guid isPermaLink="true">https://podcast.example.com/episodes/12</guid>
<pubDate>Mon, 05 Jan 2026 12:00:00 +0000</pubDate>
<enclosure url="https://cdn.example.com/ep12.mp3"
length="48211936" type="audio/mpeg" />
</item> Use Cases
| Developer | Production | Workflow |
|---|---|---|
| Generate a static feed.xml for a Hugo or Astro site | Ship a podcast feed Apple Podcasts Connect accepts on first try | Migrate 200 Substack posts into a self-hosted feed |
| Debug why an item vanishes from a reader (bad guid or date) | Add enclosures with correct byte lengths for every episode | Hand a validated feed to the marketing team for Feedly submission |
| Preview RFC 822 date conversion before writing a build script | Serve the feed from /feed.xml behind a CDN with short TTL | Audit an old feed against the warning list before a re-launch |
Common Mistakes
- Using an ISO 8601 timestamp (2026-01-05T12:00:00Z) in pubDate. Readers that follow the spec strictly drop the item; the correct form is Mon, 05 Jan 2026 12:00:00 +0000.
- Setting guid isPermaLink="true" on a relative path like /episodes/12. The attribute promises an absolute URL; use isPermaLink="false" with an internal ID or paste the full permalink.
- Omitting enclosure length. Apple Podcasts treats a missing or non-numeric length as a fatal error and refuses to index the episode.
- Pasting HTML into description without escaping. A stray & or < in a post summary makes the XML unparseable; the generator escapes these automatically, so paste plain text.
- Changing the guid of an already-published item. Readers treat the new guid as a fresh entry and re-surface old content to every subscriber.
- Hosting the feed at a URL that 302-redirects. Some aggregators follow one redirect and stop; serve the XML at its final URL or use a 301.
Best Practices
- Keep the feed URL permanent. If you ever move hosts, 301 the old path and keep the same filename so subscriber counts do not reset.
- Limit items to the 20–50 most recent posts. Full-archive feeds bloat parser memory and slow down reader apps; link to the archive from the channel description instead.
- Set ttl only when you publish on a known cadence. A 60-minute TTL tells readers to poll hourly; leaving it blank lets each reader pick its own interval.
- Run the output through the companion RSS Feed Validator before submitting to Apple Podcasts or Feedly. The local warning list catches the common grammar issues; the validator catches anything platform-specific.
- For podcasts, include at least one category element per item using the Apple-compatible category list, and keep the channel image between 1400×1400 and 3000×3000 pixels.
- Automate regeneration. Wire this generator’s output pattern into a build script so every new post is appended with a fresh guid and RFC 822 pubDate without manual edits.
Frequently Asked Questions
What is the difference between RSS 2.0 and Atom?
RSS 2.0 is the format most podcast directories and legacy feed readers still require; Atom is an IETF standard with stricter rules around uniqueness and date formatting. Apple Podcasts accepts both but historically rejects many Atom feeds with non-standard extensions, so RSS 2.0 remains the safe choice for audio. If you only syndicate a blog, either works; if you publish a podcast, ship RSS 2.0 with an enclosure element per episode.
Why must pubDate use RFC 822 format?
The RSS 2.0 specification requires pubDate in RFC 822 date-time syntax, for example Mon, 05 Jan 2026 12:00:00 +0000. Many parsers silently drop items whose pubDate cannot be parsed by that grammar, which makes posts vanish from readers even though the XML is well-formed. The generator converts any paste-able date into the correct RFC 822 string with an explicit UTC offset.
What should the guid element contain for a blog post?
Use a permanently unique identifier for each item. The common practice for blogs is the canonical permalink URL with isPermaLink="true". If the same URL will ever serve different content over time — for example a rolling homepage — set isPermaLink="false" and use an internal ID such as a database primary key. Changing the guid of a published item causes readers to treat it as a brand new entry.
How do I add a podcast enclosure to an RSS item?
An enclosure is a self-closing element with url, length, and type attributes: the absolute URL of the audio file, the file size in bytes as a plain integer, and a MIME type such as audio/mpeg. Apple Podcasts rejects feeds where length is missing or non-numeric, so the tool surfaces a warning when the byte count is blank. The generator can guess the MIME type from a .mp3, .m4a, .ogg, or .aac extension.
Can I host the generated feed on GitHub Pages or S3?
Yes. The output is a static XML file; place it at /feed.xml or /rss.xml on any static host and reference it in your HTML head with a link rel="alternate" tag. Cloudflare Pages, GitHub Pages, and S3 all serve application/rss+xml correctly once the extension is .xml. Remember to update lastBuildDate or omit it entirely so readers poll at their own cadence.
Do I need the atom:link rel="self" element?
Strictly it is optional under the RSS 2.0 spec, but validators and aggregator platforms such as Feedly and Podcasts Connect use the self link to detect feed URL changes and to verify ownership. The generator emits an atom:link placeholder; point its href at the public URL where the feed will live and include the xmlns:atom declaration already present in the output.
Does this generator send my feed data anywhere?
No. Everything runs in the browser tab. The channel fields, item list, and generated XML never leave the page, so unpublished episode titles and draft links stay local until you copy or download the file yourself.