packstub/filament-flow
Filament Flow is a Laravel Filament package for defining and managing multi-step workflows directly in your admin panel. Build, visualize, and track flows for approvals, pipelines, and status-driven records in a clean, integrated UI.
Install and wire it up in the time-honoured Filament plugin order:
composer require packstub/filament-flow
php artisan vendor:publish --tag="packstub-flow-config"
php artisan vendor:publish --tag="packstub-flow-migrations" # or copy the stub manually
php artisan migrate
php artisan filament:assets
Then register the plugin on the panel you want the automation surface to live in (in a PanelProvider's ->plugins([...])), and make sure you have at least one supervised queue worker — not because runs must be queued, but because Wait steps always are, and a panel with a dead worker will look fine right up until the first delayed step silently never fires.
Where to look first, in this order:
docs/ — customer-facing docs with screenshots; the fastest way to see the intended authoring UX.config/packstub-flow.php — queue connection, node registration, access control, styling. This is the file you will actually edit.src/Nodes/ — the Trigger / Action / Condition base classes plus the built-ins. This is the extension seam you will spend your life in.src/Engine/ — Dispatcher, Graph, Runner. Read these before you debug anything that "didn't run".resources/lang/en/flow.php — every UI string, node name, and description in one file. Publish or override it rather than fighting copy in the canvas.First use case: pick a read-only automation with zero write-back risk — a lead-routing workflow that fires on record creation and sends a notification. You get the full loop (trigger → condition → action → run history) without betting any production data on a 20-star package. Once that runs cleanly for a business cycle, add a Wait step and deliberately kill the worker mid-run, so you learn the failure shape on your terms rather than at 2am.
Author in the UI, extend in code. Treat the canvas as the configuration surface and your PHP as the logic. The leverage is in keeping the built-in node set generic and putting domain behaviour in your own node classes behind the Trigger/Action base classes. Every bespoke action you can express as a node is one fewer "can you automate X?" ticket.
Trigger programmatically from application code. The Engine\Dispatcher is the seam that matters day-to-day: you fire workflows from observers, listeners, or controllers with the subject record, rather than relying solely on the panel's trigger nodes. This keeps the automation layer an implementation detail of your app rather than a parallel universe:
// e.g. in a model observer — verify the exact signature against src/Engine/Dispatcher.php
app(Dispatcher::class)->dispatch($workflow, $model);
Wrap this in a feature flag so you can disable an automation without editing the graph.
Custom nodes, with idempotency baked in. Extend the Action base class, implement the handle method, declare your config schema so the ManageNode slide-over can collect inputs, and register the node in config. Then add the label and description to flow.php. Keep the node thin — it should translate config into a call to a service class you already test. That way the node is a five-line adapter and the business logic is testable without booting the canvas.
final class AssignOwnerAction extends Action
{
public function handle(WorkflowRun $run, array $config): void
{
app(AssignOwner::class)->for($run->subject, $config['strategy']);
}
}
Design every action so running it twice is harmless. Partial graph execution is a real state: a mid-graph failure leaves earlier side effects applied. Idempotency keys on external calls, existence checks before writes, and "send notification" steps that tolerate duplicates are the difference between a retry and an incident.
Separate the queue lane. Give workflows their own queue name and worker pool so a backlog of Wait steps or a slow SaaS call cannot starve your mail or default jobs. Set explicit per-job timeouts. The plugin gives you the runner; capacity planning and failure visibility are yours.
Webhook ingress last. The bundled controller is the entry point, but authentication is a decision you own — verify signatures, use a shared secret, and reject replays. Do not expose it publicly until you have read the controller source and added whatever check it is missing.
Use run history as your observability layer. The runs relation manager on the workflow resource is the first place to look when something "didn't happen", before you touch a log file. Correlate a run back to the triggering record and to your failed-jobs table.
Test the nodes, not the canvas. composer test (Pest), composer analyse (PHPStan level 6), and composer lint are the day-to-day loop. Write Pest tests for each custom node against a testbench boot — the package's own suite shows the pattern. You should not need bun or Vite unless you fork the Svelte side; the canvas ships prebuilt in resources/dist.
php artisan filament:assets on every upgrade. The v1.6.1 notes literally say the stylesheet was rebuilt and you should run it. Stale assets are a recurring operational step with this package; add it to your deploy script rather than remembering it.src/Engine/Graph.php before trusting that assumption either way.resources/dist/flow.css is scoped by convention, not by a guarantee — check before you restyle anything globally.resources/lang/en/flow.php is cheap; asking the package to change wording is not. Localisation is straightforward once you know this is where it lives.handle() throws on a null subject? → graph config valid? The Dispatcher is quiet by default; add your own logging at the dispatch call site rather than instrumenting the engine.resources/js/, you inherit bun, Vite, svelte-check, vitest, and the obligation to commit built dist files. Stay on the PHP side of the fence unless the visual editor is genuinely the product.How can I help you explore Laravel packages today?