hoceineel/filament-sidebar-editor
A Filament plugin for editing your admin panel sidebar without touching code. Customize navigation items, labels, links, and ordering directly from the UI so each project or team can tailor the menu to fit its workflow, keeping Filament's sidebar flexible and easy to manage.
This is a Filament panel plugin (v0.1.0, pre-1.0) that swaps the read-only navigation for one users can curate themselves: a sidebar page where groups and pages (including children) can be hidden, shown, and drag-and-drop reordered, with a live preview, "N of M shown" counters, mod+s save, reset, and "show everything".
Minimal path:
composer require the package, then register the plugin on the panel you want to expose it on — panels without it keep Filament's stock navigation:use Vendor\SidebarEditor\SidebarEditorPlugin;
public function panel(Panel $panel): Panel
{
return $panel
->plugins([
SidebarEditorPlugin::make(),
]);
}
Visit /admin/sidebar. The editor page is always pinned, so you can't lose the way back. Toggle a couple of groups off, hit mod+s, and confirm the sidebar changes on the next page load.
Decide your storage driver before anyone starts customising. The default is storeGlobally() — one layout per panel, shared by every user. For anything multi-user, chain ->storePerUser() or ->storeOnTenant() in the same make() call.
First real use case: a panel with 8–15 resources where most admins only touch three of them. Pin the two or three that must never disappear, then let each admin hide the rest.
Where to look first if something is off: the bound NavigationManager (the package wraps it via extend()), then your plugin registration order, then the store.
Per-panel configuration lives entirely on the plugin. Treat the make() chain as the config file:
SidebarEditorPlugin::make()
->storePerUser()
->slug('navigation') // avoid clashing with an existing page
->navigationLabel('Edit sidebar')
->navigationGroup('Settings')
->navigationIcon('heroicon-o-bars-3')
->navigationSort(99)
->pinnedItems([Dashboard::class, Settings::class])
->authorize(fn (User $user) => $user->is_admin)
Choose the storage driver to match the mental model, not the schema. Three real options:
storeGlobally() — the panel has one canonical layout. Good for a small ops team that agrees on structure, or as a sensible default before you have users.storeOnTenant() — a tenant-level column. One layout per tenant, shared by everyone in it. Best when an admin curates the sidebar for their organisation, and new staff inherit it.storePerUser() — individual preference. Best for internal tools with power users, worst for consistency (support tickets become "I can't see X").store() / storeUsing() — plug your own persistence (a settings table, a JSON column, Redis). Use this when you already have a per-user preference store, or when you need audit history of who changed the layout.Wrap it around an existing customisation, don't fight it. If you already call NavigationManager::extend() to inject custom items, keep doing that — the package wraps whatever manager is bound, so your items remain visible to the editor. Register the plugin after any package that rebinds the NavigationManager outright, otherwise you may wrap the wrong instance.
Pinning is your safety rail, and it's cheap — use it sparingly. A pinned page makes its whole group un-hideable. Pin the two or three things that would generate a support call if gone (dashboard, the editor itself, billing) and stop there; over-pinning leaves users with a sidebar they can't meaningfully tidy.
Use fixedMainGroup() on opinionated panels (client-facing apps, panels with a single branded group) so the top-level structure stays put while the leaf pages stay editable.
Localisation and RTL come free but are not automatic. English/French/Arabic ship, so publish translations if you add panel-specific labels, and verify RTL by switching the panel locale — the live preview respects direction, but a heavily themed panel (custom CSS, icons with directional meaning) is where RTL breaks first.
Testing strategy. composer serve (Testbench workbench) is the fastest way to eyeball the editor against a realistic navigation set; the package's own Playwright suite (npm run test:e2e) is a good template for the drag-and-drop flows that are painful to unit test. For your own app, cover the parts the package can't: an authenticated request to the editor page returns 200 (or 403 when authorize() says no), a save persists under your chosen driver, and the sidebar renders the hidden item as absent. Save yourself the trouble of asserting drag order in Livewire — assert the persisted payload instead.
authorize() hides the page; it does not disable the layout. A saved layout still applies to users who can't open the editor. That's fine for preference-driven hiding, but do not treat this as access control — permissions belong on resources and policies.mod+s is last-write-wins. Two admins saving simultaneously means one loses their layout with no warning. If that matters, use per-user storage or your own driver with optimistic concurrency (updated_at check).pinnedItems()/fixedMainGroup() rather than expecting it to appear in the editor list.slug() collisions. The default sidebar is generic; if you already have a page at that slug the route registration fails, usually with a confusing 404 rather than a clear error. Set it explicitly.navigationGroup(null) moves the editor entry out of a group — do that if you've grouped everything and don't want an extra "Settings" bucket appearing just for this page.admin but not app."0.1.*") and read the changelog before bumping; the fluent API and the store contract are the most likely things to move.NavigationManager is bound and in what order packages extended it, then confirm which driver is active (dump the store or check the tenant/user column is actually populated), then check for a pinned item forcing a group visible.store()/storeUsing() (your best hook for auditing or syncing layouts), pinnedItems() for guaranteeing critical entries, and the published translations for wording that matches your product's vocabulary.How can I help you explore Laravel packages today?