Skip to main content
The bundle provides a single extension point for item visibility: the MenuPermissionCheckerInterface. A menu can have one checker assigned; the checker decides — item by item — whether that item appears in the rendered tree.

The interface

// Nowo\DashboardMenuBundle\Service\MenuPermissionCheckerInterface
interface MenuPermissionCheckerInterface
{
    /**
     * Whether the given menu item should be visible in the current context.
     *
     * @param mixed $context Optional context (e.g. current user, request)
     */
    public function canView(MenuItem $item, mixed $context = null): bool;
}
canView receives the MenuItem entity and an optional $context value. The context is whatever you pass as the $permissionContext argument when loading the tree (e.g. the current User object or the Symfony Request).

Built-in checkers

The bundle ships two checkers.
The default checker. Every item is visible regardless of its permissionKey.
final class AllowAllMenuPermissionChecker implements MenuPermissionCheckerInterface
{
    public const DASHBOARD_LABEL = 'form.menu_type.permission_checker.allow_all';

    public function canView(MenuItem $item, mixed $context = null): bool
    {
        return true;
    }
}
When no permission checker is assigned to a menu, AllowAllMenuPermissionChecker is used automatically.

Writing a custom checker

1

Create the class

Implement MenuPermissionCheckerInterface in any service class. The Symfony service container will auto-register it:
use Nowo\DashboardMenuBundle\Service\MenuPermissionCheckerInterface;
use Nowo\DashboardMenuBundle\Entity\MenuItem;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;

final class RoleBasedMenuPermissionChecker implements MenuPermissionCheckerInterface
{
    public function __construct(
        private readonly AuthorizationCheckerInterface $authorizationChecker,
    ) {}

    public function canView(MenuItem $item, mixed $context = null): bool
    {
        $key = $item->getPermissionKey();
        if ($key === null || $key === '') {
            return true; // no key = always visible
        }
        return $this->authorizationChecker->isGranted($key, $context);
    }
}
2

Set a dashboard label (optional)

Give your checker a human-readable name in the dashboard dropdown using either approach:
final class RoleBasedMenuPermissionChecker implements MenuPermissionCheckerInterface
{
    public const DASHBOARD_LABEL = 'Role-based permission checker';
    // ...
}
If neither is set, the dashboard shows the service ID (the FQCN) as the label.
3

Assign to a menu

Open the menu in the dashboard, go to the Configuration tab (gear icon), and select your checker from the Permission checker dropdown. Or set permissionChecker directly on the Menu entity in code.

Auto-registration

Any service whose class implements MenuPermissionCheckerInterface is automatically discovered and added to the dashboard’s “Permission checker” dropdown. You do not need to add a tag in services.yaml.
Your checker will be discovered automatically as long as your service is auto-registered (e.g. via App\Service\: resource: '../src/Service/' in services.yaml). Explicit tagging with nowo_dashboard_menu.permission_checker or #[AsTaggedItem] is still supported but not required.

Pruning empty parents

After the permission checker filters individual items, the bundle performs a prune pass. Any parent item or section header that originally had children but now has none visible is removed from the tree. This prevents orphaned group labels from appearing in the rendered menu.
Before pruning (after canView filtering):
  ├── Reports (section, had 2 children, both hidden)  ← removed
  ├── Dashboard (link)                                 ← kept
  └── Settings (link, has 1 visible child)            ← kept
       └── Profile (link, visible)                    ← kept
You do not need to guard against empty sections manually. The prune pass handles this automatically, regardless of how many levels deep the tree is.

Passing permission context

The $context parameter of canView comes from the $permissionContext argument you pass when loading the tree:
{# Pass the current user as permission context #}
{% set tree = dashboard_menu_tree('sidebar', app.user) %}
// In a controller or service
$tree = $this->menuTreeLoader->loadTree('sidebar', $locale, $this->getUser());
The bundle passes this value through unchanged to every canView call. Its type and semantics are entirely up to your checker implementation.