Skip to content

Instantly share code, notes, and snippets.

@leek
Last active January 29, 2026 08:11
Show Gist options
  • Select an option

  • Save leek/eaf954511e40fecff4684838a59abe92 to your computer and use it in GitHub Desktop.

Select an option

Save leek/eaf954511e40fecff4684838a59abe92 to your computer and use it in GitHub Desktop.

Workflow Engine / Automations

A Filament plugin for building and managing automated workflows. Create powerful workflow automations with a visual builder, extensible triggers and actions, async execution, and comprehensive audit logging.

Video

screenshot

Screenshots

CleanShot 2026-01-29 at 01 19 41@2x-optimised CleanShot 2026-01-29 at 01 15 35@2x-optimised CleanShot 2026-01-29 at 01 15 53@2x-optimised CleanShot 2026-01-29 at 01 27 06@2x-optimised CleanShot 2026-01-29 at 01 25 02@2x-optimised CleanShot 2026-01-29 at 01 18 51@2x-optimised

Features

  • Visual Workflow Builder: Create and manage workflows with an intuitive drag-and-drop interface
  • Extensible Triggers: Model events, schedules, date conditions, manual triggers, and Laravel events
  • Extensible Actions: Communication (email, notifications), CRUD operations, HTTP requests, data transforms, and flow control
  • Condition Branching: Build conditional logic with true/false branches
  • Workflow Chaining: Trigger workflows from other workflows with depth protection
  • Rate Limiting: Configurable limits to prevent runaway execution
  • Async Execution: Queue-based execution with retry and backoff support
  • Workflow Secrets: Securely store API keys and tokens with encryption
  • Run History: Complete audit trail with step-by-step execution logs
  • Test Mode: Dry-run workflows to preview execution without side effects
  • Multi-tenancy Support: Built-in optional tenant scoping for SaaS applications

Installation

Add the repository to your composer.json:

{
  "repositories": [
    {
      "type": "composer",
      "url": "https://filament-workflow-engine.composer.sh"
    }
  ],
}

Once the repository has been added to the composer.json file, you can install Filament Workflows like any other composer package using the composer require command:

composer require leek/filament-workflows

You will be prompted to provide your username and password.

Loading composer repositories with package information
Authentication required (filament-workflow-engine.composer.sh):
Username: [licensee-email]
Password: [license-key]

The username will be your email address and the password will be equal to your license key. Additionally, you will need to append your fingerprint to your license key. For example, let's say we have the following licensee and license activation:

  • Contact email: [email protected]
  • License key: 8c21df8f-6273-4932-b4ba-8bcc723ef500
  • Activation fingerprint: anystack.sh

This will require you to enter the following information when prompted for your credentials:

Loading composer repositories with package information
Authentication required (filament-workflow-engine.composer.sh):
Username: [email protected]
Password: 8c21df8f-6273-4932-b4ba-8bcc723ef500:anystack.sh

To clarify, the license key and fingerprint should be separated by a colon (:).

Run the installation command:

php artisan filament-workflows:install

This will:

  • Publish the configuration file
  • Publish and run migrations

Alternatively, you can manually publish the config and migrations:

php artisan vendor:publish --tag="leek-filament-workflows-config"
php artisan vendor:publish --tag="leek-filament-workflows-migrations"
php artisan migrate

Optionally, you can publish the views:

php artisan vendor:publish --tag="leek-filament-workflows-views"

Configuration

The published config file (config/leek-filament-workflows.php) contains the following options:

