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

Tomato Menus Laravel Package

tomatophp/tomato-menus

Menu management for Laravel and TomatoPHP: build database-backed navigation menus, organize nested items, and render dynamic links in admin panels or Blade views with a simple, reusable API.

View on GitHub
Deep Wiki
Context7

Getting Started

There is no README worth reading. Go to the source in this order:

  1. composer.json — pin down laravel/framework and filament/filament constraints first. A May 2024 release sits in the Laravel 11 / Filament v3 window, so on a Laravel 12 or Filament v4 app you are testing compatibility before you are testing features.
  2. src/Providers/*ServiceProvider.php — this is where the driver binding happens (JSON vs DB), where config and migrations get published, and where the Blade components/helper macros are registered. Read it end to end; it is short and it is the map.
  3. config/tomato-menus.php (or whatever it is named after publishing) — the provider switch and the JSON file path live here. Note whether the switch is global or per-location.
  4. database/migrations/* — the tree schema. parent_id vs nested set, order, is_active, target, FK cascade behavior. This file determines half the bugs you will hit in month two.
composer require tomatophp/tomato-menus:1.1.8
php artisan vendor:publish --tag=tomato-menus-config   # verify the tag in the provider
php artisan vendor:publish --tag=tomato-menus-migrations
php artisan migrate

Then register the Filament plugin in your PanelProvider if you want the admin surface.

First use case: the primary nav of an internal admin panel, DB driver, two flat items. Render it in a layout, confirm it survives a php artisan optimize and a cache clear, then add one child item. Nesting is where the schema assumptions surface — do not start there. Get flat-render working end to end before you touch parent/child, icons, or active flags.

Implementation Patterns

Wrap it on day one, before you write a single Blade file. Define App\Menus\MenuRepository with tree(string $key): Collection and url(MenuItem $item): string, implement it over the package's models, and never let TomatoPHP\Menus\... leak into a controller, Livewire component, or view. With zero stars and zero dependents, this is not paranoia — it is the difference between a two-day swap to spatie/laravel-menu or a hand-rolled menus/menu_items pair, and a two-week excavation.

Pick the driver per surface, seeded from JSON. The JSON provider is for nav that ships with a release — diffable in code review, revertible, environment-consistent. The DB provider is for anything an admin edits at runtime. If the package only exposes a global switch (likely, given the config shape), run it in DB mode and treat a committed database/seeders/data/primary-nav.json as the fixture: seed it in DatabaseSeeder, and re-seed on deploy for surfaces you want release-controlled. You get code review of the JSON plus runtime editability, and you never depend on the package's file-write path.

Cache the resolved tree yourself and bust it with an observer. Resolve each key at most once per request behind a Cache::remember("menu.{$key}"), and register observers on the package's menu/item models so saved and deleted forget the key. Do not assume the package invalidates anything — verify it, and if it does, verify it also covers nested child edits (a child save that does not forget the parent's cached tree is the classic bug). A stale nav is silent and customer-facing; it is the failure mode that costs you the most credibility.

Own the renderer. Build a recursive Blade partial that takes your normalized tree and emits <ul>/<li> with your classes, your aria-current handling, and your active-state comparison against request()->url(). Treat any package Blade component as a reference implementation for markup shape only. Your design system changes will outnumber the package's releases by orders of magnitude; you do not want to fork views to change a class.

Filter visibility in the wrapper, not in the package. The is_active flag is almost certainly just a boolean. Do authorization in your repository:

->filter(fn ($i) => $i->is_active && Gate::allows('view-menu-item', $i))

Roll out one surface, one release cycle, then widen. Two navs sourced from different mechanisms is a debugging tax that compounds. Migrate the secondary nav only after the first has survived a deploy, a cache clear, and an admin edit.

Gotchas and Tips

Verify Laravel 12 / Filament v4 compatibility as a spike, not as a side effect. Run composer why-not against your framework constraint on a throwaway branch. Do not adopt this in the same release window as a Filament major upgrade — those two changes interacting will cost you days and you will blame the wrong one.

Read the migration adversarially. Tree packages accumulate defects exactly here:

  • Adjacency list (parent_id) or nested set? If adjacency, is there a cycle guard — can an admin make a menu its own grandchild?
  • Parent delete: cascade, nullify, or orphan? Orphans become invisible, unroutable rows. Nobody notices until someone asks where a link went.
  • Is order an integer that races two admins dragging simultaneously, or is it safe?
  • Is parent_id a real FK or an app-code convention?

Write an integrity test: create parent + children, delete the parent, assert the outcome matches what your repository expects. Five minutes now, a saved incident later.

The JSON provider may write back to disk. Check whether the Filament admin UI saves to the JSON file. If it does, you have a container problem: ephemeral storage/, file permissions, and admin edits silently diverging from the repo. Enforce read-only at runtime and write only via seeder or an explicit export command.

Debug "the menu disappeared" in this fixed order: (1) is_active on the item, (2) parent reachable in the tree (orphan?), (3) stale cache after an edit, (4) your Gate filter hiding it, (5) provider silently switched — wrong env, missing JSON path, migration not run. Expose a debug route that dumps the raw normalized array from your repository; it collapses most investigations to under a minute.

Multi-tenancy is a schema question. If per-tenant nav is plausible within a year, check now for a tenant column or scope. Retrofitting tenancy into a global menu table under production load is the migration you least want.

Keep customizations to three places: the service provider (rebind the driver if you fork), the config (provider selection), and model observers (cache invalidation, audit logging). Stay in those three and rebasing against upstream stays trivial. Mirror the repo privately with an upstream remote, keep each fix as one commit, and budget roughly a day a quarter for framework churn.

Stop-and-build threshold. If your spike turns up more than two unresolved concerns across schema integrity, cache invalidation, or Filament compatibility, build it. A menus + menu_items pair with a recursive Blade partial is three to five days of owned code, and it removes a single-maintainer dependency from your navigation surface permanently. The package's value is convenience — worth about a sprint-day, not a standing vendor risk.

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.
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
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
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core