RSS a 66 redes sociales: Facebook, Instagram, X, LinkedIn, Telegram y más Blog Afiliados Contactos
Iniciar sesión Empezar gratis
Updated: 2026-10-18
Firebase Cloud Functions RSS Feed From Firestore, Step by Step

Short answer: To publish an RSS feed from Firebase, write a Cloud Function that queries the newest published documents in Firestore, builds RSS 2.0 XML with escaped text, absolute links and RFC 822 dates, and returns it with the content type application/rss+xml and a public cache header. Add a Hosting rewrite so the function answers at a clean address such as /feed.xml, create the composite index that Firestore asks for, test the result with curl and a validator, and then give the address to an auto-poster so every new item reaches your social channels.

Firebase is a popular back end for small products, community apps, directories and news pages, because it lets one developer ship quickly. The same speed has a side effect: nobody builds the feed. Firestore stores documents, Hosting serves files, and neither has any idea what RSS is. Yet readers, aggregators and auto-posters all expect a feed address. This guide builds a Firebase Cloud Functions RSS feed with the second generation runtime, explains each decision, and ends with the checks that make it reliable in production.

When a Firebase feed makes sense

A function-based feed fits when your content already lives in Firestore:

  • news, announcements or changelogs written in an admin screen and stored as documents;
  • listings, events or offers that users or staff add through your app;
  • a static front end on Hosting that has no server code of its own;
  • a mobile app whose content should also be reachable by people who only use feed readers or social networks.

If your site is built with a static generator or a CMS, use its own feed instead; see how to find your Hugo, Jekyll or Eleventy feed. If your content sits in a headless service, the options are compared in auto-posting from Notion, Airtable or a headless CMS.

The data model

The example uses a collection named posts. Each document has a title, a slug, a summary, a status, a publication timestamp and an optional image address. The document identifier will be the GUID of the item, so it never changes even if the slug does.

posts/{postId}
  title: "Version 2.4 is out"
  slug: "version-2-4"
  summary: "What changed in the new release."
  status: "published"          // draft | scheduled | published
  publishedAt: Timestamp       // 2026-10-18 07:40:00 UTC
  imageUrl: "https://www.example.com/images/v24.png"

Use a Firestore timestamp for the date and not a string. Timestamps sort correctly, convert cleanly to JavaScript dates and let the query itself exclude items that are not public yet.

The function

Create the project with the Firebase command line tools, choose Functions with JavaScript and install the dependencies. Then put the following in index.js:

const { onRequest } = require("firebase-functions/v2/https");
const { initializeApp } = require("firebase-admin/app");
const { getFirestore, Timestamp } = require("firebase-admin/firestore");
const crypto = require("crypto");

initializeApp();
const db = getFirestore();
const SITE_URL = "https://www.example.com";

