Magento 2.4.9 for developers: the features that matter
Adobe Commerce and Magento Open Source 2.4.9 (released May 12, 2026) is the largest 2.4.x release in years, with more than 500 fixed issues and a serious push toward platform modernization. While the Google Pay Vault and new payment options get most of the merchant attention, the developer-facing changes are just as important. In this article we walk through the features that affect your codebase, with practical examples.
PHP 8.4 and 8.5 support, PHP 8.2 dropped
The most consequential change is the runtime baseline. PHP 8.2 is no longer supported. The core, its dependencies and the tooling now run cleanly on PHP 8.3, 8.4 and the brand new 8.5. The Braintree extension was updated specifically to support PHP 8.5 while keeping 8.4 compatibility.
For your own modules this means updating the constraint in composer.json before you upgrade:
{
"name": "ten50/module-catalog",
"require": {
"php": "~8.3.0 || ~8.4.0 || ~8.5.0",
"magento/framework": "*"
}
}PHP 8.4 also deprecates implicitly nullable parameters, which is a common source of warnings in older Magento modules. The fix is mechanical but easy to miss across a large codebase:
public function getProductBySku(?string $sku = null): ?ProductInterface
{
if ($sku === null) {
return null;
}
return $this->productRepository->get($sku);
}Run a static analysis pass with PHP 8.4 as the target before upgrading production. PHPStan and the bundled bin/magento dev: tooling will surface the implicit nullables and the type-declaration changes that come from the Symfony upgrade described below.
New GraphQL mutations for headless storefronts
If you run a headless or PWA storefront, 2.4.9 closes some long-standing gaps in the GraphQL schema. The clearCart mutation, previously Adobe Commerce only, is now available in Open Source too, and a new clearWishlist mutation supports bulk removal in a single call instead of looping over individual items:
mutation ClearWishlist {
clearWishlist(wishlistId: "1") {
wishlist {
id
items_count
items_v2 {
items {
id
}
}
}
}
}That single round trip replaces the old pattern of fetching every wishlist item and firing a removeProductsFromWishlist mutation per id, which mattered on accounts with large lists.
Token exchange for single sign-on
The new exchangeExternalCustomerToken mutation is the headline for integrators. It authenticates a customer with an integration token and returns both the customer token and the customer data in one response. This is exactly what you need when an external identity provider or a parent platform already authenticated the user and you want to hand them a logged-in storefront session without a second login:
mutation ExchangeToken($token: String!) {
exchangeExternalCustomerToken(token: $token) {
customer_token
customer {
firstname
lastname
email
}
}
}You take the returned customer_token and set it as the Authorization: Bearer header on subsequent requests. No more brittle workarounds with admin integration tokens or custom resolvers to bridge an external login into Magento.
Order totals excluding tax
A small but frequently requested addition: the OrderTotal type now exposes grand_total_excl_tax. Until now you had to reconstruct the net total client-side from grand_total and the tax breakdown, which was error-prone with mixed tax rates. Now it is a first-class field:
query CustomerOrders {
customer {
orders {
items {
number
total {
grand_total {
value
currency
}
grand_total_excl_tax {
value
currency
}
total_tax {
value
currency
}
}
}
}
}
}For B2B storefronts that display net prices throughout, this removes a whole class of rounding bugs.
Framework modernization: Symfony 7.4 LTS and Symfony Cache
Under the hood, all Symfony dependencies were updated to the Symfony LTS 7.4 line, and the deprecated Zend_Cache component was finally replaced with the Symfony Cache component. For most stores this is invisible, but two groups of custom code need attention.
First, any class that extends a Symfony core class may need updated type declarations and method signatures to match the new versions. Second, code that talks to Zend_Cache directly will break. The right move is to use the framework abstraction, which keeps working across the migration:
namespace Ten50\Module\Service;
use Magento\Framework\App\CacheInterface;
use Magento\Framework\Serialize\SerializerInterface;
class CatalogFeedCache
{
private const CACHE_KEY = 'ten50_catalog_feed';
private const CACHE_LIFETIME = 3600;
public function __construct(
private readonly CacheInterface $cache,
private readonly SerializerInterface $serializer
) {}
public function get(): ?array
{
$cached = $this->cache->load(self::CACHE_KEY);
return $cached ? $this->serializer->unserialize($cached) : null;
}
public function save(array $feed): void
{
$this->cache->save(
$this->serializer->serialize($feed),
self::CACHE_KEY,
['catalog'],
self::CACHE_LIFETIME
);
}
}If you grep your codebase for Zend_Cache and find direct usage, replace it with CacheInterface before upgrading. The serializer dependency is just as important: serialize()/unserialize() on raw objects was already discouraged, and the cleaner cache layer makes this a good moment to fix it.
What this means for your upgrade
Magento 2.4.9 is a modernization release more than a feature release. The payment additions help your checkout, but the real work for development teams is the PHP 8.4/8.5 jump and the Symfony migration. Plan an upgrade in three steps: bump your module constraints and run static analysis on PHP 8.4, search out direct Zend_Cache usage, and adopt the new GraphQL mutations where they simplify your storefront. Done in that order, the upgrade is smooth and you end up on a platform that is supported well into the PHP 8.5 era.
Upgrade to Magento 2.4.9?
We help you upgrade your store, modernize custom modules and adopt the new platform features safely.
Get in touch