return [
    // Customize the model classes if you need to extend them
    'models' => [
        'workflow'          => Workflow::class,
        'workflow_run'      => WorkflowRun::class,
        'workflow_run_step' => WorkflowRunStep::class,
        'workflow_secret'   => WorkflowSecret::class,
        'user'              => 'App\\Models\\User',
    ],

    // Queue configuration
    'queue' => [
        'name'       => env('WORKFLOWS_QUEUE_NAME', 'workflows'),
        'connection' => env('WORKFLOWS_QUEUE_CONNECTION', null),
    ],

    // Rate limiting configuration
    'rate_limiting' => [
        'max_concurrent_runs'   => env('WORKFLOWS_MAX_CONCURRENT_RUNS', 10),
        'max_runs_per_minute'   => env('WORKFLOWS_MAX_RUNS_PER_MINUTE', 60),
        'global_max_concurrent' => env('WORKFLOWS_GLOBAL_MAX_CONCURRENT', 100),
    ],

    // Execution configuration
    'execution' => [
        'default_failure_strategy' => env('WORKFLOWS_FAILURE_STRATEGY', 'stop'),
        'default_max_retries'      => env('WORKFLOWS_DEFAULT_MAX_RETRIES', 3),
        'retry_backoff'            => [60, 300, 900],
        'max_chain_depth'          => env('WORKFLOWS_MAX_CHAIN_DEPTH', 5),
    ],

    // Multi-tenancy configuration
    'tenancy' => [
        'enabled' => env('WORKFLOWS_TENANCY_ENABLED', false),
        'column'  => env('WORKFLOWS_TENANCY_COLUMN', 'tenant_id'),
        'model'   => env('WORKFLOWS_TENANT_MODEL', 'App\\Models\\Team'),
    ],

    // Triggerable models for workflow triggers
    'triggerable_models' => [
        // App\Models\User::class,
    ],

    // Model discovery settings
    'discovery' => [
        'enabled' => env('WORKFLOWS_DISCOVERY_ENABLED', false),
        'paths'   => [app_path('Models')],
    ],
];

Usage

Register the Plugin

In your Panel Provider (e.g., app/Providers/Filament/AdminPanelProvider.php):

use Leek\FilamentWorkflows\WorkflowsPlugin;

public function panel(Panel $panel): Panel
{
    return $panel
        // ...
        ->plugin(WorkflowsPlugin::make());
}

Customizing Navigation

WorkflowsPlugin::make()
    ->navigation(false)                    // Disable navigation items
    ->navigationGroup('Automation')        // Set navigation group
    ->navigationSort(100)                  // Set sort order

Integrate into Theme

Important

Filament v4 requires you to create a custom theme to support a plugin's additional Tailwind classes.

Add this plugin's views to your theme's theme.css file:

@source '../../../../vendor/leek/filament-workflows';

Compile your theme and run the Filament upgrade command:

npm run build
php artisan filament:upgrade

Queue Configuration

Workflows execute asynchronously via Laravel queues. Ensure a queue worker is running:

php artisan queue:work --queue=workflows

Or with Laravel Horizon, add the workflows queue to your configuration:

// config/horizon.php
'environments' => [
    'production' => [
        'supervisor-workflows' => [
            'connection' => 'redis',
            'queue' => ['workflows'],
            'balance' => 'auto',
            'processes' => 3,
            'tries' => 3,
        ],
    ],
],

Jobs are tagged with workflow and workflow_run:{id} for Horizon monitoring.

Triggerable Models

Models must be registered to trigger workflows automatically. There are two options:

Option 1: Configuration Array

Add model classes to config/leek-filament-workflows.php:

'triggerable_models' => [
    App\Models\User::class,
    App\Models\Order::class,
    App\Models\Lead::class,
],

Option 2: Auto-Discovery with Trait

Add the HasWorkflowTriggers trait to your models for more control:

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Leek\FilamentWorkflows\Concerns\HasWorkflowTriggers;

class Lead extends Model
{
    use HasWorkflowTriggers;

    /**
     * Customize display name in workflow UI.
     */
    public static function getWorkflowDisplayName(): string
    {
        return 'Sales Lead';
    }

    /**
     * Define status field for status_changed triggers.
     * Returns null if model doesn't have a status field.
     */
    public static function getWorkflowStatusField(): ?string
    {
        return 'status';
    }

    /**
     * Define watchable fields for model_updated triggers.
     * Only changes to these fields will trigger workflows.
     */
    public static function getWorkflowWatchableFields(): array
    {
        return ['status', 'priority', 'assigned_to'];
    }

