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

@justanarthur/payload-www

v2.7.0

Published

Reusable Payload CMS website template — config builder, collections, globals, blocks, fields, access, hooks, metadata (JSON-LD, hreflang), page renderers, and test helpers.

Readme

@justanarthur/payload-www

A reusable Payload CMS website template: collections, globals, blocks, fields, access, JSON-LD / hreflang metadata, Next.js page renderers, sitemap, and the default plugin set (SEO + imagehash + translator + MCP).

The lib reads your next-intl routing config so locale validation, URL shape, hreflang alternates, and the language switcher share a single source of truth with the rest of the app.

The composition root is createWWWConfig() — see Quick start below.

What's inside

| piece | exported from | purpose | |---|---|---| | Composer | /config | createWWWConfig() — returns { withWWWConfig }. One call wires Pages + Posts + Header + Footer + the default plugin set. | | Pages + Posts collections | createPagesCollection, createPostsCollection (internal — composed by the composer) | Pages (title, blocks tab, slug, drafts); Posts (title, excerpt, richText, drafts). Caching comes from the host's revalidatePlugin() registration. | | Header + Footer globals | createHeaderGlobal, createFooterGlobal (internal) | Both nav blocks with navColumn / navItem. Extend via link({ extraFields }). | | Static-page collection | createWWWCollectionGlobal (/collections) | Generic factory for system pages (404, 500, search-empty) — keyed by a discriminator, no slug. | | Default plugins | composed by createWWWConfig() | seoPlugin, imageHashPlugin, translator, mcpPlugin. Tune via defaultPluginsConfigs. | | Fields | /fields | link, linkGroup (with disableLabel / appearances / localized / relationTo / extraFields), slugField, appearanceOptions. | | Access | /access | anyone, authenticated, authenticatedOrPublished. | | Caching | Caching | Cached query getters wrap @pro-laico/payload-revalidate finders; tag-based invalidation via revalidatePlugin(); seedPayloadCache singleton seed. | | Metadata | /metadata | buildArticleLd, buildBreadcrumbsLd, buildOrganizationLd, buildWebSiteLd, buildProductLd, buildRootJsonLd; slug transforms; queryDocBySlug, queryDocByID, queryGlobal, queryDocs, queryAllDocs, queryAllLocaleSlugs; seedPayloadCache, tagsFor. | | Next.js page renderers | /render-pages | createCollectionPageExports, createRootLayoutExports; default render components PagesPage, PostsPage, HeaderPage, FooterPage, RootJsonLd. | | Sitemap | /sitemap | createSitemapFromCollections (Next.js file-convention helper). | | Plugin re-exports | /imagehash, /translator | Drop-in for hosts that don't want to import the sibling packages directly. |

Quick start

// payload.config.ts
import { buildConfig } from 'payload'
import { createWWWConfig } from '@justanarthur/payload-www/config'
import { blocks } from '@/components/blocks'
import { plugins } from '@/plugins'

const { withWWWConfig } = createWWWConfig()

export default buildConfig(withWWWConfig({
  blocks,                                  // Page block set
  plugins,                                 // host plugins appended after the lib's defaults
  collections: (defaults) => [
    ...defaults,
    Media,
    Users
  ],
  globals: (defaults) => [
    ...defaults.map(g => g.slug === 'header'
      ? { ...g, custom: { [payloadWwwName]: { path: '@/components/Header/Component#Header' } }, fields: headerFields }
      : g.slug === 'footer'
        ? { ...g, custom: { [payloadWwwName]: { path: '@/components/Footer/Component#Footer' } }, fields: footerFields }
        : g),
    createStaticGlobal(),
    createMessagesGlobal()
  ],
  defaultPluginsConfigs: {                 // tune the lib's default plugin set
    seo:        (d) => ({ ...d, collections: ['pages', 'posts'], openaiApiKey: process.env.OPENAI_API_KEY }),
    imageHash:  (d) => ({ ...d, algorithm: 'lqip-modern' }),
    translator: (d) => ({ ...d, autoTranslate: true, collections: ['pages', 'posts'], globals: ['header', 'footer', 'messages'] })
  },
  localization: { /* ... */ },
  db: postgresAdapter({ /* ... */ }),
  // ...rest of Payload config
}))

