The Magento 2 admin panel grows by accumulation. Every module adds a menu entry under its own vendor node, every config lands in its own section, and after a few years the navigation looks like a franchise directory rather than a tool. This article is for Magento 2 backend developers who want to stop signing their name on the Magento 2 admin menu and start putting functionality where operators already look for it.
The takeaway: build your Magento 2 admin menu around the platform’s existing taxonomy, not your vendor name. Declare your menu.xml nodes and system.xml sections under the core groups — Catalog, Sales, Marketing, System — where operators already look.

Why this matters now
The pattern is consistent across codebases: a module ships, a top-level menu entry appears under Vendor Name → Feature, a config section gets added under Stores → Configuration → Vendor Name, and a support ticket arrives three weeks later asking where the setting is. Multiply that by ten modules and the admin becomes a vendor catalogue, not a workspace. Nobody removes entries — they only add.
The baseline
A reasonable Magento 2 module today registers its own menu.xml node with a vendor-named parent and adds a system.xml section under a vendor-named tab. The result is discoverable for the developer who wrote it and nobody else. Operators who use the admin daily think in terms of Catalog, Sales, Marketing, Content — the built-in taxonomy Magento 2 ships. Anything outside that taxonomy creates a parallel navigation layer that competes for attention without earning it.
That’s not malicious — it’s the path of least resistance when scaffolding a module. The cost is a navigation that rewards module authors, not operators.
The trade-off
Place Magento 2 admin menu entries where the function belongs, not where the vendor lives
Magento 2’s menu.xml lets any module declare a child of any existing node — including Magento’s own top-level nodes. An import management tool belongs under System, not under AcmeCorp. A product enrichment queue belongs under Catalog. A loyalty programme grid belongs under Marketing. The operator already knows where to look; the only question is whether you meet them there.
Declare the parent as an existing core node and set sortOrder to slot the entry into a logical position within that group. An AMQP monitoring tool belongs under System → Tools alongside Import and Export — not under a vendor node:
<!-- etc/adminhtml/menu.xml -->
<add id="Brocode_AmqpMonitor::amqp_monitor"
title="AMQP Monitor"
module="Brocode_AmqpMonitor"
sortOrder="25"
parent="Magento_Backend::system_tools"
action="brocode_amqpmonitor/monitor/index"
resource="Brocode_AmqpMonitor::amqp_monitor"/>
If no existing parent fits precisely, the second-best option is a child of the closest core node rather than a new top-level vendor node. A new top-level node is justified only when the module introduces a genuinely new domain — a POS system, a B2B portal — that has no core analogue. An import tool does not meet that bar.
Strengths
- Operators find the feature through existing muscle memory. No training needed.
- The admin menu stays flat; every new entry is a refinement of existing structure rather than an expansion of it.
Costs
- The feature is less visible as “your module’s work” — which is the point, and occasionally a client relations problem. Name the menu entry after the function, add a comment in
menu.xmlattributing the module. - You need to know the core node IDs. They’re in
Magento_Backend/etc/adminhtml/menu.xmland each module’s ownmenu.xml; grep for theidattribute.
Attach config to existing tabs and sections instead of creating new ones
system.xml has three levels: tab → section → group → field. Most modules create a new tab and a new section. The tab is the top-level bucket in the left sidebar of Stores → Configuration; adding one per vendor produces a sidebar that scrolls past useful content to reach vendor-branded noise.
The fix is to declare <tab> only when genuinely warranted, and otherwise attach <section> to a core tab — or attach <group> directly to an existing core section. Magento 2’s built-in tabs (general, catalog, sales, advanced) cover the vast majority of real configuration domains.
Attach a new section to an existing tab:
<!-- etc/adminhtml/system.xml -->
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Config:etc/system_file.xsd">
<system>
<!-- No <tab> declaration — reuse the core "services" tab -->
<section id="brocode_amqpmonitor"
translate="label"
sortOrder="150"
showInDefault="1"
showInWebsite="0"
showInStore="0">
<tab>services</tab>
<label>Amqp Monitor</label>
<resource>Brocode_AmqpMonitor::config</resource>
<group id="general" translate="label" sortOrder="10" showInDefault="1" showInWebsite="0" showInStore="0">
<label>General</label>
<field id="enabled" translate="label" type="select" sortOrder="10" showInDefault="1" showInWebsite="0" showInStore="0">
<label>Enable AMQP Monitor</label>
<!-- Brocode_AmqpMonitor: controls queue health monitoring -->
<source_model>Magento\Config\Model\Config\Source\Yesno</source_model>
</field>
</group>
</section>
</system>
</config>
Go further: attach a group directly to an existing core section:
When a module only needs one or two config fields and they are a natural extension of an existing Magento 2 section, skip the new section entirely and add a group to the core one. Catalog-related image processing config belongs in catalog/product_image, not in a new vendor/image_processing section:
<!-- etc/adminhtml/system.xml -->
<system>
<section id="catalog">
<group id="acme_image_processing"
translate="label"
sortOrder="200"
showInDefault="1"
showInWebsite="0"
showInStore="0">
<label>Image Processing</label>
<field id="resize_on_import" translate="label" type="select" sortOrder="10" showInDefault="1" showInWebsite="0" showInStore="0">
<label>Resize images on import</label>
<!-- Acme_ImageProcessing: controls automatic resizing during product import -->
<source_model>Magento\Config\Model\Config\Source\Yesno</source_model>
</field>
</group>
</section>
</system>
The <section id="catalog"> reference here does not redeclare the section — it extends it. Magento 2 merges system.xml across all modules; you’re appending a group to a section that already exists.
Strengths
- Config is findable without knowing which vendor module added it — operators look in Catalog for catalog-related settings because that’s where they’ve always been.
- Fewer sidebar entries means less scrolling and less visual noise in a screen operators visit often.
Costs
- Fields buried in a core section can be harder to find in documentation specific to your module. Compensate with clear
<comment>text on each field naming the module responsible. sortOrderrequires coordination if multiple modules extend the same section. Use a number in the 150–250 range for third-party additions to avoid collisions with core groups (which tend to use 10–100).
A working example: the cleanup module
The fastest way to apply this to an existing vendor module is a dedicated cleanup module — a thin layer that declares a dependency on the vendor module and overrides only navigation and config placement. No patches, no forks, survives composer updates. For the broader module-structure rationale this builds on, see Magento 2 module architecture in practice.
Module declaration with explicit dependency:
The <sequence> entry ensures the cleanup module loads after the vendor module, so its menu.xml and system.xml merges win.
<!-- etc/module.xml -->
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
<module name="Acme_AdminCleanup">
<sequence>
<!-- Must load after every module whose navigation this cleanup touches -->
<module name="Acme_Shipping"/>
<module name="Acme_Loyalty"/>
</sequence>
</module>
</config>
Removing or moving a vendor menu entry:
Three operations cover every cleanup case. <remove> wipes a node entirely. <update> changes attributes on an existing node in place — useful for correcting a parent or sortOrder without touching the entry’s id or action. <add> re-registers a node from scratch, typically after a <remove>.
<!-- etc/adminhtml/menu.xml -->
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Backend:etc/menu.xsd">
<menu>
<!-- Option A: move an entry to a different parent in place.
Use <update> when the node id and action stay the same — only
the parent or sortOrder is wrong. -->
<update id="Acme_Loyalty::loyalty_dashboard"
parent="Magento_Backend::marketing"
sortOrder="75"/>
<!-- Option B: remove a vendor top-level node entirely... -->
<remove id="Acme_Shipping::shipping_root"/>
<!-- ...then re-add the one useful child under the correct core parent.
Use <remove> + <add> when parent, action, or title all need to change. -->
<add id="Acme_Shipping::shipping_rates"
title="Shipping Rates"
module="Acme_Shipping"
sortOrder="60"
parent="Magento_Sales::sales"
action="acme_shipping/rates/index"
resource="Acme_Shipping::shipping_rates"/>
</menu>
</config>
Grep vendor/acme/module-shipping/etc/adminhtml/menu.xml for the exact id values before writing any of these. A wrong ID fails silently — the node stays or the update is ignored.
Moving a config section to the correct domain tab:
Overriding <tab> and <sortOrder> in the cleanup module’s system.xml repositions the section without touching the vendor file. Magento 2 merges system.xml by section ID; the last-loaded value wins — which is the cleanup module, thanks to <sequence>.
<!-- etc/adminhtml/system.xml -->
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Config:etc/system_file.xsd">
<system>
<!-- Acme_Shipping originally placed this under their own "acme" tab.
Shipping belongs under "sales". Repoint the tab and fix sort order. -->
<section id="acme_shipping" sortOrder="160">
<tab>sales</tab>
</section>
<!-- Acme_Loyalty put rewards config under "acme" tab too.
Marketing is the correct domain. -->
<section id="acme_loyalty" sortOrder="170">
<tab>marketing</tab>
</section>
<!-- Acme_Payment landed in the correct "payment" tab but its sortOrder
pushes it above Magento's own payment methods. Fix the position only. -->
<section id="acme_payment" sortOrder="155"/>
</system>
</config>
Only declare the attributes that need changing. If the cleanup module redeclares a full <section> with all its attributes copied from the vendor, any change the vendor ships in an update — a new showInWebsite flag, a renamed resource, a corrected label — is silently overridden by the cleanup module and never reaches the running store. The self-closing <section id="acme_payment" sortOrder="155"/> above is the right mental model: touch one thing, leave everything else to the vendor.
Check the vendor module first — some ship a disable flag:
Before writing any override code, check the vendor’s system.xml for a field like enabled, active, or show_in_menu. Some vendors expose a config flag that hides their menu entry without any custom module needed. Magefan modules, for example, ship a Display Magefan Menu Item toggle (mfextension/menu/display) directly in Stores → Configuration — setting it to No removes the menu entry after a cache flush, no code required. That flag is always the preferable path — it is the vendor’s own supported mechanism and will not break on updates. The cleanup module approach is for when no such flag exists.