    /**
     * Conditionally disable triggers (e.g., during bulk imports).
     */
    public function shouldTriggerWorkflows(): bool
    {
        return true;
    }

    /**
     * Add custom context data available in workflows.
     */
    public function getWorkflowContextData(): array
    {
        return [
            'company_name' => $this->company?->name,
            'owner_email' => $this->owner?->email,
        ];
    }
}

Enable auto-discovery in config:

'discovery' => [
    'enabled' => true,
    'paths' => [app_path('Models')],
],

Disabling Triggers Temporarily

During bulk operations, you may want to disable workflow triggers:

// Option 1: Using Laravel's withoutEvents
Lead::withoutEvents(function () {
    Lead::insert($bulkData); // Won't trigger workflows
});

// Option 2: Using shouldTriggerWorkflows() in your model
public function shouldTriggerWorkflows(): bool
{
    return !$this->importing; // Custom flag
}

Multi-Tenancy

The plugin includes built-in multi-tenancy support, allowing you to scope all workflow data to specific tenants in SaaS applications.

Enabling Multi-Tenancy

  1. Set the environment variables in your .env:
WORKFLOWS_TENANCY_ENABLED=true
WORKFLOWS_TENANCY_COLUMN=tenant_id
WORKFLOWS_TENANT_MODEL=App\Models\Team

Or configure in config/leek-filament-workflows.php:

'tenancy' => [
    'enabled' => true,
    'column'  => 'tenant_id',
    'model'   => App\Models\Team::class,
],
  1. The migration stubs automatically include the tenant column when tenancy.enabled is true. Ensure migrations are published and run:
php artisan vendor:publish --tag="leek-filament-workflows-migrations"
php artisan migrate
  1. The plugin integrates with Filament's tenant system via Filament::getTenant(). If you're using Filament's multi-tenancy features, no additional setup is required.

How Multi-Tenancy Works

When multi-tenancy is enabled:

  • Global Scope: All queries for workflows, runs, and secrets are automatically scoped to the current tenant
  • Auto-Assignment: New records automatically receive the current tenant ID on creation
  • Data Isolation: Tenants can only see and manage their own workflows

The BelongsToTenant trait handles tenant scoping automatically:

// All queries are scoped to current tenant
$workflows = Workflow::all(); // Only returns current tenant's workflows

// Bypass tenant scope for admin/global access
$allWorkflows = Workflow::query()->withoutTenantScope()->get();

Custom Tenant Resolution

By default, the tenant ID is retrieved from Filament's context:

$tenant = Filament::getTenant();
$tenantId = $tenant?->getKey();

If you need custom tenant resolution, you can extend the models and override getCurrentTenantId():

use Leek\FilamentWorkflows\Models\Workflow as BaseWorkflow;

class Workflow extends BaseWorkflow
{
    protected static function getCurrentTenantId(): int|string|null
    {
        // Custom tenant resolution logic
        return auth()->user()?->current_team_id;
    }
}

Then update your config to use the extended model:

'models' => [
    'workflow' => App\Models\Workflow::class,
    // ...
],

Tenant Column Requirements

The tenant column must exist on these tables when multi-tenancy is enabled:

  • workflows
  • workflow_runs
  • workflow_secrets

The workflow_run_steps table does not require a tenant column as it's scoped through the parent workflow_runs relationship.

Configuring the Tenant Column

The default tenant column is tenant_id. To use a different column name:

WORKFLOWS_TENANCY_COLUMN=team_id

Ensure your tenant table's foreign key matches the configured column name.

Built-in Triggers

Type Description
model-created Fires when a model record is created
model-updated Fires when a model record is updated (optionally watch specific fields)
model-deleted Fires when a model record is deleted
status-changed Fires when a model's status field changes to/from specific values
schedule Fires on a cron schedule (hourly, daily, weekly, monthly, custom)
date-condition Fires based on date field logic (X days before/after/on)
manual User-initiated execution via UI button
event Fires when a Laravel event is dispatched

