laboiteacode/filament-dependency-graph
Visual architecture explorer for Filament that automatically discovers Eloquent models, relations, Livewire components, resources and panels with no configuration, then presents them in an interactive graph, tree, table and inspector with search, filtering and exports.
The visual architecture explorer for Filament.
Explore models, Livewire components, resources, panels and relationships from one visual workspace. Filament Dependency Graph automatically discovers the structure of a Laravel and Filament application and presents it through an interactive, navigable interface: a graph, a tree, a table, a contextual inspector, search, focus mode, filtering and exports.

Large Laravel and Filament applications are hard to understand because their structure is scattered across models, relationships, resources, panels, relation managers, policies, traits, casts and morph maps. A developer joining an existing project inspects dozens of files before understanding which models exist, how they connect, which resources expose them and where circular dependencies live. Static diagrams rot; manual documentation is never complete.
Install the package, register the plugin, open one page, and immediately answer:
app/Livewire or the legacy app/Http/Livewire convention appear in the Laravel scope even when they are not registered in a Filament panel. Typed properties, method signatures and explicit static model references become component-to-model dependencies without instantiating the component.The CI matrix is the source of truth.
| Package | PHP | Laravel | Filament |
|---|---|---|---|
| 1.x | 8.3, 8.4, 8.5 | 12.x, 13.x | 4.x, 5.x |
composer require laboiteacode/filament-dependency-graph
Register the plugin in your panel provider:
use LaBoiteACode\DependencyGraph\DependencyGraphPlugin;
public function panel(Panel $panel): Panel
{
return $panel
->plugins([
DependencyGraphPlugin::make(),
]);
}
That is all: open /admin/dependency-graph in your local environment. The plugin can be registered on several panels, each with its own options.
Class names, table names and relationship metadata are sensitive architecture information. The page is only visible when app()->isLocal() returns true. Never expose it publicly.
To enable it elsewhere, take an explicit decision:
DependencyGraphPlugin::make()
->canAccessUsing(fn (): bool => auth()->user()?->can('viewDependencyGraph') === true);
visible() is an alias of canAccessUsing(). The callback replaces the local-only rule entirely, so make it as strict as your context requires.
The default view renders the dependency graph with typed nodes: panels, resource hexagons, Livewire components, model boxes and dashed polymorphic-target diamonds. Two layouts are available from the toolbar:
Interactions: zoom and pan, drag nodes, click a node or an edge to open the native Filament inspector slide-over (the rest of the graph fades to keep focus), click the canvas to clear, use the explicit zoom controls or Fit to re-center. The expand control temporarily gives the explorer column back to the canvas; press Esc to restore it. Relation labels appear once the zoom makes them readable.
A cycle-safe, depth-limited expansion of the graph starting from the panels (or from the selected node), with the relation method displayed on every branch. Useful for answering "what hangs off this model" linearly.
A native Filament Table exposes four inventory categories - models, relations, Livewire components and resources - through compact tabs. Search, sorting, pagination, column visibility and empty states follow the panel behavior automatically. Counts, aliases, views, foreign keys, pivot tables, navigation groups and discovery status remain available, and every row opens the inspector.
Selecting a node or an edge opens a native Filament slide-over with everything the discovery collected: namespace, table, primary key, soft deletes, traits, casts, relation list with types and keys, pivot metadata, morph maps, Livewire aliases, rendered views and public APIs, panels and pages for resources, plus diagnostics when discovery was partial. Filament handles its focus trap, scroll lock, responsive placement and dismissal consistently with the rest of the panel.
Press F (or the inspector button) to restrict the graph to the neighborhood of the selected node, with a configurable depth (1, 2, 3 or unlimited) and direction (incoming, outgoing or both). The focus state lives in the URL query string, so the exact view can be shared or bookmarked.
Press / and type: the search matches class names, labels, table names, namespaces, panel ids and relation methods, grouped by node type. Selecting a result clears the transient filters, selects the node and centers the graph on it.
| Key | Action |
|---|---|
/ |
Focus search |
Esc |
Close inspector or exit focus mode |
F |
Focus selected node |
R |
Reset graph |
G |
Graph view |
T |
Tree view |
L |
Table view |
E |
Export JSON |
laravel_scope_enabled => false or ->allowLaravelScope(false).Publish the configuration file when you need to adjust discovery:
php artisan vendor:publish --tag=filament-dependency-graph-config
Every option, with its default:
use LaBoiteACode\DependencyGraph\Domain\Enums\GraphScope;
return [
// Master switch: hides the page everywhere, keeps the API and CLI working.
'enabled' => true,
// Navigation entry. Every key has a fluent twin on the plugin.
'navigation' => [
'label' => null, // defaults to the translated label
'icon' => 'heroicon-o-share',
'active_icon' => null,
'group' => null,
'sort' => null,
'parent_item' => null,
'register' => true, // false keeps the route, hides the menu entry
],
// Page placement and layout.
'page' => [
'slug' => 'dependency-graph',
'cluster' => null, // a Filament cluster class name
'max_content_width' => 'full', // any Filament\Support\Enums\Width value
],
// Scopes.
'default_scope' => GraphScope::Filament,
'laravel_scope_enabled' => true,
// Model discovery.
'model_paths' => [app_path('Models')],
'model_namespaces' => ['App\\Models\\'],
'exclude' => [
'classes' => [],
'namespaces' => [],
'tables' => [],
'relations' => [], // "App\Models\Order::customer"
],
'vendor_models' => [
'enabled' => false,
'namespaces' => [],
],
// Standalone Livewire components shown in the Laravel scope.
'livewire' => [
'enabled' => true,
'paths' => [
app_path('Livewire'),
app_path('Http/Livewire'),
],
'namespaces' => [
'App\\Livewire\\',
'App\\Http\\Livewire\\',
],
],
// Discovery behavior.
'discovery' => [
'relations' => true,
'database_schema' => true,
'docblocks' => true,
// Calls untyped methods to check whether they return a relation.
// Disabled by default: invoking arbitrary methods may trigger
// application side effects.
'heuristic_relation_invocation' => false,
],
// Graph defaults.
'graph' => [
'default_depth' => 2,
'default_direction' => 'both',
'default_layout' => 'hierarchical',
'show_panel_nodes' => true,
'show_resource_nodes' => true,
'show_orphans' => true,
],
// Snapshot cache (bypassed automatically in the testing environment).
'cache' => [
'enabled' => true,
'store' => null,
'ttl' => 3600,
],
// The page is local-only unless a visibility callback says otherwise.
'authorization' => [
'local_only' => true,
],
// Built-in exporters.
'exports' => [
'json' => true,
'mermaid' => true,
],
];
The plugin exposes the discovery knobs fluently:
DependencyGraphPlugin::make()
->defaultScope(GraphScope::Filament)
->defaultDepth(2)
->allowLaravelScope()
->scanVendorModels(false)
->scanLivewireComponents()
->excludeModels([AuditLog::class])
->registerModelPath(app_path('Domain'))
->registerModelNamespace('App\\Domain\\')
->registerLivewirePath(app_path('Domain/Livewire'))
->registerLivewireNamespace('App\\Domain\\Livewire\\')
->registerExporter(new MyGraphvizExporter())
->registerInspector(new MyAuditableInspector());
Navigation, page placement and layout follow the same conventions as the other La Boite a Code plugins. Everything is available fluently on the plugin, or under the navigation and page keys of the configuration file; the plugin wins when both are set:
use Filament\Support\Enums\Width;
DependencyGraphPlugin::make()
->navigationLabel('Architecture')
->navigationIcon('heroicon-o-share')
->activeNavigationIcon('heroicon-s-share')
->navigationGroup('Developer tools') // string, enum or closure
->navigationSort(30)
->navigationParentItem('Tooling')
->navigationBadge(fn (): string => 'beta')
->registerNavigation(false) // keep the route, hide the menu entry
->slug('architecture-map')
->cluster(\App\Filament\Clusters\Developer::class)
->maxContentWidth(Width::SevenExtraLarge)
->canAccessUsing(fn (): bool => auth()->user()?->isDeveloper() ?? false);
The page renders full width by default so large graphs get the whole viewport; set page.max_content_width (or maxContentWidth()) to any Filament\Support\Enums\Width value to constrain it.
Every label ships in English and French. Publish the language files to adjust or add locales:
php artisan vendor:publish --tag=filament-dependency-graph-translations
The Blade views are publishable for deep customization, although the stable extension points (exporters, inspectors, configuration) should cover most needs:
php artisan vendor:publish --tag=filament-dependency-graph-views
The interface is built from the native Filament Blade components (sections, tabs, buttons, selects, checkboxes, badges) and the graph palette reads your panel color scales (primary, success, info, warning, gray) at runtime, so a custom Filament theme restyles the whole page - including the graph - without any extra work.
From the UI: the toolbar downloads the current graph, filters included, as JSON or Mermaid.
From the CLI:
php artisan filament-dependency-graph:export
{--format=json : Export format, json or mermaid}
{--scope= : Graph scope, filament or laravel}
{--panel=* : Only include the given panel ids}
{--focus= : Focus on a node id, for example model:app.models.order}
{--depth= : Focus traversal depth}
{--direction=both : Focus direction, incoming, outgoing or both}
{--output= : Write the export to this file instead of standard output}
{--force : Overwrite the output file when it exists}
For example:
php artisan filament-dependency-graph:export \
--format=mermaid \
--scope=laravel \
--output=docs/dependency-graph.mmd
Both exports are deterministic: same application state, same output. Diff them in CI to detect architecture drift, or commit the Mermaid file next to your documentation.
Discovery relies on reflection and file scanning, so snapshots are cached. The cache stores the discovered snapshot only - never rendered output - and is bypassed automatically in the testing environment.
# Warm the cache, for example in a deploy pipeline
php artisan filament-dependency-graph:cache
{--scope= : Discovery scope, filament or laravel}
{--panel=* : Only discover the given panel ids}
{--no-schema : Skip database schema inspection}
{--force : Rebuild even when a cached snapshot exists}
# Clear every cached snapshot
php artisan filament-dependency-graph:clear
The cache key includes the package schema version, the runtime versions and every discovery setting, so configuration changes never serve stale graphs.
The facade proxies the DependencyGraphManager contract, so everything the UI does is scriptable:
use LaBoiteACode\DependencyGraph\Domain\Enums\GraphScope;
use LaBoiteACode\DependencyGraph\Domain\ValueObjects\GraphQuery;
use LaBoiteACode\DependencyGraph\Facades\DependencyGraph;
// Raw discovery snapshot: models, relations, Livewire components, panels,
// resources and warnings.
$snapshot = DependencyGraph::discover();
// Full graph with the default query.
$graph = DependencyGraph::graph();
// Focused sub-graph.
$graph = DependencyGraph::graph(new GraphQuery(
scope: GraphScope::Laravel,
panelIds: ['admin'],
focusNodeId: 'model:app.models.order',
depth: 2,
));
$graph->nodeCount();
$graph->edgeCount();
$graph->toArray();
// Exports.
$json = DependencyGraph::export('json');
$mermaid = DependencyGraph::export('mermaid');
DependencyGraph::clearCache();
GraphQuery accepts the scope, panel ids, node types, relation types, a focus node with depth and direction, and whether orphans are included - the same capabilities as the UI filters.
Implement the GraphExporter contract and register it on the plugin:
use LaBoiteACode\DependencyGraph\Contracts\GraphExporter;
use LaBoiteACode\DependencyGraph\Domain\Graph\Graph;
use LaBoiteACode\DependencyGraph\Domain\ValueObjects\ExportOptions;
class GraphvizExporter implements GraphExporter
{
public function format(): string
{
return 'dot';
}
public function export(Graph $graph, ExportOptions $options): string
{
// Walk $graph->nodes and $graph->edges, return DOT markup.
}
}
DependencyGraphPlugin::make()
->registerExporter(new GraphvizExporter());
The format becomes available in the UI and through the facade on panel requests. For console usage (--format=dot), register the exporter in a service provider instead, by resolving LaBoiteACode\DependencyGraph\Export\ExportManager and calling register() - the plugin only boots with its panel.
Implement the NodeInspector contract to enrich (or replace) the inspector panel for the nodes you care about. Custom inspectors are consulted before the built-in ones:
use LaBoiteACode\DependencyGraph\Contracts\NodeInspector;
DependencyGraphPlugin::make()
->registerInspector(new MyAuditableInspector());
canAccessUsing() deliberately (see the security warning).php artisan filament:assets.model_paths and model_namespaces; add your custom locations there or with registerModelPath() / registerModelNamespace(). Vendor models are excluded unless vendor_models.enabled is true.livewire.paths / livewire.namespaces, or use registerLivewirePath() / registerLivewireNamespace(). Components are visible in the Laravel scope, not the Filament scope.Order::query(). Dynamic container resolution and untyped variables cannot be inferred safely.composer test
composer analyse
composer lint
The suite runs on Pest with Orchestra Testbench against a realistic fixture domain covering every supported relation type, multi-panel resources and failure scenarios.
The codebase is layered: a framework-agnostic domain (graph model, DTOs, algorithms), an application layer (use cases), infrastructure (discovery, cache, exporters) and a Filament presentation layer. Filament 4 and 5 differences are isolated behind a compatibility adapter. Architecture decision records live in docs/adr.
See CHANGELOG.md for release notes.
See CONTRIBUTING.md. Please report security issues privately as described in SECURITY.md.
Open source under the MIT license.
How can I help you explore Laravel packages today?