Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Filament Sidebar Editor Laravel Package

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.

View on GitHub
Deep Wiki
Context7

Getting Started

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:

  1. 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(),
        ]);
}
  1. 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.

  2. 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.

  3. 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.

Implementation Patterns

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.

Gotchas and Tips

  • 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.
  • Renaming a resource class silently resurrects it. Layouts key off navigation item identity, so a renamed resource looks like a brand-new page (visible), and the stale entry drops out. Expect a "why is this back?" message after every refactor, and rename deliberately.
  • Deleted resources drop out automatically, but old saved keys may linger in your store. Add a periodic prune if you use a custom driver and care about payload size.
  • Global storage is a shared mutable document — 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).
  • Custom navigation items need explicit handling. Anything not backed by a page or resource class (closure items, items from another package) may not be enumerable — pin it or hide it via 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.
  • Panels without the plugin keep Filament's navigation, which is the correct default but easy to forget when you have several panels — the "editor missing" bug is nearly always a plugin registered on admin but not app.
  • 0.1.0 means pin it. Use an exact constraint ("0.1.*") and read the changelog before bumping; the fluent API and the store contract are the most likely things to move.
  • Debugging a wrong sidebar: first confirm which 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.
  • Extension points worth knowing: custom driver via 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.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin