Skip to main content

Generate an RSS feed

An RSS feed is a small XML document that lets readers subscribe to a site. Feeds are simple enough to build with a template string: escape the text fields, format dates in the RFC 822 style the spec requires, and serve the result with the right content type.

const SITE = "https://example.com";

const posts = [
  {
    slug: "tips-tricks",
    title: "Tips & tricks for <template> elements",
    description: "Why & how to use them",
    published: new Date("2026-06-01T10:00:00Z"),
  },
  {
    slug: "hello-world",
    title: "Hello world",
    description: "The first post",
    published: new Date("2026-05-20T08:30:00Z"),
  },
];
XML has five characters that must not appear raw in text content. The ampersand goes first, otherwise it would re-escape the others.
function escapeXml(text: string): string {
  return text
    .replaceAll("&", "&amp;")
    .replaceAll("<", "&lt;")
    .replaceAll(">", "&gt;")
    .replaceAll('"', "&quot;")
    .replaceAll("'", "&apos;");
}
RSS wants RFC 822 dates, like Mon, 01 Jun 2026 10:00:00 GMT. Date.prototype.toUTCString produces exactly that format.
function buildFeed(): string {
  const items = posts.map((post) =>
    `    <item>
      <title>${escapeXml(post.title)}</title>
      <link>${SITE}/blog/${post.slug}</link>
      <guid>${SITE}/blog/${post.slug}</guid>
      <description>${escapeXml(post.description)}</description>
      <pubDate>${post.published.toUTCString()}</pubDate>
    </item>`
  ).join("\n");

  return `<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>Example blog</title>
    <link>${SITE}/blog</link>
    <description>Posts from the example blog</description>
${items}
  </channel>
</rss>
`;
}

Deno.serve((req) => {
  const { pathname } = new URL(req.url);
  if (pathname === "/feed.xml") {
application/rss+xml is the registered media type for RSS. Readers also accept application/xml, but be precise when you can.
    return new Response(buildFeed(), {
      headers: { "content-type": "application/rss+xml; charset=utf-8" },
    });
  }
  return new Response("see /feed.xml\n");
});
The feed validates as RSS 2.0, with the ampersand and angle brackets in the first title escaped: curl -s http://localhost:8000/feed.xml | head -n 13 <?xml version="1.0" encoding="UTF-8"?> <rss version="2.0"> <channel> <title>Example blog</title> <link>https://example.com/blog</link> <description>Posts from the example blog</description> <item> <title>Tips &amp; tricks for &lt;template&gt; elements</title> <link>https://example.com/blog/tips-tricks</link> <guid>https://example.com/blog/tips-tricks</guid> <description>Why &amp; how to use them</description> <pubDate>Mon, 01 Jun 2026 10:00:00 GMT</pubDate> </item>

Run this example locally using the Deno CLI:

deno run -N https://docs.deno.com/examples/scripts/http_server_rss.ts

Did you find what you needed?

Privacy policy