createWWWConfig() takes no arguments. You tune everything via the WWWInputConfig passed to withWWWConfig. The composer reads its own package name from package.json and uses it as the custom.<packageName> key — that's how the block / collection / global custom.path entries get threaded into Payload's import map.

The defaultPluginsConfigs map lets you keep the defaults (seoPlugin, imageHashPlugin, translator, mcpPlugin) and tweak their constructor args without re-importing them. Pass (defaults) => … to merge or … to replace.

Page exports (Next.js App Router)

// app/(frontend)/[locale]/layout.tsx
import { createRootLayoutExports } from '@justanarthur/payload-www/render-pages'
import { importMap } from '@/app/(payload)/admin/importMap'
import { routing } from '@/i18n/routing'
import config from '@payload-config'
import { getServerSideURL } from '@/lib/utils/getURL'

const { default: RootLayout, generateStaticParams } = createRootLayoutExports(
  { config, importMap, routing },
  { getServerSideURL }
)

export default RootLayout
export { generateStaticParams }
// app/(frontend)/[locale]/[[...slug]]/page.tsx — Pages home + catch-all
import { createCollectionPageExports } from '@justanarthur/payload-www/render-pages'
import config from '@payload-config'
import { importMap } from '@/app/(payload)/admin/importMap'
import { routing } from '@/i18n/routing'
import { getServerSideURL } from '@/lib/utils/getURL'

const {
  default: Page,
  generateMetadata,
  generateStaticParams,
  generateSitemap
} = createCollectionPageExports(
  { config, importMap, routing, slugShape: 'catch-all' },
  { getServerSideURL }
)

export default Page
export { generateMetadata, generateStaticParams, generateSitemap }

// the doc lookup blocks so unknown slugs can return a real 404
export const instant = false

The page looks up the document before it renders anything, so a missing slug returns HTTP 404 rather than a streamed not-found body with status 200. Slugs from generateStaticParams prerender. Next renders any other slug on its first request, and blocks until the lookup is done. export const instant = false tells Next's dev-time instant validation that the route blocks on purpose. With partialPrefetching: true, Next streams the layout shell for slugs it did not prerender, so those slugs still get a soft 404.

prerenderLocales limits which locales' documents generateStaticParams prerenders at build; the rest render on their first request and are cached from there. A large multilingual collection can prerender only its main locale, e.g. prerenderLocales: ['en'] for posts, to keep the build short. It defaults to every locale.

slugShape is 'single' (default) or 'catch-all'. Use 'catch-all' if your route segment is [[...slug]] (Pages-style), 'single' for [slug] (Posts-style — file convention).

// app/(frontend)/[locale]/posts/[slug]/page.tsx
const { default: PostPage, generateMetadata, generateStaticParams } = createCollectionPageExports(
  { config, importMap, routing, slug: 'posts', slugShape: 'single' },
  { getServerSideURL }
)

createCollectionPageExports(args, deps) — args

| arg | type | default | notes | |---|---|---|---| | config | Promise<SanitizedConfig> | required | the host's payload.config.ts | | importMap | ImportMap | required | the host's app/(payload)/admin/importMap | | routing | RoutingConfig | required | { locales, defaultLocale, localePrefix, labels? } from next-intl/routing's defineRouting | | slug | string | 'pages' | collection slug to query | | slugShape | 'single' \| 'catch-all' | 'single' | drives generateStaticParams + slug segmentation | | depth | number | 2 | relationship / upload hops populated on the rendered document. 0 hands every media and relationship field to the render component as a bare id. Enumeration (generateStaticParams, generateSitemap) stays at 0 regardless. | | prerenderLocales | string[] | every served locale | locales whose documents generateStaticParams prerenders; intersected with the served locales | | resolveLocales | ResolveLocales | — | narrows routing to the locales this deployment serves, read at runtime. See Runtime locales. |

