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

Dashboard Menu Bundle Laravel Package

nowo-tech/dashboard-menu-bundle

A PHP bundle for adding a configurable dashboard menu to your application. It helps organize admin navigation and expose menu items consistently, with simple configuration for integrating dashboard links into Symfony-based projects.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and register

composer require nowo-tech/dashboard-menu-bundle

There is no Flex recipe, so register the bundle manually in config/bundles.php:

NowoTech\DashboardMenuBundle\DashboardMenuBundle::class => ['all' => true],

Then import the routes (only if you want the JSON API):

# config/routes/nowo_dashboard_menu.yaml
nowo_dashboard_menu:
    resource: '@DashboardMenuBundle/config/routes.php'   # or .../routes.yaml
    prefix: /admin/api

Where to look first, in order

  1. src/DependencyInjection/Configuration.php — the authoritative list of config keys. This is what you will edit 90% of the time; the README examples drift, the config tree does not.
  2. src/DependencyInjection/DashboardMenuExtension.php — shows how items are merged and which services/tags the bundle exposes.
  3. src/Resources/views/ — the reference Twig template and its blocks (menu, item, item_link, …) plus the exact Twig function name and argument signature.
  4. src/Controller/ — the JSON endpoint's route name, serialization shape, and whether permission filtering happens there or in the provider.
  5. README.md — for the canonical config snippet to copy.

First use case: a two-level admin sidebar in 15 minutes

Define three items in config/packages/nowo_tech_dashboard_menu.yaml, one of them with a parent, one with a role, one with a JSON translation key. Render it once with the bundle's Twig function in your layout, then hit the JSON route with curl -H 'Accept: application/json' as two different users to confirm the permission filter works on both paths. If those two outputs disagree, you have found the single most common integration bug before writing any UI.

Do not start by reading the whole repository. The bundle is small, but the config tree + extension + one template answer every day-one question.

Implementation Patterns

Treat the config file as the source of truth, not the database. There is no Gedmo/Stof tree here, so there is no lft/rgt/level column and no automatic reordering. Menu structure lives in configuration (or whatever provider you write). Consequences you should design around:

  • Menu edits ship with a deploy → they belong in code review, not in an admin UI.
  • Caching is free: the tree is deterministic per config + per user roles.
  • If you do need runtime-editable menus, write your own provider service implementing the bundle's menu interface and feed it from your own table — and keep the parent/position contract exactly as the bundle expects, so rendering and the API keep working unchanged.

Tree wiring: parent + position

nowo_tech_dashboard_menu:
    menus:
        main:
            items:
                dashboard:   { label: menu.dashboard,   route: admin_dashboard, position: 0 }
                content:     { label: menu.content,     position: 10 }
                content_news:{ label: menu.news, parent: content, route: admin_news, position: 10 }

Use sparse positions (0, 10, 20, …), never 0, 1, 2. Inserting a sibling later without renumbering everything is the whole point of a position field, and with dense numbering you lose that on the second iteration. Every item should get an explicit position; items without one fall back to declaration order, which silently changes when someone reorders YAML keys.

i18n via JSON translations

Labels are translation keys resolved at render time, so keep the JSON catalogs in the standard translations/ directory with the standard domain the bundle reads (check Configuration.php for the default translation_domain). One file per locale, flat keys:

{ "menu.dashboard": "Dashboard", "menu.news": "News" }

Reuse the same JSON catalogs for the API payload — the API returns translated labels, so the frontend never needs to know your translation keys. That is the main reason to use this bundle over hand-rolling a menu array: one label source for Twig and JSON.

Permission filtering

Filter on the server: the provider/controller must drop items the current token cannot see, and drop the descendants of any dropped parent (a child whose parent vanished must not be re-parented to the root — that produces the classic "orphan menu item appears at top level" bug). In Twig, use is_granted() only as a second line of defense around the render call, never as the primary gate.

Template override, not template fork

Copy the bundle template once into templates/bundles/DashboardMenuBundle/ and change only the blocks you need. The directory name must match the bundle's short name exactly, including casing, or the override is silently ignored and you debug the wrong file.

Integration tips

  • Frontend (Vue/React/TS): consume the JSON endpoint, render the tree client-side, and keep the same parent/position shape in your state — do not flatten it, or a future nested menu breaks your layout.
  • Cache the JSON response per user (private cache, vary by roles), not shared — see gotchas.
  • Wrap the contract in one functional test (WebTestCase) that asserts both the rendered HTML and the JSON payload for two roles. Given the package's 0 dependents and single-maintainer surface, that test is your upgrade insurance for Symfony 8.

Gotchas and Tips

Config edits do nothing in prod. The container is compiled; after changing YAML you need bin/console cache:clear. In dev the container rebuilds, which makes this bite only at deploy time.

Numeric-key merging appends. Symfony's config merge for list-like item maps does not overwrite by key the way you expect — redefining an item in a second config file can append a duplicate rather than replace it. Keep all menu definitions for a given menu name in one file, or verify with debug:config nowo_tech_dashboard_menu.

Orphaned children. Deleting or permission-hiding a parent without handling descendants produces top-level garbage. Validate at boot: a compiler pass (or a test over the fixture config) that asserts every parent reference exists and is not itself, plus every route exists in the router. Two lines of validation save an hour of prod debugging.

Cycles hang the request. a.parent = b, b.parent = a gives you infinite recursion / a stack overflow, not an exception. Guard it in the same compiler pass.

Position collisions are undefined. Two siblings with position: 10 order by whatever PHP's sort does that day. Always unique per level; add a test that asserts the full ordered list of labels for your real config, which catches collisions and accidental reorderings at once.

Permissions ≠ visibility. Hiding a link in Twig hides nothing. If the JSON endpoint returns an item, the user will find it, and if the underlying route is not #[IsGranted]-protected they will open it. Filter in the provider and secure the controllers.

Shared caches leak menus. The API response depends on the user's roles. Do not put a CDN or a shared HTTP cache in front of it without Vary on the session/roles and Cache-Control: private. Symptom: an admin sees a normal-user menu, or worse, the reverse.

Missing translation domain. If translations/messages.en.json exists but the bundle's configured domain is different, you get raw keys like menu.dashboard in the UI. Confirm with bin/console debug:translation en — it lists which domain each key resolved in.

No security token in CLI/mail context. Rendering the menu from a console command or an email template means is_granted() has no token. Either filter explicitly by passing roles to the renderer, or wrap the call; otherwise you get an exception or, worse, an empty menu in a place you cannot see.

Template override path is silent. Wrong directory name or wrong bundle casing and Symfony falls back to the vendor template with no warning. When a template edit "does nothing", check the override path before you check the block names.

Extension points worth knowing: the menu provider/registry service (for dynamic menus), the Twig function's argument for choosing a menu name (for rendering the same config twice with different scopes), the reference template's blocks (for markup changes), and the bundle config tree (for adding custom item attributes if the item definition passes them through). Everything else — the tree, sorting, filtering, JSON shape — is bundle internals you should not fork.

Upgrade posture. Symfony 8 support is recent and the package has no downstream dependents, so treat each minor as potentially BC-breaking despite semver. Pin with a caret constraint, keep the HTML+JSON functional test, and read CHANGELOG.md/UPGRADING.md on every bump. If the bundle ever stalls, the escape hatch is small: your provider + a Twig loop reproducing the same tree contract, which is why the test matters more than the abstraction.

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