payload-plugin-marketing
v0.9.6
Published
Payload CMS plugin for marketing forms, audiences, contacts, and broadcasts.
Maintainers
Readme
payload-plugin-marketing
Adds marketing REST endpoints, optional Payload admin views for audiences and broadcasts, and Form Builder integration hooks driven by a MarketingAdapter implementation.
Install
pnpm add payload-plugin-marketingnpm install payload-plugin-marketingPeer dependencies
| Package | Notes |
| --- | --- |
| payload | ^3.80.0 |
| @payloadcms/plugin-form-builder | ^3.80.0 |
| next | ^15.0.0 or ^16.0.0 (optional) |
| @payloadcms/next | ^3.80.0 (optional) |
| @payloadcms/ui | ^3.80.0 (optional) |
| react, react-dom | * (optional) |
| resend | * (optional — for Resend adapter) |
| @mailchimp/mailchimp_marketing | * (optional — for Mailchimp adapter) |
payload.config.ts
Resend (adapter + every PayloadPluginMarketingOptions field)
RESEND_API_KEY is read by your code; pass it explicitly (or rely on Resend’s default env lookup inside their SDK when you omit apiKey).
import { buildConfig } from "payload"
import { marketingPlugin } from "payload-plugin-marketing"
import { resendAdapter } from "payload-plugin-marketing/adapters/resend"
import NewsletterEmail from "@/emails/newsletter"
export default buildConfig({
plugins: [
marketingPlugin({
adapter: resendAdapter({
apiKey: process.env.RESEND_API_KEY, // optional when Resend reads env internally
defaultSender: process.env.MARKETING_FROM_EMAIL ?? "[email protected]",
// client: customResendClient, // optional injected SDK-shaped client
}),
emailBroadcastTemplates: [
{
id: "welcome",
name: "Welcome",
path: "@/emails/welcome#WelcomeEmail", // or use `path` + `exportName` below
},
{
id: "newsletter",
name: "Newsletter",
exportName: "NewsletterEmail",
path: "@/emails/newsletter.tsx",
},
{
id: "digest",
name: "Digest",
component: NewsletterEmail,
},
],
permissions: {
audiences: { read: true, write: true },
broadcasts: { read: true, write: true },
contacts: {
read: ({ req }) => Boolean(req.user),
write: async ({ req }) => Boolean(req.user),
},
},
admin: {
basePath: "/marketing",
enabled: true,
components: {
marketingMenu: {
path: "payload-plugin-marketing/admin",
exportName: "MarketingMenu",
},
audienceList: {
path: "payload-plugin-marketing/admin",
exportName: "AudienceList",
},
audienceDetail: {
path: "payload-plugin-marketing/admin",
exportName: "AudienceDetail",
},
broadcastList: {
path: "payload-plugin-marketing/admin",
exportName: "BroadcastList",
},
audienceSelect: {
path: "payload-plugin-marketing/admin",
exportName: "AudienceSelect",
},
},
},
formBuilder: {
formsSlug: "forms",
submissionsSlug: "form-submissions",
strict: false,
fields: {
acceptance: {
labels: { singular: "Consent", plural: "Consents" },
fields: {
name: { overrides: { label: "Internal acceptance key" } },
label: { overrides: { label: "Displayed consent label" } },
width: {},
required: {},
},
overrides: {},
},
checkbox: { overrides: {} },
country: { overrides: {} },
date: { overrides: {} },
email: { overrides: {} },
message: { overrides: {} },
number: { overrides: {} },
payment: { overrides: {} },
phone: { overrides: {} },
radio: { overrides: {} },
select: { overrides: {} },
state: { overrides: {} },
text: { overrides: {} },
textarea: { overrides: {} },
upload: { overrides: {} },
url: { overrides: {} },
},
},
}),
],
})Mailchimp (MailchimpAdapterOptions)
Same plugin surface as Resend; swap the adapter and include Mailchimp-specific options (siteName, required apiKey / defaultSender).
import { buildConfig } from "payload"
import { marketingPlugin } from "payload-plugin-marketing"
import { mailchimpAdapter } from "payload-plugin-marketing/adapters/mailchimp"
export default buildConfig({
plugins: [
marketingPlugin({
adapter: mailchimpAdapter({
apiKey: process.env.MAILCHIMP_API_KEY!, // must include datacenter suffix (e.g. `xxxx-us21`)
defaultSender: process.env.MARKETING_FROM_EMAIL ?? "[email protected]",
siteName: process.env.PAYLOAD_PUBLIC_SITE_NAME ?? "Example",
// client: mailchimpMarketingClient, // optional injected client with `setConfig`
}),
// …reuse `emailBroadcastTemplates`, `permissions`, `admin`, `formBuilder` from the Resend example
}),
],
})MAILCHIMP_API_KEY must include the datacenter suffix (for example xxxx-us21).
Options (PayloadPluginMarketingOptions)
| Field | Purpose |
| --- | --- |
| adapter | Required MarketingAdapter implementation |
| emailBroadcastTemplates | React email templates for broadcast UI / API (react content supported where adapter allows) |
| permissions | Per-resource read / write for audiences, broadcasts, and contacts: booleans or (args: AccessArgs) => boolean (same args as Payload collection access). Use evaluateMarketingPermissions(permissions, { req }) when using functions; resolveMarketingPermissions is boolean-only. |
| admin.basePath | Prefix for admin view paths |
| admin.enabled | Set false to skip admin UI wiring |
| admin.components | Override Payload components (marketingMenu, audienceList, audienceDetail, broadcastList, audienceSelect) |
| formBuilder.formsSlug / submissionsSlug | Form Builder collection slugs |
| formBuilder.strict | Stricter submission validation behavior |
| formBuilder.fields | Per-field overrides (MarketingFormFieldOverrides) |
The returned plugin has slug: "marketing", order: 10, and exposes options on the function object.
HTTP endpoints
Authenticated handlers register paths (relative to your Payload REST mount, often /api/* in Next):
GET /marketing/metaGET|POST /marketing/audiences,GET|PATCH|DELETE /marketing/audiences/:idGET|DELETE /marketing/audiences/:audienceId/contacts,DELETE …/contacts/:contactIdPOST /marketing/contactsGET|POST /marketing/broadcasts,GET|DELETE /marketing/broadcasts/:broadcastId,POST …/send
Calling GET /marketing/meta from Next
Prefix with the same origin + API base you use for Payload REST (here NEXT_PUBLIC_SITE_URL + /api).
const base = process.env.NEXT_PUBLIC_SITE_URL?.replace(/\/$/, "") ?? ""
const res = await fetch(`${base}/api/marketing/meta`, {
cache: "no-store",
headers: {
// Use the same auth your Payload REST routes expect (cookies, API key, etc.)
Authorization: `Bearer ${process.env.CMS_SERVICE_TOKEN}`,
},
})
if (!res.ok) {
throw new Error(`marketing meta ${res.status}: ${await res.text()}`)
}
const meta = await res.json()Adapters
import { resendAdapter, type ResendAdapterOptions } from "payload-plugin-marketing/adapters/resend"
import { mailchimpAdapter, type MailchimpAdapterOptions } from "payload-plugin-marketing/adapters/mailchimp"Config access
MARKETING_CUSTOM_CONFIG_KEY stores integration state on config.custom. Helpers:
getMarketingIntegration(config)getMarketingIntegrationFromRequest(req)tryGetMarketingIntegration(config)resolveMarketingPermissions(permissions?)— boolean flags only; throws if any value is a functionevaluateMarketingPermissions(permissions, accessArgs)— resolves booleans andAccessArgscallbacks (HTTP handlers and admin views use this internally)isMarketingResourceAllowed(permissions, resource, action, accessArgs)— single resource/action checkmarketingMetaAllowed(effectivePermissions)
Form Builder (payload-plugin-marketing/form-builder)
Exports include createMarketingFormFields, createAcceptanceBlock, submission helpers (validateSubmissionInput, submissionDataToPlainRecord, …), and hooks (createCreateLeadHook, createValidateFormSubmissionHook, …). mutateFormBuilderCollections is used internally by the plugin but exported for advanced setups.
Admin overrides (payload-plugin-marketing/admin, payload-plugin-marketing/admin/client)
Default UI path is payload-plugin-marketing/admin with named exports such as MarketingMenu, AudienceList, BroadcastList. Import payload-plugin-marketing/admin/client for client-only components when overriding.
License
Apache-2.0 (see repository root).
