briefley/filament-workflow-builder
A visual workflow builder for Filament panels. Design approval and automation flows with a drag-and-drop canvas, define states, transitions, conditions, and actions, then attach workflows to Eloquent models and manage runs from the admin UI.
Install and publish in one pass, then treat the migrations as the source of truth for the domain model before you touch the UI:
composer require briefley/filament-workflow-builder
php artisan vendor:publish --tag="filament-workflow-builder-migrations"
php artisan vendor:publish --tag="filament-workflow-builder-config"
php artisan migrate
Register the plugin on the panel(s) that should own authoring (usually an admin panel, not a customer-facing one):
use Briefley\FilamentWorkflowBuilder\FilamentWorkflowBuilderPlugin;
public function panel(Panel $panel): Panel
{
return $panel
->plugins([
FilamentWorkflowBuilderPlugin::make(),
]);
}
Where to look first, in this order:
config/filament-workflow-builder.php — queue connection, executor map, scheduling options, table names. Nearly every "why isn't it running" question resolves here.pending, running, completed, failed, skipped) and whether runs are persisted per-step or per-workflow. That shapes your reporting and retry strategy.src/ for the step executor contract and any events dispatched around transitions. Wire your own logging to those events rather than polling tables.First use case: pick something with a low ceiling but real value — an approval chain (order refund over threshold → manager → finance) or a content pipeline (draft → review → publish). Avoid starting with anything that mutates money or sends irreversible external calls; you want to learn the run/retry semantics on something you can safely replay.
Executors are the main extension surface. Each step type maps to a class implementing the executor contract; the class receives the step's configured payload plus the run context and returns success/failure. Register them in config, not by convention, so you can swap implementations per environment:
// config/filament-workflow-builder.php
'executors' => [
'send_email' => App\Workflows\Executors\SendEmailExecutor::class,
'create_invoice' => App\Workflows\Executors\CreateInvoiceExecutor::class,
'http_webhook' => App\Workflows\Executors\HttpWebhookExecutor::class,
],
Keep executors thin and idempotent. The executor should do one thing and hand off anything heavy to its own job; the workflow engine is the orchestrator, not the worker.
Triggering from application code. Don't rely on humans clicking "run" in the panel. Seed workflows, then start runs from domain events:
// app/Observers/OrderObserver.php
public function updated(Order $order): void
{
if ($order->wasChanged('status') && $order->status === 'paid') {
Workflow::where('slug', 'post-purchase')->first()
?->startFor($order);
}
}
If the package exposes a facade or a start() on the workflow model, wrap it in a small WorkflowRunner service of your own. That gives you one place to add tenancy scoping, context payloads, and telemetry — and one place to patch if the API shifts.
Human-in-the-loop steps. For approval-style steps, model them as a step that pauses the run and writes a task/notification, then resumes from a Filament action or a signed route. Persist the approver's user ID in the step record so your audit trail survives model deletions. This is the pattern that makes the package worth using over hand-rolled state machines.
Queued and scheduled execution. Point the package at a dedicated queue (e.g. workflows) in config so long step chains never starve your default queue. For scheduled workflows, confirm whether the package registers its own scheduler entry or expects you to add one to routes/console.php / Kernel — the latter is common and easy to miss:
Schedule::command('workflow:run-due')->everyMinute()->withoutOverlapping();
Testing. Fake the queue, assert on run/step rows, and unit-test executors in isolation:
Queue::fake();
$run = $workflow->startFor($order);
Queue::assertPushed(SendEmailJob::class);
$this->assertDatabaseHas('workflow_runs', [
'id' => $run->id,
'status' => 'pending',
]);
Treat the executor contract as the seam: your executor tests need no Filament, no panel, no DB beyond a fixture.
Multi-tenancy. If the admin panel is tenant-scoped, verify the workflow and run resources respect Filament's ->tenant() scoping. If they don't, add a global scope on the models via a service provider rather than forking the resource.
composer.json, commit the published config and migrations to your repo, and assume you may need to read vendor/ to answer questions. Budget for potentially maintaining a fork.DB::afterCommit() or start the run from an event listener that fires post-commit.config('app.timezone'). If your business hours are in a different zone, set it explicitly and write a test that asserts a run fires at the expected local time.failed() handler that writes the exception plus the step payload to your log channel — the payload is what you'll actually need. Enable Horizon or a similar dashboard before go-live; debugging a chain of five queued steps from logs alone is miserable.filament/filament before upgrading your panel; a Filament major bump will likely require a package major bump too.How can I help you explore Laravel packages today?