--- 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 = 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