The category edit page in the Magento admin shows you the category ID. The category tree — the thing you stare at all day — doesn’t. So every time I needed an ID to wire up an external system, or to write a layout update targeting one specific category, I’d click into the category to read the number off the page title and click back out.
It’s a tiny annoyance. The kind you absorb a hundred times before you realise you could fix it. The fix turned into a pull request against magento/magento2 that got merged into core. Anyone editing categories in stock Magento now sees the IDs in the tree because of a change you could write in a lunch break.
What stops most developers from ever opening a PR isn’t the diff — it’s not knowing what happens after you click "Create pull request": the CLA, the bot, the QA gate, the internal ticket, the wait. This is the entire journey of one small, real, merged contribution, gate by gate.
The PR this article follows is magento/magento2#25717. You can read the whole thread — it’s short.
1. The lead — the itch and why it’s worth fixing
Every change to core lands in one of three buckets:
- Bug fixes — something is broken and you make it correct. Easiest to justify.
- New features — capability that wasn’t there before. Highest bar: raise an issue first, because maintainers need to agree it belongs in core before they’ll look at code.
- Quality-of-life changes — nothing is broken and nothing new becomes possible, but the software is measurably nicer for people who use it every day. The lowest-stakes kind, and often the best first contribution precisely because there’s no architecture to debate.
My change was squarely the third kind. Categories have no external identifier. The internal ID is needed constantly — configuring external systems, writing ID-specific layout XML, debugging URL rewrites. Magento already exposes it on the category edit page title. Showing it in the tree, where you spend most of your time, was the obvious missing half.
The best first contribution isn’t something you pick off a backlog. It’s something that already annoys you in daily work. You understand the problem, you know what correct behaviour looks like, and when a reviewer asks "why do we want this?" you have a real answer.
2. The baseline — check before you build
Before writing a line, search existing issues and pull requests — open and closed. A closed PR can tell you the maintainers already considered your exact idea and turned it down, and why. That’s the cheapest code review you’ll ever get.
For anything bigger than a cosmetic tweak, open an issue to discuss the change before building it. My change was small and self-evidently consistent with existing behaviour, so I went straight to a PR. A behaviour change or new logic I’d have floated as an issue first.
3. The one-time setup
Do this on day one rather than discovering it when your PR stalls.
You need a fork of magento/magento2. Magento uses the fork & pull model: push to your copy, open a PR back to the base repo.
Sign the Adobe Contributor License Agreement. Nothing merges until it’s signed, you only do it once, and a bot prompts you on your first PR. Sign at the Adobe open-source CLA page on day one and forget it forever. Enable 2FA on your GitHub account. After your first PR you’ll get an invitation to the Magento GitHub org — accept it to claim issues.
4. The actual change
The fix lives in one method — buildNodeName() in Magento\Catalog\Block\Adminhtml\Category\Tree. Magento already assembles Name (ID) format for the edit-page title; the change appends the ID to the tree node label in the same style. One method, a handful of lines.
The mechanics matter more than the change itself:
- Branch on your fork, never on the develop branch. Name it after the work.
- Target the right base branch. Today that’s
2.4-develop. A PR aimed at the wrong branch gets bounced before anyone reviews the code. - Match existing conventions. "It already does this on the title, I’m doing it in one more place" removes the entire category of "we’d prefer you did it differently" feedback.
5. The pull request itself
A good description is the single biggest lever on how fast you get merged. A reviewer who has to ask what your change does and how to test it is a reviewer who quietly deprioritises you.
Three parts cover it: the why in one or two sentences (pre-empt the obvious question), numbered manual testing steps that a reviewer can verify in thirty seconds, and the contribution checklist showing green builds and test coverage. That’s the template for any small contribution.
6. The tradeoff — machinery and what to expect
After submitting, the @magento bot offers test-environment commands. Automated builds run — static analysis, unit and integration tests — and all must go green. Then a QA team member posts "QA Passed." A community maintainer reviews. Once accepted, the bot opens an internal tracking ticket (mine was ENGCOM-6326). That’s when a contribution crosses from "community PR" into Magento’s release pipeline.
The elapsed time is dominated by waiting, not working. My PR opened on 24 November 2019 and merged two days later — but that was luck, not the norm. Plenty of PRs sit for weeks or months. Don’t expect a two-day turnaround; expect the queue. Even after a fast merge, the change milestoned for Release 2.3.5 didn’t ship to real stores until 28 April 2020 — five months later. The wait has two stages: an unpredictable review queue, then a predictable but long release cadence. Merging is not shipping.
Stay responsive while the PR is open. A PR with no contributor response for two weeks gets closed.
One lever: community votes
Magento’s review process is partly demand-driven. The signal is 👍 reactions on an issue’s opening comment. A change tied to an issue with dozens of thumbs-up reads as "lots of people want this" and moves up the priority list. Adobe formalised this with a Community Prioritization process around 2.4.6: higher-voted items get pulled into the pipeline sooner.
If an issue already exists for your fix, link your PR to inherit its votes. If none exists, open one first — issues are easier to find and upvote than buried PRs. Use the reaction, not a "+1" comment.
7. A working example: ship it as your own module
That months-long wait has a practical escape hatch. Package the same behaviour as a small local module, run it on your stores today, and remove it once the official version ships.
How invasive the module is depends on the core code. This is the central tradeoff when building a companion module:
- If the method is public, use a plugin. An
afterplugin on the node-name builder that appends the ID is clean, non-invasive, and upgrade-safe. Remove it by deleting a few lines ofdi.xml. This is the happy path. - If the method isn’t pluggable — private, protected,
final, or logic buried mid-method — you need a class preference (rewrite). You extend the core class and override the method. This works but is brittle: a future core change to that class can silently diverge from your override.
A preference that reimplements a core method is a liability. The only clean way to delete it is getting the change into core so future-you gets the behaviour natively. The local module is a bridge, not a destination.
Running your change in production before the PR merges also means your "manual testing scenarios" aren’t hypothetical — you’ve proven the behaviour on a real store, which makes the PR more credible.
Then close the loop: once the change ships in the release you’re running, composer remove the module. A redundant override left in place is exactly how someone ends up debugging "why is this ID rendering twice" two years later.
8. Verification — it ships
The change merged into 2.3-develop and completed verification when Magento Open Source 2.3.5 shipped on 28 April 2020. A feature that’s now in stock Magento is there because of a diff writable in an afternoon. The only thing that had ever stood between me and that was not knowing the process.
Using an AI agent for test coverage
Test coverage is where first-timers most often stall. It’s a good candidate to delegate — point an agent at your changed class and the existing tests for neighbouring classes, and have it scaffold a unit test that follows patterns already in the module. A well-scoped change with a clear before/after is close to the ideal case.
What you must not do is submit what it produces without reading every line. Maintainers scrutinise tests harder than implementation — a test that passes for the wrong reason is worse than no test.
A practical loop: write the fix yourself, ask the agent to write the test, then revert your fix and confirm the test goes red. If it stays green, the test is theatre.
An AI agent lowers the activation energy of contributing — the test-scaffolding chore — without lowering the bar your PR has to clear. Everything it generates still has to survive the QA gate, the maintainer review, and your own understanding.
One thing not to delegate: choosing the contribution. Grooming the backlog — deciding which bug actually matters, whether a rough edge is worth the upgrade risk — is judgement built on knowing the codebase. Pick the work yourself, the way you picked your own itch. Let the tooling in once you’ve decided what to build.
The takeaway
The code was the easy part. It almost always is. What I didn’t have the first time was a mental model of everything between "this annoys me" and "this is in core" — the CLA, the branch target, the bot, the QA pass, the ENGCOM ticket, the merge. Now you do.
The next time Magento does something that’s broken, missing, or just needlessly tedious — don’t file it under "annoyance." File it under "my next pull request."

Leave a Reply