Advanced Host Integration

Headless runtime: dbflowlabs/core does not ship Filament pages, business guards, or assignee business rules. This page lists what your Laravel application must wire after composer require dbflowlabs/core.

Use this checklist before calling DBFlow::start() in production. Skipping a step is the most common cause of WorkflowNotFoundException, empty assignee lists, or approvals that never appear in UI.

Integration flow

Install Core → publish config/migrations → migrate
    → attach HasWorkflow to models
    → register WorkflowDefinitionProvider (host provider `booted()`)
    → register AssigneeResolver keys (when using callback / permission assignees)
    → php artisan dbflow:sync (deploy / CI)
    → expose start + inbox + business guards in your app
    → optional WorkflowHooks for host status columns

1. Install and configure

composer require dbflowlabs/core
php artisan vendor:publish --tag=dbflow-config
php artisan vendor:publish --tag=dbflow-migrations
php artisan migrate

Use ^1.0 constraints in production (for example dbflowlabs/core:^1.0). See Installation for Packagist and path-repository options.

config/dbflow.php

Key Default Purpose
enabled true Feature flag. When false, runtime APIs (start, approve, reject, cancel, reassign) throw WorkflowNotAvailableException; dbflow:sync and dbflow:validate remain available.
binding_mode code code = explicit start / startWorkflow(). ui = auto-start on model created when dbflow_workflows.model_type matches.
auth.model from env Host user model FQCN (DBFLOW_AUTH_MODEL).
auth.guard from env Guard for DbflowAuth::currentUser() (DBFLOW_AUTH_GUARD).
auth.resolver ConfigUserResolver Override when you need custom actor lookup (UUID/ULID stacks).
visual_builder_enabled false Pro / visual builder flag.

Example .env:

DBFLOW_ENABLED=true
DBFLOW_BINDING_MODE=code
DBFLOW_AUTH_MODEL=App\Models\User
DBFLOW_AUTH_GUARD=web

2. Register providers after the application boots

Create a host service provider (recommended) instead of inline registration in routes or controllers.

DBFlow Core binds WorkflowDefinitionRegistry and related services during its own boot() method. If your host provider calls DBFlow::registerDefinitionProvider() too early in register() or at the start of boot(), the registry singleton may not exist yet and php artisan dbflow:sync will report no providers.

Use $this->app->booted() so registration runs after all service providers have booted:

<?php

namespace App\Providers;

use App\Support\Dbflow\AssigneeResolvers\FinanceTeamAssigneeResolver;
use App\Support\Dbflow\Definitions\RefundDisputeWorkflowProvider;
use App\Support\Dbflow\RefundDisputeWorkflowHooks;
use DbflowLabs\Core\DBFlow;
use DbflowLabs\Core\Services\AssigneeResolverRegistry;
use DbflowLabs\Core\Services\WorkflowDefinitionRegistry;
use DbflowLabs\Core\Services\WorkflowHooksRegistry;
use Illuminate\Support\ServiceProvider;

final class AppDbflowServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        $this->app->booted(function (): void {
            if (! config('dbflow.enabled', true)) {
                return;
            }

            DBFlow::registerDefinitionProvider(
                $this->app->make(WorkflowDefinitionRegistry::class),
                $this->app->make(RefundDisputeWorkflowProvider::class),
            );

            DBFlow::registerAssigneeResolver(
                $this->app->make(AssigneeResolverRegistry::class),
                'finance_team',
                $this->app->make(FinanceTeamAssigneeResolver::class),
            );

            DBFlow::registerWorkflowHooks(
                $this->app->make(WorkflowHooksRegistry::class),
                'refund_dispute_approval',
                RefundDisputeWorkflowHooks::class,
            );
        });
    }
}

Register the provider in bootstrap/providers.php.

Core ships php artisan dbflow:sync and php artisan dbflow:validate. You may also call SyncWorkflowDefinitions from a deploy hook or custom Artisan command.

php artisan dbflow:sync
php artisan dbflow:sync --dry-run
php artisan dbflow:sync --workflow=refund_dispute_approval
php artisan dbflow:validate --strict

Programmatic alternative:

use DbflowLabs\Core\Actions\SyncWorkflowDefinitions;

/** @var array{created: list<string>, updated: list<string>, unchanged: list<string>} $summary */
$summary = app(SyncWorkflowDefinitions::class)->handle();

3. Sync definitions into the database

DBFlow::start() resolves the published version from dbflow_workflows / dbflow_workflow_versions. Registering a PHP provider alone is not enough.

Run sync:

  • After first deploy
  • After changing a WorkflowDefinitionProvider definition
  • In CI before integration tests that call DBFlow::start()

Provider definitions may include 'enabled' => true (maps to dbflow_workflows.is_enabled). Disabled workflows throw WorkflowNotAvailableException on start.

Filament definition editor after code sync

php artisan dbflow:sync publishes versions but does not write draft_definition. The Standard WorkflowResource node editor is hidden unless Workflow::hasDraft() is true — so code-synced workflows may show metadata on the edit page but no approval graph form.

If operators need the Standard editor for code-owned workflows, seed a draft after sync with SaveWorkflowDraft and the workflow's currentDefinition(). Skip workflows with source = ui or existing drafts. See Filament UI.

