JSON Feed Format: RSS Alternative with JSON 1.1 Spec
JSON Feed is an open syndication format that replaces RSS and Atom XML with a clean JSON structure. Version 1.1, published in 2020, defines a feed object with a required version URL (https://jsonic.io/version/1.1), title, and items array. Each item has id, url, title, and either content_html or content_text. The format is described at jsonfeed.org and is supported by over 200 RSS readers including Reeder, NetNewsWire, and Feedbin. JSON Feed was created by Brent Simmons and Manton Reece to solve the pain of XML parsing — a JSON feed can be parsed with a single JSON.parse() call. A minimal 3-item feed is typically 1–3 KB compared to an equivalent RSS 2.0 feed at 2–5 KB. The content type for JSON Feed is application/feed+json. This guide covers the 1.1 spec fields, required vs optional, authors and attachments, and generating feeds in JavaScript and Python.
Need to validate or pretty-print a JSON Feed response? Jsonic's formatter handles it instantly.
Open JSON FormatterJSON Feed 1.1 Top-Level Fields
The JSON Feed 1.1 spec defines a single top-level object with 14 recognized fields, of which 3 are required. The version field must be the string URL "https://jsonic.io/version/1.1" — not a number. Feed readers check this URL to determine spec version. The title field is the human-readable name of the feed displayed in feed reader sidebars. The items array contains 0 or more item objects; an empty array is valid for a feed that has not yet published.
Optional top-level fields extend the feed with metadata and behavior: home_page_url is the website associated with the feed; feed_url is the self-referencing URL of the feed itself (useful for feed readers that lose track of the original URL); description is a plain-text summary of the feed's content; user_comment is a string intended for human readers who view the raw JSON (a convention borrowed from JSONP). next_url enables pagination by pointing to an older page of items. icon is a 512-pixel square image URL used by feed reader apps; favicon is a 64-pixel icon for compact list views.
The authors field (version 1.1, replaces the singular author of version 1.0) is an array of author objects, enabling multi-author blogs and podcasts. The language field accepts a BCP 47 language tag (e.g., "en-US", "zh-Hans"). The expired boolean, when true, signals feed readers that this feed will no longer be updated — 1 boolean field that cleanly replaces the RSS ttl and skipDays heuristics. A minimal feed object with 4 fields looks like this:
{
"version": "https://jsonic.io/version/1.1",
"title": "My Blog",
"home_page_url": "https://example.com",
"feed_url": "https://example.com/feed.json"
}Even though items is required, an empty array is valid. In practice, always include home_page_url and feed_url — they are the top 2 optional fields that feed readers rely on for display and subscription management. Compare to the structure of REST API JSON responses where a similar top-level wrapper pattern is common.
Item Fields
Each object in the items array represents a single piece of content — a blog post, podcast episode, or news article. The id field is required and must be a unique, permanent string. Feed readers use id to track which items have been read; if an id changes, the item appears as new. Best practice: use the canonical URL as id or a UUID. The id must never change even if the url changes (e.g., after a URL restructure).
Content fields: content_html contains the full HTML of the post; content_text contains a plain-text version. At least 1 of the 2 must be present. Including both gives feed readers a fallback — HTML readers use content_html, email digest tools use content_text. The summary field is a plain-text excerpt shown in list views; it is separate from content_text and typically 1–2 sentences. image is a thumbnail URL; banner_image is a wider header image.
Date fields — date_published and date_modified — must use RFC 3339 format (see JSON date formatsfor a full comparison of date serialization strategies). The authors array overrides the feed-level authors for this item. The tags field is an array of plain strings used for categorization — no nesting, no IDs, just strings. The language field overrides the feed-level language for multilingual feeds. The attachments array supports podcast-style enclosures:
{
"id": "https://example.com/posts/hello-world",
"url": "https://example.com/posts/hello-world",
"title": "Hello World",
"content_html": "<p>This is my first post.</p>",
"content_text": "This is my first post.",
"summary": "An introduction to my new blog.",
"image": "https://example.com/images/hello-thumb.jpg",
"date_published": "2026-05-13T00:00:00Z",
"date_modified": "2026-05-13T00:00:00Z",
"authors": [{ "name": "Jane Smith", "url": "https://janesmith.com" }],
"tags": ["introduction", "meta"]
}A full JSON Feed 1.1 spec supports 15 item fields. The 2 most commonly omitted (incorrectly) are id permanence and RFC 3339 dates. Both cause data integrity issues in feed readers when violated.
Authors and Attachments
Authors in JSON Feed 1.1 are objects within the authors array — a change from version 1.0's singular author object. Each author object has 3 optional fields: name (display name), url (author's website), and avatar (image URL). All 3 are optional but name should always be included for human-readable feeds. Multiple authors per item enable co-authored blog posts, guest articles, and podcast episodes with 2 or more hosts. The feed-level authors array serves as the default for all items; item-level authors overrides it for specific posts.
Attachments power podcast feeds. Each attachment object in the attachments array has these fields: url (required, the media file URL), mime_type (required, e.g., "audio/mpeg"), title (display title), size_in_bytes (integer, for progress bars and download time estimates), and duration_in_seconds (integer). A podcast feed uses content_text as show notes and an attachment for the MP3. This maps directly to RSS 2.0's <enclosure> element but supports multiple attachments per item — useful for episodes with both MP3 and a chapter-marked M4A file.
{
"id": "https://example.com/podcast/ep-42",
"url": "https://example.com/podcast/ep-42",
"title": "Episode 42: JSON Feed Deep Dive",
"content_text": "In this episode we cover the JSON Feed 1.1 spec in detail.",
"date_published": "2026-05-13T09:00:00Z",
"authors": [
{ "name": "Alice Chen", "url": "https://alice.example.com", "avatar": "https://alice.example.com/avatar.jpg" },
{ "name": "Bob Lee", "url": "https://bob.example.com", "avatar": "https://bob.example.com/avatar.jpg" }
],
"attachments": [
{
"url": "https://example.com/podcast/ep-42.mp3",
"mime_type": "audio/mpeg",
"title": "Episode 42 Audio",
"size_in_bytes": 52428800,
"duration_in_seconds": 3600
}
]
}The example above shows a podcast item with 2 co-hosts and a 3600-second (1-hour) episode. The size_in_bytes value of 52428800 bytes equals approximately 50 MB — a typical hour-long MP3 at 128 kbps. Feed readers display both the episode duration and the file size in their download UI.
Generate JSON Feed in JavaScript
Building a JSON Feed in JavaScript follows the same pattern as any JSON syntax construction: create a plain JavaScript object and serialize it with JSON.stringify(feed, null, 2)for pretty-printed output or JSON.stringify(feed) for compact output. No special library is required — the entire format is a plain JSON object.
// Build a feed from an array of blog posts
const posts = [
{ id: '1', slug: 'hello-world', title: 'Hello World', html: '<p>First post.</p>', publishedAt: new Date('2026-05-13') },
{ id: '2', slug: 'second-post', title: 'Second Post', html: '<p>Second post.</p>', publishedAt: new Date('2026-05-12') },
]
const feed = {
version: 'https://jsonic.io/version/1.1',
title: 'My Blog',
home_page_url: 'https://example.com',
feed_url: 'https://example.com/feed.json',
description: 'Thoughts on software, JSON, and the web.',
authors: [{ name: 'Jane Smith', url: 'https://example.com/about' }],
items: posts.map((post) => ({
id: `https://example.com/posts/${post.slug}`,
url: `https://example.com/posts/${post.slug}`,
title: post.title,
content_html: post.html,
date_published: post.publishedAt.toISOString(),
})),
}
// Pretty-print for debugging
console.log(JSON.stringify(feed, null, 2))In a Next.js JSON API route, create a Route Handler at app/feed.json/route.ts that returns the feed with the correct content type. The App Router's GET export pattern makes this straightforward:
// app/feed.json/route.ts
import { getAllPosts } from '@/lib/posts'
export async function GET() {
const posts = await getAllPosts()
const feed = {
version: 'https://jsonic.io/version/1.1',
title: 'My Blog',
home_page_url: 'https://example.com',
feed_url: 'https://example.com/feed.json',
items: posts.map((post) => ({
id: `https://example.com/posts/${post.slug}`,
url: `https://example.com/posts/${post.slug}`,
title: post.title,
content_html: post.contentHtml,
summary: post.excerpt,
date_published: new Date(post.publishedAt).toISOString(),
date_modified: new Date(post.updatedAt).toISOString(),
tags: post.tags,
})),
}
return new Response(JSON.stringify(feed), {
headers: {
'Content-Type': 'application/feed+json',
'Cache-Control': 'public, max-age=3600, stale-while-revalidate=86400',
},
})
}Add the auto-discovery <link> tag to your root layout so feed reader browser extensions detect the feed automatically:
// app/layout.tsx — add to <head> via Next.js metadata
export const metadata = {
// ...other fields
alternates: {
types: {
'application/feed+json': 'https://example.com/feed.json',
},
},
}Generate JSON Feed in Python
Python's built-in json module handles JSON Feed serialization with no extra dependencies. Build the feed as a plain dict and serialize with json.dumps(feed, indent=2, ensure_ascii=False) — the ensure_ascii=False flag preserves non-ASCII characters in titles and content without escaping them as \uXXXX sequences, which is important for multilingual feeds. The resulting string is typically 20–30% smaller than the ASCII-escaped equivalent for CJK content.
import json
from datetime import datetime, timezone
posts = [
{"slug": "hello-world", "title": "Hello World", "html": "<p>First post.</p>"},
{"slug": "second-post", "title": "Second Post", "html": "<p>Second post.</p>"},
]
def rfc3339_now() -> str:
"""Return current UTC time as RFC 3339 string with Z suffix."""
return datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")
feed = {
"version": "https://jsonic.io/version/1.1",
"title": "My Blog",
"home_page_url": "https://example.com",
"feed_url": "https://example.com/feed.json",
"authors": [{"name": "Jane Smith", "url": "https://example.com/about"}],
"items": [
{
"id": f"https://example.com/posts/{post['slug']}",
"url": f"https://example.com/posts/{post['slug']}",
"title": post["title"],
"content_html": post["html"],
"date_published": rfc3339_now(),
}
for post in posts
],
}
print(json.dumps(feed, indent=2, ensure_ascii=False))In FastAPI, return the feed with the correct content type using a Response object. In Flask, use make_response. Both frameworks default to application/json — you must override the media_type / content type header explicitly:
# FastAPI example
from fastapi import FastAPI
from fastapi.responses import Response
import json
app = FastAPI()
@app.get("/feed.json")
async def json_feed():
feed = {
"version": "https://jsonic.io/version/1.1",
"title": "My Blog",
"home_page_url": "https://example.com",
"feed_url": "https://example.com/feed.json",
"items": await get_feed_items(), # your data source
}
return Response(
content=json.dumps(feed, ensure_ascii=False),
media_type="application/feed+json",
)
# Flask example
from flask import Flask, make_response
import json
app = Flask(__name__)
@app.route("/feed.json")
def json_feed():
feed = build_feed() # your feed dict
response = make_response(json.dumps(feed, ensure_ascii=False))
response.headers["Content-Type"] = "application/feed+json"
return responseRFC 3339 dates in Python: datetime.now(timezone.utc).isoformat() outputs 2026-05-13T00:00:00+00:00. Replace +00:00 with Z for the more compact form expected by most feed readers. For stored dates in a database, convert with datetime.fromisoformat(stored_string).replace(tzinfo=timezone.utc). See JSON date formats for a complete guide to date serialization patterns across languages.
Key terms
- JSON Feed
- An open syndication format created by Brent Simmons and Manton Reece that uses a JSON object with a
version,title, anditemsarray to represent a stream of content, replacing the XML-based RSS 2.0 and Atom formats. - application/feed+json
- The IANA-registered MIME type for JSON Feed documents; feed readers distinguish JSON feeds from generic JSON APIs by checking for this content type in the HTTP response header and in HTML
<link rel="alternate">discovery tags. - RFC 3339
- A date/time format standard that is a strict profile of ISO 8601, requiring a full date, time, and time zone offset (e.g.,
2026-05-13T14:30:00Z); JSON Feed requires RFC 3339 for all date fields, unlike RSS which uses RFC 822. - items array
- The required top-level array in a JSON Feed object where each element is an item object representing one piece of content (blog post, podcast episode, etc.), each requiring at minimum a unique
idand at least one content field. - content_html
- An item field containing the full HTML markup of the content, allowing feed readers to render formatted text, images, and links exactly as they appear on the originating website; at least one of
content_htmlorcontent_textmust be present on every item. - next_url
- An optional top-level feed field containing the URL of the next (older) page of items, enabling paginated feeds where feed readers follow the chain to load a complete history without requiring all items to be served in a single response.
- attachments
- An optional item field containing an array of media file objects (each with
url,mime_type,size_in_bytes, andduration_in_seconds), used primarily by podcast feeds to reference audio or video files associated with an episode, equivalent to RSS 2.0's<enclosure>element but supporting multiple files per item.
Frequently asked questions
What is JSON Feed and how is it different from RSS?
JSON Feed is a syndication format that uses JSON instead of RSS's XML. RSS 2.0 and Atom require XML parsers; JSON Feed is parsed with JSON.parse(). Both carry the same information (title, URL, content, date), but JSON Feed is easier to generate and consume in JavaScript and Python. JSON Feed 1.1 is the current version, published in 2020, and is supported by over 200 RSS readers including Reeder, NetNewsWire, and Feedbin. JSON Feed was created by Brent Simmons and Manton Reece specifically to eliminate the pain of XML parsing. A minimal 3-item feed is typically 1–3 KB compared to an equivalent RSS 2.0 feed at 2–5 KB. The content type for JSON Feed is application/feed+json. See also the JSON syntax guide for a primer on the underlying format.
What is the required content type for a JSON Feed?
The required content type is application/feed+json. Do not serve it as application/json — feed readers use the content type to detect feed format. Include a <link> tag in your HTML <head> to advertise the feed: <link rel="alternate" type="application/feed+json" href="/feed.json" title="My Feed">. This auto-discovery link lets feed reader browser extensions and apps detect that a JSON Feed is available on your site. Setting the correct Content-Type response header is equally important when serving the feed from your server. In Next.js, return new Response(JSON.stringify(feed), { headers: { "Content-Type": "application/feed+json" } }). See Next.js JSON API routes for the full Route Handler pattern.
Can a JSON Feed item have both content_html and content_text?
Yes — both content_html and content_text are optional, but at least one must be present per item. Including both gives feed readers a fallback: readers that render HTML use content_html; plain-text email digests use content_text. They do not have to match exactly — content_html can be richer with images, links, and formatting while content_text contains the plain prose. For blog posts, include content_html with the full post HTML and an optional content_text with a plain-text summary. For podcast episodes, use content_text as the show notes and include an attachment object for the MP3 file. This dual-field approach is one of the practical improvements JSON Feed makes over RSS, which has a single description field.
How do I paginate a JSON Feed?
Use the next_url field in the feed object to point to the URL of the next page of items. Feed readers follow next_url to load older items. There is no prev_url — feeds are consumed forward from the current page. A paginated feed typically serves the 20 most recent items by default and includes next_url pointing to /feed.json?page=2, which in turn has next_url pointing to /feed.json?page=3, and so on until the oldest page which omits next_url. Each page is a complete, valid JSON Feed — not a partial fragment. Feed readers typically load the first page on subscribe and lazily follow next_url to build a complete history.
What date format does JSON Feed use?
JSON Feed uses RFC 3339, which is a profile of ISO 8601. Examples: 2026-05-13T14:30:00Z or 2026-05-13T14:30:00+09:00. Time zone is required; bare dates without time are not valid RFC 3339. In JavaScript, new Date().toISOString() produces a valid RFC 3339 date. In Python, datetime.now(timezone.utc).isoformat() outputs 2026-05-13T14:30:00+00:00; to get the Z suffix, replace +00:00 with Z. RFC 3339 dates are more precise than RSS's RFC 822 date format and are natively parseable with Date.parse() in all modern JavaScript engines. See the full JSON date format guide for a comparison of all date serialization strategies.
What is the difference between JSON Feed 1.0 and 1.1?
Version 1.1 replaced the singular author object with an authors array, allowing multiple authors per feed or item. The version field URL changed from https://jsonic.io/version/1 to https://jsonic.io/version/1.1. All other fields are backward compatible — a feed reader that supports 1.1 can also read 1.0 feeds. The authors array contains objects with name, url, and avatar fields, enabling co-authored blog posts and podcast episodes with multiple hosts. Version 1.1 was published in 2020 and is the current recommended version. When generating a new feed, always use the 1.1 version URL. The single author field from 1.0 is deprecated but parsers should still handle it gracefully for backward compatibility.
Ready to build your JSON Feed?
Use Jsonic's JSON Formatter to validate and pretty-print your JSON Feed output. You can also learn REST API JSON response best practices to apply consistent structure across all your JSON endpoints.
Open JSON Formatter