Filament UI

Stable release: dbflowlabs/filament is 1.0 stable. Page slugs, config keys, and permission names are frozen for the 1.x public integration contract. Use ^1.0 constraints in production.

DBFlow Filament is the Standard operational UI layer for workflow administration. It runs on top of dbflowlabs/core and gives operators a Filament-native way to work with tasks, instances, and definitions.

DBFlow Filament operates workflows. DBFlow Pro (dbflowlabs/filament-pro) designs workflows visually. See Pro Visual Builder and Pro Known Limitations. Standard UI details are below.

Filament UI is only half of an integration. You still need Core runtime setup: migrations, user resolution, workflow definitions (code or UI), assignee resolvers, sync, and host business triggers (DBFlow::start()). Complete Advanced Host Integration before production use.

Security: The package ships with AllowAllPermissionChecker by default. Replace permission_checker_class in config/dbflow-filament.php before any shared or production environment — otherwise every authenticated panel user can access workflow pages and approve/reject tasks.

What you get

After DBFlowFilamentPanel::register($panel), the package exposes:

Surface Class Default URL (panel path admin)
My Workflow Tasks MyWorkflowTasks /admin/dbflow/my-workflow-tasks
Workflow Instances WorkflowInstances /admin/dbflow/workflow-instances
Instance detail ViewWorkflowInstance /admin/dbflow/workflow-instances/{record}
Workflow definitions WorkflowResource /admin/workflows

Routing notes:

  • Package pages use {panel_path}/{route_prefix}/…. Default route_prefix is dbflow (config('dbflow-filament.route_prefix')).
  • WorkflowResource is a normal Filament resource registered at the panel root — its slug is workflows, not under the dbflow prefix.
  • Named routes use route_name_prefix (default dbflow.filament.).
  • If your panel path is empty (->path('')), My Tasks becomes /dbflow/my-workflow-tasks.

The package does not auto-register pages during boot(). You must call DBFlowFilamentPanel::register($panel) from your PanelProvider (or register pageClasses() / resourceClasses() manually).

Install

composer require dbflowlabs/filament:^1.0 dbflowlabs/core:^1.0

Also listed on the Filament Plugin directory.

Production hosts should use the stable ^1.0 constraints shown above.

php artisan vendor:publish --tag=dbflow-filament-config
php artisan migrate

dbflowlabs/core is required and installs automatically as a dependency.

Two configuration files

Config file Primary switch What it controls
config/dbflow.php enabled Core runtime feature flag (false disables runtime APIs; sync/validate remain available)
config/dbflow-filament.php enabled Whether registered Filament pages/resources are exposed

DBFlowFilamentPanel checks dbflow-filament.enabled and panel_registration_mode. It does not read dbflow.enabled.

Hosts that want a single product feature flag should gate both configs (and panel registration) in application code:

if ($this->shouldRegisterDbflow()) {
  return DBFlowFilamentPanel::register($panel);
}

Typical pattern: wrap registration on config('dbflow.enabled') and a host pilot flag, not only dbflow-filament.enabled.

Register the panel

Call DBFlowFilamentPanel::register() inside your PanelProvider. This is the supported integration — do not use a separate Filament plugin class.

<?php

namespace App\Providers\Filament;

use DbflowLabs\Filament\Support\DBFlowFilamentPanel;
use Filament\Panel;
use Filament\PanelProvider;

class AdminPanelProvider extends PanelProvider
{
    public function panel(Panel $panel): Panel
    {
        $panel = $panel
            ->id('admin')
            ->path('admin')
            // ... your existing panel configuration
        ;

        if ($this->shouldRegisterDbflow()) {
            return DBFlowFilamentPanel::register($panel);
        }

        return $panel;
    }

    private function shouldRegisterDbflow(): bool
    {
        return (bool) config('dbflow-filament.enabled', true)
            && (bool) config('dbflow.enabled', true);
        // Hosts often add a product pilot flag here as well.
    }
}

