Reading time: ~9 minutes.
Yesterday’s post argued that Chain of Command is usually the right tool for overriding F&O method logic. That was deliberately one-sided — you can’t teach a decision framework in one post. Today is the other side. Event handlers still have a clear, non-trivial place in every real F&O codebase, and choosing between the two is a daily call that no Microsoft Learn page walks you through.

This post takes the short decision table from yesterday and stress-tests it — showing what each tool actually does, the sharp edges of each, and a scenario-by-scenario walkthrough that closes with a practical flowchart.
If you haven’t read the CoC post yet: Chain of Command in D365 F&O
The three kinds of “event handler” — they are not the same
First, a clarification that saves hours of confusion later. When F&O developers say “event handler,” they mean one of three different things. Microsoft’s docs treat them as one family, but the decision calculus is different for each.
1. Pre/Post handlers on methods — the [PreHandlerFor] and [PostHandlerFor] attributes. These wrap a specific method the same way CoC does, and they’re the direct alternative to CoC for method-level logic overrides.
2. Data/form event handlers — [DataEventHandler] for table events (OnInserting, OnInserted, OnUpdating, and so on), and [FormEventHandler], [FormDataSourceEventHandler], [FormControlEventHandler] for their form equivalents. These are not method overrides. They are framework events — hooks that fire at specific lifecycle points, and CoC has no equivalent for most of them.
3. Delegate subscribers — [SubscribesTo]. Microsoft (and well-designed ISV code) publishes delegate declarations at designated extension points. You subscribe to them with the SubscribesTo attribute. This is the cleanest, most forward-compatible form of extension — when a delegate exists for what you need, use it.
Conflating these three is the source of most of the “I’ve heard CoC is better” confusion you’ll see in community threads. CoC only competes with category 1. For categories 2 and 3, there is no CoC alternative — handlers are the only game in town.
Pre/Post handler anatomy — the version that actually competes with CoC
A Pre or Post handler is a static method on a class, decorated with an attribute that binds it to a specific base method. The handler receives a single XppPrePostArgs parameter that carries this, the method arguments, and (for Post handlers) the return value.
public static class MyTableEventHandler
{
[PostHandlerFor(tableStr(SalesTable), tableMethodStr(SalesTable, validateField))]
public static void SalesTable_Post_validateField(XppPrePostArgs _args)
{
SalesTable salesTable = _args.getThis();
FieldId fieldId = _args.getArg("_fieldId");
boolean ret = _args.getReturnValue();
// additional validation here
_args.setReturnValue(ret);
}
}
That’s the pattern. Compared to the CoC version of the same override, note what’s happening:
- The handler is static. No instance. No access to protected members of the base class.
- Arguments are retrieved by string name — _args.getArg(“_fieldId”). If Microsoft later renames the parameter, the string still “compiles” but silently returns the wrong value (or null). This is the brittleness Microsoft warns about.
- Return value is read and written through the args object rather than by expression. Fine in Post; meaningless in Pre (you can setReturnValue in a Pre handler but it won’t influence the base method — the base method hasn’t run yet).
Microsoft’s official documentation on this page (learn.microsoft.com/…/xpp-events) contains one of the most useful sentences in the entire F&O dev reference: “Pre and Post handlers can easily break as the result of added or removed parameters, changed parameter types, or because methods are no longer called, or called under different circumstances.” That’s Microsoft telling you, on their own reference page, that these handlers are inherently more fragile than CoC. Worth reading twice.
Data event handlers — the non-negotiable use case for handlers
Table events like OnInserting, OnInserted, OnUpdating, OnDeleting, OnDeleted, OnValidatedField don’t correspond to a specific X++ method you can wrap. They are framework events raised by the kernel around data operations, many of them implemented in the runtime rather than in X++ code. You cannot CoC-wrap them. A [DataEventHandler] subscription is the only extension mechanism.
[DataEventHandler(tableStr(CustTable), DataEventType::Inserting)]
public static void CustTable_onInserting(Common _sender, DataEventArgs _e)
{
CustTable custTable = _sender as CustTable;
// enrich the record before the insert actually runs
}
Same pattern for form controls (OnClicked, OnModified) and form data sources (OnInitialized, OnActivated, OnWritten). None of these have a CoC alternative. If someone tells you “always use CoC,” they’ve never had to react to a form-load event.
Delegate subscribers — the cleanest extension point when one exists
Microsoft publishes delegate declarations at many designated extension points throughout standard F&O code — particularly in posting, validation, and pricing flows. These are explicit invitations to extend behaviour cleanly. When a delegate exists for the scenario you need, it is almost always the right answer, ahead of both CoC and Pre/Post handlers.
[SubscribesTo(
classStr(FMRentalCheckoutProcessor),
delegateStr(FMRentalCheckoutProcessor, RentalTransactionAboutToBeFinalizedEvent))]
public static void RentalFinalizedEventHandler(
FMRental rentalRecord, Struct rentalConfirmation)
{
// respond to the published event
}
Two properties make delegates the safest choice when available. First, the method signature is enforced at compile time — a signature change in the base class fails your build instead of silently breaking your handler (unlike Pre/Post handlers). Second, delegates are Microsoft’s explicit contract that this is a supported extension point. They are less likely to move or be re-implemented in future updates than a randomly-chosen internal method. Docentric’s writeup on delegates has the best community-level explanation I’ve found of how return-value-carrying delegates work using EventHandlerResult — linked below.
The capability comparison — side by side
The short table from yesterday was a decision helper. Here’s the fuller capability grid — each row is something that either tool can or can’t do:
| Capability | Chain of Command | Event Handler |
| Access to protected members of the base class | Yes | No — the classic limitation |
| Runs in the same transaction scope as the method | Yes | Different context — use carefully in ttsbegin blocks |
| Can change return value | Directly | Yes, via args.setReturnValue() in Post handler |
| Can change method parameters | Directly on the arguments to next | Indirectly, via args.getArg/setArg |
| Works on framework/data events (OnInserting, OnModified, etc.) | No | Yes — this is what event handlers are for |
| Works on Microsoft-published delegates | No | Yes, via [SubscribesTo] |
| Call order is guaranteed | No — random | No — attribute-bound, no sequence guarantee |
| Forced to invoke the base behaviour | Yes (must call next, except on [Replaceable]) | No — pre/post are strictly around the base, never instead of it |
| Brittle to base-method signature changes | Compile-time error if signature drifts | Silent break: handler stops firing or args resolve wrong |
Four real scenarios — and the correct choice for each
Scenario 1: “When a sales order is saved, push a message to an integration queue.”
Right tool: Data event handler on SalesTable with DataEventType::Inserted.
You’re reacting to an event, not overriding logic. You don’t need to change the insert behaviour, read protected fields, or influence a return value. A DataEventHandler is clearer and more maintainable than CoC on insert() or update() — and as of Platform Update 22, even the insert() CoC option is newer than the DataEventHandler pattern, which has been stable for years. ⚠ Be mindful that OnInserted fires after the insert commits; if you need pre-commit logic, use OnInserting.
Scenario 2: “Validate a custom field on vendor master; reject the save if invalid.”
Right tool: Chain of Command on VendTable.validateWrite().
You need to change the return value conditionally and you need access to the full record state including protected members. A Post handler on validateWrite would technically work via _args.setReturnValue(false), but CoC reads dramatically cleaner and is signature-stable.
Scenario 3: “Show a warning to the user when a purchase order with a specific item is opened.”
Right tool: Form data source event handler on OnInitialized (or a form control event handler depending on exact trigger).
This is a framework event. There is no corresponding X++ method to wrap. CoC doesn’t apply. If you find yourself trying to CoC a form method here, step back — the form-nested CoC support in F&O covers data source and control methods but not every framework event.
Scenario 4: “Add custom posting logic during sales invoice posting.”
Right tool: check for a Microsoft-published delegate first. If found, use [SubscribesTo]. If none exists, CoC on the specific posting method.
Posting flows are one area where Microsoft has published many delegates specifically to avoid people monkey-patching core posting logic. Spend ten minutes looking for one before you CoC a posting method — the delegate path is dramatically safer for upgrades.
The decision flowchart, in prose
A quick mental sequence for picking between CoC, Pre/Post handlers, and data/framework event handlers. Ask these questions in order and stop at the first “yes”.
- Is there a Microsoft-published delegate for this scenario? → Use [SubscribesTo].
- Is the thing I want to react to a framework/data event (OnInserting, OnClicked, etc.), not a method body? → Use the appropriate event handler ([DataEventHandler], [FormControlEventHandler], etc.).
- Do I need to override the logic of a specific public or protected method? → Default to CoC.
- Is that method marked [Hookable(false)] or [Wrappable(false)] without a Wrappable(true) escape? → Fall back to a Pre or Post handler if the method is still subscribable, or redesign.
- Do I need access to protected fields or methods of the base class? → CoC is the only option that gives you this cleanly.
- Am I doing simple before-or-after logic that doesn’t need protected access, parameter modification, or transaction-scope-matching? → Either works; prefer Pre/Post only if you have a reason not to prefer CoC.

