# brocode by Benjamin Rosenberger — full content corpus

> An engineer shipping in the open: production-grade modules, performance studies, architecture decisions.

Generated: 2026-06-10T08:31:35+00:00

## Modules

### Magento 2 Store Overview Module

URL: https://brocode.at/modules/module-store-overview-2/
Updated: 2026-06-10T08:23:51+00:00

Read-only admin surface showing active website, store group, store view, base URLs, currency, and root category at a glance for Magento 2 multi-store setups.

<!– lead –>

Multi-store Magento 2 setups create a recurring operational friction point: confirming exactly which website, store group, or store view a request belongs to. Without a single authoritative surface, operators cross-reference the scope selector, Stores → All Stores, and Stores → Configuration to piece together context that should be obvious. The Store Overview module removes that friction entirely.

<!– wp:paragraph {"className":"brocode-impact-callout"} –>

The takeaway: one read-only admin widget that surfaces active website, store group, default store view, base URLs, currency, and root category — so operators always know which store they are looking at without switching scopes or opening multiple tabs.

<!– /wp:paragraph –>

## The problem on multi-store setups

On installations with multiple websites, store groups, and store views, three failure modes repeat themselves:

- **Wrong-scope support tickets.** A ticket arrives referencing a symptom visible only on store view B, but the operator investigating it is scoped to store view A. Two hours later the mismatch surfaces.- **Configuration changes applied to the wrong scope.** Stores → Configuration shows "Use Default" checkboxes that look identical across scopes. Without a clear ambient indicator of the active scope, a setting lands on the wrong website.- **Onboarding friction.** New team members — ops, support, QA — routinely need a guided tour of which URL maps to which website, which store group owns which root category, and which locale corresponds to which store view. There is no built-in admin surface for this.

These failures are not caused by bad processes. They are caused by absent context. The admin panel optimises for editing, not for orientation.

## What the module gives you

A lightweight, read-only dashboard widget that displays:

| Field | Value shown |

|—|—|

| Website | Code, name, and internal ID |

| Store group | Name and default store view |

| Default store view | Name, code, and locale |

| Base URL | Storefront and secure URLs for the active scope |

| Currency | Configured base currency and default display currency |

| Root category | Name and ID of the catalog root for this website |

No forms, no toggles, no editing surface — just the orientation data that prevents the failure modes above.

## Admin menu placement

Following the [brocode admin menu discipline](/blog/magento-2-admin-menu-placement/), this module registers its admin entry under **System → Tools**, not under a vendor-named top-level node. Operators already look in System for store infrastructure tooling. The Store Overview meets them there.

<!– who_for –>

## Who this is for

- **Operations and support teams** on multi-store setups who deal with scope confusion daily.- **Developers onboarding** to a complex installation who need a reliable orientation surface before touching configuration.- **QA engineers** verifying that the correct store view is active during test runs.- Anyone who has ever applied a config change to the wrong website and only noticed in production.

<!– who_skip –>

## Who should skip this

- Single-store installations. On a default Magento 2 install with one website, one store group, and one store view, the scope selector already makes context unambiguous. The module adds no value there.- Teams that have already solved orientation through a custom admin widget or a pinned Stores → All Stores bookmark.

## Installation

```bash
composer require brocode/module-store-overview
bin/magento module:enable Brocode_StoreOverview
bin/magento setup:upgrade
bin/magento cache:flush
```

No database schema changes. No configuration required after installation. The widget is visible immediately under the System menu.

## Compatibility

| Magento version | PHP | Status |

|—|—|—|

| 2.4.6 | 8.1 / 8.2 / 8.3 | Stable |

| 2.4.5 | 8.1 / 8.2 | Stable |

| 2.4.4 | 8.1 | Stable |

<!– cta_bar –>

<div class="brocode-cta-bar"><a class="wp-block-button__link wp-element-button" href="https://github.com/brosenberger/module-store-overview">View on GitHub</a><a class="wp-block-button__link wp-element-button is-secondary" href="https://github.com/brosenberger/module-store-overview/archive/refs/heads/main.zip">Download ZIP</a></div>

## FAQ

**Does this module affect store performance?**

No. The widget is rendered only on explicit admin requests to the System page. It performs a single read of the store configuration tree — no external calls, no scheduled tasks, no frontend impact.