DBFlowFilamentPanel reads config/dbflow-filament.php to decide which pages and resources to attach. Set panel_registration_mode to disabled if you prefer manual registration via DBFlowFilamentPanel::pageClasses() and resourceClasses().

With DBFlow Pro

  1. Register DBFlowFilamentProServiceProvider (auto-discovery or manual bootstrap/providers.php entry).
  2. Keep DBFlowFilamentPanel::register($panel) inside panel()not boot().
  3. Set workflow_resource_class explicitly in published config/dbflow-filament.php:
use DbflowLabs\FilamentPro\Filament\Resources\WorkflowResource as ProWorkflowResource;

'workflow_resource_class' => ProWorkflowResource::class,
  1. Run php artisan filament:assets after install or upgrade.

Custom WorkflowResource subclass

Hosts often subclass WorkflowResource (Standard or Pro) to add host-specific edit behaviour — for example seeding a draft from a published code-synced definition before the Pro canvas loads.

When you subclass the resource, also subclass the Filament pages and set protected static string $resource on each page. The package ships ListWorkflows, CreateWorkflow, and EditWorkflow with $resource hard-coded to the package WorkflowResource::class. Reusing those page classes against a host subclass causes list rows to link to the wrong route (for example filament.admin.resources.workflows.edit instead of your resource slug).

// App\Filament\Resources\Workflows\HostWorkflowResource.php
class HostWorkflowResource extends \DbflowLabs\FilamentPro\Filament\Resources\WorkflowResource
{
    public static function getPages(): array
    {
        return [
            'index' => ListHostWorkflows::route('/'),
            'create' => CreateHostWorkflow::route('/create'),
            'edit' => EditHostWorkflow::route('/{record}/edit'),
        ];
    }
}

// App\Filament\Resources\Workflows\Pages\ListHostWorkflows.php
class ListHostWorkflows extends \DbflowLabs\Filament\Resources\WorkflowResource\Pages\ListWorkflows
{
    protected static string $resource = HostWorkflowResource::class;
}

Point config('dbflow-filament.workflow_resource_class') at HostWorkflowResource::class and register the panel with DBFlowFilamentPanel::register($panel) inside panel().

Verify routes after integration:

php artisan route:list --name=workflows

You should see index, create, and edit routes under your resource slug, not a stale workflows slug from the package default.

Feature toggles

Config key Env (optional) Default Purpose
enabled DBFLOW_FILAMENT_ENABLED true Master Filament package toggle
panel_registration_mode DBFLOW_FILAMENT_PANEL_REGISTRATION explicit explicit or disabled
enable_my_tasks_page DBFLOW_FILAMENT_MY_TASKS true My Workflow Tasks page
enable_my_task_actions DBFLOW_FILAMENT_MY_TASK_ACTIONS true Approve/reject on tasks page
enable_my_task_reassign_action DBFLOW_FILAMENT_MY_TASK_REASSIGN true Reassign action on tasks page
enable_instance_cancel_action DBFLOW_FILAMENT_INSTANCE_CANCEL true Cancel action on instance detail
enable_workflow_instances_page DBFLOW_FILAMENT_INSTANCES true Instance list + detail
enable_workflow_definition_resource DBFLOW_FILAMENT_DEFINITIONS true Workflow Definition resource
enable_logs_timeline DBFLOW_FILAMENT_LOGS_TIMELINE true Audit timeline on instance detail
require_reject_note DBFLOW_FILAMENT_REQUIRE_REJECT_NOTE true Require note when rejecting from tasks UI
reject_strategy DBFLOW_FILAMENT_REJECT_STRATEGY end Core RejectTask strategy from My Tasks

Example .env:

DBFLOW_FILAMENT_ENABLED=true
DBFLOW_FILAMENT_MY_TASKS=true
DBFLOW_FILAMENT_INSTANCES=true
DBFLOW_FILAMENT_DEFINITIONS=true
DBFLOW_FILAMENT_LOGS_TIMELINE=true
DBFLOW_FILAMENT_REJECT_STRATEGY=end

