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 (not Laravel events)
DBFlow alpha does not dispatch Laravel container events for workflow lifecycle boundaries. Instead, register host callbacks through WorkflowHooks:
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 not a Laravel Event facade target.
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);
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 (Early Access)
dbflowlabs/filament-pro adds:
ProCanvasField— Filament form fieldProCanvasWorkflowDefinitionEditorResolver— replaces Standard definition editorProGraphBlueprintCompiler/WorkflowGraphJsonParser— graph JSON → Core blueprint
Publication and full builder routing continue to evolve during alpha.
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 and run your definition sync step. There is no php artisan dbflow:health command in the current alpha packages.
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.