Add DBFlow to an Eloquent Model

DBFlow is designed around a single principle: your Eloquent model is the workflow subject. A workflow is not a global pipeline — it is always bound to a specific record instance.

Model-first design

When a refund dispute is approved or a procurement request is signed off, the workflow belongs to that record. DBFlow stores workflow state in its own tables and references your model through workflowable_type and workflowable_id.

Your model stays clean. No workflow columns are required unless you choose to mirror business status locally.

Typical workflow subjects

Model Workflow
RefundDispute Support lead → amount gate → risk review
ProcurementRequest Manager → amount gate → finance review
ExpenseClaim Manager → finance chain
Vendor Compliance → legal → onboarding
Order Auto-approve below threshold, manual review above

Any model that needs multi-step human review, conditional branching, or a full audit trail is a good candidate.

Attaching DBFlow to a model

Add the HasWorkflow trait. Implement optional contracts when you need richer integration:

Contract Purpose
Workflowable Business key and display name for admin UI
WorkflowContextInterface Variables exposed to transition conditions
WorkflowRouteResolvable Link from workflow UI back to your Filament resource
<?php

namespace App\Models;

use DbflowLabs\Core\Contracts\Workflowable;
use DbflowLabs\Core\Contracts\WorkflowContextInterface;
use DbflowLabs\Core\Traits\HasWorkflow;
use Illuminate\Database\Eloquent\Model;

class ProcurementRequest extends Model implements Workflowable, WorkflowContextInterface
{
    use HasWorkflow;

    public function workflowBusinessKey(): ?string
    {
        return $this->reference_number;
    }

    public function workflowDisplayName(): string
    {
        return (string) $this->title;
    }

    public function getWorkflowVariables(): array
    {
        return ['amount' => (float) $this->amount];
    }
}

No migration changes are required on your model's table for DBFlow itself. Publish and run DBFlow migrations during installation.

HasWorkflow helpers

$request->startWorkflow('procurement_request_approval');

$request->workflowInstances();
$request->latestWorkflowInstance('procurement_request_approval');
$request->runningWorkflowInstance('procurement_request_approval');
$request->completedWorkflowInstance('procurement_request_approval'); // latest terminal: approved | rejected | cancelled
$request->hasRunningWorkflow('procurement_request_approval');
$request->workflowLogs('procurement_request_approval');

startWorkflow($key) delegates to DBFlow::start($key, $this, DbflowAuth::currentUser()).

When config('dbflow.binding_mode') is ui, workflows may auto-start on model created only when the matching dbflow_workflows row has model_type equal to the workflowable morph class and a published version exists. The default alpha mode is code, which requires an explicit start call. SyncWorkflowDefinitions does not set model_type — configure it in seeders or admin tooling. See Host Integration. SyncWorkflowDefinitions does not set model_type automatically — configure it in seeders or admin tooling.

How state is stored

dbflow_workflows                 — workflow registry and draft metadata
dbflow_workflow_versions         — versioned JSON definitions
dbflow_workflow_instances        — one row per run (status, current_node_key, active_key)
dbflow_workflow_tasks            — approval tasks per instance
dbflow_workflow_task_assignments — per-user assignments
dbflow_workflow_logs             — audit trail

This keeps your procurement_requests table focused on business data.

Multiple workflows per model

A model can participate in multiple workflow definitions. Start the intended workflow explicitly by key:

$request->startWorkflow('procurement_request_approval');
$request->startWorkflow('urgent_procurement_approval');

Use runningWorkflowInstance($workflowKey) or hasRunningWorkflow($workflowKey) to scope queries per definition.

Status mapping (optional)

DBFlow tracks workflow status on dbflow_workflow_instances. To mirror status onto your model column, use workflow hooks — not Laravel event classes:

<?php

namespace App\DBFlow\ProcurementRequest;

use DbflowLabs\Core\Contracts\WorkflowHooks;
use DbflowLabs\Core\Models\WorkflowInstance;

final class ProcurementRequestWorkflowHooks implements WorkflowHooks
{
    public function onStarted(WorkflowInstance $instance): void {}

    public function onApproved(WorkflowInstance $instance): void
    {
        $instance->workflowable?->update(['status' => 'approved']);
    }

    public function onRejected(WorkflowInstance $instance): void
    {
        $instance->workflowable?->update(['status' => 'rejected']);
    }

    public function onCancelled(WorkflowInstance $instance): void {}
}

Register hooks during application boot:

use DbflowLabs\Core\DBFlow;
use DbflowLabs\Core\Services\WorkflowHooksRegistry;

DBFlow::registerWorkflowHooks(
    app(WorkflowHooksRegistry::class),
    'procurement_request_approval',
    ProcurementRequestWorkflowHooks::class,
);

Keep models clean

Avoid embedding the full approval state machine inside the model:

// Avoid this pattern
public function approve(User $user): void
{
    if ($this->status === 'pending_finance') {
        $this->status = 'pending_manager';
        // ...
    }
}

Instead, resolve the pending task and call the runtime:

use DbflowLabs\Core\DBFlow;
use DbflowLabs\Core\Enums\WorkflowTaskStatus;

$task = $request->runningWorkflowInstance('procurement_request_approval')
    ?->tasks()
    ->where('status', WorkflowTaskStatus::Pending)
    ->first();

DBFlow::approve($task, $user);

Next step

Host Integration → — register, sync, assignees

Purchase Request Approval → — second demo scenario with status mapping

Or continue with Code-defined Workflows →

Something wrong? Open an issue on GitHub