npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@xtr-dev/payload-automation

v0.0.54

Published

PayloadCMS Automation Plugin - Comprehensive workflow automation system with visual workflow building, execution tracking, and step types

Readme

@xtr-dev/payload-automation

npm version

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.

Features

  • 🔄 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

Installation

pnpm add @xtr-dev/payload-automation
# or
npm install @xtr-dev/payload-automation

Quick Start

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,
    }),
  ],
})

Imports

// 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'

Seeding Template Workflows

Provide read-only example workflows for users to learn from and reference:

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.

Step Dependencies

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.

How Dependencies Work

  • 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

Example: Parallel and Sequential Steps

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}}',
      },
    },
  },
]

Accessing Step Output

Use {{steps.<stepName>.output.<field>}} to reference data from completed steps:

{
  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}}',
    },
  },
}

Step Types

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.

HTTP Request

Call external APIs with full configuration:

{
  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.

Document Operations

  • Create Document - Create new PayloadCMS documents
  • Read Document - Query documents with filters
  • Update Document - Modify existing documents
  • Delete Document - Remove documents

Send Email

Send notifications via PayloadCMS email adapter:

{
  type: 'send-email',
  config: {
    to: '{{trigger.doc.customer.email}}',
    subject: 'Order Confirmed',
    template: 'order-confirmation'
  }
}

Custom Steps

Steps are standard PayloadCMS tasks, so creating custom steps is just defining a TaskConfig. No proprietary APIs to learn:

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],
})

JSONata Expressions

Use {{expression}} syntax for dynamic values. JSONata provides powerful data transformation.

Available Context

  • trigger.doc - The document that triggered the workflow
  • trigger.type - Trigger type ('collection' | 'global')
  • trigger.collection - Collection slug for collection triggers
  • steps.<stepName>.output - Output from a completed step
  • steps.<stepName>.state - Step state ('succeeded' | 'failed' | 'pending' | 'skipped')

Examples

// 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)}}"
}

Custom Functions

| 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 |

Execution Tracking

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:

const runs = await payload.find({
  collection: 'workflow-runs',
  where: {
    workflow: { equals: workflowId },
    status: { equals: 'completed' },
  },
  sort: '-createdAt',
  limit: 10,
})

Plugin Configuration

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[]
}

Requirements

  • PayloadCMS ^3.37.0
  • Node.js ^18.20.2 || >=20.9.0
  • React 18+ (for client components)

Logging

Control log verbosity with PAYLOAD_AUTOMATION_LOG_LEVEL:

PAYLOAD_AUTOMATION_LOG_LEVEL=debug pnpm dev

Levels: silent | error | warn (default) | info | debug | trace

Collections

The plugin creates these collections:

| Collection | Slug | Description | |------------|------|-------------| | Triggers | automation-triggers | Reusable trigger definitions | | Steps | automation-steps | Step templates with configuration | | Workflows | workflows | Workflow definitions | | Workflow Runs | workflow-runs | Execution history |

License

MIT