npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

payload-plugin-cache

v0.9.8

Published

Payload CMS plugin for cache utilities.

Downloads

494

Readme

payload-plugin-cache

Payload CMS plugin for cache invalidation utilities.

This package connects Payload write events to external cache systems through stable cache tags. You configure collections, globals, and tag generation; adapters run invalidation against your cache implementation.

Usage

Use defineCachePlugin when your frontend also needs the same registry to resolve render-time tags. Use cachePlugin when you only need the Payload plugin.

import { buildConfig } from "payload"
import { cacheTags, defineCachePlugin, findAllReferences } from "payload-plugin-cache"
import { nextCacheAdapter } from "payload-plugin-cache/next"

const { plugin: cachePlugin, registry: cacheRegistry } = defineCachePlugin({
  adapter: nextCacheAdapter(),
  collections: {
    pages: {
      path: ({ doc }) => (typeof doc.populatedUrl === "string" ? doc.populatedUrl : undefined),
      predicates: {
        isSearchPage: ({ doc }) => doc.slug === "search",
      },
      renderTags: async ({ doc, locale, path, searchParams, predicates }) => [
        path ? cacheTags.route({ path, locale }) : null,
        cacheTags.document({ collection: "pages", id: doc.id, locale }),
        ...findAllReferences(doc).map((reference) =>
          cacheTags.document({
            collection: reference.collection,
            id: reference.id,
            locale,
          }),
        ),
        cacheTags.custom("sitemap"),
        predicates.isSearchPage ? cacheTags.custom("searches") : null,
        predicates.isSearchPage && searchParams?.get("q")
          ? cacheTags.custom(`search:${searchParams.get("q")}`)
          : null,
      ],
    },
    team: {
      revalidateOn: ["update", "delete"],
      renderTags: () => [cacheTags.custom("page")],
    },
  },
  globals: {
    settings: {
      renderTags: () => [cacheTags.global("settings"), cacheTags.custom("sitemap")],
    },
    header: {
      renderTags: () => [cacheTags.global("header")],
      revalidatePaths: () => [{ path: "/", type: "layout" }],
    },
    footer: {
      renderTags: () => [cacheTags.global("footer")],
      revalidatePaths: () => [{ path: "/", type: "layout" }],
    },
  },
})

export { cacheRegistry }

export default buildConfig({
  plugins: [cachePlugin],
  collections: [
    {
      slug: "pages",
      fields: [],
    },
    {
      slug: "team",
      fields: [],
    },
  ],
  globals: [
    {
      slug: "settings",
      fields: [],
    },
    {
      slug: "header",
      fields: [],
    },
    {
      slug: "footer",
      fields: [],
    },
  ],
})

Configured collections receive an afterOperation hook. Configured globals receive an afterChange hook. Existing hooks are preserved.

Optional globalRenderTags and globalRevalidateTags on the plugin are copied onto the returned registry and merged into every successful resolution from resolveRenderCacheTags, resolveRenderGlobalCacheTags, resolveInvalidationCacheTags, and resolveInvalidationGlobalCacheTags (including when invalidation uses renderTags because revalidateTags is omitted). Each may be a static tag array or a function ({ scope }) => tags where scope is "collection" or "global" depending on which strategy is being resolved (async returns are allowed; nested tag shapes match renderTags).

defineCachePlugin({
  adapter,
  globalRenderTags: ({ scope }) =>
    scope === "collection"
      ? [cacheTags.custom("collections-root")]
      : [cacheTags.custom("globals-root")],
  globalRevalidateTags: [cacheTags.custom("searches")],
  collections: {
    /* … */
  },
})

Core Concepts

Strategies

Strategies compute tags from collection or global documents (and optional path, locale, or predicates). Return values are tag strings or nested arrays of them.

import { cacheTags, type CollectionCacheStrategy } from "payload-plugin-cache"

export const postStrategy: CollectionCacheStrategy<"posts"> = {
  path: ({ doc }) => (doc.slug ? `/blog/${doc.slug}` : undefined),
  revalidateOn: ["create", "update", "delete"],
  renderTags: ({ doc, locale, path }) => [
    path ? cacheTags.route({ path, locale }) : null,
    cacheTags.document({ collection: "posts", id: doc.id, locale }),
    cacheTags.collection("posts"),
    cacheTags.custom("sitemap"),
  ],
}

renderTags() may return nested arrays and empty values; results are flattened, filtered, and deduped.

cacheTags helpers

