Building a Custom MSI Source Selection Algorithm: SourceSelectionInterface Explained
AI generated
M2
di.xml
Magento 2 · MSI
Building a Custom MSI Source Selection Algorithm
SourceSelectionInterface beyond Priority and Distance

Magento ships MSI with two built-in source selection algorithms: Priority and Distance. For many operations that is not enough once current warehouse utilization, per-carrier shipping cost, or a combination of several criteria needs to drive the selection. This article shows how to implement a custom algorithm as a full-fledged replacement and register it correctly via di.xml.

12 min read SourceSelectionInterface MSI di.xml Warehouse Utilization Custom Algorithm

1. Why Priority and Distance often fall short

The Priority algorithm picks sources strictly by the priority order assigned within a stock and fills an order once the next source in that list becomes eligible. The Distance algorithm adds a geographic component on top, calculating the distance between the customer address and the geocoded source coordinates and preferring the closest source. Both algorithms cover the most common cases well, but neither is aware of operational criteria such as current utilization, staff availability, or shipping cost structure.

In practice, plenty of operations need exactly those criteria: a central warehouse should be preferred as long as its capacity is not exhausted, while a secondary warehouse should only kick in once a certain utilization threshold is crossed. That logic cannot be expressed with the built-in algorithms, since both work purely on static configuration values and have no link to dynamic warehouse metrics. Writing a custom algorithm is not a nice-to-have in that scenario, it is the only clean way forward.

2. SourceSelectionInterface in detail

Every source selection algorithm implements Magento\InventorySourceSelectionApi\Api\SourceSelectionInterface with exactly one method: execute. It receives an InventoryRequestInterface holding the stock and the requested items with their SKUs and quantities, and returns a SourceSelectionResultInterface describing which quantity should be drawn from which source. A single order line can be split across multiple sources whenever one source alone cannot cover the requested quantity.

Crucially, the algorithm itself never books inventory. It only decides which source should deliver which quantity, the actual reservation and later inventory deduction is handled by downstream inventory logic. That separation is deliberate so an algorithm stays stateless and can be invoked repeatedly for preview purposes, for example to show an estimated delivery time at checkout, without actually reserving stock.


<?php
declare(strict_types=1);

namespace Mironsoft\InventoryUtilization\Model;

use Magento\InventorySourceSelectionApi\Api\SourceSelectionInterface;
use Magento\InventorySourceSelectionApi\Api\Data\InventoryRequestInterface;
use Magento\InventorySourceSelectionApi\Api\Data\SourceSelectionResultInterface;

/**
 * Contract for a custom source selection algorithm.
 */
interface UtilizationAwareSelectionInterface extends SourceSelectionInterface
{
    /**
     * Selects sources for the given inventory request, weighting current
     * warehouse utilization over static priority values.
     *
     * @param InventoryRequestInterface $inventoryRequest
     * @return SourceSelectionResultInterface
     */
    public function execute(InventoryRequestInterface $inventoryRequest): SourceSelectionResultInterface;
}

3. Example: an algorithm driven by warehouse utilization

For the practical example, the algorithm sorts sources not by a fixed priority but by current utilization. Utilization is tracked via a custom attribute per source, populated on a cron job from a warehouse management system or from the number of open pick lists. The algorithm looks up utilization for every eligible source and consistently prefers the least utilized one, as long as it can cover the requested quantity.

If a single source cannot cover the full quantity, the remainder is passed on to the next least utilized source, until the full requested quantity is covered or all sources are exhausted. If no combination of sources can deliver the full quantity, that is surfaced transparently in the result via isShippable on the affected selection item row, so downstream processes such as checkout or an automatic backorder flag can react correctly.


<?php
declare(strict_types=1);

namespace Mironsoft\InventoryUtilization\Model;

use Magento\InventorySourceSelectionApi\Api\Data\InventoryRequestInterface;
use Magento\InventorySourceSelectionApi\Api\Data\SourceSelectionResultInterface;
use Magento\InventorySourceSelectionApi\Api\Data\SourceSelectionResultInterfaceFactory;
use Magento\InventorySourceSelectionApi\Api\Data\SourceSelectionItemInterfaceFactory;
use Magento\InventoryApi\Api\GetSourceItemsBySkuInterface;

/**
 * Selects sources by current warehouse utilization instead of static priority.
 */
class UtilizationAwareSelection implements UtilizationAwareSelectionInterface
{
    /**
     * @param GetSourceItemsBySkuInterface $getSourceItemsBySku
     * @param UtilizationRepository $utilizationRepository
     * @param SourceSelectionResultInterfaceFactory $resultFactory
     * @param SourceSelectionItemInterfaceFactory $itemFactory
     */
    public function __construct(
        private readonly GetSourceItemsBySkuInterface $getSourceItemsBySku,
        private readonly UtilizationRepository $utilizationRepository,
        private readonly SourceSelectionResultInterfaceFactory $resultFactory,
        private readonly SourceSelectionItemInterfaceFactory $itemFactory
    ) {
    }