Model Updated with Watch Fields

Configure the model-updated trigger to only fire when specific fields change:

  1. In trigger configuration, enable "Watch specific fields"
  2. Select the fields to watch: status, priority, assigned_to
  3. The workflow only triggers when those fields change

The model's getWorkflowWatchableFields() method defines available fields.

Status Changed Trigger

Triggers when a model's status transitions between specific values:

Configuration:

  • Status Field: status (auto-detected or from getWorkflowStatusField())
  • From Status: pending (leave empty for "any previous status")
  • To Status: approved

Example Use Case:

Trigger when: Order status changes from "pending" to "approved"
Action: Send confirmation email to customer

Date Condition Trigger

Triggers based on date field values relative to the current time.

Use Cases:

  • Send reminder 7 days before subscription expires
  • Follow up 30 days after last contact
  • Send birthday greetings on the day

Configuration:

  • Model: Select the model (e.g., Subscription)
  • Date Field: expires_at
  • Condition: 7 days before
  • Time: 09:00 (optional, when to run)

Event Trigger

Triggers when a Laravel event is dispatched.

Configuration:

  1. Select "Event" trigger type
  2. Enter the fully-qualified event class: App\Events\OrderPlaced
  3. Optionally add conditions on event properties

Your Event Class:

<?php

namespace App\Events;

class OrderPlaced
{
    public function __construct(
        public Order $order,
        public float $total,
    ) {}
}

Dispatching:

event(new OrderPlaced($order, $order->total));

The workflow receives the event's public properties as trigger data, accessible via {{trigger.total}}.

Built-in Actions

Communication

Type Description
send_email Send an email to a user or email address
send_notification Send an in-app notification to users or roles

Records (CRUD)

Type Description
create_record Create a new model record
update_records Update existing records with filters
delete_record Delete records (soft or hard delete)
assign_record Assign a record to a user
clone_record Duplicate a record with optional field overrides

Flow Control

Type Description
condition Conditional branching with true/false action paths
http_request Make HTTP API calls with secret support
transform_data Transform, map, and compute data
run_workflow Chain another workflow (with depth protection)

Variable Interpolation

Actions support variable interpolation using the {{placeholder}} syntax:

{{trigger.name}}              - Access trigger model field
{{trigger.user.email}}        - Access related model field
{{step.stepId.output}}        - Access output from a previous step
{{var.customVariable}}        - Access workflow variable
{{context.customVariable}}    - Alias for var
{{now}}                       - Current timestamp

Example in email subject:

Welcome {{trigger.name}} to {{trigger.company.name}}!

Condition Operators

The condition action supports 18 comparison operators:

Operator Description
equals Loose equality (==)
not_equals Loose inequality (!=)
strict_equals Strict equality (===)
gt Greater than (>)
gte Greater than or equal (>=)
lt Less than (<)
lte Less than or equal (<=)
contains String contains substring
not_contains String does not contain
starts_with String starts with
ends_with String ends with
in Value in array/comma-separated list
not_in Value not in array
is_empty Value is empty/null/blank
is_not_empty Value has content
is_null Value is null
is_not_null Value is not null
is_true Value is truthy
is_false Value is falsy
matches Regex pattern match

Workflow Secrets

Secrets store sensitive data (API keys, tokens) encrypted at rest.

Creating Secrets

Navigate to Workflows → Secrets in your Filament panel and create a secret:

  • Name: Identifier used in actions (e.g., stripe_api_key)
  • Value: The sensitive value (encrypted on save)

Using Secrets in HTTP Requests

In the HTTP Request action configuration:

  1. Bearer Token Auth: Enable "Use Bearer Token" and select your secret

    Authorization: Bearer {decrypted_secret_value}
    
  2. Custom Headers: Reference secrets in header values

    {
      "X-API-Key": "{{secret.my_api_key}}"
    }

Workflow Test Mode