cacheTags.document, cacheTags.collection, cacheTags.global, cacheTags.layout, and cacheTags.route build stable tag strings. That differs from revalidatePaths, which resolves revalidatePath targets (paths and optional layout / page) through the Next adapter.

Slug arguments are generic over your Payload config (PayloadTypes / augmented GeneratedTypes), so unknown collection or global keys fail the type checker when your project provides generated types. cacheTags.route accepts an optional query as a string or URLSearchParams; pairs are canonicalized (sorted by key, then value) so reordering query parameters does not change the tag.

Predicates

On collections, predicates receive CollectionPredicateRenderArgs while renderTags is resolved, and CollectionPredicateInvalidationArgs while revalidateTags is resolved. If there is no revalidateTags, invalidation uses renderTags and predicates are only evaluated with render args. When both renderTags and revalidateTags are configured, ctx.renderTags() resolves render-time predicates lazily and reuses the already resolved path from the invalidation pass.

const pageStrategy = {
  predicates: {
    isSearchPage: async ({ payload, doc, locale }) => {
      const settings = await payload.findGlobal({ slug: "settings", depth: 0, locale })
      const searchPageId =
        typeof settings.searchPage === "string" ? settings.searchPage : settings.searchPage?.id

      return doc.id === searchPageId
    },
  },
  renderTags: ({ predicates }) => [predicates.isSearchPage ? "searches" : null],
}

revalidateTags

Collections

  • renderTags(ctx)CollectionRenderTagsContext
  • revalidateTags(ctx)CollectionInvalidationTagsContext (includes operation)
  • await ctx.renderTags() inside revalidateTags → same tags as resolveRenderCacheTags for that document (including globalRenderTags) with searchParams omitted and render-time predicate results; path resolution runs once for the invalidation pass
  • Omit revalidateTagsresolveInvalidationCacheTags uses renderTags with render context only
import { cacheTags, type CollectionCacheStrategy } from "payload-plugin-cache"

export const postStrategy: CollectionCacheStrategy<"posts"> = {
  revalidateOn: ["update", "delete"],
  renderTags: ({ doc, locale, path }) => [
    path ? cacheTags.route({ path, locale }) : null,
    cacheTags.document({ collection: "posts", id: doc.id, locale }),
  ],
  revalidateTags: async ({ doc, previousDoc, locale, operation, renderTags }) => [
    ...(await renderTags()),
    previousDoc ? cacheTags.document({ collection: "posts", id: previousDoc.id, locale }) : null,
    operation === "delete" || operation === "deleteByID" ? cacheTags.collection("posts") : null,
  ],
}

Globals

  • renderTags(ctx)GlobalRenderTagsContext
  • revalidateTags(ctx)GlobalInvalidationTagsContext (includes operation)
  • await ctx.renderTags() inside revalidateTags → same tags as resolveRenderGlobalCacheTags when the strategy defines renderTags (including globalRenderTags); otherwise []
  • Omit revalidateTagsresolveInvalidationGlobalCacheTags uses renderTags with GlobalRenderTagsContext
import { cacheTags, type GlobalCacheStrategy } from "payload-plugin-cache"

export const settingsStrategy: GlobalCacheStrategy<"settings"> = {
  renderTags: () => [cacheTags.global("settings"), cacheTags.custom("sitemap")],
  revalidateTags: async ({ doc, previousDoc, locale, renderTags }) => [
    ...(await renderTags()),
    doc.homePage
      ? cacheTags.document({ collection: "pages", id: doc.homePage as string | number, locale })
      : null,
    previousDoc?.homePage
      ? cacheTags.document({
          collection: "pages",
          id: previousDoc.homePage as string | number,
          locale,
        })
      : null,
  ],
}

revalidatePaths

Invalidation-only (no render-time analogue). Mirrors revalidateTags context: collections use CollectionInvalidationTagsContext, globals GlobalInvalidationTagsContext (including lazy ctx.renderTags() when renderTags is configured). Return values use the same nested / null / false pattern as renderTags, flattened and deduped by a stable key from path and type ( "page" when type is omitted). If revalidatePaths is omitted, path revalidation is skipped. The Next adapter invokes revalidatePath(path) with one argument when type is omitted (Next’s "page" default).

import { cacheTags, type GlobalCacheStrategy } from "payload-plugin-cache"

export const footerStrategy: GlobalCacheStrategy<"footer"> = {
  renderTags: () => [cacheTags.global("footer")],
  revalidatePaths: () => [{ path: "/", type: "layout" }],
}

Next.js

Next.js helpers are exported from payload-plugin-cache/next.

