templates/plugin/README.md
A template repo to create a Payload CMS plugin.
Payload is built with a robust infrastructure intended to support Plugins with ease. This provides a simple, modular, and reusable way for developers to extend the core capabilities of Payload.
To build your own Payload plugin, all you need is:
Here is a short recap on how to integrate plugins with Payload, to learn more visit the plugin overview page.
To install any plugin, simply add it to your payload.config() in the Plugin array.
import myPlugin from 'my-plugin'
export const config = buildConfig({
plugins: [
// You can pass options to the plugin
myPlugin({
enabled: true,
}),
],
})
The initialization process goes in the following order:
When you build a plugin, you are purely building a feature for your project and then abstracting it outside of the project.
In the Payload plugin template, you will see a common file structure that is used across all plugins:
In the root folder, you will see various files that relate to the configuration of the plugin. We set up our environment in a similar manner in Payload core and across other projects, so hopefully these will look familiar:
IMPORTANT*: You will need to modify these files.
In the dev folder, you’ll find a basic payload project, created with npx create-payload-app and the blank template.
IMPORTANT: Make a copy of the .env.example file and rename it to .env. Update the DATABASE_URL to match the database you are using and your plugin name. Update PAYLOAD_SECRET to a unique string.
You will not be able to run pnpm/yarn dev until you have created this .env file.
myPlugin has already been added to the payload.config() file in this project.
plugins: [
myPlugin({
collections: {
posts: true,
},
}),
]
Later when you rename the plugin or add additional options, make sure to update it here.
You may wish to add collections or expand the test project depending on the purpose of your plugin. Just make sure to keep this dev environment as simplified as possible - users should be able to install your plugin without additional configuration required.
When you’re ready to start development, initiate the project with pnpm/npm/yarn dev and pull up http://localhost:3000 in your browser.
Now that we have our environment setup and we have a dev project ready to - it’s time to build the plugin!
index.ts
The essence of a Payload plugin is simply to extend the payload config - and that is exactly what we are doing in this file.
export const myPlugin =
(pluginOptions: MyPluginConfig) =>
(config: Config): Config => {
// do cool stuff with the config here
return config
}
First, we receive the existing payload config along with any plugin options.
From here, you can extend the config as you wish.
Finally, you return the config and that is it!
Spread syntax (or the spread operator) is a feature in JavaScript that uses the dot notation (...) to spread elements from arrays, strings, or objects into various contexts.
We are going to use spread syntax to allow us to add data to existing arrays without losing the existing data. It is crucial to spread the existing data correctly – else this can cause adverse behavior and conflicts with Payload config and other plugins.
Let’s say you want to build a plugin that adds a new collection:
config.collections = [
...(config.collections || []),
// Add additional collections here
]
First we spread the config.collections to ensure that we don’t lose the existing collections, then you can add any additional collections just as you would in a regular payload config.
This same logic is applied to other properties like admin, hooks, globals:
config.globals = [
...(config.globals || []),
// Add additional globals here
]
config.hooks = {
...(incomingConfig.hooks || {}),
// Add additional hooks here
}
Some properties will be slightly different to extend, for instance the onInit property:
import { onInitExtension } from './onInitExtension' // example file
config.onInit = async (payload) => {
if (incomingConfig.onInit) await incomingConfig.onInit(payload)
// Add additional onInit code by defining an onInitExtension function
onInitExtension(pluginOptions, payload)
}
If you wish to add to the onInit, you must include the async/await. We don’t use spread syntax in this case, instead you must await the existing onInit before running additional functionality.
In the template, we have stubbed out some addition onInit actions that seeds in a document to the plugin-collection, you can use this as a base point to add more actions - and if not needed, feel free to delete it.
If your plugin has options, you should define and provide types for these options.
export type MyPluginConfig = {
/**
* List of collections to add a custom field
*/
collections?: Partial<Record<CollectionSlug, true>>
/**
* Disable the plugin
*/
disabled?: boolean
}
If possible, include JSDoc comments to describe the options and their types. This allows a developer to see details about the options in their editor.
Having a test suite for your plugin is essential to ensure quality and stability. Vitest is a fast, modern testing framework that works seamlessly with Vite and supports TypeScript out of the box.
Vitest organizes tests into test suites and cases, similar to other testing frameworks. We recommend creating individual tests based on the expected behavior of your plugin from start to finish.
Writing tests with Vitest is very straightforward, and you can learn more about how it works in the Vitest documentation.
For this template, we stubbed out int.spec.ts in the dev folder where you can write your tests.
describe('Plugin tests', () => {
// Create tests to ensure expected behavior from the plugin
it('some condition that must be met', () => {
// Write your test logic here
expect(...)
})
})
With this tutorial and the plugin template, you should have everything you need to start building your own plugin. In addition to the setup, here are other best practices aim we follow:
Please contact Payload with any questions about using this plugin template.