If you came up through AX 2012, your instinct for changing standard behavior is to open the object, drop into the method, and edit the code in place. That instinct will get you nowhere in Dynamics 365 Finance and Operations, because overlayering is gone. The standard application is sealed, and you extend it from the outside. The two tools that matter are Chain of Command (CoC) for wrapping methods and event handlers for hooking into them, and most of the d365 chain of command pain people hit comes from not knowing which one to reach for and what each one quietly refuses to do.
What overlayering took away, and why that is good
In the old model you edited Microsoft's code directly. It felt powerful right up until the next cumulative update landed and you had to merge your changes against a new version of every object you had touched. Big AX builds spent real budget every upgrade just reconciling conflicts, and a missed merge meant a regression nobody noticed until month end.
The F&O model flips that. Your code lives in a separate model that sits on top of the standard packages, which stay untouched. Microsoft can ship a platform update and your extensions ride along, because nothing you wrote is tangled into their source. That is the whole point of the rewrite, and it is why an AX move is not a port but a redesign, something we covered in why an AX to F&O upgrade means redesign, not re-overlay. The tradeoff is that you no longer get to touch everything. You only get to extend what Microsoft marked as extensible, and that constraint shapes every decision below.
Chain of Command: wrap and next
Chain of Command lets you write a method with the exact same signature as a standard method, in an extension class, and your version wraps the original. Inside your method you call next yourMethod(args) to invoke the base implementation. Code before next runs first, code after next runs once the base logic has finished, and you can read or modify both the incoming parameters and the return value.
[ExtensionOf(classStr(SalesLineType))]
final class SalesLineType_MyExt_Extension
{
public boolean checkUpdate()
{
// run before the standard validation
boolean ret = next checkUpdate();
if (ret && this.salesLine().MyCustomField == '')
{
ret = checkFailed("Custom field is required before update.");
}
return ret;
}
}
The reason to use CoC is that you need to influence the flow, not just observe it. You can short circuit before next, you can adjust an argument before the base method sees it, and you can override the result on the way out. That control is the thing event handlers cannot give you.
Event handlers: hook without wrapping
An event handler subscribes to an event that fires around a method or a data operation. You get pre-events that fire before the method body, post-events that fire after it, and data events on tables such as OnInserting, OnInserted, OnUpdating, and OnValidatedWrite. Your handler is a static method that the kernel calls, and it receives an XppPrePostArgs object (for method events) or the table buffer (for data events).
class MyTableEventHandler
{
[DataEventHandler(tableStr(SalesTable), DataEventType::ValidatedWrite)]
public static void SalesTable_onValidatedWrite(Common sender, DataEventArgs e)
{
SalesTable salesTable = sender as SalesTable;
ValidateEventArgs args = e as ValidateEventArgs;
if (salesTable.MyCustomField == '')
{
args.parmValidateResult(false);
}
}
}
Handlers shine for reacting to something cleanly. A post-event on a method, or OnInserting on a table, is the right call when you want to fire side effects, stamp a field, or run extra validation without taking ownership of the method's flow. They also keep your code loosely attached, which is easy to reason about when several teams extend the same object.
Choosing between them
Reach for CoC when you need to change behavior: alter parameters before the standard logic runs, skip the base call under a condition, or rewrite the return value. Reach for an event handler when you only need to observe and react: log, validate, copy a value, or trigger a downstream process. If all you want is a post-method side effect, a handler is lighter and harder to get wrong. If you need to sit inside the call and steer it, CoC is the only option that works.
There is one hard limitation to keep in mind. An event handler cannot change the parameters the wrapped method actually uses. A pre-event handler can read the args and even flip a flag through XppPrePostArgs, but it does not control the value the base method runs with the way a CoC method does by mutating an argument before next. When the requirement is genuinely "the standard method must run with a different input," that is a CoC job, full stop.
The gotchas that actually bite
You must call next, or you silently delete the base logic. This is the number one CoC mistake. A CoC method replaces the standard method, and next is what re-invokes the original. Forget it and the base implementation never runs, with no error and no warning. The code compiles, the form opens, and three weeks later someone notices that a posting routine quietly stopped doing half its work. The exception to the rule is deliberate: you may legitimately skip next to suppress the base behavior, but that must be a conscious decision you can defend, not an oversight.
The method has to be extensible, and the rules are fussy. CoC needs the target method to be hookable. Methods marked final cannot be wrapped, private methods are off limits, and some methods are excluded from extensibility by attributes on the class. You will discover this at compile time when the extension simply will not build, and the fix is usually to find a different, extensible seam rather than to fight the one you wanted. Static methods follow their own rules too. Plan your extension point before you write the body, because the cleanest looking method is often the one Microsoft sealed.
Multiple CoC extensions chain in an order you do not fully control. When two models both wrap the same method, they form a chain, and each one's next calls into the following extension. That is by design, but it means your "after next" code may run before or after another team's depending on model load order. Keep your wrapped logic self contained and avoid assumptions about what else is in the chain.
Data events are not a transaction boundary. OnInserting and OnUpdating fire inside the write, so throwing from them rolls back the operation, which is often what you want for validation. But heavy work in a data event runs on every row and inside the transaction, so it is the wrong place for slow calls or external integration. Stamp the buffer there and push the expensive work elsewhere.
Takeaway
The F&O extension model trades the freedom to edit anything for the certainty that updates will not break your work. Use Chain of Command when you need to wrap a method and steer its flow, always call next unless you have a clear reason not to, and confirm the method is extensible before you commit to it. Use event handlers when you only need to react around a method or a data write, and accept that they cannot rewrite parameters the way CoC can. Get the choice right and your customizations survive every platform update with no merge and no drama.
If you are planning an F&O build or untangling extensions that fight the standard application, we do this every day. See how we work on /service, or tell us about your environment on /contact and we will help you pick the right seam.