You can substitute custom page classes — for example view_workflow_instance_page_class — when you need host-specific layouts. The dbflow-demo project uses DemoViewWorkflowInstance for a friendlier instance detail page.

Extension contracts

Hosts customize presentation and authorization by binding class strings in config/dbflow-filament.php (*_class keys). Prefer class bindings over legacy callables (permission_checker, workflowable_label_resolver, status_badge_mapper).

Contract Config key Purpose
PermissionChecker permission_checker_class Gate package pages, resources, and table actions
WorkflowableLabelResolver workflowable_label_resolver_class Human-readable labels for workflowable records
UserDisplayResolver user_display_resolver_class Actor names in tables and timelines
UserAssigneeOptionsResolver user_assignee_options_resolver_class User picker options in definition editors
PermissionAssigneeOptionsResolver permission_assignee_options_resolver_class Labels for permission/callback assignee keys in editors
StatusBadgeMapper status_badge_mapper_class Badge color/label for workflow statuses
WorkflowDefinitionEditorResolver workflow_definition_editor_resolver Replace the standard linear editor (Pro uses this hook)

Default permission_checker_class is DbflowLabs\Filament\Support\AllowAllPermissionChecker — replace it in every non-local environment.

Permission abilities

Ability strings are configured under permissions in config/dbflow-filament.php. WorkflowFilamentPermissions resolves them before calling PermissionChecker::can().

Default ability names:

dbflow.tasks.view
dbflow.tasks.approve
dbflow.tasks.reject
dbflow.tasks.reassign
dbflow.workflow_instances.view
dbflow.workflow_instances.view_any
dbflow.workflow_instances.cancel
dbflow.definitions.view
dbflow.definitions.create
dbflow.definitions.update
dbflow.definitions.delete
dbflow.definitions.validate
dbflow.definitions.publish
dbflow.definitions.disable
dbflow.definitions.enable
dbflow.definitions.archive
dbflow.definitions.copy

dbflow.tasks.view is required for the My Workflow Tasks navigation item. If navigation is missing but direct URLs work, check your PermissionChecker mapping first.

Permission assignees vs Core resolvers

Layer API Purpose
Filament definition editor PermissionAssigneeOptionsResolver Human-readable labels for assignee keys authors can pick
Core runtime DBFlow::registerAssigneeResolver($registry, $key, $resolver) Resolves user IDs when a workflow runs

For assignees.type: permission (or callback), the JSON value / callback must match the same registry key you register in Core — it is not a Spatie permission string or Laravel Gate ability. See Code-defined Workflows.

Runtime approval identity still flows through Core's UserResolver (config/dbflow.php). Filament contracts are display and admin UX only.

Host model links

Implement DbflowLabs\Core\Contracts\WorkflowRouteResolvable on workflowable models so instance and task tables can link back to your Filament resource:

public function getWorkflowShowUrl(): ?string
{
    return PurchaseOrderResource::getUrl('edit', ['record' => $this]);
}

My Tasks actions

The My Tasks table uses MyWorkflowTaskTableActions, which delegates to MyWorkflowTaskActionRunner. That runner calls Core's ApproveTask, RejectTask, and reassign actions — the same runtime entrypoints as DBFlow::approve(), DBFlow::reject(), and DBFlow::reassign().

Reject strategy on the My Tasks page defaults to config('dbflow-filament.reject_strategy') (commonly endRejectStrategy::End).

Workflow definitions resource

WorkflowResource manages draft definitions stored in dbflow_workflows / dbflow_workflow_versions. The Standard linear editor ships with the package. Pro may replace the editor through workflow_definition_editor_resolver when installed.

Code-synced workflows and the definition editor

WorkflowResource::definitionEditorSection() is visible only when Workflow::hasDraft() is true.

