All articles
D365 CommerceCommerce RuntimeCRTDevelopment

Building Commerce Runtime Pipeline Extensions in D365 Commerce

1 April 20254 min readAD IT Consulting

The Commerce Runtime (CRT) is the business logic layer of D365 Commerce. Every operation — price calculation, loyalty point redemption, cart validation, inventory reservation — flows through a pipeline of request handlers. Understanding how to extend these pipelines cleanly is one of the most important skills in D365 Commerce development.

How CRT pipelines work

Every CRT operation is modelled as a Request/Response pair. When a Store Commerce client calls "add item to cart", it dispatches a SaveCartRequest. The runtime resolves a handler for that request type, executes it, and returns a SaveCartResponse.

Handlers are registered in a pipeline. The out-of-box Microsoft handler runs first (or last, depending on configuration), and your custom handler intercepts the flow either before or after it.

// Registering a custom pre-trigger on SaveCartRequest
public class CustomCartPreTrigger : IRequestTriggerAsync
{
    public IEnumerable<Type> SupportedRequestTypes =>
        new[] { typeof(SaveCartRequest) };

    public async Task OnExecuting(Request request)
    {
        var saveCartRequest = (SaveCartRequest)request;
        // Validate business rules before the cart is saved
        await ValidateCartBusinessRules(saveCartRequest.Cart);
    }

    public Task OnExecuted(Request request, Response response) =>
        Task.CompletedTask;
}

Three extension patterns

1. Pre/Post triggers

Triggers are the safest extension point. They do not replace Microsoft's handler — they run alongside it. Use triggers when you need to:

The key constraint: triggers cannot modify the request or response objects. They are observers, not interceptors.

2. Request handler overrides

When you need to change the actual behaviour — replace the pricing logic, swap the inventory lookup, implement a custom discount engine — you override the handler entirely.

public class CustomGetActivePricesRequestHandler
    : SingleAsyncRequestHandler<GetActivePricesRequest, GetActivePricesResponse>
{
    protected override async Task<GetActivePricesResponse> Process(
        GetActivePricesRequest request)
    {
        // Your pricing logic here
        // Call base implementation selectively if needed
        var context = request.RequestContext;
        var prices  = await CalculateCustomPrices(request.ItemIds, context);

        return new GetActivePricesResponse(prices);
    }
}

Override handlers only when triggers are insufficient. An override is harder to maintain across version upgrades because you own the entire implementation.

3. New request/response types

For net-new functionality — a custom loyalty operation, a proprietary fulfilment calculation — create your own request/response types and handlers. Register them in your CommerceRuntime.config and call them from your Retail Server controller.

Keeping extensions upgrade-safe

The most common mistake is coupling custom handlers tightly to internal Microsoft implementation details. When Microsoft refactors an internal service (which happens frequently between minor versions), tightly coupled extensions break.

Follow these rules:

Testing in isolation

CRT handlers are plain C# classes. Test them directly without a running Commerce environment:

[Fact]
public async Task SaveCart_WithInvalidItem_ThrowsCommerceException()
{
    var mockContext = new MockRequestContext();
    var request     = new SaveCartRequest(invalidCart, mockContext);
    var handler     = new CustomCartPreTrigger();

    await Assert.ThrowsAsync<CommerceException>(
        () => handler.OnExecuting(request)
    );
}

Unit tests catch the majority of logic errors before they reach the Store Commerce App. Keep them in a separate test project and run them in CI on every commit.

Wrapping up

CRT pipeline extensions give you precise control over D365 Commerce business logic. The pre/post trigger pattern covers most needs; handler overrides are for when you need full control; custom request types for genuinely new functionality. The discipline that pays off most is maintaining a clean separation between your extension code and Microsoft's internals.

In a follow-up post, we'll cover how to surface CRT operations through custom Retail Server controller APIs and consume them from Store Commerce.

Working on D365 Commerce?

We write from experience — if you need help with any of this in your own environment, get in touch.

Talk to us