const esc = (value) => String(value ?? "")
  .replace(/&/g, "&")
  .replace(/</g, "&lt;")
  .replace(/>/g, "&gt;")
  .replace(/"/g, "&quot;");

exports.feed = onRequest({ region: "europe-west1", memory: "256MiB" }, async (req, res) => {
  const snap = await db.collection("posts")
    .where("status", "==", "published")
    .where("publishedAt", "<=", Timestamp.now())
    .orderBy("publishedAt", "desc")
    .limit(20)
    .get();

  const items = snap.docs.map((doc) => {
    const p = doc.data();
    return `
    <item>
      <title>${esc(p.title)}</title>
      <link>${SITE_URL}/news/${esc(p.slug)}/</link>
      <guid isPermaLink="false">${esc(doc.id)}</guid>
      <pubDate>${p.publishedAt.toDate().toUTCString()}</pubDate>
      <description>${esc(p.summary)}</description>
    </item>`;
  }).join("");

  const newest = snap.empty ? new Date(0) : snap.docs[0].get("publishedAt").toDate();
  const xml = `<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Example Product News</title>
    <link>${SITE_URL}/news/</link>
    <description>Release notes and announcements</description>
    <language>en</language>
    <lastBuildDate>${newest.toUTCString()}</lastBuildDate>
    <atom:link href="${SITE_URL}/feed.xml" rel="self" type="application/rss+xml"/>${items}
  </channel>
</rss>`;

  const etag = '"' + crypto.createHash("sha1").update(xml).digest("hex") + '"';
  res.set("Content-Type", "application/rss+xml; charset=utf-8");
  res.set("Cache-Control", "public, max-age=300, s-maxage=600");
  res.set("ETag", etag);
  if (req.get("If-None-Match") === etag) {
    res.status(304).end();
    return;
  }
  res.status(200).send(xml);
});

Three details matter. The query filters on both the status and the date, so drafts and scheduled documents never appear. The escape helper replaces the ampersand first, otherwise the entities it creates would be escaped again. The date uses toUTCString(), which gives an English RFC 822 style value such as Sun, 18 Oct 2026 07:40:00 GMT whatever the server language. The GUID is marked with isPermaLink="false" because it is an identifier and not an address.

Create the composite index

The first time the function runs, Firestore will probably refuse the query and print an error with a link. A query that combines an equality filter on one field with a range filter and ordering on another needs a composite index. Follow the link, or define the index in firestore.indexes.json with the status field ascending and the publishedAt field descending, and deploy it. Indexes take a few minutes to build. Until then the function returns an error, so deploy the index before you connect an auto-poster, not after.

Serve the feed at a clean address

A deployed function has a long default address that includes the project and the region. Do not publish that. Add a rewrite to firebase.json so your own domain answers:

{
  "hosting": {
    "public": "public",
    "rewrites": [
      {
        "source": "/feed.xml",
        "function": { "functionId": "feed", "region": "europe-west1" }
      }
    ]
  }
}

Deploy with firebase deploy --only functions,hosting. The address https://www.example.com/feed.xml now reaches the function. A stable address matters because auto-posters remember it; changing it later means reconnecting every channel. Hosting also gives you a content delivery network in front of the function, which is why the response carries an s-maxage value: it tells the network how long it may keep the same copy.

Caching, cold starts and cost

LayerSettingEffect
Browser and readersmax-age of 300 secondsRepeated requests reuse the same file for a short time
Hosting networks-maxage of 600 secondsMost requests never reach the function or the database
FunctionOne query, limit of twentyEach real request reads a small, fixed number of documents
ETagHash of the XMLAuto-posters can receive a 304 answer instead of the whole file

Firestore bills by the number of documents read, so caching is not only a speed measure, it is a cost control. A feed that is polled every minute by several services would otherwise read twenty documents many thousands of times a day. Keep the lifetime short enough that a new post appears within minutes. A long lifetime delays every share by the same amount; the effect is described in RSS feed caching explained. The ETag logic is explained in ETags and conditional GET.

Functions that scale to zero can take a moment to start after a quiet period. If the feed matters to your business, set a minimum number of instances for the function, or rely on the cache in front of it so most requests never wake it. A slow first response can look like a timeout to an auto-poster; see RSS feed timeouts. Cloud Functions also require a project with billing enabled, so check the current terms for your account before you plan around it.

Images in the feed

Auto-posters build the preview card from the image of the item, so it is worth including one. Store the full public address of the picture in the document, for example a file in Cloud Storage with a public download address, and add it to each item as an enclosure element with the address, the file size and the real type. If you do not know the size, many readers accept a value of zero, but the type should be accurate. Keep images reasonably small and in a common format, and never point to an address that needs a login. The behaviour of these tags is explained in RSS enclosure tags explained.

Security rules and private data

The function uses the Admin SDK, which bypasses Firestore security rules. That is convenient, but it means the code alone decides what becomes public. Build the item from named fields as shown, rather than spreading the whole document into the feed. Never include internal notes, author email addresses, moderation flags or draft text. Keep your security rules strict for client apps, and let the function be the only public window onto the content.

Test the feed before connecting it

  1. Run the emulator or deploy to a test project, then request the address with curl -i and check for the 200 status, the XML content type, the cache headers and the ETag. The method is explained in debugging an RSS feed with curl.
  2. Repeat the request with the If-None-Match header and confirm that the answer is 304.
  3. Run the file through the W3C Feed Validation Service and fix every error.
  4. Add a draft and a document dated in the future, and confirm that neither appears.
  5. Publish a real test item, wait for the cache to expire and confirm that it appears at the top.

If something looks wrong later, the checklist in RSS feed not updating helps you find the cause quickly. Broken relative links are the other classic problem; see relative URLs in RSS feeds.

Common mistakes to avoid

  • Forgetting the index. The function fails with an error until the composite index exists.
  • Publishing the default function address. Use the Hosting rewrite so the address belongs to you.
  • Storing dates as strings. Use timestamps so sorting and filtering are reliable.
  • Unescaped text. One raw ampersand in a title can invalidate the whole file.
  • Unstable GUIDs. Use the document identifier and never regenerate it; see why your feed auto-posts the same article twice.
  • No cache at all. Every poll then reads the database, which costs money and time.

How PostRSS works with a Firebase feed

PostRSS has been publishing RSS and Atom feeds to social networks since 2014. Paste the address of your feed.xml rewrite, connect your accounts and choose how each post is built from the title, the description or the page title. PostRSS checks the feed as often as every minute and shares each new item, with its image and link, to the channels you choose among 66 supported networks, messengers, team chats and blogs, including LinkedIn, X, Bluesky, Mastodon and Telegram. Keyword filters, UTM parameters and posting windows let you tune each channel, and webhooks connect PostRSS to your own systems. See the PostRSS features page and the plan limits on the pricing page. More developer guides are under the Technical Guide tag.

Lectura relacionada

The bottom line

Firebase does not publish feeds, but one Cloud Function and one rewrite are enough. Query only published documents dated in the past, newest first, with a limit of twenty. Create the composite index, escape every value, use the document identifier as the GUID, format dates in English UTC, build links from a fixed site address and send cache headers with an ETag. Test with curl and a validator, keep the address unchanged and connect it to an auto-poster. After that, every item you publish in your app can reach your social channels automatically.

FAQ

Does Firebase Hosting generate an RSS feed by itself?

No. Hosting serves files and rewrites requests. You need a function or a build step that creates the XML, and a rewrite that exposes it at a clean address.

Why does my function return an index error?

The query combines an equality filter with a range filter and an order on another field, which requires a composite index. Open the link in the error message or define the index in your project, deploy it and wait until it is ready.

Can I generate the feed at build time instead?

Yes, if your content changes rarely. A script can write a static feed.xml into the public folder during deployment. A function is better when content is added through the app all day, because the feed then updates without a new deployment.

How many items should the feed contain?

Between ten and twenty of the newest published items is enough. An auto-poster only needs recent items, and a smaller file is faster to download and cheaper to produce.

How do I stop old posts from being shared again?

Give every item a stable GUID, such as the document identifier, and avoid editing the publication date of items that are already public. Some tools treat a changed date as a new item.

Can I use this feed with PostRSS?

Yes. PostRSS reads any valid RSS or Atom address that is reachable without a login. Paste the address, connect your channels and new items are shared as soon as they appear.

New guides, once a month

What changed in the networks, what broke, and how to fix it before it costs you reach.

We send a confirmation e-mail first. Unsubscribe any time.

Más herramientas de nuestro equipo

Creadas por Internet Solutions, el equipo detrás de PostRSS. Cada una te ahorra tiempo a su manera.