F
Filament5mo ago
Xavi

Dynamic sidebar

I'm using Filament 3 and want the sidebar to include a "Channels" group with dynamic items loaded from the database (one per active channel of the authenticated user). The rest of the navigation should still work via discoverResources(). The problem is that I can’t access auth()->user() inside panel() because the user isn’t authenticated yet, so I can’t generate those items there. Is there any recommended way to add user-specific dynamic items without breaking the default discovery and without overriding everything with navigationItems()? Thanks!
Solution:
I finally found the solution. I used ServiceProvider instead of Middleware, and it worked perfectly. Here's the code. ```php...
Jump to solution
9 Replies
toeknee
toeknee5mo ago
For that sir you need a middleware to do it 😉 similar to attached
<?php

namespace App\Http\Middleware;

use Closure;
use Filament\Navigation\NavigationItem;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;

class TenantCheck
{
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle(Request $request, Closure $next): Response
{

$filamentTenant = \Filament\Facades\Filament::getTenant();

if ($menuItems = $filamentTenant->customNavLinks) {
$panel = \Filament\Facades\Filament::getPanel();

$mItems = [];

foreach ($menuItems as $menuItem) {
$mItems[] =
NavigationItem::make($menuItem->label)
->group($menuItem->group ?? null)
->visible(auth()->user()->can('view', $menuItem))
->url($menuItem->url)
->icon($menuItem->icon ?? null)
->openUrlInNewTab($menuItem->new_window ?? false);
}
}

if (! empty($mItems)) {
$panel->navigationItems($mItems);
}

return $next($request);
}
}
<?php

namespace App\Http\Middleware;

use Closure;
use Filament\Navigation\NavigationItem;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;

class TenantCheck
{
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle(Request $request, Closure $next): Response
{

$filamentTenant = \Filament\Facades\Filament::getTenant();

if ($menuItems = $filamentTenant->customNavLinks) {
$panel = \Filament\Facades\Filament::getPanel();

$mItems = [];

foreach ($menuItems as $menuItem) {
$mItems[] =
NavigationItem::make($menuItem->label)
->group($menuItem->group ?? null)
->visible(auth()->user()->can('view', $menuItem))
->url($menuItem->url)
->icon($menuItem->icon ?? null)
->openUrlInNewTab($menuItem->new_window ?? false);
}
}

if (! empty($mItems)) {
$panel->navigationItems($mItems);
}

return $next($request);
}
}
customNavLinks is a relationship to a custom table.
Xavi
XaviOP5mo ago
thanks @toeknee I try this solution but Filament:getTenant() it's null. I put middleware on Panel Provider ->authMiddleware()
toeknee
toeknee5mo ago
Hmm I am sure it gets hit eventually, I did see a current_tenant_id on the user model on my instant and used that to get the tenant
Xavi
XaviOP5mo ago
Using your idea, I was able to insert the channels into navigation items, but they are not visible in the side menu.
<?php

namespace App\Http\Middleware;

use App\Models\Channel;
use Closure;
use Filament\Facades\Filament;
use Filament\Navigation\NavigationItem;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;

class TenantCheckMiddleware
{
/**
* Handle an incoming request.
*
* @param Closure(Request): (Response) $next
*/
public function handle(Request $request, Closure $next): Response
{
$panel = Filament::getPanel();

$menuItems = Channel::query()->where('user_id', auth()->id())->get();

foreach ($menuItems as $menuItem) {
$mItems[] =
NavigationItem::make($menuItem->identifier)
->group('Channels');
}

if (!empty($mItems)) {
$panel->navigationItems($mItems);
}

return $next($request);
}
}
<?php

namespace App\Http\Middleware;

use App\Models\Channel;
use Closure;
use Filament\Facades\Filament;
use Filament\Navigation\NavigationItem;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;

class TenantCheckMiddleware
{
/**
* Handle an incoming request.
*
* @param Closure(Request): (Response) $next
*/
public function handle(Request $request, Closure $next): Response
{
$panel = Filament::getPanel();

$menuItems = Channel::query()->where('user_id', auth()->id())->get();

foreach ($menuItems as $menuItem) {
$mItems[] =
NavigationItem::make($menuItem->identifier)
->group('Channels');
}

if (!empty($mItems)) {
$panel->navigationItems($mItems);
}

return $next($request);
}
}
toeknee
toeknee5mo ago
Create the group Channels first?
Xavi
XaviOP5mo ago
I add this on UserPanelProvider and it doesn't works... navigation items does not show
->navigationGroups([
NavigationGroup::make()
->label('Channel')
->icon('heroicon-o-shopping-cart'),
])
->navigationGroups([
NavigationGroup::make()
->label('Channel')
->icon('heroicon-o-shopping-cart'),
])
I have already been able to get your code @toeknee to work with the tenant (it should have been in tenantMiddleware()), but I still can't get the dynamic items to come out 😦
Solution
Xavi
Xavi5mo ago
I finally found the solution. I used ServiceProvider instead of Middleware, and it worked perfectly. Here's the code.
<?php

namespace App\Providers;

use Filament\Events\TenantSet;
use Filament\Facades\Filament;
use Filament\Navigation\NavigationItem;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\ServiceProvider;

class TenantNavigationServiceProvider extends ServiceProvider
{
public function register(): void
{

}

public function boot(): void
{
Event::listen(TenantSet::class, function (TenantSet $event) {
$tenant = $event->getTenant();
$panel = Filament::getPanel('user');


if ($tenant && $tenant->channels) {
$navigationItems = [];

foreach ($tenant->channels as $channel) {
$navigationItems[] = NavigationItem::make($channel->identifier)
->label($channel->identifier) // Ajusta según tu necesidad
->url('#') // Ajusta según tu necesidad
->icon('heroicon-o-envelope')
->badge(3)
->url(fn(): string => route('filament.user.resources.message-imaps.index', [
'tenant' => Filament::getTenant(),
'channel' => $channel->id]
))
->group('Channels');
}

// Agregar los items de navegación al panel
$panel->navigationItems(array_merge(
$panel->getNavigationItems(),
$navigationItems
));
}
});
}
}
<?php

namespace App\Providers;

use Filament\Events\TenantSet;
use Filament\Facades\Filament;
use Filament\Navigation\NavigationItem;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\ServiceProvider;

class TenantNavigationServiceProvider extends ServiceProvider
{
public function register(): void
{

}

public function boot(): void
{
Event::listen(TenantSet::class, function (TenantSet $event) {
$tenant = $event->getTenant();
$panel = Filament::getPanel('user');


if ($tenant && $tenant->channels) {
$navigationItems = [];

foreach ($tenant->channels as $channel) {
$navigationItems[] = NavigationItem::make($channel->identifier)
->label($channel->identifier) // Ajusta según tu necesidad
->url('#') // Ajusta según tu necesidad
->icon('heroicon-o-envelope')
->badge(3)
->url(fn(): string => route('filament.user.resources.message-imaps.index', [
'tenant' => Filament::getTenant(),
'channel' => $channel->id]
))
->group('Channels');
}

// Agregar los items de navegación al panel
$panel->navigationItems(array_merge(
$panel->getNavigationItems(),
$navigationItems
));
}
});
}
}
Thanks @toeknee
toeknee
toeknee5mo ago
No problem! Glad you got it to work

Did you find this page helpful?