Articles

Written for decision moments, not for traffic.

Editorial standard for everything here: if a piece contains nothing checkable, it doesn’t publish.

Decision guides

Start with the decision you’re facing.

These live as standing pages we keep current, not dated posts — the advice changes when the facts do.

Your agency left Magento. Now what?

Agency-abandonment triage: continuity, patch status, repo custody.

Read the guide →

Is renewing Adobe Commerce worth it?

Exit economics, straight: when staying is right, and what leaving costs.

Read the guide →

Magento 2 upgrade paths

EOL dates, PHP 8.5, and where Mage-OS 3.1 fits.

Read the guide →

Hyvä performance, with receipts

Field data from real rebuilds, not vendor benchmarks.

Read the guide →

Engineering Notes

Short action logs

Field notes from real closed tickets: the symptom, the cause, the fix, and the outcome.

2026-09-28

Free gifts, quantity squared: when three correct modules disagree

promotions checkout hyva mage-os
Symptom
A store that runs free-gift promotions upgraded to the latest Mage-OS (Magento 2.4.9), Hyvä Checkout, and Amasty's Free Gift extension. Gifts started misbehaving: extra free items appeared in the cart and then vanished, some carts emptied themselves partway through checkout, and gift lines reached orders at their quantity squared. 7 became 49, and 36 became 1,296.
Cause
None of the three modules was wrong on its own; it took the latest release of all three together. Magento 2.4.9 changed how products are cached as they load, so core and the free-gift extension now end up holding the same product object instead of separate copies. Core marks that shared object with configurable-product details meant for somewhere else, and the gift enters the cart carrying them, as a line tied to itself that is hidden from view and missed by the extension's own checks. Hyvä Checkout then saves the cart at every step, as it should, and each save reprocesses that line: gifts get re-added, paid lines of the same product merge into gift lines, and the extension removes the merged line as an invalid gift. Roll back any one of the three and the chain breaks.
Fix
Rather than patch one vendor's code to suit another, we fixed the interactions between them. A product entering the cart gets an isolated copy only when it carries those stray details; everything else passes through untouched. Gift lines and paid lines no longer count as the same item, even for the same product. A one-time cleanup repaired carts that were already affected.
Measurement
Two small plugins fixed it, in time for the next promotion.
2026-09-22

Upgrading to 2.4.9: the breakage is in the dependencies

mage-os upgrades maintenance
Symptom
Across our fleet's moves to Mage-OS 3.4 and 3.5 (Magento 2.4.9 baseline), problems ranged from loud to silent. Common issues:
Cause
Core had updated its libraries and third-party code hadn't caught up. Symfony Console 7 requires an int return type on execute(), so one untyped console command anywhere kills the CLI as soon as it loads. less.php 5 changed its default math mode, so bare division inside ceil() no longer evaluates. jQuery UI 1.14 deleted a core module and its RequireJS alias. An extension that still named it made RequireJS fetch one file under two IDs, bind it to one, and wait on the other forever, with no error anywhere. Zend_Http_Client is gone entirely. And a new DI argument on a core rule class was inherited, by name, by a subclass that used the same name for a different type.
Fix
Where a fixed release existed, we bumped to it, after diffing it to confirm the change was additions only. Where the package was abandoned (two modules last released in 2022 and 2023), we patched it. We re-declared the RequireJS shim without the deleted module, parenthesized the LESS division, moved the HTTP client to Laminas, and fixed the DI argument conflict. Then we checked every custom class against the new vendor tree to catch signature errors before a customer could.
Measurement
Five kinds of breakage across five stores, each traced to a single mechanism rather than worked around. In all cases, QA and CI tests caught it before it reached a merchant.
2026-09-09

Composer's -a flag can't see Magento's generated code on old versions

composer deployment performance
Symptom
We switched our fleet to use composer's authoritative classmap, one step beyond normal optimizing. On a site running Magento 2.4.7, the next deployment failed with a missing generated class error.
Cause
With an authoritative classmap, Composer's loader answers "not found" for any class missing from the classmap before it ever checks the PSR-4 maps. Magento registers generated/code with that loader at runtime, which is exactly the path -a shuts off. So under -a no generated class loads at all: not the factories, proxies, or interceptors. Bootstrap fails on whichever one compiled DI happens to ask for first.
Fix
Add generated/code/ to the project's autoload.psr-0 so Composer scans the setup:di:compile output into the classmap, and pin the build order: composer install, patch:apply, setup:di:compile, then composer dump-autoload -a --no-dev. Magento made this same change starting in 2.4.8.
Measurement
Verified in an isolated project: with the entry, generated classes land in autoload_classmap.php under -a, and without it none do. Easy fix.
2026-08-01

Seven years on the same store

maintenance stewardship
Symptom
Case studies get written about launches and rescues, so those look like the only two states a store can be in. Nobody publishes the store that just kept running.
Cause
The work that keeps a store off the rescue list is individually unremarkable — patches within days of disclosure, module updates, ERP sync fixes, small UX changes — and it only compounds if someone does it steadily for years.
Fix
We keep doing it. One B2B store carries 3,468 commits of continuous maintenance from 2018 through 2024; two more run unbroken from 2019 and 2020 to today.
Measurement
Client stores with six-to-eight-year continuous histories; none has been replatformed.
2026-07-15

