ghanem / friendship-filament
Filament admin panel plugin for the ghanem/friendship package — moderate friendships, requests and blocks.
Requires
- php: ^8.2
- filament/filament: ^4.0|^5.0
- ghanem/friendship: ^2.1
- illuminate/support: ^11.28|^12.0|^13.0
Requires (Dev)
- orchestra/testbench: ^9.0|^10.0|^11.0
- pestphp/pest: ^2.34|^3.0
README
A Filament admin panel plugin for ghanem/friendship: browse and moderate friendships, inspect any user's social graph, and see panel stats.
Requirements
- PHP 8.2+
- Laravel 11.28+
- Filament 4 or 5
ghanem/friendship2.1+
The core
ghanem/friendshippackage supports Laravel 10, but Filament itself requires Laravel 11.28+ — so this plugin starts there too, even though the package it wraps goes further back.
Installation
composer require ghanem/friendship-filament php artisan vendor:publish --tag=friendship-filament-config
Register the plugin in your panel provider:
use Ghanem\FriendshipFilament\FriendshipPlugin; public function panel(Panel $panel): Panel { return $panel->plugin(FriendshipPlugin::make()); }
Configuration
Friendships are polymorphic, so the panel needs to know how to display each
model type. Map them in config/friendship-filament.php:
'models' => [ \App\Models\User::class => [ 'label' => 'name', // see label forms below 'resource' => \App\Filament\Resources\UserResource::class, // optional deep link ], ],
Other keys in the same file:
'fallback_label' => null, // used before the "Type #id" default; same forms as 'label' 'deleted_label' => 'Deleted record', 'navigation_group' => null, 'navigation_sort' => null,
Label forms
label and fallback_label accept any of:
| Form | Example | config:cache safe |
|---|---|---|
| Attribute name | 'name' |
✅ |
| Invokable class-string | \App\Support\UserLabel::class |
✅ |
[class, method] pair |
[\App\Support\UserLabel::class, 'make'] |
✅ |
| Closure | fn ($user) => $user->full_name |
❌ |
Do not use closures in production.
php artisan config:cacheserialises config withvar_export()and throwsLogicException: Your configuration files are not serializableon any closure — and the error names your config file, not this package. Closures stay supported for local development; use one of the first three forms anywhereconfig:cacheruns.
An invokable class or [class, method] pair is resolved from the container and
receives the model:
namespace App\Support; class UserLabel { public function __invoke(\App\Models\User $user): string { return $user->first_name.' '.$user->last_name; } }
Types with no entry in models fall back to fallback_label if set,
otherwise to User #12-style labels. Rows whose model has been deleted render
as deleted_label plus the row's own morph context — Deleted record (User #12) — so orphaned rows stay identifiable.
navigation_group and navigation_sort control where FriendshipResource
appears in the panel navigation. navigation_group is only used when no
FriendshipPlugin registered on the current panel has set one via
->navigationGroup() — see Customising the plugin.
There is no plugin-level equivalent for navigation_sort; it is always read
from config.
What you get
Friendships Resource
A read-only moderation table of every friendship: sender, recipient, status badge, and timestamps, filterable by status and sender type. Admins do not create friendships — users do — so there is no create or edit page, only a list and a view page.
Friends relation manager
Add it to your own UserResource to see a user's friendships inline:
use Ghanem\FriendshipFilament\RelationManagers\FriendsRelationManager; public static function getRelations(): array { return [ FriendsRelationManager::class, ]; }
That's the only step required — the manager builds its own bidirectional
query and doesn't need a friendships() relation on your model. It lists
friendships in both directions — rows where the user sent the request and
rows where they received it — showing the other party and whether the
friendship was incoming or outgoing.
Its visibility follows your app's own authorization: if you register a policy
for Ghanem\Friendship\Models\Friendship, the relation manager is shown only
when viewAny is allowed; if you don't register one, it's shown by default.
Moderation actions
| Action | Applies to | Effect |
|---|---|---|
| Approve request | Pending | Accepts on the recipient's behalf |
| Unfriend | Accepted | Removes the friendship for both people |
| Lift block | Blocked | Lifts the block as the blocker |
Each requires confirmation and is hidden when it doesn't apply. All of them go
through the core package's public API (acceptFriendRequest(), unfriend(),
unblock()), so your event listeners still fire. Available on both the
Resource's table and the relation manager's table.
Widgets
FriendshipStatsWidget (accepted, pending, blocked counts) and
FriendshipsChartWidget (accepted friendships formed per day, last 30 days).
Both are registered by default; opt out with ->widgets(false).
Customising the plugin
FriendshipPlugin::make() ->navigationGroup('Community') ->widgets(false) // skip the dashboard widgets ->resource(false); // skip the Resource, keep the relation manager
Authorization
No policy ships with this package. Register your own policy for
Ghanem\Friendship\Models\Friendship and Filament — and the relation
manager's own visibility check (see above) — will honour it. Moderation
actions are destructive — gate them deliberately.
Note that FriendsRelationManager overrides canViewForRecord(), so the
inherited skipAuthorization() / checkPolicyExistence() toggles do not
affect whether its tab is visible; that is decided solely by the override
described above. Those toggles still apply to row-level actions inside the
relation manager.
Testing
composer test
Related packages
- ghanem/friendship — the friendship engine this plugin renders; requests, acceptance, blocking and query scopes
- ghanem/rating — polymorphic ratings and reviews for Eloquent models
- ghanem/rating-filament — Filament 4 & 5 components for
ghanem/rating
License
MIT. See LICENSE.