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