This is a fork of the official Payload Plugin Template Special thanks to the amazing team at Payload CMS for creating this excellent foundation for plugin development.
A modern, production-ready template for creating Payload CMS plugins with TypeScript, React, and comprehensive testing.
- Complete Development Environment: Pre-configured with hot-reloading, TypeScript, and ESLint
- Docker Support: Ready-to-use Docker setup for containerized development
- Comprehensive Testing: Jest test suite with examples for components, endpoints, and integration tests
- Example Components: Server and Client component examples demonstrating Payload's architecture
- Custom Endpoints: Example API endpoint implementation
- Cross-platform: Works on Windows, macOS, and Linux
- Modern Tooling: Uses pnpm workspaces, Next.js 15, and React 19
- Node.js 18+ and pnpm 8+
- Docker (optional, for containerized development)
- Clone this repository:
git clone https://github.com/yourusername/payload-plugin-template.git
cd payload-plugin-template- Install dependencies:
pnpm install- Start development:
pnpm devThis will:
- Watch and compile the plugin source code
- Start the development Payload app at http://localhost:3000
- Enable hot-reloading for both plugin and app code
payload-plugin-template/
├── src/ # Plugin source code
│ ├── components/ # React components (Server & Client)
│ ├── endpoints/ # Custom API endpoints
│ ├── __tests__/ # Jest test suites
│ ├── plugin.ts # Main plugin configuration
│ └── index.ts # Plugin exports
├── dev/ # Development Payload app
│ ├── src/
│ │ ├── app/ # Next.js app directory
│ │ └── payload.config.ts
│ └── package.json
├── dist/ # Compiled plugin output
└── package.json # Plugin package configuration
| Command | Description |
|---|---|
pnpm dev |
Start development with hot-reloading |
pnpm build |
Build the plugin for production |
pnpm test |
Run the test suite |
pnpm lint |
Lint the codebase |
pnpm dev:docker |
Start development with Docker |
For containerized development with MongoDB:
# Start Docker environment
pnpm dev:docker
# Stop containers
cd dev && pnpm docker:down
# Clean volumes
cd dev && pnpm docker:cleanThe template includes comprehensive test examples with both unit and E2E testing:
# Run all unit tests
pnpm test
# Watch mode
pnpm test:watch
# Coverage report
pnpm test:coverageTest files are located in src/__tests__/ and cover:
- Plugin initialization
- React components (Server & Client)
- Custom endpoints
- Integration scenarios
# Run E2E tests (requires dev server running)
pnpm test:e2e
# Run E2E tests with UI
pnpm test:e2e:ui
# Run specific test file
pnpm test:e2e admin-components.spec.tsE2E test files are located in e2e/tests/ and cover:
- Admin dashboard components interaction
- API endpoint functionality
- GraphQL queries
- User interface workflows
Note: Playwright is configured to use an existing dev server on port 3000. Make sure to run pnpm dev before running E2E tests.
Edit package.json with your plugin details:
{
"name": "payload-plugin-your-name",
"description": "Your plugin description",
"author": "Your Name",
"license": "MIT"
}The main plugin file is src/plugin.ts:
export const yourPlugin = (pluginOptions: PluginTypes) =>
(incomingConfig: Config): Config => {
let config = { ...incomingConfig }
// Extend collections
config.collections = [
...(config.collections || []),
// Your collections
]
// Add custom endpoints
config.endpoints = [
...(config.endpoints || []),
// Your endpoints
]
return config
}Create Server and Client components in src/components/:
Server Component (runs during SSR):
export const MyServerComponent: PayloadServerReactComponent = () => {
// Has access to server-side resources
return <div>Server Component</div>
}Client Component (runs in browser):
'use client'
export const MyClientComponent: PayloadClientReactComponent = () => {
// Can use hooks and browser APIs
const [state, setState] = useState()
return <div>Client Component</div>
}Add custom API endpoints in src/endpoints/:
export const myEndpoint: Endpoint = {
path: '/my-endpoint',
method: 'get',
handler: async (req) => {
return Response.json({ message: 'Hello!' })
}
}The plugin template includes an automatic @ alias transformation system that simplifies how you reference your components and utilities in the plugin configuration.
When you use @ aliases in your plugin code (like @components/MyComponent), the build process automatically:
- Tracks all @ alias usage in your TypeScript files
- Resolves them to the correct file paths
- Generates an
exports.jsfile with all necessary exports - Transforms the @ aliases to proper package imports (
payload-plugin-template/exports#MyComponent)
// In your plugin configuration (src/index.ts or similar)
config.admin.components = {
afterDashboard: [
// Named export from a specific path
'@components/AfterDashboard#AfterDashboard',
// Named export using shorthand (assumes export name matches last path segment)
'@components#AfterDashboardClient',
// Default export (automatically detected)
'@example/exampleComponent',
// Nested paths work too
'@utils/helpers/formatter#formatDate'
]
}The @ alias paths map directly to your file structure:
| @ Alias | Resolves To | Export |
|---|---|---|
@components/MyComponent |
dist/components/MyComponent.js or dist/components/MyComponent/index.js |
Named or default export MyComponent |
@components#MyComponent |
dist/components/index.js |
Named export MyComponent |
@utils/helper#format |
dist/utils/helper.js or dist/utils/helper/index.js |
Named export format |
The system automatically detects whether your module uses default or named exports:
// Default export (src/example/Component.tsx)
export default function Component() { ... }
// Use: '@example/Component'
// Named export (src/components/index.ts)
export const MyComponent = () => { ... }
// Use: '@components#MyComponent'During the build process, if an @ alias can't be resolved, you'll see a clear error message:
❌ ERROR: Could not resolve @ alias: @components/NonExistent
Tried paths:
- dist/components/NonExistent.js
- dist/components/NonExistent/index.js
Check if the file exists or if the @ alias path matches the actual file structure
This helps catch typos and incorrect paths early in the development process.
- Type Safety: TypeScript still validates your exports
- Automatic Export Generation: No need to manually maintain export files
- Auto-Generated Import Map: Automatically generates importMap.js on build changes
- CSS/SCSS Support: Full support for CSS and SCSS modules with automatic compilation
- Clear Error Messages: Build-time validation catches issues early
- Flexible Patterns: Support for both named and default exports
- Direct Path Mapping: Intuitive mapping from @ aliases to file structure
- Build your plugin:
pnpm build- Publish to npm:
npm publish- Users can then install your plugin:
pnpm add your-plugin-name- Always spread existing config arrays/objects to preserve data
- Provide TypeScript types for all plugin options
- Include comprehensive tests
- Add an enable/disable option
- Document all configuration options
- Use Semantic Versioning
- Tag your repository with
payload-plugin
MIT - See LICENSE for details
- For plugin template issues: Open an issue
- For general inquiries: dev@payloadcms.com
Built with ❤️ using Payload CMS