---
title: "PayloadCMS — Plugins (Overview + Official)"
tags: [payloadcms, tech-patterns]
topic: payloadcms
sources: [plugins__overview.md, plugins__seo.md, plugins__search.md, plugins__form-builder.md, plugins__redirects.md, plugins__nested-docs.md, plugins__multi-tenant.md, plugins__stripe.md, plugins__sentry.md, plugins__import-export.md, plugins__mcp.md]
created: 2026-05-15
updated: 2026-05-15
---

# PayloadCMS — Plugins (Overview + Official)

## Overview

- Plugins are functions: `(incomingConfig: Config) => Config`
- Added to `plugins: []` array in [[wiki/payloadcms/configuration|Configuration]]
- Execute **after** config validation, **before** default-option merging and sanitization
- Can add collections, globals, fields, [[wiki/payloadcms/hooks|Hooks]], endpoints, admin views
- Official plugins live in the [Payload monorepo packages directory](https://github.com/payloadcms/payload/tree/main/packages)
- Community plugins: search `payload-plugin` topic on GitHub

## Example Use Cases

- Sync collection data to HubSpot / CRM on change
- Password-protect specific documents
- Add full e-commerce backend
- Add custom reporting views to admin panel
- Encrypt specific collections
- Integrate upload collections with S3 / Cloudinary
- Add custom GraphQL queries or endpoints

## Example Plugin — addLastModified

Adds a `lastModifiedBy` relationship field to every collection:

```ts
import { Config, Plugin } from 'payload'

export const addLastModified: Plugin = (incomingConfig: Config): Config => {
  const authEnabledCollections = incomingConfig.collections.filter(
    (collection) => Boolean(collection.auth),
  )

  return {
    ...incomingConfig,
    collections: incomingConfig.collections.map((collection) => ({
      ...collection,
      fields: [
        ...collection.fields,
        {
          name: 'lastModifiedBy',
          type: 'relationship',
          relationTo: authEnabledCollections.map(({ slug }) => slug),
          hooks: {
            beforeChange: [
              ({ req }) => ({
                value: req?.user?.id,
                relationTo: req?.user?.collection,
              }),
            ],
          },
          admin: { position: 'sidebar', readOnly: true },
        },
      ],
    })),
  }
}
```

Register it:

```ts
import { buildConfig } from 'payload'
import { addLastModified } from './addLastModified'

export default buildConfig({
  plugins: [addLastModified],
})
```

> See [[wiki/payloadcms/plugins-api|Plugin API]] for the full `definePlugin` advanced API.

## Plugin Quick Reference

| Plugin | Package | Purpose |
|--------|---------|---------|
| SEO | `@payloadcms/plugin-seo` | Meta title/description/image fields + preview |
| Search | `@payloadcms/plugin-search` | Fast indexed search collection |
| Form Builder | `@payloadcms/plugin-form-builder` | Dynamic forms with email + payments |
| Redirects | `@payloadcms/plugin-redirects` | Managed redirect rules |
| Nested Docs | `@payloadcms/plugin-nested-docs` | Parent/child hierarchy + breadcrumbs |
| Multi-Tenant | `@payloadcms/plugin-multi-tenant` | Multi-tenancy with tenant scoping |
| Stripe | `@payloadcms/plugin-stripe` | Stripe payments + two-way sync |
| Sentry | `@payloadcms/plugin-sentry` | Error tracking + performance monitoring |
| Import/Export | `@payloadcms/plugin-import-export` | CSV/JSON import and export |
| MCP | `@payloadcms/plugin-mcp` | Model Context Protocol server for AI |

---

## SEO Plugin

```bash
pnpm add @payloadcms/plugin-seo
```

- Adds `meta` group (`title`, `description`, `image`) to enabled collections/globals
- Auto-generate buttons call your custom `generateTitle` / `generateDescription` / `generateImage` functions
- Live preview renders a SERP snippet with character counters
- Supports `tabbedUI` to place meta in its own tab
- Custom fields can be injected via `fields: ({ defaultFields }) => [...]`

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

seoPlugin({
  collections: ['pages', 'posts'],
  uploadsCollection: 'media',
  generateTitle: ({ doc }) => `MySite — ${doc.title}`,
  generateDescription: ({ doc }) => doc.excerpt,
  generateURL: ({ doc, collectionSlug }) =>
    `https://example.com/${collectionSlug}/${doc.slug}`,
})
```

**Key options:** `collections`, `globals`, `uploadsCollection`, `tabbedUI`, `generateTitle`, `generateDescription`, `generateImage`, `generateURL`, `interfaceName`, `fields`

---

## Search Plugin

```bash
pnpm add @payloadcms/plugin-search
```

- Creates an indexed `search` collection — sync-critical fields only (title, slug, excerpt)
- Auto-creates, updates, deletes search records via `beforeChange` / `afterDelete` [[wiki/payloadcms/hooks|Hooks]]
- First-party alternative to Algolia/Elasticsearch
- `priority` field enables custom result ranking per collection or document
- "Reindex" button in admin list view for re-syncing existing documents

```ts
import { searchPlugin } from '@payloadcms/plugin-search'

searchPlugin({
  collections: ['pages', 'posts'],
  defaultPriorities: { pages: 10, posts: 20 },
  beforeSync: ({ originalDoc, searchDoc }) => ({
    ...searchDoc,
    excerpt: originalDoc?.excerpt ?? '',
  }),
})
```

**Key options:** `collections`, `defaultPriorities`, `searchOverrides`, `beforeSync`, `syncDrafts`, `deleteDrafts`, `skipSync`, `reindexBatchSize`, `localize`

---

## Form Builder Plugin

```bash
pnpm add @payloadcms/plugin-form-builder
```

- Adds `forms` and `form-submissions` collections
- Admin builds form schema on-the-fly; frontend maps over blocks to render UI
- Supports email notifications with dynamic `{{field_name}}` interpolation
- Payment field integrates with any payment processor via `handlePayment` callback
- Upload field requires `uploadCollections` in config; supports multipart or pre-upload flows

```ts
import { formBuilderPlugin } from '@payloadcms/plugin-form-builder'

formBuilderPlugin({
  fields: { text: true, email: true, select: true, payment: false },
  redirectRelationships: ['pages'],
  defaultToEmail: 'team@example.com',
})
```

**Field types available:** text, textarea, select, radio, email, state, country, checkbox, number, message, date, payment, upload

**Gotcha:** `forms` collection is publicly readable by default — override access control if you have authenticated front-end users.

---

## Redirects Plugin

```bash
pnpm add @payloadcms/plugin-redirects
```

- Adds `redirects` collection with `from` (text) and `to` (URL or document relationship) fields
- Plugin only stores rules — you implement redirect logic in your frontend (Next.js middleware, Express, etc.)
- Supports optional `redirectTypes` (`['301', '302']`) and `overrides` for custom fields

```ts
import { redirectsPlugin } from '@payloadcms/plugin-redirects'

redirectsPlugin({
  collections: ['pages'],
  redirectTypes: ['301', '302'],
})
```

---

## Nested Docs Plugin

```bash
pnpm add @payloadcms/plugin-nested-docs
```

- Adds `parent` relationship field (self-referencing within the collection)
- Adds `breadcrumbs` array field populated up the full ancestor tree
- Cascades changes recursively — editing a grandparent updates all descendants
- Supports localization automatically if `localization` is set in [[wiki/payloadcms/configuration|Configuration]]

```ts
import { nestedDocsPlugin } from '@payloadcms/plugin-nested-docs'

nestedDocsPlugin({
  collections: ['pages'],
  generateLabel: (_, doc) => String(doc.title),
  generateURL: (docs) =>
    docs.reduce((url, doc) => `${url}/${String(doc.slug)}`, ''),
})
```

**Key options:** `collections`, `generateLabel`, `generateURL`, `parentFieldSlug`, `breadcrumbsFieldSlug`

---

## Multi-Tenant Plugin

```bash
pnpm add @payloadcms/plugin-multi-tenant
```

- Adds `tenant` relationship field to all specified collections
- Adds tenant selector to admin panel; list views auto-filter by selected tenant
- Supports `isGlobal: true` for one-doc-per-tenant pattern
- `userHasAccessToAllTenants` enables super-admin bypass
- Exports `useTenantSelection` React hook for custom components

```ts
import { multiTenantPlugin } from '@payloadcms/plugin-multi-tenant'

multiTenantPlugin<Config>({
  collections: {
    pages: {},
    navigation: { isGlobal: true },
  },
  tenantsSlug: 'tenants',
})
```

**Gotcha:** By default, deleting a tenant removes all its documents. Set `cleanupAfterTenantDelete: false` to disable. Add strong access control on the `tenants` collection.

---

## Stripe Plugin

```bash
pnpm add @payloadcms/plugin-stripe
```

- Proxies Stripe REST API through Payload access control at `/api/stripe/rest`
- Handles Stripe webhooks at `/api/stripe/webhooks`
- `sync` config sets up two-way sync: creates hooks + webhook handlers automatically
- Adds `stripeID` and `skipSync` fields to synced collections

```ts
import { stripePlugin } from '@payloadcms/plugin-stripe'

stripePlugin({
  stripeSecretKey: process.env.STRIPE_SECRET_KEY,
  stripeWebhooksEndpointSecret: process.env.STRIPE_WEBHOOKS_ENDPOINT_SECRET,
  webhooks: {
    'customer.subscription.updated': ({ event, stripe }) => { /* ... */ },
  },
  sync: [
    {
      collection: 'customers',
      stripeResourceType: 'customers',
      stripeResourceTypeSingular: 'customer',
      fields: [{ fieldPath: 'name', stripeProperty: 'name' }],
    },
  ],
})
```

**Gotcha:** REST proxy (`rest: true`) exposes full Stripe API to authenticated users — disable in production, use `stripeProxy` server-side instead. For Vercel serverless, install `@vercel/functions` for webhook `waitUntil` support.

---

## Sentry Plugin

```bash
pnpm add @payloadcms/plugin-sentry
```

- Requires Sentry Next.js setup first: `npx @sentry/wizard@latest -i nextjs`
- Passes Sentry instance directly into plugin; no DSN config needed in plugin options
- Captures errors ≥ 500 by default; add codes with `captureErrors: [400, 403]`
- Supports `context` callback for enriching events with locale, user, etc.
- For Postgres query traces, inject Sentry-patched `pg` driver into `postgresAdapter`

```ts
import * as Sentry from '@sentry/nextjs'
import { sentryPlugin } from '@payloadcms/plugin-sentry'

sentryPlugin({
  Sentry,
  options: {
    captureErrors: [400, 403],
    context: ({ defaultContext, req }) => ({
      ...defaultContext,
      tags: { locale: req.locale },
    }),
  },
})
```

---

## Import/Export Plugin

```bash
pnpm add @payloadcms/plugin-import-export
```

- Adds `exports` and `imports` upload collections (hidden from nav by default)
- Export formats: CSV (underscore-notation for nested fields) and JSON
- Uses Payload [[wiki/payloadcms/jobs-queue|Jobs Queue]] by default — requires `jobs.autoRun` config
- Import modes: `create`, `update`, `upsert` (with `matchField` for non-ID matching)
- Per-batch hooks (`before` / `after`) for data transformation and logging
- Field-level `custom['plugin-import-export']` config for disabled fields and `beforeExport`/`beforeImport` hooks

```ts
import { importExportPlugin } from '@payloadcms/plugin-import-export'

importExportPlugin({
  collections: [
    { slug: 'users' },
    {
      slug: 'pages',
      export: { format: 'csv', disableDownload: false },
      import: { defaultVersionStatus: 'draft' },
    },
    { slug: 'posts', export: false },
  ],
  exportLimit: 10000,
})
```

**Gotcha:** Users with read access to the exports upload collection can download data bypassing normal access control. Always override `overrideExportCollection` to add access restrictions for sensitive collections. Add `jobs.autoRun` or set `disableJobsQueue: true` per collection for synchronous mode.

---

## MCP Plugin

```bash
pnpm add @payloadcms/plugin-mcp
```

- Adds a Model Context Protocol server at `/api/mcp`
- Adds an `API Keys` collection for managing per-key capabilities
- Exposes Payload collections and globals as MCP tools (`find`, `create`, `update`, `delete`)
- Two-step access: (1) enable in config, (2) allow in admin API key
- Supports custom `tools`, `prompts`, and `resources` with Zod parameter schemas
- Use `select` parameter to limit returned fields and reduce token usage
- Use `overrideResponse` to sanitize sensitive data before it reaches the model

```ts
import { mcpPlugin } from '@payloadcms/plugin-mcp'

mcpPlugin({
  collections: {
    posts: {
      enabled: { find: true, create: true, update: true, delete: false },
      description: 'Published blog posts with title, body, and tags.',
    },
  },
  globals: {
    'site-settings': { enabled: { find: true, update: true } },
  },
})
```

**Connect Claude Code:**
```bash
claude mcp add --transport http Payload http://127.0.0.1:3000/api/mcp \
  --header "Authorization: Bearer MCP-USER-API-KEY"
```

**Gotcha:** Models receive full documents by default — use `select` or `overrideResponse` to strip sensitive/large fields. Only enable the operations a model actually needs. Detect MCP context in hooks via `req.payloadAPI === 'MCP'`.

---

## Gotchas

- Plugins run after config validation but before sanitization — spread existing arrays/objects, never replace
- `forms` collection access is public by default — override for authenticated apps
- Stripe REST proxy must be disabled in production
- Multi-tenant: tenant deletion cascades to documents by default
- Import/Export: jobs runner required unless `disableJobsQueue: true`
- MCP: two-step access (config + API key) — enabling in config alone is not enough
- Sentry: complete Next.js Sentry wizard setup before adding the plugin

## Related

- [[wiki/payloadcms/configuration|Configuration]]
- [[wiki/payloadcms/hooks|Hooks]]
- [[wiki/payloadcms/collections|Collections]]
- [[wiki/payloadcms/jobs-queue|Jobs Queue]]
- [[wiki/payloadcms/plugins-api|Plugin API]]
