RFC: Source Namespace Extensions for JSON Feed
Oct 29, 2026 · @Tim Jansen
Latest version on GitHub: RFC-1-Source-for-JSON-Feeds.md · Change history
Summary
This proposal adds five fields from the RSS source namespace
to JSON Feed: who a post replies to, where its comments are, how many comments it has, the author's accounts on other services, and the text as Markdown, mainly for comments. They live in one extension object, _source, so a reader can treat a JSON feed exactly like an RSS feed that uses the namespace.
It also adds one thing the namespace lacks today: a total comment count next to the number of direct replies. The namespace only has the first-level count, so a reader that wants to show the size of the whole thread, as Facebook does, has no way to get it.
Why now. Social-RSS systems like rss.chat already link posts, replies and comment feeds with source:inReplyTo, source:comments, source:account and source:markdown
. JSON Feed has no fields for any of this, so a blog that publishes JSON Feed drops out of those conversations. Mirroring the RSS elements one to one keeps a single model for both formats: publishers can generate both from the same data, and readers can use one code path.
The _source object
Everything goes into one object named _source, following JSON Feed's extension rules: the name starts with an underscore, and the object says which spec it implements in about.
"_source": {
"about": "https://github.com/bigwide-net/RFCs/blob/main/RFC-1-Source-for-JSON-Feeds.md"
}
- It may appear on an item (
inReplyTo,comments,accounts,markdown) and on the feed itself (accounts). aboutshould behttps://github.com/bigwide-net/RFCs/blob/main/RFC-1-Source-for-JSON-Feeds.md, this document. This is not its final location: the URL will change once the spec has a permanent home. Readers must therefore ignore the value ofabout: they recognize the extension by the name_sourcealone, and read its fields whateveraboutsays, or when it is missing.- Every field is optional. A reader ignores fields it doesn't know and values of the wrong type, so the object can grow later.
Item: inReplyTo
inReplyTo
names the post this item answers. Usually it is just the permalink of that post, as a string:
"_source": {
"about": "https://github.com/bigwide-net/RFCs/blob/main/RFC-1-Source-for-JSON-Feeds.md",
"inReplyTo": "https://rss.chat/?id=204"
}
When the target has no URL (a Bluesky post, say), use an object with the id and isPermaLink: false, like the RSS element's attribute:
"inReplyTo": { "value": "at://did:plc:xyz/app.bsky.feed.post/3k2", "isPermaLink": false }
To also name the feed the replied-to post comes from, use the object form with feedUrl and feedTitle, like the attributes of the same names on <source:inReplyTo>:
"inReplyTo": {
"value": "https://demo.rss.chat/?id=798",
"feedUrl": "https://demo.rss.chat/users/dave/rss.xml",
"feedTitle": "Dave Winer"
}
| Field | Type | Meaning |
|---|---|---|
value | string | Required. The permalink of the post replied to, or its id when isPermaLink is false. |
isPermaLink | boolean | false when value is an id, not a URL. Defaults to true, so it can be omitted for permalinks. |
feedUrl | URL | The feed containing the post replied to. Omitted when unknown. Same as source:inReplyTo@feedUrl. |
feedTitle | string | The title of that feed. Omitted when the feed has none. Same as source:inReplyTo@feedTitle. |
Readers show it as "In reply to …": a link for a permalink, plain text for an id. When feedTitle
is given, they show it as the link text instead of the URL, so "In reply to Dave Winer" still links to the post. A post replies to one item; for several, pick the main one.
Item: comments
comments says where an item's comments are and how many there are. It has three fields, all optional:
| Field | Type | Meaning |
|---|---|---|
feedUrl | URL | A feed (RSS, Atom or JSON) whose items are the direct replies to this item. Same as source:comments@feedUrl. |
count | integer ≥ 0 | Number of direct replies: the items in feedUrl. Same as source:comments@count. |
total | integer ≥ 0 | New. Number of replies at all levels: direct replies, their replies, and so on. Never less than
count. |
"_source": {
"about": "https://github.com/bigwide-net/RFCs/blob/main/RFC-1-Source-for-JSON-Feeds.md",
"comments": {
"feedUrl": "https://rss.chat/users/dave/comments/205.xml",
"count": 2,
"total": 31
}
}
Why both counts. Readers show reply counts in different styles. Some show only the direct replies, as Twitter/X does; others show all replies in the thread, as Facebook does. Which style to support is the reader's choice, so the feed gives both numbers and leaves it to the reader. Publishers that only know one number give count; a publisher with flat comments (WordPress without threading) has count = total and may omit total.
Replies are posts too, so an item in a comment feed carries its own comments for the next level. Counts are a snapshot: readers should not expect them to update with every new comment, and should treat them as a hint, not a promise of what the feed holds.
Item: markdown
markdown holds an item's text as Markdown, like <source:markdown>
in RSS. It is meant primarily for comment feeds, the items of a feed that a comments.feedUrl points to, but publishers may use it on posts as well.
{
"id": "https://rss.chat/?id=205",
"url": "https://rss.chat/?id=205",
"authors": [{ "name": "Dave Winer" }],
"date_published": "2026-07-08T14:42:55Z",
"content_html": "<p>hey manton, great to see you here.</p><p>this is the <strong>web</strong> btw.</p>",
"_source": {
"about": "https://github.com/bigwide-net/RFCs/blob/main/RFC-1-Source-for-JSON-Feeds.md",
"markdown": "hey manton, great to see you here.\n\nthis is the **web** btw."
}
}
- Publishers still write
content_htmlorcontent_text(JSON Feed requires one), with the same text, for readers that don't knowmarkdown. - Readers showing an item prefer
markdownovercontent_htmlandcontent_text, as they prefer<source:markdown>over<description>in RSS. Markdown is easier to render safely: a reader can build the item from it without trusting the publisher's HTML. - A reader that shows posts as full web pages may keep using
content_htmlfor posts, since they often use HTML that Markdown can't express.
Feed and item: accounts
accounts maps each service to the author's account on it, like source:account elements in RSS. On the feed it names the feed author's accounts, like source:account
on an RSS channel:
{
"version": "https://jsonfeed.org/version/1.1",
"title": "Example Blog",
"_source": {
"about": "https://github.com/bigwide-net/RFCs/blob/main/RFC-1-Source-for-JSON-Feeds.md",
"accounts": {
"socialspec": "@example.com",
"mastodon": "@"
}
},
"items": []
}
- Each key is a service name and must be lower-case (
mastodon, notMastodon). Readers ignore keys that are not. When converting from RSS, wheresource:account'sserviceis case-insensitive, lower-case it. - Each value is the user name on that service, a string written the way the service writes it (with the
@where it has one). - There is exactly one account per service; a key must not appear twice.
- Readers may ignore services they don't know.
On an item. accounts may also appear in an item's _source, where it names the accounts of that item's author. This matters for feeds that collect posts from many people, such as a comment feed, an aggregator or a group blog: there the feed-level accounts can't say who wrote each item.
{
"id": "https://rss.chat/?id=205",
"url": "https://rss.chat/?id=205",
"authors": [{ "name": "Dave Winer" }],
"content_html": "<p>hey manton, great to see you here.</p>",
"_source": {
"about": "https://github.com/bigwide-net/RFCs/blob/main/RFC-1-Source-for-JSON-Feeds.md",
"accounts": {
"socialspec": "@scripting.com",
"mastodon": "@"
}
}
}
- The same rules apply as on the feed: lower-case service keys, one account per service.
- Item-level
accountsreplace the feed-level ones for that item; they are not merged. A reader looking up an item's author uses the item'saccountsif present, otherwise the feed's. An empty object ("accounts": {}) says the item's author has no known accounts, so the feed's must not be used. - If the item has several
authors,accountsbelongs to the first one. - Publishers whose items all come from the feed author put
accountson the feed only, not on every item.
Mapping to RSS and Atom
Each field has an RSS counterpart, so a publisher can write both formats from the same data and a reader can convert between them without loss. Older WordPress and Atom feeds express some of the same things; readers that support them can fill _source's fields from them.
JSON _source | RSS source namespace | WordPress RSS | Atom (RFC 4685) |
|---|---|---|---|
inReplyTo (string) | <source:inReplyTo> | — | <thr:in-reply-to href> |
inReplyTo (object, isPermaLink: false) | <source:inReplyTo isPermaLink="false"> | — | <thr:in-reply-to ref> without href |
inReplyTo.feedUrl | <source:inReplyTo feedUrl> | — | source on <thr:in-reply-to>
|
inReplyTo.feedTitle | <source:inReplyTo feedTitle> | — | — |
comments.feedUrl | <source:comments feedUrl> | <wfw:commentRss> | <link rel="replies" type="application/atom+xml"> |
comments.count | <source:comments count> | <slash:comments> | thr:count on that replies link |
comments.total | none yet (see open questions) | — | <thr:total> |
markdown (items, mainly comments) | <source:markdown> | — | — |
feed accounts | <source:account service> on the channel | — | — |
item
accounts | <source:account service> on the item | — | — |
When a feed carries both the source elements and an older form, the source elements win.
Atom counts. RFC 4685 has two numbers: thr:count on a replies link counts the entries in that linked feed, <thr:total> counts all responses to the entry. Take thr:count for count and <thr:total> for total. Blogger writes only <thr:total>; its comment feed is flat and holds every comment, so there the total is also the first-level count.
Notes for publishers and readers
Publishers
- Use absolute
http(s)URLs. Write_sourceonly when it has at least one field. - If you also publish RSS, write the same values there:
inReplyTo,comments,accountsandmarkdownshould never disagree between your two feeds. - A comment feed may be RSS, Atom or JSON Feed; readers should handle all three.
- Serve comment feeds with
Access-Control-Allow-Origin: *, so that every web client can read them. Comments are made to be handled client-side, without a server in between: an active thread can change any minute, and a server that polls feeds is too slow to keep up.
Readers
- Feeds are untrusted: drop URLs that are not
http(s), counts that are not non-negative integers, andtotalvalues belowcount. - An invalid
inReplyTo.feedUrlor an emptyfeedTitleis dropped on its own; the reply itself stays. - To find an item author's accounts, check the item's
accountsfirst and fall back to the feed's. - If
countpromises comments butfeedUrlreturns an empty feed or an error, say so instead of showing "no comments". - Not every publisher sends CORS headers yet, so browser-based readers still need a fallback (a proxy) for comment feeds without them.
Full example
{
"version": "https://jsonfeed.org/version/1.1",
"title": "Example Blog",
"home_page_url": "https://example.com/",
"feed_url": "https://example.com/feed.json",
"_source": {
"about": "https://github.com/bigwide-net/RFCs/blob/main/RFC-1-Source-for-JSON-Feeds.md",
"accounts": { "socialspec": "@example.com" }
},
"items": [
{
"id": "https://example.com/posts/42",
"url": "https://example.com/posts/42.html",
"content_html": "<p>I disagree, and here is why…</p>",
"date_published": "2026-10-29T09:00:00Z",
"_source": {
"about": "https://github.com/bigwide-net/RFCs/blob/main/RFC-1-Source-for-JSON-Feeds.md",
"inReplyTo": {
"value": "https://rss.chat/?id=204",
"feedUrl": "https://rss.chat/users/dave/rss.xml",
"feedTitle": "Dave Winer"
},
"comments": {
"feedUrl": "https://example.com/posts/42/comments.json",
"count": 3,
"total": 7
}
}
}
]
}
Open questions
- A
totalattribute for RSS. The same gap exists in<source:comments>. Proposal: an optionaltotalattribute,<source:comments count="2" total="31" feedUrl="…"/>, so rss.chat can show thread size. This needs the namespace's maintainers to agree. - Field names.
totalmirrors Atom'sthr:total;totalCountwould be more explicit. Pick one before anyone ships it. - Where
aboutpoints. It names this document at a temporary location. Where should the permanent home be, and should it instead name the RSS namespace (http://source.scripting.com/)? Either way readers ignore the value, so changing it breaks nothing.