import { cacheLife } from "next/cache"
import { resolveRenderCacheTags } from "payload-plugin-cache"
import { setCacheTags } from "payload-plugin-cache/next"

import { cacheRegistry } from "@/payload.config"

export async function CachedPageContent({
  payload,
  page,
  locale,
  path,
  searchParams,
}: {
  payload: import("payload").Payload
  page: Record<string, unknown>
  locale: string
  path: string
  searchParams?: Record<string, string | string[] | undefined>
}) {
  "use cache"
  cacheLife("max")

  const tags = await resolveRenderCacheTags({
    registry: cacheRegistry,
    payload,
    collection: "pages",
    doc: page,
    locale,
    path,
    searchParams,
  })

  setCacheTags(tags)

  return page
}

setCacheTags() calls cacheTag() for each resolved tag. Pass a second argument { payload, logRenderTags: true } to log each tag with payload.logger. Call cacheLife() in the cached function or component where your app owns the caching profile.

The Next.js adapter calls revalidateTag(tag, profile) and, when path entries are passed, invokes revalidatePath(path) or revalidatePath(path, type) for each resolved path (profile still applies only to revalidateTag).

import { nextCacheAdapter } from "payload-plugin-cache/next"

nextCacheAdapter({
  profile: "max",
  logRevalidatingTags: true,
  logRevalidatingPaths: true,
})

Other Adapters

Built-in adapters: noopCacheAdapter, webhookCacheAdapter, multiCacheAdapter. Custom adapters implement CacheAdapter and receive deduped tags plus InvalidationContext, and optionally paths (payload-plugin-cachenext/cache uses these for revalidatePath). webhookCacheAdapter posts JSON { tags, paths } ( paths may be [] ).

import {
  multiCacheAdapter,
  noopCacheAdapter,
  webhookCacheAdapter,
  type CacheAdapter,
} from "payload-plugin-cache"

const loggingAdapter: CacheAdapter = {
  invalidate(tags, context, paths) {
    context.payload.logger.info(`Invalidating ${tags.length} tags, ${paths?.length ?? 0} paths`)
  },
}

const adapter = multiCacheAdapter(
  loggingAdapter,
  webhookCacheAdapter({ endpoint: "https://frontend.example.com/api/revalidate" }),
  noopCacheAdapter(),
)

createRecordingAdapter() is exported for tests.

Reference Tagging

findAllReferences() recursively scans common Payload relationship shapes, dedupes references, and guards circular objects. It supports polymorphic relationships, populated relationship values, and objects with { collection, id }.

import { cacheTags, findAllReferences } from "payload-plugin-cache"

function referencedDocumentTags(doc: Record<string, unknown>, locale: string) {
  return findAllReferences(doc).map((reference) =>
    cacheTags.document({
      collection: reference.collection,
      id: reference.id,
      locale,
    }),
  )
}

Revalidation Rules

revalidateOn defaults to ["create", "update", "delete"].

Payload operations map as follows:

  • create: create
  • update: update, updateByID
  • delete: delete, deleteByID

Set req.context.disableRevalidate = true to skip invalidation for migrations, imports, nested writes, or bulk updates (for example payload-plugin-urls update-urls recalculates populatedUrl with this flag). Draft documents (_status: "draft") are skipped automatically.

When req.locale === "all", invalidation runs for each code in localization.locales (or from the plugin option locales when set). If that list is empty, "en" is used once. Missing req.locale: use localization.defaultLocale when it appears in configured locales; otherwise the first locale in the list.

Exports

  • Plugins: defineCachePlugin, cachePlugin
  • Tag helpers: cacheTags, compactTags, dedupeTags, compactPaths, dedupePaths, revalidatePathKey
  • Resolvers: resolveRenderCacheTags, resolveInvalidationCacheTags, resolveRenderGlobalCacheTags, resolveInvalidationGlobalCacheTags, resolveInvalidationCachePaths, resolveInvalidationGlobalCachePaths, normalizeSearchParams (collection/global render vs invalidation use distinct Resolve*CacheTagsArgs types; invalidation requires operation typed as PayloadCollectionAfterOperation or PayloadGlobalBeforeOperation from Payload hooks)
  • Dependencies: findAllReferences, toDocumentReference, isPayloadId, documentReferenceKey, parseDocumentReferenceKey, hasDefaultPopulateChanges
  • Adapters: nextCacheAdapter from payload-plugin-cache/next, plus noopCacheAdapter, webhookCacheAdapter, multiCacheAdapter, createRecordingAdapter
  • Next helper: setCacheTags from payload-plugin-cache/next