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 Topbar Menu Laravel Package

vaslv/filament-topbar-menu

A Filament 5 plugin that adds a configurable topbar menu right after the logo, linking to external URLs or named Laravel routes with nested dropdowns, icons and favicons, visibility rules, automatic caching, a full resource with drag-and-drop reordering, a responsive mobile panel, and 13 bundled tra...

View on GitHub
Deep Wiki
Context7

Getting Started

Three commands and one line of provider code get you a working menu:

composer require vaslv/filament-topbar-menu
php artisan migrate   # migration is auto-loaded, no publish required
// app/Providers/Filament/AdminPanelProvider.php
use Vaslv\FilamentTopbarMenu\TopbarMenuPlugin;

public function panel(Panel $panel): Panel
{
    return $panel
        // ...
        ->plugin(TopbarMenuPlugin::make());
}

That single plugin() call buys you two things: a Topbar Menu resource in the panel navigation (full CRUD, drag-to-reorder, activate toggle, and a read-only View page), and the rendered menu itself in the topbar right after the logo via PanelsRenderHook::TOPBAR_LOGO_AFTER. No assets to build, no layout overrides, no extra step.

Where to look first, in order:

  1. The Topbar Menu resource in the panel sidebar — it is the day-to-day surface; everything else is optional configuration.
  2. The plugin's fluent options (->resource(false), ->mobileSidebar(), ->renderHook(...)) if you need to place the menu differently.
  3. The published config (php artisan vendor:publish --tag=filament-topbar-menu-config) only once you hit a behaviour you want to change.

First use case — the obvious one, and usually the reason you installed it: put a few external service links (Grafana, Sentry, docs, your marketing site) and one or two deep links into your own Filament app next to the panel logo. Add them through the resource, done. If you want to see every feature at once before designing your own tree:

php artisan db:seed --class="Vaslv\FilamentTopbarMenu\Database\Seeders\TopbarMenuSeeder"

The seeder is idempotent (matched by parent + label), so re-running it updates demo items in place rather than duplicating them — safe to leave in a scratch environment.

Implementation Patterns

Treat the resource as the source of truth; use the model for reproducible deployments. The CRUD resource is where you'll actually live, but for anything that must survive environments (staging seeds, blue/green deploys, feature flags), create items programmatically with TopbarMenuItem::create([...]) — same schema, same cache invalidation path, same rendering.

TopbarMenuItem::create([
    'label'  => 'Grafana',
    'type'   => 'url',
    'url'    => 'https://grafana.example.com',
    'target' => '_blank',
]);

TopbarMenuItem::create([
    'label'            => 'Monthly report',
    'type'             => 'route',
    'route'            => 'reports.show',
    'route_parameters' => ['report' => 'monthly'],
]);

Two link types, one contract. type => 'url' for anything outside your app; type => 'route' for internal targets and route_parameters for anything with a slug or id. Missing routes are skipped at render time rather than throwing, so a renamed route costs you a menu item, not a 500.

Dropdowns are just parent–child rows. A top-level item with at least one visible child becomes a Filament dropdown group — the toggle is a pure toggle, exactly like the panel's native topNavigation() groups. A group's own url/route is only used when it has no visible children (it falls back to a plain link, or disappears if it has no URL either). The practical consequence: a landing page for a dropdown group must be an explicit child, not the parent's own URL.

$services = TopbarMenuItem::create([
    'label' => 'Services', 'type' => 'url', 'url' => '#', 'sort' => 1,
]);
TopbarMenuItem::create([
    'label' => 'All services', 'type' => 'route', 'route' => 'services.index',
    'parent_id' => $services->id, 'sort' => 1,
]);
TopbarMenuItem::create([
    'label' => 'Status page', 'type' => 'url', 'url' => 'https://status.example.com',
    'parent_id' => $services->id, 'sort' => 2,
]);

Visibility is evaluated per request, not baked in. The three audiences (everyone / authenticated / guests) plus the roles restriction and hide_on_current_domain are checked on every render, which means a dropdown can flip between "group" and "plain link" depending on who is looking. The View page is the only place in the panel where a roles restriction is visible — the form deliberately doesn't edit that key. Use it as your audit surface for anything imported or hand-written.

Caching is invisible until it isn't. Renders hit the cache, not the database; the cache is flushed on model changes. That means the resource feels instant and the menu costs you one cache read per page. Don't fight it — just know that anything you write outside Eloquent (raw SQL, DB::table() imports, a saveQuietly() that skips events depending on how the flush is wired) is the one place you may need to clear it manually.

