
Short answer: DatoCMS does not create an RSS feed for your website, but its GraphQL Content Delivery API returns everything a feed needs. Send one query for the newest published articles with a read-only token, map each record to an item with an absolute link, the first publication date and a summary, escape the text, return the RSS content type and a cache validator and test the live address. Add a webhook so that the feed refreshes when editors publish, then connect the address to an auto-poster so that every new article is shared on your social channels.
DatoCMS is a headless content platform. Editors work in a clear administration area, and developers read the content through GraphQL and render it in any front end. Because the system does not know your page addresses, it cannot write a feed for you. The feed is a small piece of server code that turns the API answer into XML, and it must use the same addresses, slugs and dates as your pages. This guide shows the query, the template, the response headers and a test routine, and it explains the choices that keep an auto-poster from sharing the wrong things.
A feed item needs a title, a permanent address, a date and a summary. Add them to the article model on purpose, so that editors fill them in while they write instead of someone patching the feed later.
| Feed element | Source in DatoCMS | Rule |
|---|---|---|
| Title | a single-line text field | Plain text, no markup |
| Link and identifier | a slug field and your site address | Absolute, https, never changes |
| Publication date | _firstPublishedAt | The first time the record went live |
| Description | a summary field | One or two complete sentences |
| Image | an image field | Absolute address with a type |
DatoCMS adds a few meta fields to every record, among them the date of the first publication and the date of the last publication. Use the first one. An editor who corrects a typo in an old article changes the second one, and a feed that used it would push the item to the top and make an auto-poster share the article again. The causes and cures of repeated posts are covered in why a feed auto-posts the same article twice.
The Content Delivery API accepts a POST request with a bearer token. Create a token that can only read published content and keep it in an environment variable on the server, never in code that reaches the browser.
// feed.js (server side, Node 18 or later)
const SITE = "https://example.com";
const QUERY = `
query Feed {
allArticles(first: 20, orderBy: _firstPublishedAt_DESC) {
title
slug
summary
_firstPublishedAt
}
}`;
export async function loadArticles() {
const res = await fetch("https://graphql.datocms.com/", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer " + process.env.DATOCMS_READ_TOKEN,
},
body: JSON.stringify({ query: QUERY }),
});
if (!res.ok) throw new Error("DatoCMS returned " + res.status);
const json = await res.json();
if (json.errors) throw new Error(JSON.stringify(json.errors));
return json.data.allArticles;
}The model name, the field names and the ordering value depend on your project, so check the schema explorer of your own project before you copy anything. By default the API returns published content only; drafts are returned when a special header asks for them, and a public feed must never send it. The same approach for other headless systems is described in the Contentful feed guide and the Sanity feed guide.
Text from a content system can contain ampersands, angle brackets and quotation marks, and any of them can break an XML document. Escape every value before it goes into the template.
const ENT = {
"&": "&" + "amp;",
"<": "&" + "lt;",
">": "&" + "gt;",
'"': "&" + "quot;",
"'": "&" + "apos;",
};
const esc = (s) => String(s ?? "").replace(/[&<>"']/g, (c) => ENT[c]);
export function render(articles) {
const newest = articles.length ? new Date(articles[0]._firstPublishedAt) : new Date(0);
const items = articles.map((a) => {
const url = `${SITE}/blog/${a.slug}/`;
return `<item>
<title>${esc(a.title)}</title>
<link>${esc(url)}</link>
<guid isPermaLink="true">${esc(url)}</guid>
<pubDate>${new Date(a._firstPublishedAt).toUTCString()}</pubDate>
<description>${esc(a.summary)}</description>
</item>`;
}).join("\n");
return `<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"><channel>
<title>Example Journal</title>
<link>${SITE}</link>
<description>News and guides from Example Journal.</description>
<lastBuildDate>${newest.toUTCString()}</lastBuildDate>
${items}
</channel></rss>`;
}The channel date comes from the newest article and not from the clock, so that the file does not change when nothing was published. The identifier of each item is its permanent address, built from a fixed constant so that every link is absolute. If your website builds addresses with a routing function, call the same function here, so that the feed and the pages cannot disagree about a slug.
The handler sets the content type, a cache lifetime and a validator, and it answers with a 304 status when the client already holds the current version.
import { createHash } from "node:crypto";
export async function handleFeed(req, res) {
const xml = render(await loadArticles());
const etag = '"' + createHash("sha1").update(xml).digest("hex") + '"';
res.setHeader("Content-Type", "application/rss+xml; charset=utf-8");
res.setHeader("Cache-Control", "public, max-age=300");
res.setHeader("ETag", etag);
if (req.headers["if-none-match"] === etag) {
res.statusCode = 304;
return res.end();
}
res.statusCode = 200;
res.end(xml);
}The validator is a hash of the finished document, so it changes exactly when the content changes. A 304 answer saves bandwidth for you and for every tool that polls the feed. The mechanism is explained in the guide to ETags and conditional requests, and the status codes in the guide to feed status codes.
A feed cached for a few minutes is fine, but DatoCMS can make it faster. The platform can call a webhook whenever a record is published or unpublished. Point that webhook at an endpoint of your site that clears the cached feed, or triggers a rebuild, and a new article shows up within seconds. Keep a short cache lifetime as a safety net, because a webhook can fail silently. Test the whole chain by publishing a draft and watching the feed, since a stale feed delays every social post.
DatoCMS also supports scheduled publishing. A record scheduled for tomorrow is not published yet, so it does not appear in the API until the moment arrives, which is exactly what a feed needs. If you import old articles, keep their original dates in a field of your own and use it for the item date, because a feed full of items with today’s date makes an auto-poster share the whole archive in one afternoon. For the effect of old dates see the guide to feed order and backdated posts.
Auto-posters build the best social posts from a feed that gives them a short summary and a picture. If the article has a cover image, add it as an enclosure with an absolute address, a type and a length. DatoCMS serves images from its own image service and accepts parameters in the address, so request a sensible width, for example about twelve hundred pixels, which is large enough for every network. Do not put modular content blocks or structured text into the description. Those fields are meant for rendering pages, and converting them to HTML for a feed is a separate task. A short plain summary written for the purpose is better for readers, and it is safer for social posts.
GraphQL has a habit that surprises many developers: a request can return a 200 status and still contain an errors list instead of data. The handler above checks for it and throws, which is right, but think about what the feed does next. If the API is down for a minute, the best answer is the last good copy of the feed, not an empty document. An empty feed is valid XML, and an auto-poster that reads it may conclude that nothing has changed, or, worse, a cache may keep it for hours. Store the last successful XML in memory or in a file and serve it with a short lifetime while the problem lasts.
DatoCMS can store localized fields in one record. A feed should contain one language only, because a social channel is written for one audience. Pass the locale to the query and build a separate feed address for each language, each with its own channel. A feed that mixes translations makes an auto-poster share the same story in two languages one after the other, and readers will call it spam. See the guide to multilingual feeds for more on this.
Request the route locally, then test the deployed address from outside, because a local test says nothing about the hosting platform and the certificate in front of your application.
curl -i http://localhost:3000/feed.xml
curl -i https://example.com/feed.xml
curl -i -H 'If-None-Match: "paste-the-etag-here"' https://example.com/feed.xmlA longer routine is in the feed validation guide, and a shorter one in debugging a feed with curl.
PostRSS has been publishing RSS and Atom feeds to social networks since 2014. Paste the address of your feed, 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 entry, 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. See the PostRSS features page and the plan limits on the pricing page. More developer guides are under the Technical Guide tag.
A DatoCMS feed is a short handler in your own code, and it has to get the details right: a read-only token kept on the server, the first publication date, a required summary field, slugs that match your page addresses, escaped text, absolute links, the correct content type and a validator that lets clients skip unchanged files. Refresh it with a webhook, test the live address with curl and a validator, then connect it to an auto-poster. After that, every article your editors publish reaches your readers and your social channels without extra work.
No. DatoCMS delivers content through its APIs, so you build the feed in your own code from the records that the API returns.
The first publication date. The date of the last publication changes with every edit and would push old articles to the top of the feed.
It should not. The default behaviour of the API returns published content only, and a public feed must never ask for drafts.
Set up a webhook for publish events that clears the cached feed or triggers a rebuild, and keep a short cache lifetime as a safety net.
Yes. PostRSS reads any valid RSS or Atom address that is reachable without a login. Paste the address, connect your channels and new articles are shared as soon as they appear.
What changed in the networks, what broke, and how to fix it before it costs you reach.
Built by Internet Solutions, the team behind PostRSS. Each product saves you time in its own way.