Skip to main content
The bundle maps to two database tables: dashboard_menu and dashboard_menu_item. Understanding their fields is the foundation for working with the bundle programmatically or through the dashboard. Menu is the container that groups a set of items under a unique code. A single code (e.g. sidebar) can have several Menu rows, each with a different context, to serve different tenants or configurations.
// Nowo\DashboardMenuBundle\Entity\Menu
#[ORM\Entity(repositoryClass: MenuRepository::class)]
#[ORM\Table(
    name: 'dashboard_menu',
    uniqueConstraints: [
        new ORM\UniqueConstraint(
            name: 'uniq_menu_code_context',
            columns: ['code', 'attributes_key'],
        ),
    ],
)]
class Menu
{
    private ?int $id = null;
    private string $code = '';
    private string $contextKey = ''; // canonical key for (code + context) uniqueness
    private ?string $name = null;
    private ?string $icon = null;
    private ?array $context = null; // JSON key-value map
    // ... CSS class overrides, flags, permission checker
}

Unique constraint

The pair (code, attributes_key) has a unique constraint on the table. attributes_key is the result of Menu::canonicalContextKey() — a sorted, JSON-encoded version of the context array. An empty string represents “no context” (the fallback row for that code).
Two Menu rows with the same code but different contexts are valid. Two rows with the same code and the same context (or both with no context) will fail the unique constraint.

Identity and classification fields

code
string
required
Short identifier for this menu, e.g. sidebar or topbar. Combined with context to identify a unique variant.
name
string | null
Human-readable label shown in the dashboard UI.
context
array<string, bool|int|string> | null
Optional JSON key-value map that distinguishes this menu from others sharing the same code (e.g. {"partnerId": 1}). null or [] means this row is the fallback for that code.
icon
string | null
Optional icon identifier for the menu container (e.g. heroicons:bars-3).
base
bool
When true, the menu is a “base” menu whose code cannot be changed after creation.

CSS class overrides

Each field overrides the corresponding global config value when set. Leave null to fall back to config defaults.
classMenu
string | null
CSS class for the root <ul> element (e.g. nav flex-column).
ulId
string | null
Optional id attribute for the root <ul> element.
classItem
string | null
CSS class for each <li> element (e.g. nav-item).
CSS class for each <a> element (e.g. nav-link).
classChildren
string | null
CSS class for nested <ul> elements (e.g. nav flex-column ms-2).
classSectionLabel
string | null
CSS class for the <span> wrapping a section item’s label (e.g. navigation-header).
classCurrent
string | null
Class added to <a> when its route matches the current request (e.g. active).
classBranchExpanded
string | null
Class added to <li> when the current route is within that branch (e.g. active-branch).
classHasChildren
string | null
Class added to <li> when the item has children.
classExpanded
string | null
Class added to <li> when the item’s children block is expanded.
classCollapsed
string | null
Class added to <li> when the item’s children block is collapsed.

Behaviour flags

permissionChecker
string | null
Service ID of the MenuPermissionCheckerInterface implementation to use for this menu. null defaults to allow-all.
depthLimit
int | null
Maximum depth to render. null = unlimited. 1 = root items only, 2 = root + one level of children, etc.
collapsible
bool | null
When true, the entire menu is wrapped in a collapsible toggle block.
collapsibleExpanded
bool | null
When collapsible is true, controls whether the block starts open (true) or closed (false).
nestedCollapsible
bool | null
When true, each item that has children renders an expand/collapse control.
nestedCollapsibleSections
bool | null
When nestedCollapsible is true, controls whether section-type items also render a collapse toggle. Defaults to true for backwards compatibility.

Cascade delete

The items collection is mapped with cascade: ['persist', 'remove'] and orphanRemoval: true. Deleting a Menu automatically removes all of its MenuItem rows.
MenuItem represents a single entry in the tree. It implements TranslatableInterface so its label can be stored in multiple locales.
// Nowo\DashboardMenuBundle\Entity\MenuItem
#[ORM\Entity(repositoryClass: MenuItemRepository::class)]
#[ORM\Table(name: 'dashboard_menu_item')]
#[ORM\Index(name: 'idx_menu_position', columns: ['menu_id', 'position'])]
#[ORM\Index(name: 'idx_parent_id',     columns: ['parent_id'])]
class MenuItem implements TranslatableInterface
{
    public const ITEM_TYPE_LINK    = 'link';
    public const ITEM_TYPE_SECTION = 'section';
    public const ITEM_TYPE_DIVIDER = 'divider';

    public const LINK_TYPE_ROUTE    = 'route';
    public const LINK_TYPE_EXTERNAL = 'external';

    private ?int $id = null;
    private ?Menu $menu = null;
    private ?MenuItem $parent = null;
    private Collection $children;
    private int $position = 0;
    private ?string $label = null;
    private ?array $translations = null; // {"en": "Home", "es": "Inicio"}
    private ?string $itemType = self::ITEM_TYPE_LINK;
    private ?string $linkType = self::LINK_TYPE_ROUTE;
    private ?string $routeName = null;
    private ?array $routeParams = null;
    private ?string $externalUrl = null;
    private ?string $permissionKey = null;
    private ?string $icon = null;
    private bool $targetBlank = false;
}

Label and translations

label
string | null
Default/fallback label. Used when no translation exists for the current locale. null is valid for divider items that have no label.
translations
array<string, string> | null
Per-locale label overrides stored as JSON (e.g. {"en": "Home", "es": "Inicio"}). The repository resolves the correct label at load time via getLabelForLocale(locale).
MenuItem implements TranslatableInterface, which requires:
public function getLabel(): string;
public function getLabelForLocale(string $locale): string;
The resolution is: translations[$locale] if present, otherwise label.

Item type

itemType
'link' | 'section' | 'divider'
Controls how the item is rendered.
  • link — a clickable <a> element (default)
  • section — a non-linked label used as a group header
  • divider — a horizontal rule with no label
How the URL is generated. null when the item has children and acts as a parent node without its own destination.
routeName
string | null
Symfony route name when linkType = route.
routeParams
array<string, mixed> | null
Route parameters as a JSON map (e.g. {"tab": "configuration"}). Missing path parameters are filled from the current request.
externalUrl
string | null
Full URL when linkType = external.
targetBlank
bool
When true, the rendered link gets target="_blank" rel="noopener noreferrer".

Tree positioning

parent
MenuItem | null
Parent item. null means this is a root-level item. Cascade delete is set on the column so removing a parent removes all its descendants.
position
int
Sort order within the same parent. Items are ordered ASC by position. Default is 0.

Other fields

icon
string | null
Icon identifier passed to Symfony UX Icons (e.g. heroicons:home, bootstrap:house). Resolved through the configured icon_library_prefix_map before rendering.
permissionKey
string | null
Arbitrary string passed to the menu’s MenuPermissionCheckerInterface implementation. How it is interpreted depends on your checker. See Permissions.

Context resolution

How multiple Menu rows for the same code are resolved at render time.

Permissions

How permissionKey and permission checkers control item visibility.

Tree structure

How the flat list of items is assembled into a nested tree.