**Can I control which scope the widget reflects?**

The widget reflects the scope currently selected in the admin scope switcher. If you switch from "All Store Views" to a specific website, the widget updates to show that website’s data.

**Does it work with custom website or store group names?**

Yes. The widget reads from the store configuration model directly, so any customisations to website name, store group name, or store view locale are reflected automatically.

**Is there a multi-site / multi-instance version?**

Not currently. The widget scopes to the currently logged-in admin’s active scope selection. A cross-instance overview would require a separate aggregation layer outside Magento.

## Related

- [AMQP Monitor module](https://github.com/brosenberger/module-amqp-monitor) — another operator-first System → Tools entry that follows the same admin placement discipline.- [Honor your inner monk: simpler adminhtml UIs](/blog/magento-2-admin-menu-placement/) — the rationale behind how this module registers its admin entries.

## Blog

### Magento Core Contribution: What Really Happens

URL: https://brocode.at/blog/magento-core-contribution/
Updated: 2026-06-08T11:06:15+00:00

The full journey of one small, merged Magento 2 core contribution: the CLA, the bot, the QA gate, the ENGCOM ticket, and the months-long wait.

## I Added One Field to Magento Core — Here’s Everything That Happened

The category edit page in Magento admin shows the category ID. The category tree does not. That means every time you need an ID for external system mapping, a layout XML target, or URL rewrite debugging, you click into the category, read one number, and click back out.

It is a tiny annoyance, but it happens every day. This article explains how that annoyance became a real merged pull request in `magento/magento2`, and what actually happens after you click create pull request.

> The PR this article follows is [magento/magento2#25717](https://github.com/magento/magento2/pull/25717).

## lead

This is for Magento engineers who want to contribute upstream but have never seen the full path between idea and release. You will see the real workflow gates and the practical decisions that keep a small PR moving.

## baseline

A practical baseline is simple: pick one recurring friction point, make a small consistent fix, and present it with clear reasoning and reproducible tests. For this case, the baseline issue was category ID visibility in the admin tree.

## 1. The itch and why it is worth fixing

Not every core contribution needs to be a huge feature. Most useful contributions fit into one of three types:

- Bug fixes where behavior is clearly wrong.- New features where capability does not exist yet.- Quality of life improvements where daily friction is removed.

This change was quality of life. Nothing was broken. The ID was available, just in the wrong place for day to day work. That is still a valid reason to improve core.

The best first contribution is often your own recurring pain point. You already understand the context, you know what good looks like, and you can answer the review question why does this matter with real usage examples instead of theory.

## 2. Check if it already exists

Before writing code, search open and closed issues and pull requests. Closed discussions are often the fastest way to learn why an idea was rejected or what constraints maintainers care about.

For larger behavior changes or new feature work, open an issue first and align on direction. For small and obviously consistent UI improvements, a focused PR can be enough.

## tradeoff

The tradeoff is speed now versus coordination later. Going straight to code can be fast, but without prior context you risk duplicate effort or rejection. Opening an issue first improves alignment but adds upfront time. For tiny consistency fixes, direct PR is often reasonable; for behavioral changes, issue first is safer.

## 3. One time setup that blocks many first PRs

You need a GitHub account and a fork of `magento/magento2`. Magento uses fork and pull workflow, so your branch lives on your fork and targets Magento develop.

You also need the Adobe CLA signed. This is the most common hidden blocker for first time contributors. Sign once and then you are done.

Enable GitHub 2FA as well. After your first PR, you may receive Magento org invitation flow that unlocks issue claiming and smoother contribution handling.

## 4. The actual code change

The fix itself was small. It touched one method that builds category tree labels in admin and aligned output with the existing title style that already includes ID.

The important mechanics around a tiny change are:

- Create a dedicated branch on your fork.- Target the correct develop branch for the active release line.- Follow existing conventions so reviewers see consistency instead of novelty.

When your change matches existing behavior in another place, review friction drops immediately.

## 5. Pull request structure that speeds review

A strong PR description does three things:

1. Explain the why, not only the what.

2. Provide reproducible manual test steps.

3. Show checklist discipline for quality and test coverage.

For this contribution the test scenario was simple: open Catalog to Categories and verify each tree item now includes category ID the same way the title does.

The easier your review path is, the faster maintainers can approve.

## 6. What happens after submit

After submission, automation and human workflow started:

- `@magento` bot posted commands and workflow guidance.- CI checks ran and needed to pass.- QA gate confirmation was posted.- Community maintainer reviewed.- Internal ENGCOM ticket was created.- Engcom team merged into develop.

From outside, this can look opaque. In reality it is a sequence of explicit gates, each with different owners.

## 7. The real timeline is mostly waiting

The surprising part is not coding time, it is waiting time.

In this specific case, review and merge were fast, but release delivery was much slower. The change merged quickly into develop and still waited for the release train before reaching stores.

This is normal. You should separate two timelines:

- Review queue timeline, unpredictable.- Release cadence timeline, predictable but slower.

Silence does not automatically mean rejection. Usually it means queueing or release staging.

## One lever you do have: community votes

Magento prioritization is influenced by visible demand. Reactions and linked issues can increase priority signal for maintainers.

Practical approach:

- Link PR to an existing issue when possible.- Open issue first when none exists.- Use reactions instead of noisy plus one comments.

Votes do not override technical quality, but they can affect sequencing between similarly solid contributions.

## working_example

A practical contribution loop that works:

1. Capture one concrete pain point from daily work.

2. Build the smallest change that aligns with existing behavior.

3. Write a PR description with context and manual verification steps.

4. Respond quickly to maintainer feedback.

5. Track release milestone and communicate expected ship timing.

## 8. Ship now, upstream in parallel

If you need behavior immediately, ship a local module while upstream PR moves through process.

If extension point is public, plugin is usually clean and removable later.

If extension point is not pluginable, class preference can work but increases maintenance risk.

That tradeoff itself is a reason to upstream: once core ships your behavior, you can delete local override and reduce long term fragility.

## verification

You know this workflow worked when:

- The PR has reproducible test steps and a clear why.- CI and QA gates are green.- Maintainer feedback is resolved without long silence.- The merge lands in develop.- The change appears in the targeted release line.

## Letting an AI agent help with tests

A practical place for AI support is test scaffolding. New contributors often stall on framework specific testing patterns even when they fully understand the fix itself.

A useful workflow is:

1. Write the fix yourself.

2. Ask AI to draft tests matching nearby module patterns.

3. Revert the fix and confirm tests fail.

4. Restore fix and confirm tests pass.

Never submit generated tests blindly. You should be able to explain each assertion and why it would fail when behavior regresses.

AI can lower activation energy, but quality bar stays exactly the same.

## The takeaway

The code is usually the easy part. Contribution confidence comes from understanding the gates between idea and release: CLA, branch targeting, automation, QA, maintainer review, internal ticketing, merge, and release train.

The next time a repeated Magento friction point shows up in your daily work, treat it as a contribution candidate. Small, focused improvements are often the best first core PRs.

### Honor your inner monk: simpler adminhtml UIs for complex Magento 2 work

URL: https://brocode.at/blog/magento-2-admin-menu-placement/
Updated: 2026-06-10T08:20:39+00:00

Magento 2 modules accumulate vendor-named menu entries and config tabs that operators never asked for. This article shows how to move functionality into the core navigation taxonomy — with concrete menu.xml and system.xml examples.

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:

```xml
<!-- 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.xml attributing the module.

- You need to know the core node IDs. They’re in Magento_Backend/etc/adminhtml/menu.xml and each module’s own menu.xml; grep for the id attribute.

## 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:

```xml
<!-- 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:

```xml
<!-- 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.

- sortOrder requires 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.

```xml
<!-- 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>.

```xml
<!-- 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>.

```xml
<!-- 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.

Magefan’s built-in menu visibility toggle in Stores → Configuration — set to No to remove the admin menu entry without any custom code.

## 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 → Configuration sidebar has no vendor-named tab that requires mental translation to a function.

- bin/magento setup:upgrade produces no duplicate section or menu ID warnings in var/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 under Services, no vendor-named navigation nodes.

- Magento 2 menu.xml reference — node attributes, ACL wiring, and sort order rules.

- Magento 2 system.xml reference — 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”.