Responsive: one menu, two copies. Below lg, the desktop copy is inside the container Filament hides, so the package renders a second copy behind its own button next to the ☰. The default is a slide-in panel of its own; ->mobileSidebar() instead appends it to the panel's own sidebar below its navigation. Choose the sidebar variant when your panel navigation is short (one list, no second button); leave the default when it's long (otherwise the menu sits below every navigation item and has to be scrolled to). ->mobile(false) disables the mobile copy entirely.

Multi-panel setups. Put ->resource(false) on public-facing panels that should display the menu but not manage it; keep the resource only on your admin panel. ->resourceNavigationGroup('Settings') and ->resourceNavigationSort(...) slot the management resource where your existing navigation expects it.

Turn off the View page if it doesn't fit your model. FILAMENT_TOPBAR_MENU_VIEW_PAGE=false (or 'enable_view_page' => false) drops the View action, button, and redirects, restoring Filament's stock create/edit flow. Your own resourceCreatePageRedirect() / resourceEditPageRedirect() are respected either way.

Gotchas and Tips

  • Deleting a group deletes its children. parent_id cascades on delete and there is no extra confirmation. If you need soft-delete semantics, add deleted_at yourself — the package won't.
  • Exactly two levels render. Giving a parent a parent keeps the rows but drops the grandchildren off the menu silently. Moving a group under another group is the same trap: rows survive, they simply stop rendering.
  • The roles restriction fails closed. If your user model doesn't expose hasAnyRole() (e.g. spatie/laravel-permission), every role-restricted item is hidden from everyone. The seeder also skips its "Admin Tools" example in that case — and removes it on re-run if roles support has since been removed.
  • Route name picker hides anything containing livewire. It's a substring match, so a route of yours with that word in the name won't appear in the picker. Set route directly on the model for those.
  • sort is a position among siblings, not a global rank. The table lists top-level items at their sort position with children underneath; ties break by id. Drag-and-drop writes sibling sort values only — it never changes a parent. Use the Parent item field for that.
  • open_external_links_in_new_tab only sets the form default. The per-item target is authoritative: _self always same tab, _blank always new tab. Don't expect the config to override an explicit choice.
  • The View route is conditional. It's registered only while enable_view_page is on. Flip the option on a route-cached panel and re-run php artisan route:cache — otherwise the package notices the missing route and hides the View button (a graceful degradation, but a confusing one if you're wondering where it went).
  • ext-intl is optional but not free. Without it, hide_on_current_domain stops matching internationalized domains, and favicons for IDN domains can't be resolved. Everything else works. Install it if you serve customers on 例え.jp-style domains.
  • ->mobileSidebar() needs a sidebar. A panel with navigation(false) has nowhere to render into — the default slide-in panel brings its own, so use that there. Likewise, if you've moved the desktop menu to a custom hook outside .fi-topbar-start and un-hid .fi-topbar-nav-groups with CSS, ->mobile(false) avoids showing two copies below lg.
  • The mobile panel is teleported to <body>. That's deliberate (topbar's overflow-x: clip and stacking context would otherwise trap it), and it's why it inherits fi-body-has-topbar offsetting. If you're debugging layout, inspect the body, not the topbar.
  • Favicon precedence is embedded > favicon URL > icon name. One marker per item, not a stack. The favicon is resolved and cached, never fetched at render time — so a dead favicon URL is a silent fallback to your icon, not a visible failure.
  • Translation is a flat file per locale. Add resources/lang/{locale}/filament-topbar-menu.php with a key set identical to en; a missing key shows the English fallback rather than breaking the page, but keep them in sync for maintainability.
  • This is a young package (v0.x, small user base). Pin the version in composer.json, read CHANGELOG.md before bumping, and treat the CI matrix (PHP 8.2–8.4, MySQL 8, PostgreSQL 16, one leg without ext-intl) as the source of truth for "supported", not your local SQLite run — the package ships hand-written SQL (scopeTreeOrdered()) and driver-aware migrations, and SQLite tolerates shapes MySQL rejects. If you extend or reorder the tree query yourself, test against the DB engine you deploy on.
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.
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
robertmarney/lara-hierarchial-collections
simonschaufi/rule-doc-generator
emissaoja/emissaoja
blackbricksoftware/gravityforms-civicrm-form-processor
al-rahabi/fancy-crud
jinas/dhivatar-component
mltstephane/laravel-quality
bernskiold/laravel-talentlms
bernskioldmedia/laravel-form-architect