DBFlow Architecture
DBFlow is a DAG workflow runtime embedded inside your Laravel application. It is a schema-driven execution engine that advances object-bound state machines with database-backed concurrency guarantees.
High-level topology
┌──────────────────┐ ┌─────────────────────┐ ┌──────────────────────────┐
│ Laravel 13 App │────▶│ DBFlow DAG Runtime │────▶│ dbflow_workflow_* tables│
│ (Eloquent Model)│ │ (DBFlow::start/…) │ │ MySQL / PG / SQLite │
└──────────────────┘ └─────────────────────┘ └──────────────────────────┘
│ │
│ ▼
│ ┌─────────────────────┐
└────────────────▶│ WorkflowHooks │
│ (host callbacks) │
└─────────────────────┘
Pro licensing and dbflow.dev HQ services are separate from the open-core runtime. Your host application validates Pro entitlements; Core does not require a license check to execute workflows during local development.
DAG runtime mechanics
A workflow definition is a directed acyclic graph stored as JSON (schema_version: 1.0). Each node is one of five primitives:
| Node type | Responsibility |
|---|---|
| start | Single entry point |
| approval | Suspends until an authorized actor approves or rejects; creates WorkflowTask rows |
| condition | Metadata/documentation node; routing uses outgoing transitions[].condition |
| action | Invokes a registered ActionManager handler key |
| end | Terminal node; sets instance completion status |
Execution lifecycle
- Resolve definition — Load the active published version for the workflow key.
- Start instance — Create
dbflow_workflow_instanceswithactive_keyset while running. - Traverse — Walk transitions from
start. Conditions evaluate immediately; approvals create tasks and return; actions execute inline. - Persist logs —
WorkflowLoggerwritesdbflow_workflow_logsrows (WorkflowLogEventtypes). - Invoke hooks —
WorkflowHookscallbacks fire on started / approved / rejected / cancelled boundaries.
Approvals do not block the PHP process. The HTTP request returns after persisting the pending task. Resumption happens when an actor calls DBFlow::approve() or DBFlow::reject() on the WorkflowTask.
Concurrency: active_key
Parallel requests are a common source of corrupted hand-rolled workflows. DBFlow stores a unique active_key column on dbflow_workflow_instances while an instance is running.
Format:
{workflowKey}:{workflowableType}:{workflowableId}
There is no separate dbflow_active_keys table.
-- Simplified: active_key lives on the instance row
ALTER TABLE dbflow_workflow_instances
ADD active_key VARCHAR(255) NULL,
ADD UNIQUE KEY uq_dbflow_workflow_instances_active_key (active_key);
When the instance reaches a terminal status, active_key is cleared. Starting a second concurrent instance for the same workflowable slot throws WorkflowAlreadyRunningException. Acting on a non-pending task throws TaskNotPendingException or UserCannotApproveTaskException.
Properties:
- Atomic — enforced inside database transactions with row-level checks
- Database-native — no Redis requirement; works on MySQL, PostgreSQL, and SQLite
- Fail-fast — duplicate starts and stale task actions surface explicit exceptions
Lifecycle hooks and Laravel events
DBFlow dispatches Laravel container events under DbflowLabs\Core\Events\* (WorkflowStarted, WorkflowCompleted, TaskApproved, TaskReassigned, ActionFailed, and others). Use these when you prefer listeners, queues, or broadcasting.
For synchronous host side effects on the business model, register callbacks through WorkflowHooks and TaskHooks:
use DbflowLabs\Core\DBFlow;
use DbflowLabs\Core\Services\WorkflowHooksRegistry;
DBFlow::registerWorkflowHooks(
app(WorkflowHooksRegistry::class),
'refund_dispute_approval',
RefundDisputeWorkflowHooks::class,
);
interface WorkflowHooks
{
public function onStarted(WorkflowInstance $instance): void;
public function onApproved(WorkflowInstance $instance): void;
public function onRejected(WorkflowInstance $instance): void;
public function onCancelled(WorkflowInstance $instance): void;
}
WorkflowLogEvent is an enum used to classify rows in dbflow_workflow_logs. It is separate from both Laravel Event classes and WorkflowHooks callbacks.
User resolution
Actors passed to DBFlow::start(), approve(), reject(), and cancel() are resolved through DbflowLabs\Core\Contracts\UserResolver.
The default ConfigUserResolver resolves the Eloquent user model from:
config('dbflow.auth.model')config('auth.providers.users.model')App\Models\Userfallback
Override the resolver class via config('dbflow.auth.resolver') when your host uses UUID/ULID primary keys or custom authentication stacks.
Runtime entrypoints
All adapters should call the static DBFlow facade class — do not instantiate action classes manually in host code:
DBFlow::start(string $workflowKey, Model $workflowable, mixed $startedBy = null, array $metadata = []);
DBFlow::approve(WorkflowTask $task, mixed $actor = null, ?string $comment = null);
DBFlow::reject(WorkflowTask $task, mixed $actor = null, ?string $comment = null, RejectStrategy $strategy = RejectStrategy::Starter, ?string $targetNodeKey = null);
DBFlow::cancel(WorkflowInstance $instance, mixed $actor = null, ?string $comment = null);
DBFlow::reassign(WorkflowTask $task, mixed $fromActor, string $toUserId, ?string $comment = null);
Filament integration surface
Standard UI registers through:
DbflowLabs\Filament\Support\DBFlowFilamentPanel::register($panel);
Key pages:
MyWorkflowTasks— assignee task inboxWorkflowInstances/ViewWorkflowInstance— operator visibility and timelineWorkflowResource— definition draft/edit/publish
Timelines are rendered with WorkflowInstanceTimelinePresenter and the dbflow-filament::components.timeline Blade partial.
DBFlowFilamentPanel gates on dbflow-filament.enabled only — hosts should also gate registration on dbflow.enabled and product feature flags. Replace default AllowAllPermissionChecker before production.
Pro canvas (DBFlow Pro)
dbflowlabs/filament-pro adds:
ProCanvasField— Filament form fieldProCanvasWorkflowDefinitionEditorResolver— replaces Standard definition editorProGraphBlueprintCompiler/WorkflowGraphJsonParser— graph JSON → Core blueprint
Pro 1.0 ships draft save (ProCanvasDraftSaveAdapter), canvas validation, and Standard WorkflowResource integration. A dedicated standalone builder route remains deferred.
Deployment recommendations
The table below describes a typical Laravel host application stack. DBFlow Core does not require Redis, queues, or Horizon — workflow traversal and approvals run synchronously inside the request that calls DBFlow::start(), approve(), or reject().
| Environment | Database | Queue (host app) | Cache (host app) |
|---|---|---|---|
| Local | SQLite | sync | file |
| Staging | MySQL 8.0+ | redis (optional) | redis (optional) |
| Production | PostgreSQL 16+ | redis/horizon (optional) | redis (optional) |
Verify workflow tables after deploy with php artisan migrate --force, run php artisan dbflow:sync, and schedule php artisan dbflow:process-timeouts when definitions use approval deadlines. There is no php artisan dbflow:health command.
Related guides
- Host Integration → — sync, assignees, exceptions, host guards
- Conditions → —
transitions[].conditionrouting - Workflow Hooks → — host callback reference
- Testing Workflows → — PHPUnit coverage patterns
Security notes
- License keys should never be logged in plaintext.
- Public documentation routes are English-only via locale middleware in DBFlow HQ.
- Admin operations on dbflow.dev run through the private Filament panel.