The customer grid meets MySQL's 16-column FULLTEXT cap

mysql erp admin
Symptom
Admin customer grid reindex failing after an ERP integration added sync-ID attributes to the grid.
Cause
Magento folds every searchable grid column into one InnoDB FULLTEXT index, and MySQL caps FULLTEXT indexes at 16 columns. Nothing warns you; the 17th searchable attribute simply breaks the index.
Fix
Excluded ERP sync IDs from FULLTEXT indexing — they're exact-match keys, not search terms, so they never belonged in it.
Measurement
Grid search and reindex stable since; sync IDs still filterable exact-match.
2026-07-10

Platform upgrades as a fleet process, not annual projects

mage-os maintenance process
Symptom
A new platform release lands and the store owner braces for a project: scoping, a quote, a multi-week branch, a risky deploy weekend.
Cause
The cost was never the release itself; it was treating each store's upgrade as a bespoke engagement with its own plan and its own definition of "looks fine to me."
Fix
Upgrades run as one batch across the fleet: Mage-OS 3.0 tracking issues opened across nine-plus client repositories the same day, with identical CI process (builds, static analysis, store smoke tests) proving each upgrade PR the same way on every store.
Measurement
One store's cadence this year: 2.2.1 in March, 2.3.0 in May, security patches between — as routine maintenance, not fires.
2026-04-01

Authorize.net still doesn't speak IPv6. In 2026.

payments networking
Symptom
A merchant's payment stack couldn't follow their infrastructure onto IPv6: Authorize.net's API remains IPv4-only, and IPv6 customer addresses don't survive its request fields.
Cause
Gateway limitation, not a store bug. No IPv6 support, no timeline published for adding them.
Fix
Migrated the merchant to CyberSource — same Visa family, so tokenized customer profiles had a migration path — and added full IPv6 support to our CyberSource module's IP validation and sanitization (released April 2026).
Measurement
IPv6 traffic transacting cleanly since cutover.
2026-03-10

The compromise that lasted 85 minutes

security incident-response maintenance
Symptom
At 7:25 one February morning, an FTP login from an unfamiliar country uploaded a PHP shell to a store we maintain, modified two Magento core files, and dropped a database browser alongside. The goal, from what they touched, was checkout card skimming.
Cause
A legacy FTP account, one of five on the server, most unused for years. Whether the credentials were guessed or harvested elsewhere, the lesson is the same: every dormant account is standing attack surface.
Fix
Detection did its half: an hourly Sansec malware scan flagged the new files 42 minutes after they landed. Maintenance mode, every attacker file removed, core files restored, IP blocked. Site clean and back online at minute 85. Then prevention: all FTP accounts disabled and reissued only on demonstrated need; the encryption key rotated; the admin list cut from thirty-some accounts to the dozen in use; and PCI 4.0 password and inactivity enforcement deployed.
Measurement
42 minutes from shell upload to detection; 85 minutes from upload to clean and back online. The skimming attempt failed; it tampered with a payment integration the store doesn't use.
2026-01-17

Leaving Adobe Commerce: what actually breaks (very little)

mage-os migration
Symptom
A Commerce store moving to Mage-OS loses the Commerce-only features it actually used — here, target rules (related-product rules) and a couple of staging-schema leftovers (row_id) baked into custom code.
Cause
Years of customization quietly assuming Commerce internals. The list is always shorter than merchants fear, but it's never zero.
Fix
Built a drop-in target-rule replacement module and deployed it disabled ahead of the cutover; cleaned row_id/store-scope assumptions out of CMS seed patches. The composer-level platform swap itself executed over two days.
Measurement
Store has taken three routine Mage-OS version updates since migration with no Commerce-related regressions.
2025-06-30

What inheriting a Magento store actually involves

onboarding maintenance
Symptom
A subscription store arrived with years of prior-team work — a heavy ERP integration, a custom subscription engine — and production that had drifted from the repo in ways nobody had documented.
Cause
Nothing in the repo said what was actually deployed. Untracked changes lived only on production, security was multiple patches behind, and PHP 8 incompatibilities and dead modules had accumulated unnoticed.
Fix
Two months of baseline before any feature: reconcile production drift back into git, update Magento to be current on security patches, fix everything static inspection surfaced, and remove dead modules and their dependencies. Feature work started once things were clean and ready.
Measurement
103 commits in year one, every one of them building on a repo that finally matched production.
2025-03-31

A Hyvä re-front is an integration inventory, not a theme

hyva frontend performance
Symptom
Everyone agrees the frontend needs replacing, and someone has already found Hyvä. The quote everyone expects is for a theme. The work that shows up is not a theme.
Cause
Hyvä replaces Magento's entire frontend stack, so nothing a third party shipped for the old one survives: loyalty widgets, promo banners, brand pages, payment buttons, and gift card templates all need native re-implementation — and some modules can't make the trip at all.
Fix
Inventory every frontend integration before quoting, rebuild them natively on Hyvä while the backend and checkout stay put, and delete the old theme's leftovers instead of leaving them to rot. On one cutover the schedule was dominated by a single vendor's module suite.
Measurement
One launch: median page load 6.5s to 1.9s the night of cutover; CrUX good-LCP share 52% to 88% the following month.
The standard

No thought-leadership filler. Real work, real results.

Have the decision these were written for?