Learn

An RSS feed example, explained

This is a complete RSS 2.0 feed that every feed reader accepts, followed by what each part is for and what goes wrong when it is missing. The Atom and JSON Feed versions of the same feed are at the end.

Check a feed

A complete example

The feed below has one channel and two items. Everything in it is used by feed readers; nothing is decoration. Copy it, replace the addresses and the texts, and the feed works in Inoreader and in every other reader.

<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
     xmlns:atom="http://www.w3.org/2005/Atom"
     xmlns:content="http://purl.org/rss/1.0/modules/content/"
     xmlns:media="http://search.yahoo.com/mrss/">
  <channel>
    <title>Northline Dispatch</title>
    <link>https://northline.news/</link>
    <description>Local news from the northern harbour towns.</description>
    <language>en</language>
    <atom:link href="https://northline.news/feed/" rel="self" type="application/rss+xml"/>
    <atom:link href="https://pubsubhubbub.appspot.com/" rel="hub"/>
    <item>
      <title>Harbour authority approves the second ferry berth</title>
      <link>https://northline.news/2026/10/second-ferry-berth/</link>
      <guid isPermaLink="false">northline-2026-1842</guid>
      <pubDate>Thu, 16 Oct 2026 13:05:00 +0200</pubDate>
      <description>The vote, which had been postponed twice, passed 7 to 2. Work starts in March.</description>
      <content:encoded><![CDATA[<p>The vote, which had been postponed twice, passed 7 to 2.</p><p>Work starts in March, and the first ferry is due in the summer.</p>]]></content:encoded>
      <media:thumbnail url="https://northline.news/images/berth.jpg"/>
    </item>
    <item>
      <title>Council vote on the northern bypass moved to November</title>
      <link>https://northline.news/2026/10/bypass-vote-november/</link>
      <guid isPermaLink="false">northline-2026-1841</guid>
      <pubDate>Thu, 16 Oct 2026 09:40:00 +0200</pubDate>
      <description>The environmental report arrived three weeks late.</description>
      <content:encoded><![CDATA[<p>The environmental report arrived three weeks late, and the committee wants the winter traffic counts first.</p>]]></content:encoded>
    </item>
  </channel>
</rss>

The sections below take the example apart in the order a feed reader meets it: the address, the way the server delivers the document, the document itself, the channel and the items.

The feed address

A feed has one address, and it stays the same for the life of the feed. Readers store the address when a person subscribes and fetch it for years, so a feed that moves loses the readers who do not notice the move. Serve the feed over https, and if an old http address exists, answer it with a permanent redirect (301) to the https one. Do not put a session id, a date or a tracking parameter in the address: every variant is a separate feed to a reader.

The address in the example is https://northline.news/feed/. The feed names it in its own self link, which is the next thing a reader looks for.

Delivery

The server sends the document with a content type that says what it is. application/rss+xml is the type for RSS, application/atom+xml for Atom and application/feed+json for JSON Feed; application/xml and text/xml also work for the XML formats. The charset in the header and the encoding in the XML declaration should agree.

Compress the answer. A feed of 200 KB travels as about 30 KB with gzip or brotli, and readers ask for compression on every request. Answer within a few seconds: readers give up between 10 and 60 seconds, and a feed that is slow because it is built on every request is served from a cache instead.

Keep the feed at a size readers can handle. The last 20 to 50 items are enough; older posts belong to an archive page, not to the feed. Inoreader reads feeds up to 100 MB, and most readers stop long before that.

Conditional requests

Readers check a feed many times a day, and most of the time nothing has changed. The server can say so without sending the feed again. It sends an ETag header, a Last-Modified header or both with every answer; the reader sends them back as If-None-Match and If-Modified-Since on its next check, and the server answers 304 Not Modified with no body when the feed has not changed.