The visual builder includes a "Test" button that allows dry-run execution:

  • Simulates side-effect actions (emails, notifications, HTTP requests) without executing them
  • Shows step-by-step execution preview with resolved variables
  • Displays condition evaluation results and which branches would execute
  • Does not create WorkflowRun records or persist any changes

Scheduled Workflows

For schedule-based triggers, add the processing command to your scheduler in routes/console.php:

use Illuminate\Support\Facades\Schedule;

Schedule::command('workflows:process-scheduled')->everyMinute();

Programmatic Execution

Triggering Workflows via Code

use Leek\FilamentWorkflows\Engine\WorkflowExecutor;
use Leek\FilamentWorkflows\Enums\TriggerType;
use Leek\FilamentWorkflows\Jobs\ExecuteWorkflowJob;
use Leek\FilamentWorkflows\Models\Workflow;

$workflow = Workflow::find($id);
$executor = app(WorkflowExecutor::class);

// Start a workflow run
$run = $executor->start(
    workflow: $workflow,
    triggerModel: $order,              // Optional: model that triggered
    triggerSource: TriggerType::MANUAL,
    triggeredBy: auth()->id(),
);

// Execute synchronously
$result = $executor->execute($run);

// Or dispatch for async execution
ExecuteWorkflowJob::dispatch($run->id);

Checking Run Status

use Leek\FilamentWorkflows\Enums\RunStatus;

$run = WorkflowRun::find($runId);

if ($run->status === RunStatus::COMPLETED) {
    // Workflow finished successfully
}

if ($run->status === RunStatus::FAILED) {
    $error = $run->error_message;
}

Debugging Workflows

Viewing Run History

Each workflow has a "Runs" relation manager showing:

  • Status: Pending, Running, Completed, Failed
  • Trigger: What initiated the run (Manual, Model Event, Schedule, etc.)
  • Triggered By: User who initiated (if applicable)
  • Steps: Expandable step-by-step execution log with input/output data
  • Duration: Time taken to complete

Step Statuses

Status Meaning
pending Not yet executed
running Currently executing
completed Successfully finished
failed Error occurred (see error message)
skipped Condition branch not taken

Common Issues

Workflow not triggering:

  • Verify model is in triggerable_models config or has HasWorkflowTriggers trait
  • Check shouldTriggerWorkflows() returns true on the model
  • Ensure a queue worker is running: php artisan queue:work --queue=workflows
  • Check rate limits haven't been exceeded
  • Verify the workflow is set to "Active"

Step failing:

  • Expand the step in run history to see the error message
  • Check Laravel logs (storage/logs/laravel.log) for stack traces
  • Verify variable placeholders like {{trigger.field}} resolve correctly
  • For HTTP requests, check the response status and body in step output

Variables not resolving:

  • Ensure the trigger model has the expected attributes
  • Check for typos in placeholder names (case-sensitive)
  • Use {{trigger.relation.field}} syntax for related model fields
  • Verify the relation is loaded (eager loading recommended)

Creating Custom Triggers

Triggers define when a workflow should execute. You can create custom triggers by implementing the BaseTrigger interface.

Trigger Interface

<?php

namespace App\Workflows\Triggers;

use Filament\Forms\Components\Select;
use Filament\Forms\Components\TextInput;
use Leek\FilamentWorkflows\Triggers\Contracts\BaseTrigger;

class PaymentReceivedTrigger implements BaseTrigger
{
    /**
     * Unique identifier for this trigger type (kebab-case).
     */
    public static function type(): string
    {
        return 'payment-received';
    }

    /**
     * Human-readable name for the trigger selection UI.
     */
    public static function name(): string
    {
        return 'Payment Received';
    }

    /**
     * Description shown in the trigger selection grid.
     */
    public static function description(): string
    {
        return 'Triggers when a payment is received and matches specified criteria';
    }

    /**
     * Heroicon name for UI display.
     */
    public static function icon(): string
    {
        return 'heroicon-o-currency-dollar';
    }