3 tricky interview questions (with verified answers)
As with yesterday’s post, each answer is grounded in named, verifiable sources (linked in the References section).
Q1. A Pre handler and a CoC extension both wrap the same method. Which one runs first?
Trick answer. Microsoft’s documentation is explicit that neither subscribers nor CoC extensions have a guaranteed sequence. The CoC docs say the system “randomly picks” among wrapped methods, and the events reference page states: “Since the binding between the publisher and subscribers is done through attributes, there’s no way of specifying the sequence in which subscribers are called.” Sertan Yaman’s empirical test, running mixed CoC + event handlers in a real environment, confirms the ordering is non-deterministic across both mechanisms and can vary between environments. Design for order-independence — assuming “pre handler runs first” is a bug waiting to happen.
Q2. How do you pass state between a Pre handler and a Post handler on the same method?
Both handlers are static methods on a handler class. Naïve option: a static field on that class — works for single-threaded cases but is fragile under concurrent calls. The cleaner, documented community technique (Ievgen’s AX blog, hosted on community.dynamics.com) uses XppPrePostArgs itself as the carrier: _args.addArg(‘myFlag’, value) in the Pre handler, _args.getArg(‘myFlag’) in the Post handler. The args collection is per-invocation, so state travels with the call rather than sitting in a shared field. ⚠ This relies on undocumented-but-stable behaviour of XppPrePostArgs; if this is critical path code, wrap it in a comment explaining what you’re doing and why.
Q3. Can you subscribe a Pre/Post event handler to a protected or private method on a standard class?
Directly, no. By default, only public methods are subscribable. Sertan Yaman documents a legacy workaround: overlayer the standard object and add a [Hookable(true)] attribute on the protected method. This is a relic of the overlayering era and is not recommended for modern F&O — overlayering on Microsoft packages is unsupported for most of the application, and even where technically possible it compromises upgrade safety. The correct modern answers are: (a) if you have access to protected members via CoC, use CoC — it sees protected members natively, which is one of its core advantages over Pre/Post; or (b) redesign to depend on a public method or a published delegate instead. If an interview candidate answers “just overlayer and add Hookable(true),” that’s a signal they learned F&O in the AX 2012 era and haven’t updated their mental model.
What to actually do
- Don’t treat “CoC vs event handlers” as one question. It’s three: (a) method override — CoC vs Pre/Post, (b) framework event — use the appropriate data/form event handler, (c) published extension point — use SubscribesTo. The wrong grouping is most of the confusion.
- Default order of preference for reaction: SubscribesTo (if a delegate exists) → data/framework event handler (if the trigger is an event) → CoC (if you’re overriding method logic) → Pre/Post (only when CoC is blocked or unavailable).
- Never rely on call order — between handlers, between CoC extensions, or between the two. Microsoft’s docs say it’s random; community tests confirm it; your bug reports will too if you ignore this.
- Take Microsoft’s own warning seriously: Pre/Post handlers are more fragile than CoC. Prefer CoC for method overrides when both are feasible.
References
- Microsoft Learn — Events and delegates (learn.microsoft.com/en-us/dynamics365/fin-ops-core/dev-itpro/dev-ref/xpp-events). Authoritative. Covers delegate syntax, SubscribesTo attribute, Pre/Post handler pattern, and the explicit fragility warning. Last updated February 2025.
- Microsoft Learn — Class extension: Method wrapping and Chain of Command (learn.microsoft.com/en-us/dynamics365/fin-ops-core/dev-itpro/extensibility/method-wrapping-coc). Covers CoC syntax and the Wrappable/Hookable attribute semantics.
- Sertan Yaman — D365 FO Extensibility Part 3: Event handlers and delegates (devblog.sertanyaman.com/2017/05/01/ax7-extensibility-part-3-event-handlers-and-delegates-hooks/). The PreHandlerFor / PostHandlerFor example code on InventTable, and the overlayer + [Hookable(true)] workaround for protected methods referenced in Q3.
- Docentric — Delegates and Event Handlers in D365FO (ax.docentric.com/delegates-and-event-handlers-in-d365fo/). Best community-level explanation of delegates, return-carrying EventHandlerResult pattern, and when to prefer SubscribesTo over other extension mechanisms. Updated February 2026.
- Ievgen’s AX blog — Trick to pass a value between Pre and Post event handler using XppPrePostArgs (community.dynamics.com). Source for the Q2 answer on carrying state between handlers via the args object.
- Mastering D365FO — Event Handlers in D365FO with Practical Examples (masteringd365fo.blogspot.com, April 2025). Practical Table, Form, and DataSource event handler patterns with current code samples.
Next up in this series: The 5 X++ Extension Pitfalls I See on Almost Every Project. Shorter post, opinion voice, list format.
