obsidian/wiki/payloadcms/plugin-seo.md
2026-05-15 16:26:04 +01:00

4.9 KiB

title aliases tags sources created updated
SEO Plugin
payload-seo
plugin-seo
meta-fields
payloadcms
plugins
seo
meta
admin
raw/plugins__seo.md
2026-05-15 2026-05-15

@payloadcms/plugin-seo adds a managed meta field group (title, description, image) to collections and globals, with auto-generate functions and a real-time search-engine preview.

Installation

pnpm add @payloadcms/plugin-seo

Basic Setup

import { seoPlugin } from '@payloadcms/plugin-seo'

buildConfig({
  plugins: [
    seoPlugin({
      collections: ['pages'],
      uploadsCollection: 'media',
      generateTitle: ({ doc }) => `Website.com — ${doc.title}`,
      generateDescription: ({ doc }) => doc.excerpt,
    }),
  ],
})

Options Reference

Option Type Description
collections string[] Collection slugs to enable SEO on
globals string[] Global slugs to enable SEO on
uploadsCollection string Upload collection slug for the image subfield
tabbedUI boolean Append an SEO tab (default: false)
fields fn Extend or override the default meta fields
generateTitle fn Return a custom meta title from doc data
generateDescription fn Return a custom meta description from doc data
generateImage fn Return a custom meta image from doc data
generateURL fn Return the canonical URL shown in the preview
interfaceName string Rename the generated TypeScript/GraphQL interface

fields — adding custom fields

seoPlugin({
  fields: ({ defaultFields }) => [
    ...defaultFields,
    { name: 'ogTitle', type: 'text' },
  ],
})

Generate function arguments

All generate* functions receive the same argument object:

Arg Description
doc Current document data
req Payload request (user, payload, i18n)
locale Active locale
collectionSlug / globalSlug Slug of the owning collection/global
id Document ID
publishedDoc The published version
hasPublishPermission / hasSavePermission User permission flags

tabbedUI Note

When tabbedUI: true, the plugin appends an SEO tab. If your collection has no existing tabs field as its first field, Payload creates a Content tab automatically.

If you need sidebar/top-level fields alongside tabbedUI, define the first field as type: 'tabs' yourself — don't let Payload auto-create the Content tab.

Direct Field Imports

Import fields individually for fine-grained placement:

import {
  MetaTitleField,
  MetaDescriptionField,
  MetaImageField,
  PreviewField,
  OverviewField,
} from '@payloadcms/plugin-seo/fields'

MetaImageField({ relationTo: 'media', hasGenerateFn: true })
MetaDescriptionField({ hasGenerateFn: true })
MetaTitleField({ hasGenerateFn: true })

PreviewField({
  hasGenerateFn: true,
  titlePath: 'meta.title',
  descriptionPath: 'meta.description',
})

OverviewField({
  titlePath: 'meta.title',
  descriptionPath: 'meta.description',
  imagePath: 'meta.image',
})

You still need the plugin registered in buildConfig to configure the generation functions. Direct imports don't inherit plugin config automatically.

Override character limits via minLength/maxLength on individual field components, or titleOverrides/descriptionOverrides on OverviewField.

TypeScript

import type { GenerateTitle, GenerateDescription, GenerateURL } from '@payloadcms/plugin-seo/types'
import type { Page } from './payload-types'

const generateTitle: GenerateTitle<Page> = async ({ doc }) =>
  `Website.com — ${doc?.title}`

Key Takeaways

  • Plugin adds meta.title, meta.description, meta.image fields to enabled collections/globals
  • "Auto-generate" buttons call your custom generate* functions — great for AI-assisted or rule-based SEO
  • Real-time search-engine preview and character counters help editors write effective meta without leaving the Admin Panel
  • Extend meta fields with og:title, json-ld, or any custom field via the fields option
  • tabbedUI: true wraps fields in a tab — requires the first field to already be a tabs field to avoid an unwanted Content tab auto-creation
  • Direct field imports give placement flexibility but still require the plugin to be registered for generate* functions to work
  • All generate* functions share the same argument shape — use req.payload for DB lookups or third-party AI calls

Sources