/>
A Payload CMS (3.0) plugin to add subscriber features into a site or app
npm install payload-subscribers-pluginA plugin to manage subscribers and the "channels" they can subscribe to.
This includes ways to allow your subscribers to:
- Sign up or sign in by requesting a magic link email
- Verify the magic link to authenticate
- Opt in or out of "opt-in channels"
You manage the opt-in channels via the Payload admin.
The plugin relies on your email adapter configured in your payload config to send emails.
That is all this plugin does currently. Potential features might include email authoring and send scheduler or simple CRM features.
``bash`
pnpm add payload-subscribers-plugin
You need to have an email adapter configured in your Payload config.
Add the plugin to your Payload config.
`typescript
// payload.config.ts
export default buildConfig({
plugins: [
payloadSubscribersPlugin({
// Add slugs of your collections which should have a relationship field to the optInChannels.
collections: {
posts: true,
},
// Easily disable the collection logic.
disabled: false,
// Specify the collection to use as the subscribers collection
// - Optional. If not specified, the plugin will add a 'subscribers' collection.
// - Sets auth if not already
// - Adds (or overrides) fields: email, firstName, status, optIns,
// verificationToken, verificationTokenExpires, and source
subscribersCollectionSlug?: CollectionSlug
// Provide a custom expiration for magic link tokens. The default is 30 minutes.
tokenExpiration: 60 * 60,
}),
],
})
`
Place the SubscriberProvider at the a good location in your app structure. For example, in your root layout:
`typescript
// layout.tsx
import { SubscriberProvider } from 'payload-subscribers-plugin/ui'
const Layout: React.FC<{ children: React.ReactNode }> = ({ children }) => {
return (
Then you can use the components in your app:
`typescript
// page.tsximport { RequestOrSubscribe } from 'payload-subscribers-plugin/ui'
const Page = () => {
return (
classNames={{ button: 'customCssClassNames', container: 'customCssClassNames', emailInput: 'customCssClassNames' }}
/>
)
}
`_IMPORANT:_ Be sure to create a /verify route
`typescript
// verify/page.tsximport { VerifyClient } from '@/components/VerifyClient.js'
const Page = () => {
return (
classNames={{ button: 'customCssClassNames', container: 'customCssClassNames', emailInput: 'customCssClassNames' }}
/>
)
}
`🟢🔵🔴 Features
$3
#### collections
You can specify collections in the plugin options which will be amended to include a relationTo field referring to the optInChannels collection. Right now this does not override the plugin-added subscribers collection, which is still used for the primary record of subscribers and used for authentication. The collections amended with an optIns can be used, for example, to manage your subscription channels and any email campaigns related.
#### disabled
#### tokenExpiration
$3
#### optInChannels
Seeded when plugin inits.
- Fields
- title: text
- description: text
- active: boolean
- slug: text
#### subscribers
Seeded when plugin inits.
- Fields
- email: text
- first name: text
- status: Subscribed | Unsubscribed | Pending verification (default)
- opt-ins: referenceTo optInChannels hasMany
- source: text
- verificationToken: text hidden
---
$3
#### OptedInChannels
_THE FIELD SPEC IS CURRENTLY NOT EXPORTED_ Documenting here in case that seems useful in the future.
This is the same field used by the plugin collections to amended a relationTo field referring to the optInChannels collection.
---
$3
#### requestMagicLink
Takes an email, verifies it, registers it if unknown, constructs a magic link, and uses your Payload emailAdapter to sendEmail.
#### verifyMagicLink
Takes an email and token, verifies the token, and authenticates the user, using Payload's HTTP-only cookies auth.
#### getOptInChannels
Returns all active optInChannels data.
#### subscribe a user, or update a subscriber's opt-ins.
Takes an email and list of optInChannel IDs, verifies them, and if the authenticated subscriber matches the email will update the channels that subscriber is opted into.
#### TO DO: unsubscribe
The subscribe endpoint will remove all optIns. But need a way to set the subscriber status to "unsubscribed"
---
$3
---
$3
- All App Components are client components that consume hooks, server components, server functions. Including the useSubscriber context, and so the must be used within the children descendent tree of the SubscriberProvider provider.
- All App Components accept a classNames prop to specify CSS class names to add to the different parts of the component
#### RequestOrSubscribe
Shows the Subscribe component to authenticated subscribers, otherwise shows RequestMagicLink.
`typescript
// Provide the URL the user should go to after clicking the link in the email and having it verified
afterVerifyUrl={new URL(window.href)}
// Provide your own global class names to add to the component elements. Optional
classNames={{
button: 'customCssClassNames',
container: 'customCssClassNames',
emailInput: 'customCssClassNames',
error: 'customCssClassNames',
form: 'customCssClassNames',
loading: 'customCssClassNames',
message: 'customCssClassNames',
section: 'customCssClassNames',
}}
// Called after a subscribers opt-ins have been updated. Optional
handleMagicLinkRequested={async (result: RequestMagicLinkResponse) => {}}
// Called after a subscribers opt-ins have been updated. Optional
handleSubscribe={async (result: SubscribeResponse) => {}}
// Provided your own button component. Optional
renderButton={({ name, onClick, text }) =>
}
// Provide the URL to your route that has the VerifyMagicLink component on it.
verifyUrl={verifyUrl}
/>
`#### RequestMagicLink
Form to input email address and get a magic link email sent.

`typescript
// Provide the URL the user should go to after clicking the link in the email and having it verified
afterVerifyUrl={new URL(window.href)}
// Provide your own global class names to add to the component elements. Optional
classNames={{
button: 'customCssClassNames',
container: 'customCssClassNames',
emailInput: 'customCssClassNames',
error: 'customCssClassNames',
form: 'customCssClassNames',
message: 'customCssClassNames',
}}
// Called after a subscribers opt-ins have been updated. Optional
handleMagicLinkRequested={async (result: RequestMagicLinkResponse) => {}}
// Provided your own button component. Optional
renderButton={({ name, onClick, text }) =>
}
// Provide the URL to your route that has the VerifyMagicLink component on it.
verifyUrl={verifyUrl}
/>
``html
`#### VerifyMagicLink
Component that verifies a magic link using expected url parameters.

`typescript
// Provide your own global class names to add to the component elements. Optional
classNames={{
button: 'customCssClassNames',
container: 'customCssClassNames',
error: 'customCssClassNames',
form: 'customCssClassNames',
loading: 'customCssClassNames',
message: 'customCssClassNames',
}}
// Called after a magic link email has been sent. Optional
handleMagicLinkRequested={async (result: RequestMagicLinkResponse) => {}}
// Called after a magic link has been verified. Optional
handleMagicLinkVerified={async (result: RequestMagicLinkResponse) => {}}
// Provided your own button component. Optional
renderButton={({ name, onClick, text }) =>
}
// Provide the URL to your route that has the VerifyMagicLink component on it.
// Used when this VerifyMagicLink component provides an option to request another link
// when verifying the current one fails.
verifyUrl={verifyUrl}
>
// Provide children to render after link is verified. Optional
// Since you provide the verifyUrl to any of the plugin components, you can include a forwardUrl
// as a search param, which your route can then use here.
``html
verifying...
{renderButton({ name: "request", onClick: handleRequestAnother, text:"Request another magic
link", })} {children}
`#### Subscribe
Allows a subscriber to select from among all active optInChannels.

`typescript
// Provide the URL the user should go to after clicking the link in the email and having it verified
afterVerifyUrl={new URL(window.href)}
// Provide your own global class names to add to the component elements. Optional
classNames={{
button: 'customCssClassNames',
container: 'customCssClassNames',
emailInput: 'customCssClassNames',
error: 'customCssClassNames',
form: 'customCssClassNames',
loading: 'customCssClassNames',
message: 'customCssClassNames',
section: 'customCssClassNames',
}}
// Called after a subscribers opt-ins have been updated. Optional
handleSubscribe={async (result: SubscribeResponse) => {}}
// Provided your own button component. Optional
renderButton={({ name, onClick, text }) =>
}
// Provide the URL to your route that has the VerifyMagicLink component on it.
verifyUrl={verifyUrl}
/>
``html
Subscribe
Opt-in Channels
verifying...
`#### SubscriberMenu
A simple user menu, most useful for testing. Seen in the screenshots above. Includes a "welcome" message, a link to a /subscribe route, and a log out button.
`typescript
// classNames prop classNames={{
button: 'customCssClassNames',
container: 'customCssClassNames',
}}
subscribeUrl={new URL('/subscribe', serverURL)}
/>
``html
``Community contributions are welcome! I haven't organized around that yet, so let me know if you're interested by opening an issue on the GitHub repo.