Skip to main content
A single menu code (e.g. sidebar) can have many Menu database rows. Each row carries an optional context — a JSON key-value map (e.g. {"partnerId": 1, "operatorId": 1}) stored in the attributes column. At render time you pass an ordered list of context objects; the bundle tries each one in turn and returns the first row whose (code, context) matches.

How context sets work

You pass a context set: an ordered list of context objects, from most specific to least specific. The bundle iterates through the list and returns the first Menu row that matches code + context. The final element is usually {} or null to catch the fallback row.
Context sets: [{partnerId:1, operatorId:5}, {partnerId:1}, {}]

1. Look for Menu where code='sidebar' AND context={partnerId:1, operatorId:5}  → found? use it.
2. Look for Menu where code='sidebar' AND context={partnerId:1}               → found? use it.
3. Look for Menu where code='sidebar' AND context={}  (fallback)              → found? use it.
4. No match → return empty tree.
An empty context ({}) or null matches the fallback row — the Menu row created with no context. This is the default menu for that code when no more-specific variant exists.

Canonical context key

The uniqueness of each row is enforced by the attributes_key column, which stores the result of Menu::canonicalContextKey():
public static function canonicalContextKey(?array $context): string
{
    if ($context === null || $context === []) {
        return ''; // empty string = fallback row
    }
    ksort($context, SORT_STRING); // keys sorted for stable comparison
    return json_encode($context, JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR);
}
The method sorts keys alphabetically before encoding so that {"b":1,"a":2} and {"a":2,"b":1} produce the same key and are treated as the same context.

Twig

Pass contextSets as the third argument to dashboard_menu_tree() and dashboard_menu_config():
{% set contextSets = [
    { 'partnerId': 1, 'operatorId': 5 },
    { 'partnerId': 1 },
    {}
] %}

{% set tree   = dashboard_menu_tree('sidebar', null, contextSets) %}
{% set config = dashboard_menu_config('sidebar', contextSets) %}

{% include '@NowoDashboardMenuBundle/menu.html.twig' with {
    menuTree:   tree,
    menuCode:   'sidebar',
    menuConfig: config,
} %}
When you omit contextSets, the bundle defaults to [null, []] — it tries the no-context row first (effectively: no tenant filtering).

JSON API

Send the context sets as the _context_sets query parameter — a JSON-encoded array of objects:
GET /api/menu/sidebar?_context_sets=%5B%7B%22partnerId%22%3A1%2C%22operatorId%22%3A5%7D%2C%7B%22partnerId%22%3A1%7D%2C%7B%7D%5D
Decoded, _context_sets is:
[{"partnerId": 1, "operatorId": 5}, {"partnerId": 1}, {}]
The _locale parameter overrides the request locale independently of context resolution.

Resolution walkthrough

1

Receive the context sets

The tree loader receives contextSets (or defaults to [null, []]). It passes the list to the MenuRepository.
2

Query for matching menu

The repository iterates through the context sets and issues a query for each one: SELECT ... FROM dashboard_menu WHERE code = :code AND attributes_key = :key. The first query that returns a row wins.
3

Fall back to no-context

If none of the specific contexts match, passing {} or null as the last entry matches the row with attributes_key = '' (the fallback menu).
4

Load items and build tree

Once the correct Menu row is found, the items are loaded for that menu ID and assembled into the tree. See Tree structure.

Setting context on a Menu row

When you create or update a Menu entity in code, use setContext() — this automatically updates the contextKey:
$menu = new Menu();
$menu->setCode('sidebar');
$menu->setContext(['partnerId' => 1, 'operatorId' => 5]);
// $menu->getContextKey() === '{"operatorId":5,"partnerId":1}'
Do not set contextKey directly. Always use setContext() so the canonical key stays in sync.

Common patterns

When you have one menu per code and no tenant variation, you do not need context sets at all. The bundle defaults to the fallback row:
{% set tree = dashboard_menu_tree('sidebar') %}
Keep the last entry in every context set as {} or null. Without a fallback entry you will get an empty tree when no specific variant exists.