Approve and Reject Tasks
Approval nodes suspend a workflow until an authorized actor approves or rejects. This page covers pending tasks, the runtime API, reject strategies, and audit logging.
Pending tasks
When the runtime reaches an approval node, DBFlow creates a WorkflowTask in dbflow_workflow_tasks and one or more rows in dbflow_workflow_task_assignments. The instance stays in running status until the task is acted on.
use DbflowLabs\Core\Enums\WorkflowTaskAssignmentStatus;
use DbflowLabs\Core\Enums\WorkflowTaskStatus;
use DbflowLabs\Core\Models\WorkflowTaskAssignment;
$instance = $dispute->runningWorkflowInstance('refund_dispute_approval');
$pendingTask = $instance?->tasks()
->where('status', WorkflowTaskStatus::Pending)
->first();
$myAssignments = WorkflowTaskAssignment::query()
->where('assignee_user_id', $user->getKey())
->where('status', WorkflowTaskAssignmentStatus::Pending)
->get();
Assignees are defined in the workflow JSON under each approval node's config.assignees. See Assignee resolution and Host Integration.
type |
Alpha runtime |
|---|---|
user |
Resolves a single user ID from value. |
callback |
callback (or value) must match DBFlow::registerAssigneeResolver() key. |
permission |
value is a resolver registry key — not Spatie Permission or Laravel Gate. |
role |
Schema only — unsupported at runtime (throws InvalidWorkflowDefinitionException). |
Approve a task
Pass the WorkflowTask model to the static runtime entrypoint:
use DbflowLabs\Core\DBFlow;
DBFlow::approve($task, $user, 'Looks good, approved.');
Security: When
$actorisnull, Core skips pending-assignment validation on approve. Always pass the authenticated user in production.
When approved, DBFlow:
- Writes a
task_approvedentry todbflow_workflow_logs - Marks the task and relevant assignments complete
- Advances the workflow to the next node, or completes the instance
- Clears
active_keyon the instance when it reaches a terminal state
Reject a task
use DbflowLabs\Core\DBFlow;
use DbflowLabs\Core\Enums\RejectStrategy;
DBFlow::reject($task, $user, 'Missing invoice attachment.', RejectStrategy::Starter);
The fourth argument is the reject strategy. The fifth argument ($targetNodeKey) is only used with RejectStrategy::SpecificNode.
Reject strategies
| Enum case | Stored value | Behaviour |
|---|---|---|
RejectStrategy::Starter |
starter |
Return toward the initiator / restart path |
RejectStrategy::PreviousNode |
previous_node |
Return to the immediately preceding approval node |
RejectStrategy::SpecificNode |
specific_node |
Return to a named node (pass $targetNodeKey) |
RejectStrategy::End |
end |
Terminate the workflow as rejected |
DBFlow::reject($task, $user, 'Escalation required.', RejectStrategy::PreviousNode);
DBFlow::reject(
$task,
$user,
'Send back to support lead.',
RejectStrategy::SpecificNode,
'support_lead_review',
);
DBFlow::reject($task, $user, 'Rejected permanently.', RejectStrategy::End);
The Standard Filament My Tasks page defaults to RejectStrategy::End unless you override config('dbflow-filament.reject_strategy').
See Reject Strategies for a focused guide on when to use each enum case.
Approval modes
Multi-assignee approval nodes support three modes via approval_mode in the node config:
| Enum case | Value | Behaviour |
|---|---|---|
ApprovalMode::Any |
any |
Any one assignee can approve |
ApprovalMode::All |
all |
All assignees must approve |
ApprovalMode::Sequential |
sequential |
Assignees approve in sequence order |
Comments
Comments are optional on approve and recommended on reject. They are stored on dbflow_workflow_logs.comment.
In the Filament UI, comments appear in the timeline next to the actor name and timestamp.
Audit logs
Every approve and reject action is recorded automatically through WorkflowLogger. Query logs via the model helper or directly:
foreach ($dispute->workflowLogs('refund_dispute_approval')->with('actor')->get() as $log) {
echo "{$log->created_at} — {$log->event->value}";
}
WorkflowLogEvent classifies log rows (workflow_started, task_approved, task_rejected, workflow_completed, and others). These are log event types, not Laravel Event classes dispatched to the container.
Logged fields include:
- Event type (
WorkflowLogEvent) - Actor user (when available)
- Related task (when applicable)
- Comment (when provided)
- JSON payload metadata
- Timestamp
Double-submit protection
DBFlow stores a unique active_key on dbflow_workflow_instances while an instance is running. Format: {workflowKey}:{morphClass}:{workflowableId}.
If a second start is attempted for the same workflowable slot, the runtime throws WorkflowAlreadyRunningException. If a task is no longer pending, callers may receive TaskNotPendingException or UserCannotApproveTaskException. Starting against a missing or unpublished definition throws WorkflowNotFoundException, WorkflowNotPublishedException, or WorkflowNotAvailableException.
use DbflowLabs\Core\DBFlow;
use DbflowLabs\Core\Exceptions\TaskNotPendingException;
use DbflowLabs\Core\Exceptions\WorkflowAlreadyRunningException;
try {
DBFlow::start('refund_dispute_approval', $dispute, $user);
} catch (WorkflowAlreadyRunningException) {
// Already in flight for this record + workflow key
}
try {
DBFlow::approve($task, $user);
} catch (TaskNotPendingException) {
// Another actor already completed this task
}
All approval actions run inside database transactions.
Cancel a running workflow
use DbflowLabs\Core\DBFlow;
DBFlow::cancel($instance, $user, 'Withdrawn by submitter.');
Core marks the instance cancelled, cancels pending tasks, clears active_key, and invokes WorkflowHooks::onCancelled(). It does not verify that $user is the original submitter — enforce withdraw/cancel policy in your host layer before calling cancel().
Whether business actions may continue after cancel (for example confirming a draft document) is also host policy, not enforced by Core.
What's next
-
Host Integration → — sync, assignees, guards, exception reference
-
Workflow Timeline → — present audit history in Filament
-
Filament Resource Actions → — start workflows from resources
-
Code-defined Workflows → — node types and transition rules
-
Architecture → —
active_keyconcurrency internals