Fixing Klevu Stock Status with Magento MSI Multi-Source Inventory

If you’re running Klevu alongside Magento’s Multi-Source Inventory (MSI) and products are showing the wrong stock status in your Klevu index, the cause turned out to be fairly subtle - and the fix is a single config setting.

The problem

Products that were genuinely in stock were being indexed as out of stock (or the reverse) in Klevu. The Magento storefront showed the correct status, but Klevu disagreed. This only happened on a store using MSI with stock assigned to a non-default source.

Why it happens

Klevu’s product indexing module calculates stock status through ProductStockStatusProvider, and the method it uses is controlled by this config path:

klevu/indexing/product_stock_status_calculation_method

There are four options:

  • stock_item
  • stock_registry
  • is_available
  • is_salable

The default is stock_item. With that setting, Klevu calls getFromStockItem(), which reads the is_in_stock flag off the product’s StockItem extension attribute:

private function getFromStockItem(
ProductInterface $product,
?StoreInterface $store,
): bool {
$extensionAttributes = $product->getExtensionAttributes();
$stockItem = $extensionAttributes?->getStockItem();

// Replicating original behaviour pre: 4.4.0
return $stockItem
? (bool)$stockItem->getIsInStock()
: $this->getFromStockRegistry($product, $store);
}

That extension attribute is backed by the legacy cataloginventory_stock_item table. And here’s the catch: under MSI, that legacy table is only kept in sync from the default source.

You can see this in Magento core, in SetDataToLegacyCatalogInventoryAtSourceItemsSavePlugin::afterExecute():

foreach ($sourceItems as $sourceItem) {
if ($sourceItem->getSourceCode() !== $this->defaultSourceProvider->getCode()) {
continue;
}
// ...
$sourceItemsForSynchronization[] = $sourceItem;
}

$this->setDataToLegacyCatalogInventory->execute($sourceItemsForSynchronization);

Any source item that isn’t the default source is skipped. So if your sellable stock lives on a custom source, the legacy table never reflects it, and getFromStockItem() reads stale data. Klevu then indexes the wrong status.

The fix

Switch the calculation method to stock_registry:

klevu/indexing/product_stock_status_calculation_method = stock_registry

With that setting Klevu calls getFromStockRegistry() instead, which goes through StockRegistryInterface::getStockStatus():

private function getFromStockRegistry(
ProductInterface $product,
?StoreInterface $store,
): bool {
/**
* MSI has a plugin on this method to get the correct stock.
*/

$stockStatus = $this->stockRegistry->getStockStatus(
productId: $product->getId(),
scopeId: $store
? (int)$store->getWebsiteId()
: null,
);

return (bool)$stockStatus->getStockStatus();
}

As the comment notes, MSI plugs into this method to return the correct aggregated stock across all sources assigned to the website’s stock - not just the default source. That’s exactly what you want when you’re running multi-source.

The setting lives under Stores → Configuration → Klevu → Developer Settings → Stock Status Calculation Method, and it’s a global (default scope) setting. You can also set it directly:

bin/magento config:set klevu/indexing/product_stock_status_calculation_method stock_registry

One thing worth noting from the field comment in the admin: changing this setting requires a discovery or update of the product catalog before it takes effect on existing indexes. So after switching, requeue your products:

bin/magento klevu:indexing:entity-update --entity-types KLEVU_PRODUCT --entity-ids all

Which option to pick

If you’re on MSI, stock_registry is the right choice - it’s MSI-aware. The is_available and is_salable options load the product per store and call isAvailable() / isSalable() respectively, which are heavier (they hit the product repository) but reflect salable quantity logic. The default stock_item is fine on single-source setups but, as above, will silently break once stock moves off the default source.