    /**
     * Filament color for badges (primary, success, warning, danger, info).
     */
    public static function color(): string
    {
        return 'success';
    }

    /**
     * Filament form schema for trigger configuration.
     */
    public static function configSchema(): array
    {
        return [
            Select::make('payment_type')
                ->label('Payment Type')
                ->options([
                    'one_time' => 'One-Time Payment',
                    'recurring' => 'Recurring Payment',
                    'any' => 'Any Payment',
                ])
                ->default('any')
                ->required(),
            TextInput::make('minimum_amount')
                ->label('Minimum Amount')
                ->numeric()
                ->prefix('$')
                ->placeholder('Any amount'),
        ];
    }

    /**
     * Default configuration values.
     */
    public static function defaultConfig(): array
    {
        return [
            'payment_type' => 'any',
            'minimum_amount' => null,
        ];
    }

    /**
     * Determine if workflow should trigger for this event.
     */
    public function shouldTrigger(array $config, mixed $subject, array $context = []): bool
    {
        // $subject is the payment model
        if (! $subject instanceof \App\Models\Payment) {
            return false;
        }

        // Check payment type
        if ($config['payment_type'] !== 'any' && $subject->type !== $config['payment_type']) {
            return false;
        }

        // Check minimum amount
        if (! empty($config['minimum_amount']) && $subject->amount < $config['minimum_amount']) {
            return false;
        }

        return true;
    }

    /**
     * Extract context data for variable resolution in actions.
     */
    public function getContextData(array $config, mixed $subject, array $context = []): array
    {
        return [
            'payment' => $subject,
            'amount' => $subject->amount,
            'customer_email' => $subject->customer->email ?? null,
        ];
    }

    /**
     * Dynamic description based on configuration (HTML-safe).
     */
    public static function getConfiguredDescription(array $config): string
    {
        $type = $config['payment_type'] ?? 'any';
        $amount = $config['minimum_amount'] ?? null;

        $desc = match ($type) {
            'one_time' => 'one-time payments',
            'recurring' => 'recurring payments',
            default => 'any payment',
        };

        if ($amount) {
            $desc .= " over <strong>\${$amount}</strong>";
        }

        return "Triggers on {$desc}";
    }

    /**
     * Validate trigger configuration.
     */
    public function validateConfig(array $config): array
    {
        $errors = [];

        if (empty($config['payment_type'])) {
            $errors[] = 'Payment type is required';
        }

        return [
            'valid' => empty($errors),
            'errors' => $errors,
        ];
    }
}

Registering Custom Triggers

Register your trigger in a service provider:

use Leek\FilamentWorkflows\Facades\Workflows;
use App\Workflows\Triggers\PaymentReceivedTrigger;

public function boot(): void
{
    Workflows::triggers()->register(PaymentReceivedTrigger::class);
}

Creating Custom Actions

Actions define what happens when a workflow executes. Create custom actions using the WorkflowAction trait.

Action Class

<?php

namespace App\Workflows\Actions;

use Filament\Forms\Components\Select;
use Filament\Forms\Components\Textarea;
use Filament\Forms\Components\TextInput;
use Leek\FilamentWorkflows\Concerns\WorkflowAction;
use Leek\FilamentWorkflows\Context\WorkflowContext;

class CreateSlackMessageAction
{
    use WorkflowAction;

    /**
     * Unique identifier for this action type.
     */
    public static function workflowType(): string
    {
        return 'create_slack_message';
    }

    /**
     * Human-readable name.
     */
    public static function workflowName(): string
    {
        return 'Send Slack Message';
    }

    /**
     * Brief description.
     */
    public static function workflowDescription(): string
    {
        return 'Send a message to a Slack channel or user';
    }

    /**
     * Category for grouping in the action selection grid.
     */
    public static function workflowCategory(): string
    {
        return 'Communication';
    }

    /**
     * Heroicon name.
     */
    public static function workflowIcon(): string
    {
        return 'heroicon-o-chat-bubble-left';
    }

    /**
     * Filament color name.
     */
    public static function workflowColor(): string
    {
        return 'info';
    }

