Filament UI
Stable release:
dbflowlabs/filamentis 1.0 stable. Page slugs, config keys, and permission names are frozen for the 1.x public integration contract. Use^1.0constraints 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
AllowAllPermissionCheckerby default. Replacepermission_checker_classinconfig/dbflow-filament.phpbefore 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}/…. Defaultroute_prefixisdbflow(config('dbflow-filament.route_prefix')). WorkflowResourceis a normal Filament resource registered at the panel root — its slug isworkflows, not under thedbflowprefix.- Named routes use
route_name_prefix(defaultdbflow.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
- Register
DBFlowFilamentProServiceProvider(auto-discovery or manualbootstrap/providers.phpentry). - Keep
DBFlowFilamentPanel::register($panel)insidepanel()— notboot(). - Set
workflow_resource_classexplicitly in publishedconfig/dbflow-filament.php:
use DbflowLabs\FilamentPro\Filament\Resources\WorkflowResource as ProWorkflowResource;
'workflow_resource_class' => ProWorkflowResource::class,
- Run
php artisan filament:assetsafter 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 end → RejectStrategy::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:
- 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.
-
Disable the definition resource (
enable_workflow_definition_resource = false) when definitions are owned entirely by code or Pro. -
Treat UI edits as ops-only — publishing from Filament creates a new version; code sync behaviour for
source = codevssource = uidiffers. 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
- Install Core + Filament
^1.0packages; run migrations. - Publish
dbflow-filament-config; setpermission_checker_class(not allow-all). - Register
DBFlowFilamentPanel::register($panel)insidepanel()behind host feature flags. - If using Pro: verify
DBFlowFilamentProServiceProvideris loaded; setworkflow_resource_classin config; publish/copy Pro config; runfilament:assets. - Register definition providers in a host service provider
booted()callback; register resolvers and hooks; runphp artisan dbflow:sync. - If using the definition editor for code workflows, seed drafts after sync (see above).
- Call
DBFlow::start()from host business actions; guard downstream operations. - Log in as assignee → My Workflow Tasks → approve or reject.
What's next
- Permissions → — authorization extension points and ability mapping
- Pro Visual Builder → — LogicFlow canvas (DBFlow Pro)
- Workflow Timeline → — audit trail presentation on instance detail
- Filament Resource Actions → — start workflows from your own resources
- Host Integration → — Core wiring, sync, assignees, guards
- Host Reference Patterns → — optional ERP-style integration patterns
- Refund Approval → — full end-to-end demo walkthrough