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

@foundrykit/advanced-seo-plugin

v0.2.0

Published

A plugin for Payload CMS that provides advanced SEO features, including automatic generation of meta tags, sitemaps, and structured data based on your content.

Readme

advanced-seo-plugin

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).

What this plugin provides

  • Meta sidebar group (per-document)
    • meta.title, meta.url (canonical), meta.image, meta.description, meta.noindex, meta.nofollow, meta.alternates (hreflang), meta.jsonLd
  • global-seo Global for site defaults and toggles
  • Auto-generation hooks
    • generateTitle 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)
  • Helpers and builders
    • resolveMeta — resolve final title/description/image/url/robots/alternates/jsonLd
    • toNextMetadata — build Next.js Metadata objects from docs/globals
    • JSON-LD builders: webPageJsonLd, articleJsonLd, productJsonLd, organizationJsonLd, breadcrumbJsonLd, buildJsonLd
    • Field factories / UI components: overview, SERP preview, upload field, alternates, etc.
  • Fully typed (TypeScript) exports for config and return values

Installation

pnpm add advanced-seo-plugin
# or
npm install advanced-seo-plugin

Quick start (Payload config)

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 '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.

Plugin options and usage

All options are exported typesafe. The most-used options are shown below with examples.

  • collections: Partial<Record<CollectionSlug, true>> — which collections receive the meta group
  • disabled?: boolean — set to true to disable behaviour while preserving schema
  • locales?: string[] — BCP47 locale codes used by generateAlternateURL
  • mediaCollection?: 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>
    • Runs on 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>
    • Runs on 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>
    • Runs on afterRead once per configured locale when the global-seo global has autoGenerateAlternates enabled and the doc has no manual alternates entered.
    • Return 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>
    • Runs on afterChange only when the document has no meta.image and the global-seo global has enableOgGenerator enabled.
    • Return a PNG/JPEG buffer; the plugin saves it to the configured media collection and writes the media id back to 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:

  • Generators only run when a field is empty — manual editor input always wins.
  • generateTitle and generateURL run in parallel on beforeChange.

Global SEO (admin global)

The plugin registers a global-seo global. Open Admin → Globals → Global SEO to configure:

  • Site name and Twitter handle
  • Default title, description, and default image
  • Site-wide robots directive (e.g. noindex, nofollow) — used when per-doc checkboxes are not set
  • autoGenerateAlternates toggle — enables generateAlternateURL behaviour
  • enableOgGenerator toggle — enables generateOgImage behaviour
  • JSON-LD templates you can reference from code

Use payload.findGlobal({ slug: 'global-seo' }) to read global values in server code.

Helpers

  • resolveMeta(doc, { globals? })
    • Returns resolved values: { title, description, image, url, noindex, nofollow, alternates, jsonLd }.
    • Resolution order (highest → lowest):
      • title: meta.titleglobals.defaultTitledoc.title''
      • description: meta.descriptionglobals.defaultDescriptiondoc.description''
      • image: meta.imageglobals.defaultImagenull (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.jsonLdnull

Example:

import { resolveMeta } from '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?)
    • Build a Next.js Metadata object for the App Router. Recommended for pages in app/.

Example (app router):

import { toNextMetadata } from '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.

JSON-LD builders

Tree-shakeable builders help create structured data objects you can inject into the page head.

import {
  webPageJsonLd,
  articleJsonLd,
  productJsonLd,
  organizationJsonLd,
  breadcrumbJsonLd,
  buildJsonLd,
} from '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.

Field factories & admin UI components

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 'advanced-seo-plugin'

Common elements:

  • OverviewField — character counts and warnings (title ≤ 60, description ≤ 160)
  • PreviewField — Google SERP-style preview
  • AlternatesField — array of { locale, url }
  • structuredDataRow — JSON editor for meta.jsonLd

You can import these and include them in your own collection fields if you prefer a customized layout.

Examples and edge cases

  • Manual values always win. Generators only populate when the meta field is empty.
  • Auto alternates run only when global-seo.autoGenerateAlternates is true and no manual alternates exist.
  • OG generation runs only when global-seo.enableOgGenerator is true and meta.image is empty. The plugin writes the created media ID back to meta.image.

Development

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

Contributing

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.

License

MIT