    /**
     * Picks the least utilized source(s) able to cover the requested quantity.
     *
     * @param InventoryRequestInterface $inventoryRequest
     * @return SourceSelectionResultInterface
     */
    public function execute(InventoryRequestInterface $inventoryRequest): SourceSelectionResultInterface
    {
        $selectionItems = [];
        foreach ($inventoryRequest->getItems() as $item) {
            $remaining = (float) $item->getQty();
            $sourceItems = $this->getSourceItemsBySku->execute($item->getSku());
            usort(
                $sourceItems,
                fn ($a, $b) => $this->utilizationRepository->get($a->getSourceCode())
                    <=> $this->utilizationRepository->get($b->getSourceCode())
            );
            foreach ($sourceItems as $sourceItem) {
                if ($remaining <= 0) {
                    break;
                }
                $qtyToDeduct = min($remaining, (float) $sourceItem->getQuantity());
                $selectionItems[] = $this->itemFactory->create([
                    'sourceCode' => $sourceItem->getSourceCode(),
                    'sku' => $item->getSku(),
                    'qtyToDeduct' => $qtyToDeduct,
                    'qtyAvailable' => (float) $sourceItem->getQuantity(),
                ]);
                $remaining -= $qtyToDeduct;
            }
        }
        return $this->resultFactory->create(['selectionItems' => $selectionItems]);
    }
}

4. Registering the algorithm in di.xml

For the new algorithm to appear as a selectable option on a stock in the admin, it must be registered in di.xml as a virtual entry on sourceSelectionAlgorithmList, together with a descriptive code and title. This entry extends an already existing list from the InventorySourceSelectionApi module via argument merging, a full preference override of the provider itself is not required and would create unnecessary conflict potential on Magento upgrades.

After registration, the algorithm shows up in the stock configuration under Source Selection Algorithm as an additional choice. It is important that setup:di:compile runs after every change to this list, since Magento caches the provider list in generated code and changes are otherwise silently ignored even in developer mode.


<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <type name="Magento\InventorySourceSelectionApi\Model\SourceSelectionService">
        <arguments>
            <argument name="sourceSelectionAlgorithms" xsi:type="array">
                <item name="utilization_aware" xsi:type="array">
                    <item name="code" xsi:type="string">utilization_aware</item>
                    <item name="title" xsi:type="string">Utilization Aware</item>
                    <item name="algorithm" xsi:type="object">
                        Mironsoft\InventoryUtilization\Model\UtilizationAwareSelection
                    </item>
                </item>
            </argument>
        </arguments>
    </type>
</config>

5. Testability and isolating external data

A custom algorithm should never access an external data source such as a WMS directly, it should go through a repository, injected here as UtilizationRepository. That lets unit tests fully control utilization values through a mock object, without ever opening a real database connection or API call. For the algorithm itself, a straightforward integration test that sets up several sources with different utilization values and checks the order and quantity split of the selection items is usually sufficient.

A test for the case where no source can cover the full quantity is particularly important. This is where a common bug in custom implementations shows up: if the remaining quantity is not correctly carried over between sources, an order can be wrongly marked as fully deliverable even though real stock is short. That kind of bug often goes unnoticed in a test environment, since realistic shortage scenarios are rarely simulated there, and only surfaces in production.

6. Performance with many sources and line items

For orders with many line items and stocks with a double-digit number of sources, the utilization lookup per item can quickly turn into a bottleneck if it hits the database or an external system separately for every SKU. Caching utilization values within a single execute call therefore makes sense, since a source's utilization practically never changes while a single order is being processed. A simple array as a local cache inside the repository is usually enough.

The same principle applies to the GetSourceItemsBySkuInterface call: with several line items sharing the same SKUs, the source item lookup should not be repeated redundantly. In practice it pays off to batch all required SKUs before the actual selection runs, instead of calling the standard method inside the loop per item the way the simplified example above does for readability.

7. Combining criteria instead of a full replacement

A common mistake with custom algorithms is ignoring all existing criteria entirely. A combination usually works better: utilization only decides between sources with equal or similar priority, while an explicitly configured higher priority is still respected. That prevents a location deliberately configured as an emergency warehouse from suddenly being preferred just because it happens to be lightly utilized at that moment.

In practice this means grouping sources by the priority configured on the stock first, and sorting by utilization only within a priority group. That keeps the familiar Priority semantics as a coarse frame, while utilization acts as a tiebreaker inside that frame, which in most operations is much closer to the intended logic than pure utilization sorting across all sources.

