Skip to content

Custom Step Types

Create your own step types for domain-specific logic by implementing the StepType interface.

StepType Interface

php
interface StepType
{
    public static function getName(): string;
    public function execute(array $definition, ExecutionContext $context): StepResult;
    public function validate(array $definition): void;
}
MethodPurpose
getName()Returns the type name used in definitions ('send_email')
execute()Performs the step logic, returns a StepResult
validate()Validates the step definition at workflow creation time

1. Create the Step Type

php
namespace App\StepTypes;

use Workflowable\Workflowable\Contracts\StepType;
use Workflowable\Workflowable\Engine\ExecutionContext;
use Workflowable\Workflowable\Engine\StepResult;

class SendEmailStepType implements StepType
{
    public static function getName(): string
    {
        return 'send_email';
    }

    public function execute(array $definition, ExecutionContext $context): StepResult
    {
        try {
            $to = $context->getVariable($definition['to_variable'] ?? 'email');
            $template = $definition['template'];

            Mail::to($to)->send(new DynamicMail($template, $context->getVariables()));

            return StepResult::success(['email_sent' => true, 'sent_to' => $to]);
        } catch (\Throwable $e) {
            return StepResult::failed($e->getMessage());
        }
    }

    public function validate(array $definition): void
    {
        if (empty($definition['template'])) {
            throw new \InvalidArgumentException('Send email step requires a "template" field.');
        }
    }
}

2. Register the Step Type

In a service provider:

php
use Workflowable\Workflowable\Registries\StepTypeRegistry;

public function boot(): void
{
    app(StepTypeRegistry::class)->register('send_email', \App\StepTypes\SendEmailStepType::class);
}

3. Use in Workflow Definitions

php
'steps' => [
    'notify_customer' => [
        'type' => 'send_email',
        'template' => 'order-confirmation',
        'to_variable' => 'customerEmail',
    ],
],
'transitions' => [
    'notify_customer' => 'completed',
]

StepResult

php
// Step completed successfully - data merged into workflow variables
StepResult::success(['key' => 'value']);

// Step failed
StepResult::failed('Payment gateway timeout');

// Step is waiting (e.g., for an external callback or async result)
StepResult::waiting();

Custom Action Handlers vs Custom Step Types

For simpler cases where you just need to run some logic, register an action handler instead of a full step type:

php
// In config/workflowable.php
'actions' => [
    'send_slack_notification' => \App\Actions\SendSlackNotification::class,
],
php
class SendSlackNotification
{
    public function handle(ExecutionContext $context): array
    {
        $message = $context->getVariable('notification_message');
        Slack::send($message);
        return ['slack_sent' => true];
    }
}

Action handlers are used with the built-in action step type and don't require implementing the StepType interface. Use a custom step type when you need:

  • Custom validation logic
  • A different execution model (e.g., waiting, webhooks)
  • Reusable behavior across multiple workflows with type-specific configuration