Alternatively, disable enable_workflow_definition_resource when definitions are code-only.

4. Assignee resolution (callback / permission)

Approval nodes declare assignees under config.assignees. The runtime supports:

type Runtime behaviour
user Single user ID in value (string).
callback callback (or value) must match a key passed to DBFlow::registerAssigneeResolver().
permission value is also a resolver registry key — not Spatie Permission, not Laravel Gate.
role Listed in schema for editors; not supported by the open-core runtime (throws InvalidWorkflowDefinitionException).

Example callback assignee in definition JSON:

'assignees' => [
    'type' => 'callback',
    'callback' => 'finance_team',
],

Resolver implementation:

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

final class FinanceTeamAssigneeResolver implements AssigneeResolver
{
    public function resolve(WorkflowInstance $instance, array $nodeDefinition): array
    {
        // Return list<string> user IDs (int, UUID, or ULID keys as strings)
        return [/* ... */];
    }
}

See Code-defined Workflows for full examples.

5. Start workflows with metadata (optional)

DBFlow::start(
    'refund_dispute_approval',
    $dispute,
    $submitter,
    ['submit_comment' => 'Please review'],
);

Metadata is stored on dbflow_workflow_instances.metadata. It does not replace WorkflowContextInterface::getWorkflowVariables() for transition conditions.

6. Act on tasks — always pass an actor

DBFlow::approve($task, $user, 'Approved.');
DBFlow::reject($task, $user, 'Missing receipt.', RejectStrategy::End);

When $actor is null, Core skips pending-assignment checks on approve. Production code should always pass the authenticated user.

7. Reassign — host authorization required

DBFlow::reassign($task, $fromActor, $toUserId, 'Handover to colleague.');

Core replaces a single pending assignment. It does not enforce who may reassign — gate reassign() in Filament actions or services. Filament Standard exposes a reassign action on My Tasks when enable_my_task_reassign_action is true.

8. Cancel — host authorization required

DBFlow::cancel($instance, $user, 'Withdrawn by submitter.');

Core does not enforce “only the submitter may cancel”. Gate cancel() in your Filament actions or services (for example compare $instance->started_by_user_id).

Cancelled instances set status = cancelled and clear active_key. Whether the business record may proceed after cancel is host policy (hooks or guards).

9. Query helpers on the model

HasWorkflow exposes:

$model->runningWorkflowInstance('workflow_key');
$model->completedWorkflowInstance('workflow_key'); // approved | rejected | cancelled
$model->hasRunningWorkflow('workflow_key');
$model->workflowLogs('workflow_key');

Use completedWorkflowInstance() when business actions (confirm order, post payment) must check the latest terminal workflow outcome.

10. UI options

Approach When to use
dbflowlabs/filament Recommended Standard UI: My Tasks, instances, timelines, optional definition resource. Register via DBFlowFilamentPanel::register($panel) and replace permission_checker_class.
Host-built Filament inbox Custom task list when you need a deeply embedded ERP UX; still call DBFlow::approve() / reject() with an authenticated actor.
API only Mobile or external approvers.

Core does not require Filament. Filament UI is a separate Composer package.

Filament Standard checklist (when using dbflowlabs/filament)

  1. composer require dbflowlabs/filament:^1.0 dbflowlabs/core:^1.0; migrate.
  2. Publish dbflow-filament-config; set permission_checker_class (not AllowAllPermissionChecker).
  3. Call DBFlowFilamentPanel::register($panel) when both dbflow.enabled and your host feature flag are on.
  4. Implement WorkflowRouteResolvable on workflowable models for links back to business resources.
  5. Bind PermissionAssigneeOptionsResolver so definition editors show labels for your Core assignee resolver keys.
  6. After sync, seed drafts if operators need the Standard definition editor for code workflows.

See Host Reference Patterns for a full module layout, boot order, and ERP pilot case study.

11. Business guards (host responsibility)

Core does not know your domain rules. Examples the host must implement:

  • Block confirm() while hasRunningWorkflow() is true
  • Hide legacy approval UI when DBFlow engine is active
  • Map hook outcomes to document status columns

Runtime exceptions (quick reference)

Exception Typical cause
WorkflowNotFoundException Key missing — sync not run or typo
WorkflowNotPublishedException No current_version_id / active version
WorkflowNotAvailableException is_enabled = false or workflow archived
WorkflowAlreadyRunningException Duplicate active_key for same workflowable
InvalidWorkflowDefinitionException Invalid graph or empty assignee resolution
UserCannotApproveTaskException Actor has no pending assignment
TaskNotPendingException Stale task action / double submit

Approval timeouts

When approval nodes include config.timeout.due_in (ISO 8601 duration) and on_timeout: reject_end, schedule:

php artisan dbflow:process-timeouts

Run every 5–15 minutes via cron or your scheduler. Filament Standard editors can set timeout fields; Core executes them.

UI binding mode (binding_mode = ui)

When DBFLOW_BINDING_MODE=ui, HasWorkflow auto-starts matching workflows on model created. You must also set dbflow_workflows.model_type to the workflowable morph class for each workflow row. php artisan dbflow:sync does not set model_type automatically — configure it in seeders or admin tooling.

What's next

Something wrong? Open an issue on GitHub