HTTP/1.1 200 OK
Content-Type: application/rss+xml; charset=utf-8
ETag: "7a3f9c2e"
Last-Modified: Thu, 16 Oct 2026 11:05:00 GMT
GET /feed/ HTTP/1.1
If-None-Match: "7a3f9c2e"
If-Modified-Since: Thu, 16 Oct 2026 11:05:00 GMT

HTTP/1.1 304 Not Modified

WordPress does this unless a caching plugin turns it off. A feed that never answers 304 costs its server the whole document on every check by every reader, which for a popular feed is thousands of downloads a day for nothing.

Well-formed XML

An RSS or Atom feed is an XML document, and an XML parser stops at the first error. The errors that break most feeds are not in the items but around them: a blank line or a PHP warning printed before the <?xml declaration, a byte order mark in front of it, a bare & in a title, an HTML entity such as &nbsp; that XML does not define, and ]]> inside a CDATA section.

Inoreader repairs these before reading the feed and the validator lists every repair it made. Readers that do not repair feeds reject the whole document, so a repair in the report is a fix worth making at the source.

Three rules keep a feed well-formed: print nothing before the declaration, escape text that goes into an element (&amp;, &lt;, &gt;) or wrap it in a CDATA section, and use only the five entities XML defines (&amp;, &lt;, &gt;, &quot;, &apos;) or numeric ones such as &#160;.

Encoding

Declare the encoding once and serve the bytes in it. The XML declaration in the example says encoding="UTF-8", the HTTP header says charset=utf-8, and the bytes are UTF-8. When the three disagree, every reader guesses, and the guesses differ, which is how a feed shows accented letters correctly in one reader and as question marks in another.

UTF-8 is the encoding to use. A feed in windows-1251, ISO-8859-1 or Shift_JIS works when it is declared and served consistently, but a template that mixes a UTF-8 database with a Latin-1 declaration produces text no reader can fix.

The channel

The channel carries the feed's own name, the address of the site and a description. All three are required in RSS 2.0, and readers show the title and the link in the subscription list. The language element helps readers that translate or read aloud.

The root element declares the namespaces the feed uses. The example declares three: atom for the self link and the hub, content for the full text of an item, and media for pictures. A namespace that is used but not declared is an XML error; a namespace with a wrong address, such as http://backend.userland.com/rss2 instead of none at all for RSS 2.0, makes some readers treat the feed as an unknown format.

The Atom equivalent of the channel is the feed element with id, title and updated, all three required.

The self link names the feed's own address from inside the document:

<atom:link href="https://northline.news/feed/" rel="self" type="application/rss+xml"/>

Readers use it to keep the subscription pointing at the right address when the feed is reached through a redirect, a mirror or a cache, and WebSub hubs use it to know which topic to deliver. In Atom the same element is <link rel="self" href="..."/>.

Item ids

Every item carries an id that never changes, and readers use it to tell a new post from one they already have. In RSS the element is guid; in Atom it is id; in JSON Feed it is the id field. The example uses isPermaLink="false" with an id from the site's own database, which is the safest form: it survives a change of the post's address.

When the id changes, a reader shows the post again as new. When every item has the same id, a reader shows only one of them. When there is no id at all, readers fall back to the link, and after that to the title, and the result differs per reader. Inoreader records which feeds have unstable ids and identifies their items by other fields; the validator says so in the Updates section.

A post that is edited keeps its id. An id is not a version number.

Dates

Each item carries the date it was published. RSS 2.0 uses the RFC 822 format, Thu, 16 Oct 2026 13:05:00 +0200, with a numeric time zone; Atom and JSON Feed use RFC 3339, 2026-10-16T13:05:00+02:00. Readers sort by this date and show it to the person reading, so a wrong date puts a new post at the bottom of the list.

Two mistakes are common. All items carry the same date, usually the time the feed was generated, so readers cannot order them. Or the date is in the future, because the server's clock or the time zone is wrong, and readers that hide future posts show nothing.

An item without a date gets the time the reader first saw it, which is the time of the reader's next check, not the time of publishing.

