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:
- Validate inputs before processing
- Enrich a response with additional data
- Audit or log operations without altering the result
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:
- Depend on interfaces, not concrete types — use
IRequestHandler, not the specific Microsoft handler class - Never reflection-hack into internal services — if you need internal data, raise a support request for an official API
- Register handlers in extension assemblies only — never modify
Microsoft.Dynamics.*config files directly - Validate your handler registration — missing a
commerceRuntimeconfig entry is a common cause of silent failures
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.