PayloadCMS Automation Plugin - Comprehensive workflow automation system with visual workflow building, execution tracking, and step types
npm install @xtr-dev/payload-automation
A workflow automation plugin for PayloadCMS 3.x. Build visual workflows triggered by document changes, webhooks, or manual execution.
> Pre-release Warning: This package is currently in active development (v0.0.x). Breaking changes may occur before v1.0.0.
- 🔄 Visual workflow builder in PayloadCMS admin
- ⚡ Run workflows when documents are created/updated/deleted
- 🎯 Trigger workflows via webhooks
- 📊 Track workflow execution history
- 🔧 HTTP requests, document operations, email sending
- 🔗 Use data from previous steps in templates
- ⚙️ Step dependencies with parallel and sequential execution
``bash`
pnpm add @xtr-dev/payload-automationor
npm install @xtr-dev/payload-automation
`typescript
import { buildConfig } from 'payload'
import { workflowsPlugin } from '@xtr-dev/payload-automation/server'
export default buildConfig({
// ... your config
plugins: [
workflowsPlugin({
collectionTriggers: {
posts: true, // Enable all CRUD triggers for posts
users: {
create: true, // Only enable create trigger for users
update: true
}
},
enabled: true,
}),
],
})
`
`typescript
// Server plugin
import { workflowsPlugin } from '@xtr-dev/payload-automation/server'
// Client components
import { StatusCell, ErrorDisplay } from '@xtr-dev/payload-automation/client'
// Types
import type { WorkflowsPluginConfig, SeedWorkflow } from '@xtr-dev/payload-automation'
`
Provide read-only example workflows for users to learn from and reference:
`typescript
import type { SeedWorkflow } from '@xtr-dev/payload-automation'
const exampleWorkflows: SeedWorkflow[] = [
{
slug: 'example-welcome-email',
name: 'Example: Send Welcome Email',
description: 'Send email when user is created',
triggers: [
{
type: 'collection-hook',
parameters: {
collectionSlug: 'users',
hook: 'afterChange',
},
condition: 'trigger.operation = "create"',
},
],
steps: [
{
name: 'Send Email',
type: 'send-email',
input: {
to: '{{trigger.doc.email}}',
subject: 'Welcome!',
text: 'Thanks for joining us!',
},
},
],
},
{
slug: 'example-order-processing',
name: 'Example: Order Processing Pipeline',
description: 'Process order with validation, inventory check, and notifications',
triggers: [
{
type: 'collection-hook',
parameters: {
collectionSlug: 'orders',
hook: 'afterChange',
},
condition: 'trigger.operation = "create"',
},
],
steps: [
{
name: 'Validate Order',
slug: 'validate-order', // Unique identifier for this step
type: 'http-request-step',
input: {
url: 'https://api.example.com/validate',
method: 'POST',
body: {
orderId: '{{trigger.doc.id}}',
items: '{{trigger.doc.items}}',
},
},
},
{
name: 'Check Inventory',
slug: 'check-inventory',
type: 'http-request-step',
input: {
url: 'https://api.example.com/inventory/check',
method: 'POST',
body: {
items: '{{trigger.doc.items}}',
},
},
// Dependencies reference other steps by slug
dependencies: ['validate-order'],
},
{
name: 'Create Shipment',
slug: 'create-shipment',
type: 'create-document',
input: {
collection: 'shipments',
data: {
orderId: '{{trigger.doc.id}}',
status: 'pending',
items: '{{trigger.doc.items}}',
},
},
// This step waits for both validation and inventory check
dependencies: ['validate-order', 'check-inventory'],
},
{
name: 'Send Confirmation Email',
slug: 'send-email',
type: 'send-email',
input: {
to: '{{trigger.doc.customer.email}}',
subject: 'Order Confirmed',
text: 'Your order {{trigger.doc.id}} has been confirmed!',
},
// Dependencies reference step slugs
dependencies: ['create-shipment'],
},
],
},
]
workflowsPlugin({
seedWorkflows: exampleWorkflows,
// ... other config
})
`
Seeded workflows:
- Are automatically created on first startup
- Use slug as unique identifier (stable across renames)
- Automatically update when definition changes in code
- Cannot be edited or deleted via UI or API
- Show a warning banner in the admin panel
- Can be duplicated to create editable versions
See docs/SEEDING_WORKFLOWS.md for detailed documentation.
Steps can declare dependencies on other steps to control execution order. Dependencies reference steps by their slug (not by name or index), making them stable across renames and reordering.
- Parallel Execution: Steps without dependencies run in parallel
- Sequential Execution: Steps with dependencies wait for their dependencies to complete successfully
- Multiple Dependencies: A step can depend on multiple other steps (all must succeed)
- Failure Handling: If a dependency fails, the dependent step is skipped
- Slug-based: Dependencies reference step slug fields, not names or positions
`typescript`
steps: [
{
name: 'Fetch User Data',
slug: 'fetch-user',
type: 'http-request-step',
// No dependencies - runs immediately
},
{
name: 'Fetch Order Data',
slug: 'fetch-orders',
type: 'http-request-step',
// No dependencies - runs in parallel with fetch-user
},
{
name: 'Generate Report',
slug: 'generate-report',
type: 'http-request-step',
dependencies: ['fetch-user', 'fetch-orders'], // Reference by slug
// Waits for both API calls to complete
input: {
url: 'https://api.example.com/reports',
body: {
user: '{{steps.FetchUserData.output.user}}',
orders: '{{steps.FetchOrderData.output.orders}}',
},
},
},
]
Use {{steps. to reference data from completed steps:
`typescript`
{
name: 'Process Result',
slug: 'process-result',
type: 'create-document',
dependencies: ['api-call'], // Reference by slug
input: {
collection: 'results',
data: {
// Access output from the "API Call" step
apiResponse: '{{steps.APICall.output.data}}',
status: '{{steps.APICall.output.status}}',
},
},
}
> Note: Steps are just regular PayloadCMS tasks. This plugin uses Payload's built-in job queue system, so you can leverage all of Payload's task features including retries, scheduling, and monitoring. Any TaskConfig you create is a valid step.
The plugin comes with a few built-in step types found below.
`typescript`
{
type: 'http-request-step',
config: {
url: 'https://api.example.com/webhook',
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: '{"orderId": "{{trigger.doc.id}}"}'
}
}
Supports GET, POST, PUT, DELETE, PATCH. Authentication via Bearer tokens, API keys, or basic auth.
- Create Document - Create new PayloadCMS documents
- Read Document - Query documents with filters
- Update Document - Modify existing documents
- Delete Document - Remove documents
`typescript`
{
type: 'send-email',
config: {
to: '{{trigger.doc.customer.email}}',
subject: 'Order Confirmed',
template: 'order-confirmation'
}
}
Steps are standard PayloadCMS tasks, so creating custom steps is just defining a TaskConfig. No proprietary APIs to learn:
`typescript
import type { TaskConfig } from 'payload'
// Define the step configuration
export const SlackNotificationStep: TaskConfig<'slack-notification'> = {
slug: 'slack-notification',
label: 'Send Slack Message',
inputSchema: [
{ name: 'channel', type: 'text', required: true },
{ name: 'message', type: 'textarea', required: true },
],
outputSchema: [
{ name: 'messageId', type: 'text' },
{ name: 'timestamp', type: 'text' },
],
handler: async ({ input, req }) => {
try {
const response = await fetch('https://slack.com/api/chat.postMessage', {
method: 'POST',
headers: {
'Authorization': Bearer ${process.env.SLACK_TOKEN},
'Content-Type': 'application/json',
},
body: JSON.stringify({
channel: input.channel,
text: input.message,
}),
})
const data = await response.json()
return {
output: {
messageId: data.message.ts,
timestamp: new Date().toISOString(),
},
state: 'succeeded'
}
} catch (error) {
return {
output: {},
state: 'failed',
errorMessage: error instanceof Error ? error.message : 'Unknown error'
}
}
},
}
// Register in plugin config
workflowsPlugin({
steps: [SlackNotificationStep],
})
`
Use {{expression}} syntax for dynamic values. JSONata provides powerful data transformation.
- trigger.doc - The document that triggered the workflowtrigger.type
- - Trigger type ('collection' | 'global')trigger.collection
- - Collection slug for collection triggerssteps.
- - Output from a completed stepsteps.
- - Step state ('succeeded' | 'failed' | 'pending' | 'skipped')
`javascript
// Access trigger data
{{trigger.doc.id}}
{{trigger.doc.customer.email}}
// Use previous step output
{{steps.createOrder.output.id}}
{{steps.fetchUser.output.name}}
// Conditions (in step config)
{{trigger.doc.status = 'published'}}
{{trigger.doc.total > 100}}
// String transformation
{{$uppercase(trigger.doc.title)}}
{{$join(trigger.doc.tags, ', ')}}
// Object construction
{
"orderId": "{{trigger.doc.id}}",
"total": "{{$sum(trigger.doc.items.price)}}"
}
`
| Function | Description |
|----------|-------------|
| $now() | Current ISO timestamp |$uuid()
| | Generate UUID v4 |$default(value, fallback)
| | Return fallback if null |$coalesce(a, b, ...)
| | First non-null value |$env('PUBLIC_*')
| | Access PUBLIC_ env vars |
All workflow executions are stored in the workflow-runs collection with:
- Trigger data that initiated the run
- Step-by-step results with status and duration
- Execution logs with timestamps
- Total duration and final status
Query runs programmatically:
`typescript`
const runs = await payload.find({
collection: 'workflow-runs',
where: {
workflow: { equals: workflowId },
status: { equals: 'completed' },
},
sort: '-createdAt',
limit: 10,
})
`typescript
interface WorkflowsPluginConfig {
// Enable/disable the plugin
enabled?: boolean
// Collection triggers
collectionTriggers?: {
[collectionSlug: string]: boolean | {
afterChange?: boolean
afterDelete?: boolean
afterRead?: boolean
// ... other hooks
}
}
// Global triggers
globalTriggers?: {
[globalSlug: string]: boolean | { / hooks / }
}
// Custom step definitions
steps?: StepDefinition[]
}
`
- PayloadCMS ^3.37.0
- Node.js ^18.20.2 || >=20.9.0
- React 18+ (for client components)
Control log verbosity with PAYLOAD_AUTOMATION_LOG_LEVEL:
`bash`
PAYLOAD_AUTOMATION_LOG_LEVEL=debug pnpm dev
Levels: silent | error | warn (default) | info | debug | trace
The plugin creates these collections:
| Collection | Slug | Description |
|------------|------|-------------|
| Triggers | automation-triggers | Reusable trigger definitions |automation-steps
| Steps | | Step templates with configuration |workflows
| Workflows | | Workflow definitions |workflow-runs` | Execution history |
| Workflow Runs |
MIT