Filament Resource Actions
Alpha notice: Patterns below reflect the current
dbflow-demointegration. Your resource structure may differ.
Filament resource actions are the right place to start a workflow from business records. Approvals and rejections for assignees usually happen on My Tasks (MyWorkflowTasks), which already wraps DBFlow::approve() and DBFlow::reject().
Keep resource classes thin: extract lifecycle actions into a concern or dedicated action class, call Core through DbflowLabs\Core\DBFlow, and let hooks map workflow outcomes back to host status columns.
Pattern: HasLifecycleActions concern
The dbflow-demo project uses a trait per resource — for example RefundDisputeResource\Concerns\HasLifecycleActions and ProcurementRequestResource\Concerns\HasLifecycleActions.
Responsibilities split cleanly:
| Method | Role |
|---|---|
getLifecycleHeaderActions() |
Register Filament Action instances |
makeSubmit…Action() |
Visibility rules + DBFlow::start() |
submitThroughDbflow() |
Runtime call + exception handling |
makeLifecycleAction() |
Pure host mutations without DBFlow |
runPureHostLifecycleMutation() |
Shared notification + update() helper |
Start workflow from a resource
Submit actions call DBFlow::start() with the workflow key, workflowable model, and authenticated user:
use App\DBFlow\RefundDispute\RefundDisputeWorkflow;
use DbflowLabs\Core\DBFlow;
use DbflowLabs\Core\Exceptions\WorkflowAlreadyRunningException;
use Illuminate\Support\Facades\Auth;
protected function submitThroughDbflow(RefundDispute $record): void
{
try {
DBFlow::start(
RefundDisputeWorkflow::KEY,
$record,
Auth::user(),
);
Notification::make()->title('Submitted for review.')->success()->send();
} catch (WorkflowAlreadyRunningException) {
Notification::make()->title('A workflow is already running.')->danger()->send();
}
}
Use named constants for workflow keys (RefundDisputeWorkflow::KEY) so resources and seeders stay aligned.
Visibility rules
Show submit only when no running workflow
->visible(fn (RefundDispute $record): bool =>
$record->status === RefundDisputeStatus::Investigating
&& ! $record->hasRunningWorkflow(RefundDisputeWorkflow::KEY)
&& ! $record->isClosed()
)
hasRunningWorkflow($workflowKey) comes from the HasWorkflow trait.
Approve / reject on My Tasks, not the resource
Assignees act on WorkflowTaskAssignment rows through MyWorkflowTaskTableActions. Those actions call MyWorkflowTaskActionRunner, which invokes Core approve/reject handlers.
If you need approve/reject on a resource page, resolve the pending task explicitly:
use DbflowLabs\Core\DBFlow;
use DbflowLabs\Core\Enums\WorkflowTaskStatus;
use DbflowLabs\Core\Enums\RejectStrategy;
$task = $record->runningWorkflowInstance('refund_dispute_approval')
?->tasks()
->where('status', WorkflowTaskStatus::Pending)
->first();
if ($task !== null) {
DBFlow::approve($task, auth()->user(), 'Approved from resource.');
}
Prefer My Tasks for routine approvals so permission checks stay centralized in WorkflowFilamentPermissions and your PermissionChecker implementation.
Implement WorkflowRouteResolvable on workflowable models so DBFlow instance/task tables can link back to your Filament edit page (getWorkflowShowUrl()).
Guard downstream business actions
Block conversion, payment, fulfillment, or archive actions until host status reflects approval:
->visible(fn (ProcurementRequest $record): bool =>
$record->status === ProcurementRequestStatus::Approved
)
In the demo, archiveRecord is only visible when status is Approved. DBFlow does not own ERP or payment integration — those remain host actions gated by WorkflowHooks outcomes.
Map workflow outcomes with hooks
Register WorkflowHooks so terminal workflow states update your model columns:
// AppServiceProvider::boot()
DBFlow::registerWorkflowHooks(
app(WorkflowHooksRegistry::class),
ProcurementRequestWorkflow::KEY,
ProcurementRequestWorkflowHooks::class,
);
ProcurementRequestWorkflowHooks::onApproved() sets status to approved. onRejected() sets rejected. onStarted() moves draft/rejected records to pending_review.
Keep ERP adapters, notifications, and inventory logic outside Core — trigger them from hooks or listeners you control.
Present workflow state on the resource
Demo resources use a presenter class (RefundDisputeWorkflowPresenter) to surface:
- Running workflow status label
- Pending task count
- Link to
ViewWorkflowInstanceor My Tasks
This keeps infolist/table columns readable without bloating the resource class.
Dual-mode demo (DBFlow on/off)
dbflow-demo uses DbflowSettings::isEnabled() to fall back to pure host lifecycle buttons when DBFlow is disabled — useful for comparisons and tests. Production hosts typically omit the fallback and always route submissions through DBFlow.
Checklist
- Extract actions into a concern or action class.
- Start workflows with
DBFlow::start($key, $model, $user). - Guard submit with
hasRunningWorkflow($key). - Register
WorkflowHooksfor status mapping. - Gate downstream mutations on host
status, not on guessed workflow helpers. - Link operators to My Tasks and instance detail for approvals and audit history.
What's next
- Filament UI → — register Standard pages
- Workflow Timeline → — audit presentation
- Refund Approval → — reference implementation
- Purchase Request Approval → — procurement variant