How the workflow was created Draft state after sync
Created via WorkflowResource → Create Draft exists (CreateWorkflowDraft) — editor shows
Synced from WorkflowDefinitionProvider Published version exists, no draft — edit page metadata only, no node editor

php artisan dbflow:sync writes published versions; it does not populate draft_definition. This is a common “sync succeeded but the edit form is empty” surprise.

Host options:

  1. Seed a draft after sync (recommended for code-first pilots that still want the Standard editor):
use DbflowLabs\Core\Actions\SaveWorkflowDraft;
use DbflowLabs\Core\Models\Workflow;

$workflow = Workflow::query()->where('key', 'your_workflow_key')->first();

if ($workflow && ! $workflow->hasDraft() && $workflow->hasPublishedVersion()) {
    $definition = $workflow->currentDefinition();
    $definition['key'] = $workflow->key;
    $definition['name'] = $workflow->name;

    app(SaveWorkflowDraft::class)->handle($workflow, $definition);
}

Run this after php artisan dbflow:sync in boot, deploy, or your own Artisan command. Skip workflows with source = ui or workflows that already have a draft.

  1. Disable the definition resource (enable_workflow_definition_resource = false) when definitions are owned entirely by code or Pro.

  2. Treat UI edits as ops-only — publishing from Filament creates a new version; code sync behaviour for source = code vs source = ui differs. Document ownership for your team before operators publish from the UI.

Customize navigation

// config/dbflow-filament.php
'navigation_group' => 'Workflow',
'navigation_sort' => [
    'my_tasks' => 10,
    'workflow_instances' => 20,
    'workflow_definitions' => 25,
],

Optional should_register_navigation callable gates all package navigation items for the current user.

Troubleshooting

Symptom Likely cause What to check
No Workflow navigation group Panel not registered or Filament disabled DBFlowFilamentPanel::register(), dbflow-filament.enabled, panel_registration_mode !== disabled
Navigation missing but routes work Permission mapping PermissionChecker must allow dbflow.tasks.view
Empty task list after submit Assignee resolution Resolver registered? Returns user IDs? Current user in assignee set?
Unknown assignee resolver on start Missing Core registration DBFlow::registerAssigneeResolver() for each permission / callback key
404 on package page URLs Panel path mismatch Compare {panel_path}/{route_prefix}/… with Panel::path()
Definition edit page has no node form No draft after code sync Seed draft with SaveWorkflowDraft or create via WorkflowResource
Everyone can approve Default permission checker Replace AllowAllPermissionChecker
UI works but actions fail Core auth / runtime DBFLOW_AUTH_MODEL, assignee IDs; Filament does not gate on dbflow.enabled
Standard linear editor with Pro installed workflow_resource_class still points at Standard Set Pro (or host subclass) in config/dbflow-filament.php; confirm Pro provider is loaded
RouteNotFoundException on DBFlow nav or definition list rows DBFlowFilamentPanel::register() called from boot() Register inside panel(); subclass List/Create/Edit pages when using a custom WorkflowResource
Pro canvas unstyled Filament assets stale php artisan filament:assets; browser cache

End-to-end Filament checklist

  1. Install Core + Filament ^1.0 packages; run migrations.
  2. Publish dbflow-filament-config; set permission_checker_class (not allow-all).
  3. Register DBFlowFilamentPanel::register($panel) inside panel() behind host feature flags.
  4. If using Pro: verify DBFlowFilamentProServiceProvider is loaded; set workflow_resource_class in config; publish/copy Pro config; run filament:assets.
  5. Register definition providers in a host service provider booted() callback; register resolvers and hooks; run php artisan dbflow:sync.
  6. If using the definition editor for code workflows, seed drafts after sync (see above).
  7. Call DBFlow::start() from host business actions; guard downstream operations.
  8. Log in as assignee → My Workflow Tasks → approve or reject.

What's next

Something wrong? Open an issue on GitHub