Extending D365 Without Changing Standard Code: Extensions and Chain of Command

What is the hardest part of an ERP upgrade? Often it is not learning what the new version can do. It is making sure the customer-specific features you have built up over the years still work once the update is in.
If you have developed on Microsoft Dynamics AX 2012, you will know the Compare and merge work that came with every upgrade. When Microsoft had changed a standard object that your team had also modified, someone had to look at the differences between the two versions and decide which changes to keep. The more standard objects a project had modified, the more there was to check.
Microsoft Dynamics 365 Finance & Operations (D365 F&O) changed this way of working. Instead of editing standard source code, you build what the business needs as separate extensions.
This post looks at how development changed between AX 2012 and D365 F&O, how extensions and Chain of Command (CoC) work, and the rules JGS follows when we build on D365.
Why AX 2012 upgrades were hard
AX 2012 organized application objects into 16 layers. Customizations usually lived in the CUS layer, and an object definition changed in a higher layer took precedence over the definition below it. This is called overlayering.
Overlayering made it easy to bend standard functionality to fit a customer's business. At upgrade time, though, it could become a problem. Say Microsoft changed the standard customer setup, and the customer had modified the same object. The new standard changes then had to be compared with the existing customization, and sometimes the code had to be reworked to keep the customer's feature while taking in the new standard behavior.
The more standard objects a project had changed, the harder this became. The effort to build a customization and the effort to keep it working through updates turned out to be two separate costs.
D365 F&O keeps standard and extension code apart
D365 F&O manages development elements in packages and models, and supports adding the changes you need to standard functionality as extensions. It is a different structure from layer-based overlayering in AX 2012: rather than changing a standard object itself, developers add new elements or behavior through supported extension points. The main options are:
- Table extensions add fields, relations and other elements without editing the standard table definition.
- Form extensions add controls and other elements to an existing page.
- Enum extensions add values to enums that are set up to allow extension.
- Event handlers subscribe to events that standard code raises and run the business logic you need.
- Chain of Command (CoC) wraps a standard method that allows extension, so you can add logic before and after the existing processing.

"Extension" covers the whole approach; CoC is the specific way to extend what a method does. Developers need to weigh the requirement against what standard allows to be extended, and pick the right tool.
How Chain of Command works
CoC adds processing to the execution flow of an existing method without copying or editing its implementation. In a normal CoC method, the wrapper must call next. That call passes execution on to the next implementation in the chain and finally to the base implementation.
There are some rules:
- The method you wrap must, as a rule, be
publicorprotected. - Methods marked
[Hookable(false)]or[Wrappable(false)]cannot be wrapped. finalmethods generally cannot be wrapped either, unless they are explicitly marked[Wrappable(true)].- The
nextcall generally cannot sit inside a conditional or a loop. The exception is a method marked[Replaceable], where the call to the base implementation can be skipped conditionally.
So at JGS, technical design does not stop at the method name. Our rule is to check the access level, the extensibility attributes, the method signature and the existing logic before deciding how to extend.
Here is an example of the structure of a CoC extension on the save validation method of the customer table, CustTable:
[ExtensionOf(tableStr(CustTable))]
final class CustTableJGSAgile_Extension
{
public boolean validateWrite()
{
boolean ret = next validateWrite();
if (ret)
{
// Standard validation passed:
// run the additional checks from the design
// and set ret to false if one fails
}
return ret;
}
}
This code only illustrates the shape of a CoC method. It is not a finished business rule. In real development you confirm that the target method can be extended, implement rules such as approval status or edit rights separately, and test that the check runs on every path where the data can change.
JGS decides the extension approach in design
Keeping standard code untouched is not something you can enforce only during development. If a design assumes changes to standard source, the developer has to go back and rework the design.
We first check whether standard features and setup alone can meet the requirement. Where something more is needed, the design states the target object, the extension type and why it was chosen.

In design review, any item that relies on overlayering or direct changes to standard source is treated as a blocker. This is an internal JGS development rule.
Object names are decided in design as well. New objects and fields get an identifying prefix to avoid clashes, and extensions of standard objects and CoC classes carry the model or solution identifier. For example, extending the customer table in the JGS Agile Package model could look like this:
- Table extension:
CustTable.JGSAgileExtension - CoC class:
CustTableJGSAgile_Extension
Customer-specific models follow that customer's naming standard and solution identifier.
Another rule is that nothing gets built that is not in the design. If development shows that a new field or method is needed, we check whether the design needs to change before adding code. That makes it easy to trace which requirement each object exists for.
Before development starts, we confirm the actual signature and access conditions of the standard methods involved and that the target objects exist. Developers write code against the approved design, and reviewers check that every changed file is within the design's scope and that no standard file has been edited directly.
Example: locking customer records pending approval
Let's take a concrete case. The finance team asks: "Regular users shouldn't be able to edit customer records that are waiting for approval."
Before building anything, you need to settle when a record is locked, who can still edit it, and how the approval itself is allowed to go through. Following the JGS process, the work goes like this.
1. Check standard. First see whether the approval workflows and security settings in D365 can meet the requirement. Only if standard setup cannot do it do we look at additional development.
2. Design the extension. Decide whether a separate approval status field is needed or whether existing status information can be used. If a field has to be added through a table extension, record the reason in the design, and choose a supported extension point, such as CoC or an event, for the extra validation.
3. Close open items before development. Confirming that validateWrite() can be extended is not enough. You also have to work out how the rule should behave not only on the normal page but in the Excel add-in, data entities, batch jobs and other X++ paths. And because approvers still need to change the approval status, you need a rule for that exception.
4. Build to the approved design. If CoC was chosen, keep the standard validation flow and add the extra check. Error messages go into labels so they can be translated and maintained.
5. Check the result and the test scope. Review whether standard source was changed and whether the objects match the list in the design. Then verify that the lock on records pending approval works as intended on the normal page and on the data integration paths.

The point of this example is not adding an if to a save validation method. It is designing clearly which work to restrict and which to allow, while keeping the standard processing intact.
The customer approval lock above is a hypothetical scenario for illustration. It does not describe a feature JGS has delivered or finished testing.
Do extensions make upgrades easy?
Keeping standard and custom code apart means less comparing and merging at upgrade time than when standard objects are edited directly. But extensions do not make the impact of updates disappear. If Microsoft changes the signature or behavior of a method you extend, your extension code can be affected. After an update, you still need to check that extensions are compatible and, where needed, run builds and regression tests.
JGS builds this maintenance view into its development rules. Our Korean VAT filing package, JGSKVAT, was developed entirely as extensions, without changing standard source. The JGS Agile Package we are building in house follows the same principle. As of October 16, 2026, the planned development work for the JGS Agile Package is complete and the features are in testing and verification. That does not mean final test sign-off has been given.
Keeping to standard is preparing for the next update
An ERP system keeps changing after go-live. The business changes, new features are added, and Microsoft keeps updating the standard product. If existing customizations mean large parts have to be rebuilt each time, the cost is not only the initial implementation but years of maintenance.
JGS uses extensions and CoC not just to add features, but to build something that can be maintained alongside standard functionality over time. If you are moving AX 2012 customizations to D365 F&O, or planning further development on D365 F&O, it pays to separate standard and extension work from the design stage.
For questions about D365 F&O development and enhancement projects, get in touch: https://jgs.ai.kr/support/contact
JGS Consulting Corp. — Designing the future of business.
한국어버전은 네이버 블로그에서 보실 수 있습니다: https://blog.naver.com/jgsconsulting
Comments
Post a Comment