Stable IDs for Feed Entries - Let a Post Change Without Becoming New Again
A post is published, corrected the next morning, and moved to a cleaner URL a month later. To a reader, it is still the same post. To a feed reader, however, that continuity depends on a small field that is easy to regenerate by accident: the entry identifier.
If a feed generator derives identity from a title, timestamp, or current slug, an ordinary edit can produce a different value. Some clients may then present the revision as a new entry. The opposite mistake is also possible: reusing an old identifier for genuinely new content can make that content look like an update. The difficult part is not producing a unique-looking string. It is deciding what the string means and preserving that meaning over time.
Identity, location, and time answer different questions
A feed entry usually carries several values that look interchangeable but are not:
- Identifier: Which logical entry is this?
- Permalink: Where can a person retrieve its web representation?
- Publication time: When did it first become public?
- Modification time: When was this representation last revised?
A permalink can also serve as an identifier, and that is often a reasonable design. But the two roles remain different. A redirect may move retrieval from an old URL to a new one while the post's identity stays unchanged. Likewise, correcting a paragraph should change modification metadata, not silently create another logical entry.
Think of a library book. Its shelf position tells you where to find it; its catalog number tells you which book the record describes. Sometimes a system uses one value for both jobs. Trouble begins when software assumes that moving the book must also give it a new identity.
RSS, Atom, and JSON Feed express the rule differently
The three formats agree on the need for continuity, but their requirements are not identical. Flattening them into one rule can create invalid output.
RSS 2.0: guid is optional but useful
The RSS 2.0 specification defines guid as a string that uniquely identifies an item. An aggregator may use it to determine whether an item is new. That wording matters: RSS does not prescribe one universal deduplication algorithm, so no publisher can promise identical behavior in every reader.
RSS also gives isPermaLink a surprising default. If the attribute is omitted or set to true, the value is treated as a permanent URL associated with the item. An opaque internal value therefore needs an explicit isPermaLink="false":
<item>
<title>A Durable Post Title</title>
<link>https://example.com/posts/durable-title/</link>
<guid isPermaLink="false">post:1842</guid>
<pubDate>Sat, 31 Oct 2026 00:00:00 GMT</pubDate>
</item>
The identifier above is only an illustrative publisher-controlled string. Its uniqueness depends on the publisher never assigning post:1842 to another item in that feed. The RSS Advisory Board's best-practices profile recommends providing a guid for every item, even though the core format does not require one.
Atom 1.0: atom:id is required and permanent
Atom is stricter. RFC 4287 section 4.2.6 requires exactly one atom:id in each entry. Its value must be an IRI, must be created to assure uniqueness, and does not have to resolve in a browser. The RFC explicitly says that it must not change when an entry is relocated, migrated, syndicated, republished, exported, imported, or revised.
<entry xmlns="http://www.w3.org/2005/Atom">
<title>A Durable Post Title</title>
<id>tag:example.com,2026:post-1842</id>
<author><name>Example Author</name></author>
<link rel="alternate"
href="https://example.com/posts/durable-title/" />
<published>2026-10-31T00:00:00Z</published>
<updated>2026-10-31T08:15:00Z</updated>
</entry>
Atom compares IDs character by character and case-sensitively. Two strings that happen to resolve to the same resource are still different IDs if their characters differ. Normalizing or rebuilding an ID during every render is therefore risky. In Atom, entries with the same atom:id represent the same entry; atom:updated communicates revision time.
The example uses a tag: URI. RFC 4151 defines this scheme for identifiers that can be unique without being fetchable. It combines an authority, a date associated with that authority, and a publisher-managed specific part. It is one available strategy, not a requirement. An HTTPS IRI or another valid, durable IRI can also satisfy Atom.
JSON Feed: id and url are separate fields
JSON Feed 1.1 requires every item to have a string id that remains unique within that feed over time. An updated item keeps its ID, and a new item must not reuse a previous one. The optional url is the permalink. It may equal the ID, but the format keeps the roles visibly separate:
{
"id": "post:1842",
"url": "https://example.com/posts/durable-title/",
"title": "A Durable Post Title",
"date_published": "2026-10-31T00:00:00Z",
"date_modified": "2026-10-31T08:15:00Z",
"content_html": "<p>Corrected content.</p>"
}
JSON Feed tells readers to discard an item without an ID. That is stronger than RSS, where guid is optional. It is another reason not to treat the formats as cosmetic serializations of one loosely defined object.
Choose an identifier strategy that can survive change
There are two common, defensible strategies.
Use a stable permalink as the ID
This is simple and human-readable. It works well when published URLs are genuinely durable, redirects are maintained, and a migration can preserve the original identifier string even if the current link changes. RSS naturally supports this with its default permalink behavior, and Atom permits dereferenceable IRIs.
The limitation is organizational rather than syntactic. If a CMS automatically reconstructs the ID from the current domain and slug, a domain migration or slug edit changes identity. A URL-based ID is stable only when the stored original URL is treated as immutable identity, not recalculated from today's routing configuration.
Use an opaque, stored publication ID
A database-backed value can separate identity from routing:
feed_entry_id = tag:example.com,2026:post-1842
current_url = https://example.com/posts/durable-title/
This makes slug and domain changes easier to model. It also creates obligations: the value must be globally suitable for Atom, unique in the intended scope, never reassigned, and exported during migrations. An opaque ID is not automatically safer merely because it looks technical. Without durable storage and uniqueness rules, it is just another mutable string.
A practical CMS can mint the identifier once when an entry first enters the public feed, store it in a dedicated immutable column, and expose the current permalink separately. If the CMS emits multiple formats, it should define an explicit mapping for each one. A legacy RSS guid is allowed to be an arbitrary string when marked non-permalink, while an Atom ID must be an IRI; blindly copying any historical RSS value into Atom may not conform.
Edits and migrations need an identity policy
The policy can be small, but it should answer awkward cases before a migration script answers them accidentally.
- Correct a typo or expand a section: keep the entry ID; update modification metadata where the format provides it.
- Change a slug: keep the ID; update the current link and preserve an HTTP redirect when practical.
- Move to another domain: preserve already published IDs. New entries may follow a new minting policy, but the transition should not rewrite history.
- Import from another CMS: carry existing identifiers when they are available and valid for the target format. Record any necessary mapping instead of deriving everything from import time.
- Republish substantially new work: decide editorially whether it is a revision or a new entry. A new publication should receive a new ID; an update should retain the old one.
- Delete and later restore an entry: restore its ID as well. Do not recycle that identifier for unrelated content.
Changing a bad identifier policy on an established feed requires restraint. Replacing every existing ID with a cleaner scheme can itself make old entries appear new in some clients. Often the least disruptive approach is to preserve historical values, apply the corrected policy to future entries, and migrate old IDs only when there is a specific, tested reason.
Validate the feed, then inspect behavior cautiously
The W3C Feed Validation Service documentation lists checks for missing RSS GUIDs, insufficiently unique values, duplicate Atom IDs, and invalid permalink forms. Validation is valuable because it catches structural mistakes such as placing an arbitrary RSS string under the default permalink interpretation.
It cannot prove how every reader will display a revision. Client state, import history, and implementation choices remain outside a syntax validator's view. A cautious test uses a non-production feed and observes a sequence: publish one item, fetch it, edit content without changing the ID, then change the link while retaining identity. The expected model is one evolving entry, but any claim about a particular reader should come from an actual test of that reader and version.
Feed snapshots also deserve regression tests. Given the same database row, a rebuild should emit the same ID. Editing a title or slug should not alter it. Creating a new row should never reuse a deleted row's value. Those tests protect the publishing contract more directly than checking whether the output merely contains a field named guid or id.
A small field carries the history
A durable entry identifier lets content change without pretending that the entry has become something else. RSS offers a flexible, optional guid; Atom requires a permanent IRI; JSON Feed requires a stable string within the feed. Their syntax differs, but the publishing discipline is similar: mint identity deliberately, store it, and do not recompute it from fields expected to change.
There is no universal need to abandon URL-based IDs, nor is an opaque value automatically better. The useful question is narrower: can this exact value survive the edits, route changes, exports, and migrations the publication reasonably expects? If not, the identifier is describing today's presentation rather than the entry's continuity.
References
- RSS Advisory Board, “RSS 2.0 Specification (Current),” version 2.0.11.
- RSS Advisory Board, “Really Simple Syndication Best Practices Profile,” version 1.0.
- IETF, RFC 4287, “The Atom Syndication Format”.
- IETF, RFC 4151, “The 'tag' URI Scheme”.
- Brent Simmons and Manton Reece, “JSON Feed Version 1.1”.
- W3C, “Feed Validation Service Documentation”.
