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

146 lines
4.9 KiB
Markdown

---
title: "SEO Plugin"
aliases: [payload-seo, plugin-seo, meta-fields]
tags: [payloadcms, plugins, seo, meta, admin]
sources: [raw/plugins__seo.md]
created: 2026-05-15
updated: 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
```bash
pnpm add @payloadcms/plugin-seo
```
## Basic Setup
```ts
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
```ts
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:
```ts
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
```ts
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
## Related
- [[wiki/payloadcms/plugins|Plugins Overview + Official]]
- [[wiki/payloadcms/plugin-search|Search Plugin]]
- [[wiki/payloadcms/plugin-nested-docs|Nested Docs Plugin]]
- [[wiki/payloadcms/collection-config|Collection Config]]
- [[wiki/payloadcms/fields-tabs|Tabs Field]]
- [[wiki/payloadcms/custom-components|Custom Components]]
## Sources
- `raw/plugins__seo.md` — https://payloadcms.com/docs/plugins/seo