415 lines
13 KiB
Markdown
415 lines
13 KiB
Markdown
---
|
|
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]]
|