guava/filament-clusters
guava/filament-clusters helps organize Filament admin panels by grouping resources, pages, and navigation items into configurable clusters, making large Laravel applications easier to structure and navigate. Define clusters in PHP to keep related tools tidy.
Install and pin to the tested minor.
composer require guava/filament-clusters:^1.5
composer why-not guava/filament-clusters # confirm resolved Laravel/Filament constraints
There's no migration, no publishable asset build, no service provider step in the normal case. The install should be a one-line diff.
Read two things before writing code: the package's cluster base class in src/ (the class your definitions extend) and the README section on assigning resources. Those two decide everything downstream. Do not let the description's "cluster multiple fields into one visually" steer you — the docs and the PM notes describe navigation/URL clustering. Spend 15 minutes on a throwaway cluster to confirm which it actually is.
First use case. Pick two or three low-traffic resources from one product domain (Settings or Support), group them, then diff the routes — that diff is the real day-one deliverable:
php artisan route:list --path=admin > routes-before.txt
# build a cluster, assign the resources
php artisan route:list --path=admin > routes-after.txt
diff routes-before.txt routes-after.txt
Everything that later breaks — bookmarks, route() calls, seeded permissions, feature tests — is visible in that diff.
Baseline shape (verify the exact FQCN against the package source):
namespace App\Filament\Clusters;
use Guava\FilamentClusters\Cluster;
class Billing extends Cluster
{
protected static ?string $navigationIcon = 'heroicon-o-credit-card';
protected static ?string $navigationLabel = 'Billing';
protected static ?int $navigationSort = 20;
}
class InvoiceResource extends Resource
{
protected static ?string $cluster = Billing::class;
}
Put cluster resources under app/Filament/Clusters/Billing/Resources/. The directory layout matters more than the generator you use.
One cluster = one product domain = one URL prefix. Billing, Catalog, Support, Settings are clusters. Reports is a feature list — that belongs in a navigation group, not a URL segment. Getting this mapping right is the whole design job; the rest is typing.
Keep cluster classes thin — labels, icons, sort, prefix only. Nothing domain-specific inside the cluster class. This is what makes the exit to Filament-native clustering (where things are heading) a mechanical base-class swap plus directory moves, rather than an admin rewrite. It's also what justifies carrying a single-maintainer dependency.
Assign resources and custom pages with one line each. protected static ?string $cluster = Billing::class; is the entire integration for a resource; custom pages pick up the prefix and breadcrumb trail the same way. This is the everyday payoff — the nth Billing screen costs one line, not a routing decision.
Sort at the cluster level, then within. Set $navigationSort on the cluster for domain ordering; sort resources inside a domain normally. Mixing cluster-level and resource-level sort intent is the number-one source of "why is this menu in the wrong place" bug reports.
Park non-resource pages inside the cluster. Cluster dashboards, settings pages, report pages belong under the cluster prefix. Half a domain at /admin/billing/invoices and half at /admin/misc-report defeats the purpose and confuses users.
One PR per cluster, never bundled with a framework bump. Rollback must be one revert. The Laravel 12 upgrade and a cluster reorg must be separate commits.
Test at HTTP level, not through getUrl(). A feature test hitting /admin/billing/invoices proves the route prefix and cache behaviour; a test calling InvoiceResource::getUrl() proves neither. One smoke test per cluster.
Route names change when a resource moves into a cluster. filament.admin.resources.invoices.index becomes something like filament.admin.clusters.billing.resources.invoices.index. That breaks hardcoded route() calls, seeded ACL/permissions, Gate definitions, and external deep links. Grep for the old names before merging and treat the PR as a URL-break change with a written note.
Run the cache passes before you call it done. Clusters that only fail under production caching are launch-day incidents:
php artisan filament:optimize
php artisan route:cache
php artisan config:cache
php artisan optimize:clear
If route:cache chokes, that's a hard stop, not a workaround.
Don't run two conventions. Filament-native clustering plus this package in the same panel gives you two mental models for one sidebar and inconsistent navigation. Decide once, write it in the team's admin conventions doc.
Debugging order when a cluster route 404s: (1) php artisan route:list --path=admin | grep <cluster> — does the route exist? (2) is the resource's $cluster the imported class, not a string? (3) php artisan optimize:clear — stale component cache after moving files is the most common cause. (4) check getSlug() isn't overriding the prefixed path.
Verify the 1.5.0 compatibility claim at the source. Confirm composer.json actually widened laravel/framework and filament/filament constraints rather than only editing the CI matrix. A CI-only change looks identical in a changelog and fails at install time.
Prefer overriding methods on your own cluster subclass (custom breadcrumbs, scoped nav labels) rather than subclassing package internals — keeps upgrades clean and behaviour visible to the team.
Scope check: no per-cluster ACLs. The package handles navigation grouping and URL prefixes only. Permissions still belong on the underlying resources via policies. If cluster-scoped authorization was your reason to reach for this package, you'll write the glue anyway — measure whether owning the small amount of routing yourself is the better trade.
Pin, name an owner, set a re-evaluation trigger. Single maintainer, zero dependents, Filament absorbing similar grouping into core. Pin ^1.5, name an internal owner, and put a calendar item at the next Filament major to re-evaluate against native clustering.
How can I help you explore Laravel packages today?