createCollectionPageExports(args, deps) — deps

| dep | type | notes | |---|---|---| | getServerSideURL | () => string | host's absolute-URL helper | | pagePathPrefix | string \| Record<locale, string> | URL segment the collection is mounted under, used for generateSitemap and for canonical/hreflang URLs. Pass a record to localize the segment ({ en: 'posts', sk: 'prispevky' }); locales absent from it fall back to the default locale. The sitemap index route always uses the default locale's segment. | | fallback | ReactNode | rendered while the found document streams. Defaults to nothing. The lookup itself never streams, so a missing document still returns a 404. |

What you get back

{
  default: Page,           // the page component
  generateMetadata,        // Next.js MetadataRoute hook
  generateStaticParams,    // Next.js static-params hook
  generateSitemap          // MetadataRoute.Sitemap producer for /sitemap.ts
}

createRootLayoutExports(args, deps)

| arg | type | notes | |---|---|---| | config | Promise<SanitizedConfig> | required | | importMap | ImportMap | required | | routing | RoutingConfig | required | | depth | number | relationship / upload hops populated on the header + footer globals. Defaults to 2. | | resolveLocales | ResolveLocales | same as on createCollectionPageExports; pass the same resolver to both |

| dep | type | notes | |---|---|---| | getServerSideURL | () => string | if provided, the SEO plugin's RootJsonLd (Organization / WebSite JSON-LD) is auto-injected as the first child of <html> | | providers | (args) => ReactNode | wraps {children} between the rendered Header and Footer | | htmlAttrs | (locale) => HTMLAttributes<HTMLHtmlElement> | extra <html> attributes per locale (defaults: lang={locale}, suppressHydrationWarning) |

The layout reads header + footer globals in parallel and wraps the children in <NextIntlClientProvider>. Locale is pulled from the [locale] route segment and validated against routing.locales — unknown locales trigger notFound().

