obsidian/wiki/payloadcms/plugins.md
2026-05-15 16:21:59 +01:00

13 KiB

title tags topic sources created updated
PayloadCMS — Plugins (Overview + Official)
payloadcms
tech-patterns
payloadcms
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
2026-05-15 2026-05-15

PayloadCMS — Plugins (Overview + Official)

Overview

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:

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:

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

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

See wiki/payloadcms/plugins-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

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 }) => [...]
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

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

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

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
import { redirectsPlugin } from '@payloadcms/plugin-redirects'

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

Nested Docs Plugin

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

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

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

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

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

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

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