What to skip
- A top-level menu node for a single feature. If the module has one grid and one config section, it does not need its own top-level node. Find the closest core parent and use it.
- A vendor-named tab in
Stores → Configuration. Tab proliferation is the config equivalent of a cluttered menu. If there is no genuine reason an operator would think “I need to look under AcmeCorp“, the tab should not exist. sortOrder="10"for third-party groups. That range belongs to core. Use 150+ to avoid collisions with Magento 2 updates.
Verification
Your Magento 2 admin menu is clean when:
- A new operator can find the module’s primary grid without being told which menu section it lives under.
Stores → Configurationsidebar has no vendor-named tab that requires mental translation to a function.bin/magento setup:upgradeproduces no duplicate section or menu ID warnings invar/log/system.log.- Every config field carries a
<comment>naming the module — compensation for the deliberate absence of a vendor-branded container.
Related modules
- If you want a worked example of an operator-first admin tool that already follows this Magento 2 admin menu placement discipline, see the Magento 2 Store Overview module.
Related reading
- brocode/module-amqp-monitor — a real-world example of building correctly from the start: menu under
System → Tools, config underServices, no vendor-named navigation nodes. - Magento 2
menu.xmlreference — node attributes, ACL wiring, and sort order rules. - Magento 2
system.xmlreference — tab, section, group, and field declarations with all supported attributes. - Laws of UX — Hick’s Law — the decision-time research behind “fewer visible options, faster operator”.
Leave a Reply