Render modules (the page's collection component, the header and footer globals) receive locale and the routing the request is served with as props, next to data, config and importMap.

Runtime locales (per deployment)

One codebase deployed per client, with one database each, has to give Payload the same localization everywhere: the superset of every locale any client serves. Each deployment then serves a subset with its own default, stored in its database. resolveLocales narrows the static routing to that subset at runtime:

type RuntimeLocales = { locales?: readonly string[] | null; defaultLocale?: string | null }
type ResolveLocales = (routing: RoutingConfig) => Promise<RuntimeLocales | null | undefined>
// i18n/locales.ts
import { localesFromGlobal } from '@justanarthur/payload-www/render-pages'

// reads `enabledLocales` + `defaultLocale` (unlocalized fields) off the `site` global
export const resolveLocales = localesFromGlobal({ globalSlug: 'site' })
// app/(frontend)/[locale]/layout.tsx
createRootLayoutExports({ config, importMap, routing, resolveLocales }, { getServerSideURL })

// app/(frontend)/[locale]/[[...slug]]/page.tsx
createCollectionPageExports({ config, importMap, routing, resolveLocales, slugShape: 'catch-all' }, { getServerSideURL })

routing stays the superset; resolveLocales narrows it on every render. With it:

  • a locale outside the served set 404s in the layout and the page, and gets no metadata;
  • generateStaticParams (layout and page) lists served locales only; prerenderLocales is intersected with them;
  • hreflang alternates, canonical URLs and generateSitemap cover served locales only, and the served default gets no prefix under localePrefix: 'as-needed' and is the x-default;
  • the sitemap enumerates documents in the served default locale.

The result is sanitized by narrowRouting(routing, runtime): locales routing doesn't know are dropped, an unset or disabled default falls back to routing.defaultLocale when served, else to the first served locale, and an empty result keeps the full routing, so a fresh database with no settings saved still serves every locale. Without resolveLocales nothing changes.

The resolver runs on every render, so it must be cached. localesFromGlobal({ globalSlug, localesField?, defaultLocaleField? }) reads through queryGlobal, so it is 'use cache'd and tagged like any global, and revalidatePlugin() refreshes it when the global is saved. A hand-written resolver should be a 'use cache' function with a tag its source busts. resolveRouting(routing, resolveLocales) returns the narrowed routing for host code that needs it too (a locale switcher, getRequestConfig).

The lib doesn't own the host's middleware: next-intl's createMiddleware(routing) still decides which locale an unprefixed URL is served in and which prefixes it redirects away. A host whose default differs per deployment builds the middleware from the narrowed routing (e.g. createMiddleware(narrowRouting(routing, await readLocales()))), with a cache of its own, since 'use cache' doesn't apply there.

Links

import { linkHref } from '@justanarthur/payload-www/render-pages'

linkHref(link, { locale, routing, pagePathPrefixes: { posts: 'posts' } })

linkHref turns a link() field value into an href. An internal link resolves to the referenced document's path in locale, prefixed per routing (no prefix for the default locale under as-needed) and nested per its slug; pagePathPrefixes mounts a collection under its URL segment. Custom URLs come back as typed. Render modules get locale and routing as props, so a host header passes them straight through. The default HeaderPage / FooterPage use it. buildLocalizedPath(locale, pagePathPrefix, slug, { routing }) is the same path builder for anything that isn't a link() value.

Sitemap

// app/(frontend)/sitemap.ts
import { createSitemapFromCollections } from '@justanarthur/payload-www/sitemap'
import config from '@payload-config'
import { getServerSideURL } from '@/lib/utils/getURL'

export default createSitemapFromCollections({
  getServerSideURL,
  pagePathPrefix: ''                    // empty for root-mounted (Pages)
  // pagePathPrefix: '/posts'           // use this for the Posts catch-all
}, {
  getServerSideURL,
  pagePathPrefix: '/posts'
})

createSitemapFromCollections(...args) takes one or more deps-shaped objects (the same shape createCollectionPageExports's deps accept) and returns a Next.js MetadataRoute.Sitemap-compatible function. Mount one per collection under a sub-route, or a single call for the root.

The lib reads through @pro-laico/payload-revalidate's cached finders, so registering revalidatePlugin() in payload.config.ts makes every collection / global save revalidate its tags automatically — no lib-side hooks to wire. Edits refresh the sitemap without manual rebuilds.

Static pages (404 / 500 / system)

There's no dedicated createStaticPageExports (yet). System pages render via a normal createCollectionPageExports({ slug: 'static-pages', slugShape: 'single' }) mount, addressed by a discriminator instead of a slug:

// app/(frontend)/[locale]/not-found.tsx
const { default: NotFound } = createCollectionPageExports(
  { config, importMap, routing, slug: 'static-pages', slugShape: 'single' },
  { getServerSideURL }
)

export default NotFound

The static-pages collection is built via createWWWCollectionGlobal({...}, { slug: 'static-pages', renderPath: '@/components/StaticPage/Component#StaticPage', isDraft: false }). Editors pick a kind ('not-found', 'server-error', 'search-empty', 'offline'), populate the blocks tab with the same block set you passed to createWWWConfig, and the host's not-found / server-error route renders the row. populatePublishedAt is wired automatically; revalidation comes from the host's revalidatePlugin() registration.

Collections

import {
  createWWWCollectionGlobal,         // generic factory (used internally for static-pages)
  queryDoc                           // server-side helper used by renderers
} from '@justanarthur/payload-www/collections'

createWWWCollectionGlobal(fields, { slug, renderPath, isGlobalConfig?, isDraft?, useAsTitle? }):

| arg | type | notes | |---|---|---| | fields | Field[] | the collection's field set (the factory adds slug, publishedAt, access, the populatePublishedAt beforeChange) | | slug | string | collection slug | | renderPath | string | import-map path to the render component ('@/components/Foo/Component#Foo') | | isGlobalConfig | boolean | true for globals, false (default) for collections | | isDraft | boolean | true (default) enables Payload's drafts + autosave; false for system pages | | useAsTitle | string | the admin title, and the field the slug is generated from (see slugField) | | nestedSlugs | boolean | _ in the slug nests the url; on for Pages, whose route is a catch-all |

The factory wires custom[packageName] = { path: renderPath }, access (create/update/delete require auth, read is anyone or authenticatedOrPublished depending on isDraft), and populatePublishedAt (beforeChange). It does not install revalidation hooks — those live in the host's revalidatePlugin() registration (see Caching below).

Caching

The lib's cached query layer (queryDocBySlug, queryGlobal, queryDocs, queryAllDocs, queryAllLocaleSlugs, queryDocByID) wraps @pro-laico/payload-revalidate's finders in 'use cache' + cacheLife('weeks') scopes. Setup:

  1. Install the peer deps at the workspace root (not in the lib's dependencies — file: links in a published package.json break downstream installs):

    bun add @pro-laico/core @pro-laico/payload-revalidate
  2. Register revalidatePlugin() last in payload.config.ts#plugins:

    import { revalidatePlugin } from '@pro-laico/payload-revalidate'
    
    export default buildConfig({
      // ...
      plugins: [...otherPlugins, revalidatePlugin()],
    })
  3. Turn on Next's cacheComponents pipeline in next.config.ts:

    const nextConfig: NextConfig = { cacheComponents: true, /* ... */ }
  4. Use the lib's getters in renderers — they're 'use cache'-wrapped already. No need to re-wrap or hand-tag:

    import { queryDocBySlug, queryGlobal } from '@justanarthur/payload-www/metadata'
    
    const doc = await queryDocBySlug({ collectionSlug: 'pages', slug, locale })
    const header = await queryGlobal({ globalSlug: 'header', locale })
  5. seedPayloadCache({ config }) runs once per process to initialize the pro-laico cache helpers from the host's Payload config. createCollectionPageExports and createRootLayoutExports call it automatically — hosts with custom layouts call it directly:

    import { seedPayloadCache } from '@justanarthur/payload-www/metadata'
    
    seedPayloadCache({ config })
  6. List with queryDocs, never a raw payload.find — a read inside 'use cache' that doesn't go through the finders carries no tags, so nothing the admin does can bust it. queryDocs tags a per-collection content list plus every returned doc and what is populated into it:

    import { queryDocs } from '@justanarthur/payload-www/metadata'
    
    const { docs, totalPages } = await queryDocs({
      collectionSlug: 'posts',
      locale,
      sort: '-publishedAt',
      limit: 10,
      depth: 1
    })

    createWWWConfig declares that content scope on every collection over all its top-level fields, so creating, publishing, unpublishing, deleting or really changing any doc refreshes every listing of its collection — a moved date or category reorders an archive even when that post wasn't on the page. Saves that change nothing and draft saves leave it alone. For a narrower listing, declare a scope through defaultPluginsConfigs.revalidate and pass it as list.

The cached profile is cacheLife('weeks') — Next 16's built-in long-tail profile (5 m stale, 1 w revalidate, 30 d expire). No custom cacheLife config entry needed. Invalidations are purely tag-based: revalidatePlugin() fires the same tags the finders emit, so every save / delete / publish of a Payload doc revalidates the cached render without manual rebuilds.

Fields

import { link, linkGroup, appearanceOptions, slugField } from '@justanarthur/payload-www/fields'

// link({ extraFields: [...] })        — append host fields (description, navHover, …)
// linkGroup({ appearances: ['default', 'outline'] })
// slugField({ useAsTitle: 'title', nested: true })   — slug that follows the title until unlocked
// appearanceOptions                    — for selects that should match link `appearances`

link({ extraFields }) is the extension point for host-specific nav-link shapes. The lib's createHeaderGlobal / createFooterGlobal accept navColumnLinkFields / navItemLinkFields that are forwarded into their navColumn / navItem blocks.

slugField({ useAsTitle, nested }) is a localized, unique slug. With useAsTitle it pairs the slug with a hidden, localized slugLock checkbox and renders an admin field with a Lock / Unlock button:

  • Locked (new docs start here): the slug follows the title as you type, read-only.
  • Unlocked: you type the slug yourself. Diacritics are transliterated (Prenájom → prenajom).
  • Nested (nested: true, used by Pages): / nests like _, so about/team saves as about_team and serves /about/team. A title that spells its path with spaced slashes, Products / Rental, generates products_rental; any other title replaces only the last segment, so a nested page stays under its parent. Delivery/Receiving or 24/7 stay one segment. Flat slugs (posts, categories) never nest.
  • A lock that was never stored (a new doc, a locale written for the first time) turns on only when the slug is the one the title produces. The index page's '' and seeded slugs stay unlocked, and valid slugs are never rewritten, so existing URLs stay put.

formatSlug, formatSlugSegment, generateSlug(title, parent?), slugFromTitle and slugParent(slug) are the same helpers, exported for migrations. Adding slugLock to an existing collection needs a migration for the new column. Leave existing docs unlocked so their live URLs don't move.

Access

import { anyone, authenticated, authenticatedOrPublished } from '@justanarthur/payload-www/access'
  • anyone — always true.
  • authenticated — true when the request has a user.
  • authenticatedOrPublished — true when authenticated or the doc is _status: 'published'.

Metadata

import {
  buildArticleLd,           // Article JSON-LD
  buildBreadcrumbsLd,       // BreadcrumbList JSON-LD
  buildOrganizationLd,
  buildWebSiteLd,
  buildProductLd,
  buildRootJsonLd,          // combined Organization + WebSite + Product (used by RootJsonLd)
  queryDocBySlug,           // cached collection fetch
  queryDocByID,             // cached collection fetch by id
  queryGlobal,              // cached global fetch
  queryDocs,                // cached, tagged listing (where / sort / limit / page)
  queryAllDocs,             // for generateStaticParams
  queryAllLocaleSlugs,      // for hreflang alternates
  seedPayloadCache,         // one-time seed for the pro-laico cache helpers
  tagsFor,                  // build cache tags by hand (passthrough of @pro-laico/payload-revalidate)
  paramsSlugToSlug,         // turn [locale]/[[...slug]] params → stored slug
  slugToParamsSlug          // turn stored slug → params for generateStaticParams
} from '@justanarthur/payload-www/metadata'

Blocks

import { RenderBlocks } from '@justanarthur/payload-www/blocks'

export function PageBody({ blocks }) {
  return <RenderBlocks blocks={blocks} />
}

RenderBlocks reads each block's custom[packageName].path from Payload and dynamically imports the matching component from the host's importMap.

Plugin re-exports

import { imageHashPlugin } from '@justanarthur/payload-www/imagehash'
import { translator }      from '@justanarthur/payload-www/translator'
import { mcpPlugin }       from '@justanarthur/payload-www/mcp'
import { seoPlugin }       from '@justanarthur/payload-plugin-seo'          // no re-export here, import directly

Use these if you want to compose the default plugin set manually outside createWWWConfig. Full plugin docs:

Public subpath exports

The package's package.json#exports map:

| Subpath | What's there | |---|---| | @justanarthur/payload-www/config | createWWWConfig, WWWConfigApi, WWWInputConfig | | @justanarthur/payload-www/render-pages | createCollectionPageExports, createRootLayoutExports, PagesPage, PostsPage, HeaderPage, FooterPage, RootJsonLd, localesFromGlobal, resolveRouting, narrowRouting, linkHref, buildLocalizedPath + types (ResolveLocales, RuntimeLocales, RoutingConfig, …) | | @justanarthur/payload-www/pages | subset of /render-pages (no createRootLayoutExports, no PostsPage, no RootJsonLd) | | @justanarthur/payload-www/sitemap | createSitemapFromCollections | | @justanarthur/payload-www/blocks | RenderBlocks, RenderBlocksProps | | @justanarthur/payload-www/collections | createWWWCollectionGlobal, queryDoc | | @justanarthur/payload-www/fields | link, linkGroup, appearanceOptions, slugField, formatSlug, formatSlugSegment, generateSlug, slugFromTitle, slugParent, SLUG_LOCK_FIELD, LinkAppearances, LinkOptions, SlugFieldOptions | | @justanarthur/payload-www/fields-client | SlugField (the admin component slugField mounts) | | @justanarthur/payload-www/access | anyone, authenticated, authenticatedOrPublished | | @justanarthur/payload-www/metadata | buildArticleLd, buildBreadcrumbsLd, buildOrganizationLd, buildWebSiteLd, buildProductLd, buildRootJsonLd, queryDocBySlug, queryDocByID, queryGlobal, queryDocs, queryAllDocs, queryAllLocaleSlugs, seedPayloadCache, tagsFor, paramsSlugToSlug, slugToParamsSlug + types | | @justanarthur/payload-www/utils | generateImportName, getFromImportMap | | @justanarthur/payload-www/imagehash | imageHashPlugin, BlurhashPluginOptions (re-export of @justanarthur/payload-imagehash-plugin) | | @justanarthur/payload-www/translator | translator (re-export of @justanarthur/payload-plugin-translator) | | @justanarthur/payload-www/mcp | mcpPlugin, MCPPluginConfig (re-export of @payloadcms/plugin-mcp) | | @justanarthur/payload-www/import-map-provider | setImportMapProvider, getImportMap (stubs in the current build — reserved for future use) |

There is no root import (.) and no /server, /with-www-config, /globals, /hooks, /render-utils, /render-components, /render-metadata, /components, /seed, /test, /data-seed, /data-test, /data-collections in the published exports — paths the previous README advertised that will fail at import time.

What the previous README got wrong

For agents migrating from older docs:

  • createWWWConfig({ locales, blocks }) → now createWWWConfig() (no args). Locales come from your Payload localization config; blocks come from the WWWInputConfig.blocks field.
  • defaultPlugins callback → now defaultPluginsConfigs: { seo, imageHash, translator, mcp } map on WWWInputConfig.
  • createRevalidateCollectionHook({ collectionSlug, urlPathPrefix, … }) (older CHANGELOG) and createRevalidateCollectionGlobalHook() (no args, 1.0.0) → both removed. Caching now goes through @pro-laico/payload-revalidate: install it as a peer dep, register revalidatePlugin() last in payload.config.ts#plugins, and the lib's cached query getters (queryDocBySlug, queryGlobal, queryAllDocs, queryAllLocaleSlugs, queryDocByID) wrap its finders so the revalidation tags line up automatically. See Caching above.
  • @justanarthur/payload-www/cache-keys subpath (older [Unreleased]) → removed. There is no in-tree tag vocabulary to export — tags come from @pro-laico/payload-revalidate's finders.
  • defaultPluginsConfigs.revalidate slot (older [Unreleased]) → removed. The in-lib revalidation composer is gone; revalidation is the host's responsibility via revalidatePlugin().
  • createSitemapFile → not implemented. The current export is createSitemapFromCollections, used per-collection from your app/(frontend)/sitemap.ts.
  • createPreviewHandler, LocaleSwitcher, LivePreviewListener, PageShowcase, HomePage, createStaticPageExports, createStaticPagesCollection → none of these exist as exports in the current build. System pages render through a normal createCollectionPageExports mount (see Static pages above).
  • Pages / Posts slug is localized: true — slug lives in <collection>_locales. There is no localized or nested option on slugField; nesting is built in.

Building

bun install
bun run build      # bunup + scripts/strip-createRequire.mjs
bun run typecheck  # tsc --noEmit
bun run test       # vitest run

The lib uses the same bunup + src/exports/* shim pattern as the sibling plugins. One shim file per subpath under src/exports/, each re-exporting from the implementation.

Licence

MIT