8. Configurability via system.xml instead of hardcoding

For the algorithm to be tunable in production without a code change, it needs its own system.xml section covering at least the threshold considered critical utilization and the cache TTL for utilization values. That matches the general principle that every new module ships its own settings, and it avoids a new deployment for every small adjustment.

An acl.xml belongs alongside it, restricting access to this configuration to a dedicated permission so not every admin user with general catalog rights can change the utilization logic. A dedicated menu item under Stores Configuration rounds the module off and makes the setting directly discoverable for the fulfillment team instead of hiding it inside a generic inventory section.

9. Common pitfalls with custom source selection algorithms

The most common mistake is booking inventory or creating reservations inside the algorithm itself. That contradicts the MSI architecture, where execute only makes a selection and the actual inventory handling is left to downstream order placement or shipment logic. Breaking that separation leads to duplicate bookings as soon as the same algorithm is also invoked for a plain checkout preview.

A second, often overlooked issue is missing tests for the case of insufficient total quantity across all sources. If that case is not handled cleanly, checkout can end up making false delivery promises. Equally important: the algorithm must be registered through sourceSelectionAlgorithmList, not as a preference on the default provider, otherwise the custom option silently disappears again on the next Magento minor upgrade.

Algorithm Criterion Data Source Typical Use
Priority Fixed priority order per stock Source configuration One clearly prioritized main warehouse with backup locations
Distance Geographic distance to shipping address Geocoded source address Regionally distributed warehouses focused on shipping time
Utilization Aware (custom) Current warehouse utilization Custom attribute / WMS integration Load balancing between several equivalent locations
Combined (Priority + Utilization) Priority group, then utilization Source configuration + custom attribute Emergency warehouse should stay secondary despite low utilization
Cost-based (possible) Shipping cost per carrier and source Carrier rate API Cost optimization across several shipping carriers

Mironsoft

Magento development, module consulting, and system architecture

A Magento project that needs a second opinion or experienced execution?

We build custom Magento modules, advise on architecture decisions, and take on complex implementations, from service contract planning to production-ready deployment.

Architecture Consulting

Have module and system architecture thought through properly before you build.

Custom Module Development

Build custom Magento modules cleanly, following best practices.

Code Review & Audit

Have existing modules reviewed for performance, security, and maintainability.

10. Summary

Custom Source Selection Algorithm: Key Takeaways

SourceSelectionInterface

Exactly one execute method that selects sources without booking inventory itself.

Custom criteria

Utilization, cost, or combinations can be modeled where Priority and Distance fall short.

di.xml registration

As an entry in sourceSelectionAlgorithmList, not as a preference, plus setup:di:compile.

Clean separation

Isolate selection logic, configuration, and external data sources behind repository interfaces.

11. FAQ: Custom Source Selection Algorithm: Key Takeaways

1Does a custom source selection algorithm need to book inventory?
No, the interface only covers selection. The actual reservation and inventory deduction is handled by downstream inventory logic, the algorithm only returns the selection items.
2Can a custom algorithm coexist with Priority and Distance?
Yes, all registered algorithms appear side by side as options in the stock configuration. Which algorithm is actually used is set individually per stock by the administrator.
3Where is a custom algorithm registered?
In di.xml as an entry in the sourceSelectionAlgorithmList argument on SourceSelectionService, with a unique code, title, and class reference. A full preference override of the provider is not required.
4Why doesn't the new algorithm show up in the admin?
Usually a missing setup:di:compile after the di.xml change, since Magento caches the provider list in generated code. Without recompiling, the change has no effect even in developer mode.
5How should the algorithm handle insufficient total stock?
It should split the available quantity across all eligible sources and surface any uncoverable remainder transparently in the result. Downstream processes such as checkout or backorder logic then evaluate that result accordingly.
6Should the algorithm access an external WMS directly?
No, going through a dedicated repository interface is better. That keeps the algorithm testable and lets unit tests fully mock the data source.
7How should custom algorithms be tested?
With integration tests that set up several sources with different values and check the order and quantity split of the selection items. A test for insufficient total quantity is particularly important.
8Is it worth combining Priority with a custom criterion?
In most operations, yes, because pure utilization sorting would ignore deliberately set priorities such as emergency warehouses. Grouping by priority with utilization as a tiebreaker usually matches real-world practice better.
9What performance pitfalls exist with many line items?
Repeated per-item lookups of utilization values or source items can become a bottleneck on large orders. A local cache within a single execute call and batched lookups mitigate that significantly.
10Does a custom algorithm need its own system.xml?
Yes, at least for thresholds and cache configuration a dedicated settings section with acl.xml and a menu item should exist, instead of hardcoding values in code.