Problems with Standard Menus and Our Solution
The standard bitrix:menu component generates menus from .menu.php files or site structure. For simple sites, this is sufficient, but when there are thousands of products in the catalog and you need to manage a megamenu with images, banners, columns, performance suffers. We have developed a module that solves these problems: it provides flexible management through an interface with caching and full megamenu support. The module is suitable for projects with any number of items — tested on catalogs up to 10,000 positions.
Why Standard bitrix:menu Doesn't Handle Large Catalogs Well
One of the main problems is the lack of tagged caching. Each request rebuilds the tree from files, and with dynamic addition of items through the administrative interface, it becomes impossible. Our module stores everything in the database and caches the tree with a tag; invalidation occurs on any change. This yields a 10-50x speed improvement compared to file parsing.
How We Implemented a Scalable Data Model
We used Bitrix ORM (tables b_vendor_menu_*) with adjacency list for hierarchy storage. Why adjacency list instead of nested sets? For menus with frequent structure changes (additions, deletions, moves), adjacency list is faster on write, and reading with caching negates the difference.
Data Model
Module vendor.megamenu:
-
b_vendor_menu_config— menu configurations: id, code, name, site_id, lang, is_active -
b_vendor_menu_item— menu items: id, menu_id, parent_id, sort, title, url, url_type (absolute/relative/component), target, icon_id, image_id, css_class, visibility (all/authorized/unauthorized), condition (JSON), is_active -
b_vendor_menu_column— megamenu columns for second-level items: id, item_id, title, sort, items (JSON — links without separate records) -
b_vendor_menu_banner— banners in megamenu: id, item_id, image_id, url, title, sort
Menu Item Tree
Items are stored in adjacency list (parent_id). To build the tree, iterative traversal is used to avoid recursion in PHP:
class MenuTreeBuilder { public function build(int $menuId): array { $items = MenuItemTable::getList([ 'filter' => ['MENU_ID' => $menuId, 'IS_ACTIVE' => 'Y'], 'order' => ['PARENT_ID' => 'ASC', 'SORT' => 'ASC'], ])->fetchAll(); $tree = []; $index = []; foreach ($items as $item) { $item['children'] = []; $index[$item['ID']] = &$item; } foreach ($index as &$item) { if ($item['PARENT_ID']) { $index[$item['PARENT_ID']]['children'][] = &$item; } else { $tree[] = &$item; } } return $tree; } } The tree is fully cached with the tag menu_{code}_{lang}. Invalidation occurs on any change to an item or banner of that menu.
Item Visibility
Menu item visibility conditions:
-
visibility = 'authorized'— item hidden for guests -
visibility = 'unauthorized'— item hidden for logged-in users -
condition(JSON) — arbitrary conditions: user groups, region, request parameters
class VisibilityChecker { public function isVisible(array $item): bool { global $USER; if ($item['VISIBILITY'] === 'authorized' && !$USER->IsAuthorized()) return false; if ($item['VISIBILITY'] === 'unauthorized' && $USER->IsAuthorized()) return false; $condition = $item['CONDITION'] ?? []; if (!empty($condition['user_groups'])) { $userGroups = $USER->GetUserGroupArray(); if (!array_intersect($condition['user_groups'], $userGroups)) return false; } return true; } } Visibility conditions are applied after retrieving the cached tree — the cache stores the full tree, filtering occurs in memory.
Megamenu with Columns and Banners
For top-level items, you can configure a megamenu dropdown with columns:
[Catalog] ├─ Column 1: Electronics │ ├─ Smartphones │ ├─ Laptops │ └─ Tablets ├─ Column 2: Home Appliances │ ├─ Refrigerators │ └─ Washing machines └─ Banner: [Weekly Deals → /sale/] Columns are stored in b_vendor_menu_column, banners in b_vendor_menu_banner. The component generates the HTML structure of the megamenu; styling is done via CSS Grid.
Drag-and-Drop Interface
The administrative interface is built using the Sortable.js library. Items can be dragged to the desired position and parent element. During dragging, an AJAX request updates sort and parent_id in a single transaction.
Active Item
The active menu item is determined by the current URL:
// Exact URL match or path start match function isActive(array $item, string $currentUrl): bool { if ($item['URL'] === $currentUrl) return true; if ($item['URL_TYPE'] === 'section' && str_starts_with($currentUrl, $item['URL'])) return true; return false; } The active item and all its ancestors get the CSS class active.
Comparison with the Standard Component
| Criterion | Standard bitrix:menu |
Our Module |
|---|---|---|
| Admin management | Only via files | Via visual drag-and-drop |
| Megamenu | Requires manual markup | Built-in columns and banners |
| Caching | File-based, no tags | Tagged, event-based invalidation |
| Group visibility | Only via condition in file | Interface + arbitrary conditions |
| Implementation | 2-3 days setup | 10 days turnkey |
Work Process
We go through all stages from analysis to deployment:
- Analysis — study catalog structure, visibility requirements, integrations.
- Design — database schemas, tree prototype, approval with the client.
- Development — creation of ORM module, components, admin interface.
- Testing — verification on real data, load testing.
- Deployment — installation on the live site, caching configuration, training for content managers.
What You Get in the End?
- Module with ORM tables, a component for menu output, administrative interface.
- Documentation for setup and use.
- Source code in a repository.
- Technical support for 30 days after launch.
Development Timeline
| Stage | Duration |
|---|---|
| ORM tables, tree model | 1 day |
| Tree building, caching | 1 day |
| Item visibility conditions | 1 day |
| Megamenu: columns, banners | 2 days |
| Drag-and-drop interface | 2 days |
| Site components (HTML + CSS) | 2 days |
| Testing | 1 day |
Total: 10 to 15 working days depending on complexity.
Menu Speed Optimization
To speed up loading, use tagged caching: the component is cached for 3600 seconds with the `menu_{code}` tag. On any change to an item or banner, the cache is automatically cleared via an agent. For large catalogs (10,000+), we recommend enabling Bitrix composite cache and setting up deferred menu loading via `Bitrix\Main\Page\FrameStatic`.The adjacency list approach is described in the article Adjacency List. Megamenu styling is done using CSS Grid.
Our experience in developing Bitrix modules is over 7 years. We have completed 50+ projects creating custom solutions. We guarantee deadlines and quality. Contact us for an assessment of your task within one day — we will prepare the optimal solution. Get a project consultation: just write to us, and we will discuss the details.

