Payload Plugins
Pluginspayload-revalidate

Atomic model

See exactly what goes stale when you edit a document — the id-keyed entries, the tag names the plugin builds, and a field-by-field map of what each change busts.

For AI / LLMs: View Markdown

Edit one document and only the pages that show it go stale. This page explains the model that makes that possible, the tags it builds under the hood, and precisely what each kind of change invalidates.

You never spell tags by hand — the getters and hooks build them. Read this when you want to trust the result, or predict a blast radius before you ship.

The atomic model

Every document is its own cache entry. Lists store ids, not content. References stay ids and render through the referenced doc's own entry. The payoff: a content edit re-materializes exactly one small entry — everywhere that document appears — while every page, list, and card around it survives.

Three rules, one consequence each:

  1. Every doc renders through an id-keyed getter (findDocByID) → a content edit busts posts:{id} and re-materializes exactly that entry, at every usage site at once.
  2. Lists cache ids only (findIds) → list entries change on membership/order events, never on content. A title edit re-renders one card, not the archive.
  3. References stay ids (depth: 0) → an entry never bakes in another doc's content, so it never needs to purge when that doc changes. The id is stable; freshness lives in the referenced doc's own entry, and Next recomposes the page from surviving entries.

Populated content that DOES get baked in (depth > 0) is still tagged for correctness — the walk finds it, including inside Lexical richText. It's flagged in dev (console + the /dev/revalidate map) as a refactor candidate, with the exact field paths.

How an entry's tags are generated — the walk runs inside the finder, on whatever you fetched:

The tag vocabulary

Tags are id-based and colon-joined. You never spell these — the helpers and hooks build them.

TagMeaningBusted when
posts:42one doc + any entry baking it inany write to doc 42
posts:my-slugalias doc tag (idField, default slug)same — old AND new alias on renames
posts:list:recenta declared list scopemembership events + its declared fields change
poststhe bare (unscoped) list tagmembership events only
global:headera globalthe global's writes
allon every tagged entryrevalidateAll() / post-seed flush
…:draftdraft-lane variant of any of the abovelane-aware — draft saves touch only these

For the rare raw-tag need, tagsFor(payload) builds prefix-correct tags. See the Exports for its import path.

What busts when

The write side computes each bust from which fields changed:

The full field-by-field map:

You change…Busts
a content field (body, title, …)posts:{id} + alias — doc only, the overwhelmingly common case
a scope's declared field (publishedAt)doc tags + every scope declaring that field (posts:list:recent, …)
the idField (slug)doc tags + old and new alias — id-lists hold ids, so no list bust
membership (create / delete / publish / unpublish)doc tags + bare list + all declared scopes
trash / restore-from-trash (trash: true)same as membership — a soft delete changes list membership exactly like a delete, it just arrives as an update
anything, on a draft-only docthe :draft variants only

Scope fields support dotted paths (contact.email, meta.featured) — the diff compares the exact path. Relationship and upload determinants compare by id, so a populated doc never reads as a change against its previous raw id.

Lanes

extraTags follow the lanes. Published writes bust the base tag. Draft saves bust only the :draft variants.

Rules

rules (whenFields-gated) cover flows the automation can't see. They fire when their declared fields changed, or on any membership event. Deletes fire rules unconditionally.

Rules fire on any membership event even when the publish-time field diff shows nothing — a publish's field delta may have arrived across earlier draft saves and be invisible to the publish-time diff. See Manual rules for a worked example.

Seeding

Every hook honors context.disableRevalidate, which the payload-seed engine sets. The plugin's after-seed listener then flushes the seeded surface once — declared scopes, extraTags, and rule targets for the touched slugs.

Next

  • Writing a getter — turn this model into 'use cache' functions.
  • Examples — detail pages, lists, globals, sitemaps, migrations.
  • Reference — plugin options, exports, and behavior notes.
  • Troubleshooting — the /dev/revalidate map and common failure modes.

On this page