Adds a meta sidebar group to selected collections and registers a global-seo global for site-wide defaults. Optional generator hooks fill in titles, canonical URLs, hreflang alternates and OG images when fields are empty. Helpers resolve final metadata, build Next.js Metadata objects and produce JSON-LD. Requires Payload 3.37 or later.
A plugin for Payload CMS that provides advanced SEO features, including automatic generation of meta tags, sitemaps, and structured data based on your content.
Installation
npm i @foundrykit/advanced-seo-plugin3
Stars
0
Forks
1
Open Issues
6 months ago
Last Updated
0
Weekly Downloads
v0.2.3
npm Version
349.4 KB
Unpacked Size
1
Dependencies
Trends appear after 7 days of data. Daily snapshots have not started yet.
Trends appear after 7 days of data. Daily snapshots have not started yet.
Each point is npm's rolling 7-day download count.
Health Score
Composite score based on GitHub activity, stars, npm downloads, publish recency, dependency count, and package size.
Badge for your README
[](https://payloaddirectory.dev/plugins/1167498539-root)<a href="https://payloaddirectory.dev/plugins/1167498539-root"><img src="https://payloaddirectory.dev/badge/1167498539-root.svg" alt="Payload Directory"></a>README
A comprehensive SEO plugin for Payload CMS v3. It injects a meta sidebar group into configured collections, registers a global-seo global for site defaults, and exports composable utilities for title/URL generation, hreflang alternates, OG image generation, and JSON-LD structured data.
This README documents the plugin features and shows how to use them in common scenarios (Payload config, Next.js App Router, and small helper examples).
meta.title, meta.url (canonical), meta.image, meta.description, meta.noindex, meta.nofollow, meta.alternates (hreflang), meta.jsonLdglobal-seo Global for site defaults and togglesgenerateTitle and generateURL (run on save / beforeChange)generateAlternateURL (run on read to auto-fill hreflang alternates)generateOgImage (run on change/publish to create an OG image when missing)resolveMeta — resolve final title/description/image/url/robots/alternates/jsonLdtoNextMetadata — build Next.js Metadata objects from docs/globalswebPageJsonLd, articleJsonLd, productJsonLd, organizationJsonLd, breadcrumbJsonLd, buildJsonLdpnpm add @foundrykit/advanced-seo-plugin
# or
npm install @foundrykit/advanced-seo-plugin
Add the plugin to your payload.config.ts and enable it for the collections you want to enhance.
// payload.config.ts
import { buildConfig } from 'payload'
import { advancedSeoPlugin } from '@foundrykit/advanced-seo-plugin'
export default buildConfig({
plugins: [
advancedSeoPlugin({
collections: { posts: true, pages: true },
// optional generators shown below
}),
],
})
After registering the plugin you will see a new SEO (meta) group in the admin sidebar for each configured collection. Editors can set values manually or rely on the auto-generation hooks.
All options are exported typesafe. The most-used options are shown below with examples.
collections: Partial<Record<CollectionSlug, true>> — which collections receive the meta groupdisabled?: boolean — set to true to disable behaviour while preserving schemalocales?: string[] — BCP47 locale codes used by generateAlternateURLmediaCollection?: string[] — media collection slug(s) used by the image picker (default ['media'])Hook generators (each may return the expected value or null to skip):
generateTitle(args) => string | null | Promise<string | null>
beforeChange when meta.title is empty. Use to apply site title templates.Example:
advancedSeoPlugin({
collections: { posts: true },
generateTitle: ({ data }) => `${data.title} | My Site`,
})
generateURL(args) => string | null | Promise<string | null>
beforeChange when meta.url is empty. Useful for canonical URL construction.Example:
advancedSeoPlugin({
collections: { posts: true },
generateURL: ({ data }) => `https://example.com/blog/${data.slug}`,
})
generateAlternateURL({ collectionSlug, doc, locale }) => string | null | Promise<string | null>
afterRead once per configured locale when the global-seo global has autoGenerateAlternates enabled and the doc has no manual alternates entered.null to skip creating an alternate for a specific locale.Example:
advancedSeoPlugin({
collections: { posts: true },
locales: ['en', 'fr', 'de'],
generateAlternateURL: ({ doc, locale }) => `https://example.com/${locale}/blog/${doc.slug}`,
})
generateOgImage({ collectionSlug, doc }) => Buffer | null | Promise<Buffer | null>
afterChange only when the document has no meta.image and the global-seo global has enableOgGenerator enabled.meta.image.Example using a hypothetical renderer:
advancedSeoPlugin({
collections: { posts: true },
generateOgImage: async ({ doc }) => {
// renderOgImageBuffer should return a Buffer (PNG/JPEG)
return await renderOgImageBuffer({ title: doc.meta?.title ?? doc.title })
},
})
Notes:
generateTitle and generateURL run in parallel on beforeChange.The plugin registers a global-seo global. Open Admin → Globals → Global SEO to configure:
robots directive (e.g. noindex, nofollow) — used when per-doc checkboxes are not setautoGenerateAlternates toggle — enables generateAlternateURL behaviourenableOgGenerator toggle — enables generateOgImage behaviourUse payload.findGlobal({ slug: 'global-seo' }) to read global values in server code.
resolveMeta(doc, { globals? })
{ title, description, image, url, noindex, nofollow, alternates, jsonLd }.title: meta.title → globals.defaultTitle → doc.title → ''description: meta.description → globals.defaultDescription → doc.description → ''image: meta.image → globals.defaultImage → null (always returned as URL or null)url: meta.url → ''noindex / nofollow: meta.{noindex,nofollow} → false (global robots used only when neither checkbox is set)alternates: meta.alternates (auto-generated alternates merged by afterRead)jsonLd: meta.jsonLd → nullExample:
import { resolveMeta } from '@foundrykit/advanced-seo-plugin'
const globals = await payload.findGlobal({ slug: 'global-seo' })
const resolved = resolveMeta(doc, { globals })
// resolved.title, resolved.image, resolved.alternates, etc.
toNextMetadata(doc, globals?, options?)
Metadata object for the App Router. Recommended for pages in app/.Example (app router):
import { toNextMetadata } from '@foundrykit/advanced-seo-plugin/next'
export async function generateMetadata({ params }) {
const doc = await payload.findByID({ collection: 'posts', id: params.id })
const globals = await payload.findGlobal({ slug: 'global-seo' })
return toNextMetadata(doc, globals, { openGraphType: 'article' })
}
What toNextMetadata sets for you: title, description, robots, alternates.canonical, alternates.languages, openGraph, and twitter with sensible defaults.
Tree-shakeable builders help create structured data objects you can inject into the page head.
import {
webPageJsonLd,
articleJsonLd,
productJsonLd,
organizationJsonLd,
breadcrumbJsonLd,
buildJsonLd,
} from '@foundrykit/advanced-seo-plugin'
Examples:
const pageLd = webPageJsonLd({ name: 'Home', url: 'https://example.com', description: 'Welcome' })
const articleLd = articleJsonLd({
headline: 'My article',
url: 'https://example.com/article',
author: { name: 'Jane Smith' },
publisher: { name: 'Acme', logo: 'https://example.com/logo.png' },
datePublished: '2024-01-01',
})
const merged = buildJsonLd(articleLd, { headline: 'Overridden headline' })
buildJsonLd(base, overrides) merges values while preserving @context and @type from the base template.
All UI pieces are exported so you can compose custom admin layouts instead of using the default meta group.
import {
OverviewField,
MetaTitleField,
MetaUrlField,
MetaImageField,
MetaDescriptionField,
MetaNoindexField,
MetaNofollowField,
AlternatesField,
PreviewField,
structuredDataRow,
} from '@foundrykit/advanced-seo-plugin'
Common elements:
OverviewField — character counts and warnings (title ≤ 60, description ≤ 160)PreviewField — Google SERP-style previewAlternatesField — array of { locale, url }structuredDataRow — JSON editor for meta.jsonLdYou can import these and include them in your own collection fields if you prefer a customized layout.
global-seo.autoGenerateAlternates is true and no manual alternates exist.global-seo.enableOgGenerator is true and meta.image is empty. The plugin writes the created media ID back to meta.image.To develop locally (the repo contains a dev Payload app under dev/):
# Start the dev MongoDB
pnpm dev:db:up
# Start the dev server (Next.js + Payload dev app)
pnpm dev
# Run tests
pnpm test:int
# Build the plugin
pnpm build
If you plan to extend generators or add new JSON-LD templates, follow the existing naming and typing conventions and add unit tests for the generator hooks.
MIT
payload seo analyzer
Offline on-page SEO analysis, admin dashboard, sitemaps and JSON-LD for Payload 3 and Next.js
600/wk16v3
payload plugin seo jsonld
JSON-LD builders for Payload v3 shops: Product, breadcrumbs, item lists, website and organization
650/wk1v3
payload plugin sitemap
Generate sitemap.xml files from Payload content with a background job and store them in any storage adapter
640/wk5v4
sitemap
Chunked, lazily cached XML sitemaps with hook-driven invalidation and robots.txt helpers
6123/wk15v3
seo
Locale-aware SEO fields, metadata resolution, robots, exact redirects and sitemap helpers for Payload
530/wk0v4
seo
Manage SEO metadata for Payload collections and globals from within the admin panel
100169.7k/wk45.4kv3