The link is the absolute address of the post, one per item, starting with https://. A relative link such as /2026/10/berth/ is resolved differently by different readers, and a link that points at the home page makes every item open the same page.

The title is plain text. Markup inside a title is shown as text by some readers and as markup by others; an entity that is escaped twice (&amp;amp;) shows as &amp;. Keep titles short and put the rest in the content.

Content

description holds a summary or the whole text, and content:encoded holds the full HTML when the description is a summary. Readers show content:encoded when it is present and description otherwise. Atom has summary and content; JSON Feed has content_html and content_text.

Put HTML inside a CDATA section or escape it. Readers remove scripts, frames and forms before showing an item, so there is no point sending them. A feed that carries only teasers sends readers to the site for every post, which some publishers want; a feed with the full text keeps readers reading.

Images

Readers show a picture for each item in their list views. The picture comes from media:thumbnail, from an image enclosure, or from the first <img> in the content. An image in the content needs its address in src: lazy-loading markup puts a placeholder in src and the real address in data-src, which readers never run the JavaScript to swap. Use absolute addresses; a relative one such as /images/berth.jpg is wrong in every reader that does not know the site.

<media:thumbnail url="https://northline.news/images/berth.jpg"/>

When a feed carries no picture at all, Inoreader fetches a cover from the article's page for part of its items. A media:thumbnail on every item is the certain way.

Enclosures

An enclosure attaches a file to an item: the audio of a podcast episode, a video, a PDF. It needs the absolute address, the size in bytes and the type:

<enclosure url="https://northline.news/audio/episode-42.mp3" length="23487211" type="audio/mpeg"/>

One enclosure per item is what every reader supports. A podcast feed adds the iTunes namespace elements on top of this, which the podcast directories require; the enclosure is what the players play.

The same feed in Atom

Atom is the other XML format readers accept, with the same information under other names:

<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <id>https://northline.news/</id>
  <title>Northline Dispatch</title>
  <updated>2026-10-16T13:05:00+02:00</updated>
  <link href="https://northline.news/"/>
  <link href="https://northline.news/atom/" rel="self"/>
  <link href="https://pubsubhubbub.appspot.com/" rel="hub"/>
  <entry>
    <id>tag:northline.news,2026:1842</id>
    <title>Harbour authority approves the second ferry berth</title>
    <link href="https://northline.news/2026/10/second-ferry-berth/"/>
    <updated>2026-10-16T13:05:00+02:00</updated>
    <published>2026-10-16T13:05:00+02:00</published>
    <summary>The vote, which had been postponed twice, passed 7 to 2.</summary>
    <content type="html"><![CDATA[<p>The vote, which had been postponed twice, passed 7 to 2.</p>]]></content>
  </entry>
</feed>

The same feed in JSON Feed

JSON Feed is the same thing in JSON, for sites that would rather not produce XML:

{
  "version": "https://jsonfeed.org/version/1.1",
  "title": "Northline Dispatch",
  "home_page_url": "https://northline.news/",
  "feed_url": "https://northline.news/feed.json",
  "hubs": [{"type": "WebSub", "url": "https://pubsubhubbub.appspot.com/"}],
  "items": [
    {
      "id": "northline-2026-1842",
      "url": "https://northline.news/2026/10/second-ferry-berth/",
      "title": "Harbour authority approves the second ferry berth",
      "date_published": "2026-10-16T13:05:00+02:00",
      "content_html": "<p>The vote, which had been postponed twice, passed 7 to 2.</p>",
      "image": "https://northline.news/images/berth.jpg"
    }
  ]
}

Serve it as application/feed+json. Readers that support JSON Feed, Inoreader among them, treat it exactly like the XML feeds.

Check the feed

The RSS feed validator fetches a feed the way a reader does and reports on every point above: the delivery, the document, the items, and for feeds Inoreader already follows, how the checks of the last days went. The feed viewer shows the items as readers show them.