gheith3/filament-relation-pages
Filament Relation Pages lets you add fully custom, free-form tabs alongside Relation Managers in any resource—no forced table or relationship. Build pages with Filament schema components, Blade/HTML, Alpine.js, and more. Includes an artisan generator.
Install the package:
composer require gheith3/filament-relation-pages
(No manual service provider registration needed—auto-discovered.)
Generate a relation page:
php artisan make:filament-relation-page BuildingSummaryPage --resource=Buildings
This creates:
app/Filament/Resources/Buildings/RelationPages/BuildingSummaryPage.phpresources/views/filament/resources/buildings/building-summary-page.blade.phpAdd content to the generated class:
use gheith3\FilamentRelationPages\RelationPage;
use Filament\Schemas\Components\Section;
use Filament\Schemas\Schema;
class BuildingSummaryPage extends RelationPage
{
protected static ?string $title = 'Summary';
protected static string $icon = 'heroicon-o-chart-bar';
public function content(Schema $schema): Schema
{
return $schema->components([
Section::make('Overview')->schema([
// Add Filament components here
]),
]);
}
}
Register the page in your resource:
public static function getRelations(): array
{
return [
RelationPages\BuildingSummaryPage::class,
// Other relations...
];
}
Create a custom dashboard tab for a parent model (e.g., Building) that displays aggregated data (e.g., summary stats, charts, or custom HTML) without forcing a table or relationship. Example:
Company resources showing revenue trends.Project resources with Alpine.js-powered interactivity.Extend RelationPage:
Override content(Schema $schema) to define Filament schema components or use Blade views for raw HTML/Alpine.js.
Leverage Filament’s Schema System:
public function stats(Schema $schema): Schema
{
return $schema->columns(2)->schema([
TextEntry::make('active_users')->state($this->ownerRecord->users()->active()->count()),
TextEntry::make('recent_activity')->state($this->ownerRecord->lastActivity()),
]);
}
Access these in Blade via {{ $this->stats }}.
Dynamic Badges:
public static function getBadge(): ?string
{
return $this->ownerRecord->pendingTasks()->count();
}
Conditional Visibility:
public static function canViewForRecord(Model $ownerRecord): bool
{
return $this->ownerRecord->isPremium;
}
Lazy Loading:
protected static bool $isLazy = true; // Loads content only when tab is clicked
Mix Filament Components + Raw HTML:
<div class="space-y-6">
{{ $this->content }} <!-- Filament schema -->
<div x-data="{ open: false }">
<!-- Alpine.js interactivity -->
</div>
</div>
Pass Livewire Props:
public static function getDefaultProperties(): array
{
return ['theme' => 'dark'];
}
Access via $this->theme in the class or Blade.
Reuse Across Resources:
Create a base RelationPage class (e.g., BaseAnalyticsPage) and extend it for consistency.
Panel-Specific Placement:
Use --panel=admin in the Artisan command to place pages in app/Filament/Admin/Resources/....
Static Method Conflicts:
Override canViewForRecord(), getTabComponent(), or getDefaultProperties() only if needed. Filament expects these methods to exist, but modifying their signatures may break compatibility.
Livewire Property Locking:
$ownerRecord and $pageClass are #[Locked]—avoid modifying them directly in client-side logic (e.g., Alpine.js). Use emit() to trigger server-side updates.
Schema Method Naming:
Only methods with the signature *($schema): Schema are auto-discovered. Name them descriptively (e.g., financialSummary()).
Blade View Paths:
Ensure Blade views match the generated path (resources/views/filament/resources/{resource}/{page-name}.blade.php). Use php artisan view:clear if paths break after updates.
Lazy Loading Overhead:
Set $isLazy = true for heavy pages, but test performance—each tab click triggers a Livewire mount.
Tab Not Appearing? Check:
getRelations().canViewForRecord() returns true.Schema Not Rendering? Verify:
{{ $this->stats }} requires stats(Schema $schema)).$ownerRecord is accessible (debug with dd($this->ownerRecord)).Alpine.js Not Working? Ensure the Blade view includes Filament’s Livewire/Alpine setup:
@filamentScripts
<div x-data="{}">...</div>
Custom Stubs: Publish and modify stubs for project-wide templates:
php artisan vendor:publish --tag=filament-relation-pages-stubs
Global Badges/Icons:
Extend the RelationPage class to add default styles or logic:
abstract class CustomRelationPage extends RelationPage
{
protected static string $icon = 'heroicon-o-document-text';
}
Shared Logic: Create a trait for reusable functionality (e.g., caching, permissions):
trait HasCachedData
{
protected ?array $cachedData;
public function getCachedData(): array
{
return $this->cachedData ??= $this->fetchData();
}
}
Testing:
Mock ownerRecord in unit tests:
$page = new BuildingSummaryPage();
$page->ownerRecord = Building::factory()->create();
$this->assertTrue($page->canViewForRecord($page->ownerRecord));
getDefaultProperties() or canViewForRecord():
public static function getDefaultProperties(): array
{
return ['users' => $ownerRecord->users()->with('roles')->get()];
}
$isLazy = true and load data only when the tab is active.No Plugin Registration Needed:
The package works without calling ->plugin(RelationPagesPlugin::make()) in your panel. The RelationPagesPlugin class exists for Filament’s plugin directory convention only.
Filament Version Compatibility: Test with both Filament v4 and v5. The package abstracts differences, but complex schemas may need adjustments for breaking changes (e.g., component signatures).
How can I help you explore Laravel packages today?