    /**
     * Filament form schema for configuration.
     */
    public static function workflowConfigSchema(): array
    {
        return [
            Select::make('channel')
                ->label('Channel')
                ->options([
                    '#general' => '#general',
                    '#alerts' => '#alerts',
                    '#sales' => '#sales',
                ])
                ->required(),
            TextInput::make('title')
                ->label('Message Title')
                ->placeholder('Use {{trigger.name}} for dynamic values')
                ->helperText('Supports variable interpolation: {{trigger.field}}, {{step.stepId.output}}'),
            Textarea::make('message')
                ->label('Message Body')
                ->rows(4)
                ->required()
                ->helperText('Supports variable interpolation'),
        ];
    }

    /**
     * Default configuration values.
     */
    public static function workflowDefaultConfig(): array
    {
        return [
            'channel' => '#general',
            'title' => '',
            'message' => '',
        ];
    }

    /**
     * Execute the action. Config values are pre-resolved with variables.
     */
    public function handle(array $config, ?WorkflowContext $context = null): array
    {
        $channel = $config['channel'];
        $title = $config['title'] ?? '';
        $message = $config['message'];

        // Your Slack integration logic here
        // Example using Laravel's Slack notification channel:
        // Notification::route('slack', config('services.slack.webhook'))
        //     ->notify(new SlackMessage($channel, $title, $message));

        return [
            'success' => true,
            'channel' => $channel,
            'message_sent' => true,
            'sent_at' => now()->toIso8601String(),
        ];
    }

    /**
     * Optional: Custom validation.
     */
    public static function validateWorkflowConfig(array $config): array
    {
        $errors = [];

        if (empty($config['channel'])) {
            $errors[] = 'Slack channel is required';
        }

        if (empty($config['message'])) {
            $errors[] = 'Message body is required';
        }

        return [
            'valid' => empty($errors),
            'errors' => $errors,
        ];
    }
}

Registering Custom Actions

Register your action in a service provider:

use Leek\FilamentWorkflows\Facades\Workflows;
use App\Workflows\Actions\CreateSlackMessageAction;

public function boot(): void
{
    Workflows::actions()->register(CreateSlackMessageAction::class);
}

Authorization

Resource Policies

Create policies to control access to workflow resources:

<?php

namespace App\Policies;

use App\Models\User;
use Leek\FilamentWorkflows\Models\Workflow;

class WorkflowPolicy
{
    public function viewAny(User $user): bool
    {
        return $user->hasPermission('workflows.view');
    }

    public function view(User $user, Workflow $workflow): bool
    {
        return $user->hasPermission('workflows.view');
    }

    public function create(User $user): bool
    {
        return $user->hasPermission('workflows.create');
    }

    public function update(User $user, Workflow $workflow): bool
    {
        return $user->hasPermission('workflows.edit');
    }

    public function delete(User $user, Workflow $workflow): bool
    {
        return $user->hasPermission('workflows.delete');
    }
}

Register in AuthServiceProvider:

protected $policies = [
    \Leek\FilamentWorkflows\Models\Workflow::class => \App\Policies\WorkflowPolicy::class,
    \Leek\FilamentWorkflows\Models\WorkflowSecret::class => \App\Policies\WorkflowSecretPolicy::class,
];

Requirements

  • PHP 8.2 or higher
  • Laravel 10.x or higher
  • Filament 4.x or higher

Changelog

Please see CHANGELOG for more information on what has changed recently.

Issues & Support

Report bugs and request features on the public issue tracker.

Security

If you discover any security-related issues, please email [email protected] instead of using the issue tracker.

Credits

License

This is a commercial product. You must purchase a license to use this plugin in production.

Purchase a license at Anystack.sh

Code Distribution

None of this plugin’s licenses permit publicly sharing its source code. As a result, you cannot build an application that uses this plugin and then publish that application’s code in an open-source repository, on hosting services, or through any other public code-distribution platform.

Comments are disabled for this gist.