Model-first workflow runtime for Laravel applications.
DBFlow Core lets you add approval workflows, tasks, transitions, rejection flows, and audit logs to any Eloquent model without building a heavy BPM system from scratch.
It is the open-source runtime foundation of the DBFlow ecosystem. Host-specific business adapters, Filament UI packages, and the visual workflow Builder are distributed separately.
Note
DBFlow Core 1.1.0 extends the stable 1.0 runtime with additive contracts (context, delegation, SLA, reliable actions, outbound webhooks). See API stability, UPGRADE-1.1.md, and CHANGELOG.md.
- Package Overview
- What Core Provides
- Requirements
- Installation
- Laravel Integration
- Configuration
- Minimal Usage
- Runtime API Summary
- Assignee Types (Runtime)
- Host Responsibilities
- Host Integration Checklist
- Package Boundaries
- DBFlow Ecosystem
- Development
- API stability
- Versioning
- Support
- License
| Item | Value |
|---|---|
| Package name | dbflowlabs/core |
| Namespace | DbflowLabs\Core |
| License | MIT |
| Repository | github.com/dbflow-labs/dbflow-core |
| Default branch | main |
| Stability | Stable (1.1.0) |
| Author | Baron Wang hello@dbflow.dev |
| Documentation | dbflow.dev/docs |
| Laravel compatibility | 13.x |
| PHP requirements | 8.3, 8.4 |
DBFlow Core provides the runtime foundation required for deterministic, schema-driven workflow execution:
- Workflow definitions — code-first and array-hydrated workflow schema management.
- Topology validation — validation for nodes, transitions, and directed workflow routing.
- State machine persistence — workflow instances, task states, assignments, and transition records.
- Task runtime — task creation, assignment handling, approval actions, rejection actions, and cancellation.
- Approval modes — runtime support for common approval strategies.
- Transition handling — controlled movement between workflow nodes.
- Append-only audit logs — historical workflow activity records for traceability.
- Action node failure handling —
ActionFailedevents and audit entries when handlers throw; optionalstop_on_errorto abort traversal. - Workflow Context — additive context contract and field catalog for definition validation and runtime reads.
- Delegation — time-bounded delegation rules, effective-assignee resolution, and pending-task migration.
- SLA runtime — fixed-duration policies with reminders, overdue handling, escalation, and queue recovery.
- Reliable actions — persisted action executions with blocking/non-blocking modes, retry/skip, and recovery commands.
- Outbound webhooks — SSRF/TLS guards, HMAC signing, idempotency keys, and host secret resolution.
- Extension points — assignee resolvers, workflow hooks, condition handling, action handlers, and runtime capability gates.
Note
Core focuses entirely on the workflow runtime engine. It contains no frontend assets, Filament resources, visual canvas, or host-specific business models.
- PHP
^8.3 - Laravel
13.x/ Illuminate^13.0components - MySQL
8.0or later (production and certification target) - SQLite (supported for development and package tests only)
- PostgreSQL is not part of the v1.1 stable certification matrix unless separately certified for your deployment
- A host user model, usually
App\Models\User - A user table (or equivalent) for actor and assignee references; UUID/ULID primary keys are supported in v0.3+
composer require dbflowlabs/coreReleases are tagged on GitHub, for example:
v1.1.0
Hosts on 1.0.x can upgrade with composer require dbflowlabs/core:^1.1 — see UPGRADE-1.1.md.
The core service provider is registered automatically via Laravel package discovery:
DbflowLabs\Core\Providers\DBFlowServiceProvider::classphp artisan vendor:publish --tag=dbflow-configThis publishes:
config/dbflow.php
Publishing the configuration file is optional, but recommended when the host application needs to customize authentication, user resolution, or runtime feature flags.
php artisan vendor:publish --tag=dbflow-migrations
php artisan migrateDBFlow Core creates only dbflow_* tables, preserving schema separation from host application tables.
During local package development, migrations may also be loaded directly through Laravel's loadMigrationsFrom() behavior. Publishing migrations is optional — a host application can run php artisan migrate without publishing, as long as the package service provider is registered.
Publish the package config (optional but recommended):
php artisan vendor:publish --tag=dbflow-configThe package ships a framework-neutral config/dbflow.php. It does not hard-code a host user model or guard — set those in your application .env (or override the published file in the host).
Package defaults (illustrative — see the published file for the exact source):
return [
'enabled' => env('DBFLOW_ENABLED', true),
'binding_mode' => env('DBFLOW_BINDING_MODE', 'code'),
'auth' => [
'model' => env('DBFLOW_AUTH_MODEL'),
'guard' => env('DBFLOW_AUTH_GUARD'),
'table' => env('DBFLOW_AUTH_TABLE'),
'connection' => env('DBFLOW_AUTH_CONNECTION'),
'resolver' => DbflowLabs\Core\Support\ConfigUserResolver::class,
],
'expression' => [
'strict' => env('DBFLOW_EXPRESSION_STRICT', false),
],
'visual_builder_enabled' => env('DBFLOW_VISUAL_BUILDER_ENABLED', false),
// Plus v1.1 sections: reassignment, delegation, sla, actions, webhook
];v1.1 configuration keys are documented in docs/reference/v1.1-configuration.md.
Host example — typical Laravel app after publish (you may add env() fallbacks in your copy only):
'auth' => [
'model' => env('DBFLOW_AUTH_MODEL', 'App\\Models\\User'),
'guard' => env('DBFLOW_AUTH_GUARD', 'web'),
'table' => env('DBFLOW_AUTH_TABLE', 'users'),
'resolver' => DbflowLabs\Core\Support\ConfigUserResolver::class,
],Recommended .env for that host example:
DBFLOW_ENABLED=true
DBFLOW_BINDING_MODE=code
DBFLOW_AUTH_MODEL=App\Models\User
DBFLOW_AUTH_TABLE=users
DBFLOW_AUTH_GUARD=web
DBFLOW_EXPRESSION_STRICT=falseConfigUserResolver supports integer and string primary keys at runtime. User references are stored as strings in dbflow_* tables.
Note
Set DBFLOW_ENABLED=false to disable the workflow runtime. When disabled, DBFlow::start() / approve() / reject() / cancel() / reassign() / delegation APIs throw WorkflowNotAvailableException, timeout/SLA/action artisan commands that require the runtime fail, and runtime action bindings (StartWorkflow, etc.) are not registered. Definition-management bindings remain available (registerDefinitionProvider, registerAssigneeResolver, etc.), migrations still load, and php artisan dbflow:sync / dbflow:validate remain registered so hosts can sync or validate definitions before re-enabling.
Code-first integration: register → sync → attach model → run → guard host actions.
Register workflow definitions, assignee resolvers, and hooks during application boot. A host service provider is usually the best place for this.
use DbflowLabs\Core\DBFlow;
use DbflowLabs\Core\Services\AssigneeResolverRegistry;
use DbflowLabs\Core\Services\WorkflowDefinitionRegistry;
use DbflowLabs\Core\Services\WorkflowHooksRegistry;
DBFlow::registerDefinitionProvider(
app(WorkflowDefinitionRegistry::class),
$myDefinitionProvider,
);
DBFlow::registerAssigneeResolver(
app(AssigneeResolverRegistry::class),
'finance_team',
$myAssigneeResolver,
);
DBFlow::registerWorkflowHooks(
app(WorkflowHooksRegistry::class),
'refund_approval',
MyRefundHooks::class,
);A WorkflowDefinitionProvider returns a validated array definition (nodes, transitions, approval config). See DbflowLabs\Core\Contracts\WorkflowDefinitionProvider and package tests under tests/Feature/SyncWorkflowDefinitionsTest.php.
Important
Registering a provider alone is not enough for DBFlow::start(). The runtime resolves an active published row from dbflow_workflow_versions. After registration, sync code-first definitions into dbflow_* tables.
Call SyncWorkflowDefinitions from a deploy hook, or use the official Artisan command:
php artisan dbflow:sync
php artisan dbflow:sync --dry-run
php artisan dbflow:sync --workflow=refund_approvalProgrammatic alternative:
use DbflowLabs\Core\Actions\SyncWorkflowDefinitions;
/** @var array{created: list<string>, updated: list<string>, unchanged: list<string>} $summary */
$summary = app(SyncWorkflowDefinitions::class)->handle();Validate definitions in CI:
php artisan dbflow:validate --strictProcess overdue approval tasks on the legacy timeout path (schedule via cron, for example every minute):
php artisan dbflow:process-timeoutsWhen using v1.1 SLA and reliable actions, also schedule dispatch/recover commands and run a queue worker — see docs/operations/queue-and-scheduler.md. Check readiness with:
php artisan dbflow:diagnosticsAlternative for interactive or UI-owned workflows: CreateWorkflowDraft → PublishWorkflowDraft (see package actions and Filament packages).
Re-run sync after changing a code-first definition. UI-owned workflows (source = ui) are not overwritten as the active version pointer; see SyncWorkflowDefinitions source for details.
Use the HasWorkflow trait on Eloquent models that participate in workflows.
Recommended for most hosts — implement Workflowable for business key and display metadata used in logs and UI adapters:
use DbflowLabs\Core\Contracts\Workflowable;
use DbflowLabs\Core\Traits\HasWorkflow;
final class RefundRequest extends Model implements Workflowable
{
use HasWorkflow;
public function workflowBusinessKey(): ?string
{
return $this->reference_no;
}
public function workflowDisplayName(): string
{
return 'Refund Request';
}
}For condition routing — also implement WorkflowContextInterface so condition transitions can read business variables:
use DbflowLabs\Core\Contracts\WorkflowContextInterface;
public function getWorkflowVariables(): array
{
return [
'total_amount' => (float) $this->amount,
'supplier_type' => $this->supplier_type,
];
}WorkflowContextInterface is separate from Workflowable. Models that need condition nodes should implement both (or pass metadata['variables'] when calling DBFlow::start()).
When binding_mode is code (default), start workflows explicitly:
$instance = $refundRequest->startWorkflow('refund_approval');
// equivalent to DBFlow::start('refund_approval', $refundRequest, auth()->user());When binding_mode is ui, matching published workflows with a model_type binding may auto-start on Model::created. ERP-style hosts usually keep code and trigger from business actions (submit, confirm, etc.). Auto-start runs inside the created event, not the model's own persistence transaction; if a matching workflow fails to start (e.g. a misconfigured assignee resolver), the exception is reported via report() and swallowed so the model is still created and other matching workflows still get a chance to start — it does not roll back model creation or block sibling workflows.
Start a workflow for an Eloquent model after sync has published the definition:
use DbflowLabs\Core\DBFlow;
$instance = DBFlow::start(
'refund_approval',
$refundRequest,
auth()->user(),
[
// Host-defined keys — stored as JSON on dbflow_workflow_instances.metadata
'submit_comment' => 'Q2 budget exception',
// Used for condition routing when the model does not implement WorkflowContextInterface
'variables' => ['priority' => 'high'],
],
);Note
Metadata contract (stable): Core persists the entire $metadata array on the workflow instance. It does not assign special meaning to keys such as submit_comment — naming conventions are host-defined. For condition nodes, prefer WorkflowContextInterface::getWorkflowVariables(); otherwise pass metadata['variables'].
Approve a pending task:
DBFlow::approve(
$task,
auth()->user(),
'Approved.',
);Reject a pending task (default strategy returns flow toward the starter):
use DbflowLabs\Core\Enums\RejectStrategy;
DBFlow::reject(
$task,
auth()->user(),
'Amount exceeds policy.',
RejectStrategy::Starter,
);DBFlow::cancel() stops a running instance — similar in product language to "withdraw", but it is a distinct Core API from approve/reject:
DBFlow::cancel(
$instance,
auth()->user(),
'Withdrawn by submitter.',
);What Core does:
- Sets instance status to
cancelled - Cancels pending tasks and assignments
- Clears
active_keyso the same workflowable may be started again (if the host allows) - Writes
WorkflowLogEvent::WorkflowCancelled, oneWorkflowLogEvent::TaskCancelledper pending task, and callsWorkflowHooks::onCancelled
What Core does not do:
- Does not enforce who may cancel. Core does not check
started_by_user_idor approver roles. Compareinstance->started_by_user_id(or your own policy) in the host before callingcancel(). - Does not define post-cancel business rules. Whether a cancelled workflow blocks
confirm,ship,post, etc. is entirely a host responsibility (see Host Responsibilities). - Does not error when called on an already-terminal instance.
cancel()on acancelled/approved/rejectedinstance is a silent no-op: it returns the instance unchanged and writes no additional log or event (verified byCancelWorkflowTest::cancel_on_terminal_instance_is_idempotent_and_does_not_duplicate_logs, part of the frozen 1.0 API). If the host needs to distinguish "I just cancelled it" from "it was already terminal", check$instance->status->isTerminal()before callingcancel().
| Terminal status | Typical host canConfirm strategy (examples only) |
|---|---|
running |
Block until approved or cancelled |
approved |
Allow downstream business action |
rejected |
Block until a new start() completes successfully |
cancelled |
Host choice: allow action, require re-submit, or keep blocked |
DBFlow::reassign() replaces a single pending assignment with a new assignee. It does not advance the workflow.
DBFlow::reassign(
$task,
auth()->user(),
$replacementUserId,
'Reassigned while on leave.',
);What Core does:
- Marks the actor's pending assignment as
reassigned - Creates a new
pendingassignment for$toUserId(inheritssequencein Sequential mode) - Writes
WorkflowLogEvent::TaskReassigned, dispatchesTaskReassigned, and callsTaskHooks::onReassigned
What Core does not do:
- Does not enforce admin/delegate policies. Host must authorize who may reassign before calling
reassign(). - Does not support add-sign / delegate-to-multiple. Only one assignment is replaced per call.
Create a time-bounded delegation rule, then optionally migrate matching pending tasks:
$delegation = DBFlow::createDelegation(
$delegator,
$delegate,
now(),
now()->addDays(7),
auth()->user(),
workflowKey: 'refund_approval',
);
DBFlow::migratePendingTasksToDelegate($delegation, auth()->user(), dryRun: true);
DBFlow::revokeDelegation($delegation, auth()->user());See docs/architecture/reassignment-and-delegation.md.
Legacy timeout (v1.0 path) — approval nodes may declare:
{
"timeout": {
"due_in": "P3D",
"on_timeout": "reject_end"
}
}due_in— ISO 8601 duration (for examplePT24H,P3D). When a task is created, Core writesworkflow_tasks.due_at.on_timeout— optional. Onlyreject_endis supported on this path. When omitted, overdue tasks are logged only and remainpending.
Schedule:
php artisan dbflow:process-timeoutsv1.1 SLA extends approvals with reminders, overdue notifications, and escalation via persisted WorkflowSlaEvent rows (dbflow:sla-dispatch / dbflow:sla-recover). Tasks on the v1.1 SLA path are excluded from dbflow:process-timeouts. See docs/architecture/sla-runtime.md.
Models using HasWorkflow can inspect runtime state without raw SQL:
$order->hasRunningWorkflow('refund_approval');
$order->runningWorkflowInstance('refund_approval');
$order->completedWorkflowInstance('refund_approval');
$order->workflowLogs('refund_approval');Use these helpers in host guards (for example, disable a Filament Confirm action while hasRunningWorkflow() is true).
Action nodes execute registered ActionHandler implementations during traversal. When a handler throws:
Default (fire-and-forget):
- Core writes
WorkflowLogEvent::ActionFailedto the audit log - Core dispatches
DbflowLabs\Core\Events\ActionFailedwith the instance, node, and exception - Traversal continues to the next node so non-critical automations do not block approval flow
Opt-in abort (stop_on_error: true):
{
"type": "action",
"config": {
"action_key": "post_to_ledger",
"stop_on_error": true
}
}- Core still logs and dispatches
ActionFailed - Core then throws
ActionExecutionFailedException, stopping traversal before downstream nodes run - Use for side effects that must succeed before the workflow advances (for example ERP posting)
Set DBFLOW_EXPRESSION_STRICT=true when condition nodes should reject invalid or missing variables instead of treating them as false.
Use DbflowLabs\Core\DBFlow as the single runtime entry point for workflow operations.
| Method | Purpose | Returns |
|---|---|---|
start($workflowKey, $workflowable, $startedBy = null, $metadata = []) |
Create a running instance | WorkflowInstance |
approve($task, $actor = null, $comment = null) |
Approve a pending task | WorkflowInstance |
reject($task, $actor = null, $comment = null, $strategy, $targetNodeKey = null) |
Reject a pending task | WorkflowInstance |
cancel($instance, $actor = null, $comment = null) |
Cancel a running instance | WorkflowInstance |
reassign($task, $fromActor, $toUserId, $comment = null, $idempotencyKey = null, $assignmentId = null) |
Reassign a pending assignment to another user | WorkflowInstance |
createDelegation(...) |
Create a time-bounded delegation rule | WorkflowDelegation |
revokeDelegation($delegation, $revokedBy = null, $reason = null) |
Revoke an active delegation | WorkflowDelegation |
migratePendingTasksToDelegate($delegation, ...) |
Migrate matching pending tasks to the delegate | array |
registerDefinitionProvider($registry, $provider) |
Boot-time code definition registration | void |
registerAssigneeResolver($registry, $key, $resolver) |
Boot-time assignee resolver registration | void |
registerWorkflowHooks($registry, $workflowKey, $hooks) |
Boot-time lifecycle hooks | void |
registerTaskHooks($registry, $workflowKey, $hooks) |
Boot-time task-level hooks | void |
Registration helpers are usually called from a host service provider. Runtime actions (start / approve / reject / cancel / reassign / delegation APIs) are usually called from host services, controllers, or UI actions.
Approval nodes declare assignees under config.assignees. The schema lists four types; open-core runtime support differs:
assignees.type |
Supported at runtime | Notes |
|---|---|---|
user |
Yes | Single user id in value (string or int). Fine for demos; use callback in production. |
callback |
Yes | callback (or value) must match a key registered via DBFlow::registerAssigneeResolver(). |
permission (resolver alias) |
Yes | value is a resolver registry key, not a Laravel Gate name or Spatie permission string. |
role |
No | Listed in the schema for forward compatibility, but rejected by validators during code sync. Use callback and resolve roles in the host. |
Examples:
// Fixed user (demo / tests)
'assignees' => ['type' => 'user', 'value' => '1'],
// Host-registered resolver (roles, departments, dynamic rules)
'assignees' => ['type' => 'callback', 'callback' => 'finance_team'],
// Resolver key alias — NOT a framework permission string
'assignees' => ['type' => 'permission', 'value' => 'approve_refunds'],// Register the resolver key used above
DBFlow::registerAssigneeResolver(
app(AssigneeResolverRegistry::class),
'approve_refunds',
new ApproveRefundsAssigneeResolver(),
);Anti-pattern:
// Does NOT auto-resolve Laravel / Spatie permissions
'assignees' => ['type' => 'permission', 'value' => 'approve-refunds'],WorkflowDefinitionSchema::runtimeSupportedAssigneeTypes() is the canonical list for code-first definitions.
Before exposing a Submit for approval action in your UI, verify:
- The workflow is published (
SyncWorkflowDefinitionsorPublishWorkflowDraft) andis_enabled - Every approval node can resolve to at least one assignee user id at runtime
- Every
callback/permission(resolver alias) key has a matchingAssigneeResolverregistered at boot
If resolution fails or the workflow is missing, start() throws (for example InvalidWorkflowDefinitionException). Core does not fall back to a default approver.
Core is a runtime engine. The following are not provided and must be implemented (or installed via dbflowlabs/filament) in the host application:
| Responsibility | Provided by Core? | Typical host implementation |
|---|---|---|
| Submit / start UI | No | Filament Action, API endpoint, or service method calling DBFlow::start() |
| Approve / reject UI | No | Task inbox page, or dbflowlabs/filament |
| Withdraw / cancel UI | No | Action calling DBFlow::cancel() after host authorization |
| Business action guards | No | Before confirm / post / ship, check hasRunningWorkflow() or latest terminal status |
| Assignee configuration | No | AssigneeResolver implementations, deploy-time sync |
| Coexistence with other approval systems | No | Host config to choose one engine per document type |
UI options:
- Minimal: two host buttons (submit + cancel) on an edit page — Core only, no extra packages.
- Full inbox: install
dbflowlabs/filamentfor standard Filament task UI. - Visual builder: commercial
dbflowlabs/filament-pro(see DBFlow Ecosystem).
Core does not know about Filament, ERP document types, or plugin mutual-exclusion switches — those remain host concerns.
composer require dbflowlabs/core(pair withdbflowlabs/filament:^1.0when using the Filament adapter).php artisan vendor:publish --tag=dbflow-configand setDBFLOW_AUTH_*.php artisan migrate(migrations load from the package; publishing optional).- Implement
WorkflowDefinitionProvider(s) and register them in a host service provider. - Register
AssigneeResolver(s) for everycallback/permission(resolver alias) key used in definitions. - Run
php artisan dbflow:sync(or callSyncWorkflowDefinitionsfrom a deploy hook). - Add
HasWorkflow(+Workflowable/WorkflowContextInterfaceas needed) to host models. - Implement host UI or services that call
DBFlow::start()/approve()/reject()/cancel(). - Implement business guards (for example, block
confirmwhile a workflow isrunning). - Optionally install
dbflowlabs/filamentfor a standard approval inbox instead of building UI from scratch.
DBFlow Core is intentionally small and runtime-focused.
The following are outside this package:
- Host business adapters, such as ERP-specific approval logic
- Filament resources, panels, widgets, and actions
- Visual workflow Builder UI
- Commercial Builder features
- Premium action handlers
- Enterprise white-label extensions
Unresolved premium action types raise PremiumFeatureMissingException.
Premium or host-specific action handlers can be registered through ActionManager, or provided by separate extension packages.
DBFlow is designed as a layered ecosystem:
| Package | Role | License |
|---|---|---|
dbflowlabs/core |
Runtime engine | MIT |
dbflowlabs/filament |
Standard Filament UI integration | MIT / open-source |
dbflowlabs/filament-pro |
Visual workflow Builder and advanced UI features | Commercial |
Core runs the workflow. Filament packages provide user interfaces. Host applications provide business adapters.
Cross-package contracts for pending-task queries, runtime actions, events, and version alignment are documented in:
docs/integration/filament.md— public API surface fordbflowlabs/filamentintegratorsdocs/integration/acceptance-checklist.md— release verification checklist
Target version pairing for 1.1:
| Package | Constraint |
|---|---|
dbflowlabs/core |
^1.1 |
dbflowlabs/filament |
^1.1 (requires core ^1.1) |
dbflowlabs/filament-pro |
^1.1 (optional; requires core + filament ^1.1) |
Hosts remaining on the 1.0 UI packages should stay on dbflowlabs/core:^1.0 until Filament/Pro are upgraded together.
Choosing a UI path:
- Need only submit / withdraw on a host edit page? Implement host Filament (or Blade) actions that call
DBFlow::start()/cancel()— Core alone is sufficient. - Need a task inbox, approval history, and standard admin resources? Add
dbflowlabs/filament. - Need a visual workflow designer? Use
dbflowlabs/filament-pro.
Install dependencies:
composer installValidate the package metadata:
composer validate --strict --no-check-lockRun the test suite:
composer testThe CI pipeline validates the package against PHP 8.3 and 8.4 with PHPUnit, PHPStan, and coverage gates (runtime API ≥ 80%, src/ ≥ 70%).
From 1.0.0, these surfaces remain frozen until the next major release:
DBFlow::start(),approve(),reject(),cancel(),reassign()DBFlow::register*boot-time registration methodsTaskHooksinterface methodsWorkflowTaskQueryServicepublic query API (seedocs/integration/filament.md)DbflowLabs\Core\Events\*event constructor properties
Additive in 1.1.0 (compatible with the 1.0 freeze; new public methods and events):
DBFlow::createDelegation(),revokeDelegation(),migratePendingTasksToDelegate()- Assignment provenance fields, SLA / reliable-action / webhook contracts documented under
docs/ - Runtime capability gates (
RuntimeCapability,RuntimeCapabilityRegistry)
Draft and builder management actions are marked @internal and are not covered by the stability guarantee. Use artisan commands or the Filament Builder package instead of binding those classes directly.
Automated contract tests: EcosystemContractTest, PublicApiContractTest, V10CompatibilityTest.
DBFlow Core 1.1.0 is the current stable release (built on the frozen 1.0 public API).
- From
1.0.x: follow UPGRADE-1.1.md - From
0.xor RC tags: follow UPGRADE-1.0.md, then UPGRADE-1.1.md - Review CHANGELOG.md before upgrading
- Test workflow definitions and runtime transitions in a staging environment after upgrades
- Avoid relying on
@internaldefinition-management actions
For architecture alignment or integration questions, open a GitHub Issue or contact:
- Email: hello@dbflow.dev
- Website: dbflow.dev
DBFlow Core is open-sourced software licensed under the MIT license.