Acumatica · Customization

Acumatica Event Handler Order Precedence — The Definitive Guide

"The definitive guide" is a strong title to put on an event-ordering post, so let me be precise about what I actually mean: not just the order the five or six main cache events.

John Kihiu12 min read

"The definitive guide" is a strong title to put on an event-ordering post, so let me be precise about what I actually mean: not just the order the five or six main cache events fire in (that's well covered elsewhere), but the finer-grained precedence question that actually causes bugs on real instances - when a DAC has multiple attributes and multiple extensions all subscribing to the same event on the same field, what fires first, and how much of that can you actually rely on.

Within one field: declaration order, top to bottom, no exceptions

For attributes stacked on a single property, execution order for a shared event (say, two attributes both implementing IPXFieldVerifyingSubscriber) follows declaration order exactly as written in the source, top to bottom. This is deterministic and safe to rely on, unlike most of the other orderings in this post:

C#
[PXDefault(TypeCode.Decimal, "0.0")]   // runs first
[PXPercentRange(0, 100)]                // runs second, sees the default already applied
[PXUIField(DisplayName = "Discount %")] // display-only, order irrelevant here
public decimal? UsrDiscountPct { get; set; }

Multiple graph extensions on the same event: registration order, not source order

This is where it gets less predictable. When two separate PXGraphExtension classes (from two different customization projects, possibly two different vendors) both declare a RowPersisting<SOOrder> handler, the order they fire in follows the order the extensions were registered with the graph at runtime - which in practice tracks customization project publish order on that specific instance, not anything visible in either extension's source file. Republishing customizations in a different order, or moving a project to a fresh instance where publish history differs, can change this order. I've seen this bite exactly once in a way that mattered: a client's staging instance had projects published in a different sequence than production, and a rarely-hit edge case behaved differently between the two purely because of handler order, with no code difference at all.

Never write logic that depends on which extension's handler runs first

If handler ordering between two independent extensions would change your logic's correctness, that's a design smell, not a configuration problem to solve by carefully sequencing publishes. Either consolidate the coupled logic into a single extension so ordering is explicit in one file, or make each handler's logic self-contained by computing what it needs directly rather than assuming a sibling handler already set something up.

Extension handlers vs the base graph's own handler on the same event

When the base graph itself already has a handler for an event (common - Acumatica's shipped graphs are full of RowPersisting and FieldUpdated logic) and your extension adds another handler for the same event on the same DAC, both run; your extension doesn't replace the base handler the way a method override would. Extension event handlers are additive by design, layering on top of base behavior rather than substituting for it. If you genuinely need to suppress or change base handler behavior rather than add alongside it, that's a job for [PXOverride] against the specific method, not another event subscriber hoping to run "instead of" the base logic.

Attribute-level events fire before graph-level handlers for the same field

For a given field, attribute-declared event logic (FieldVerifying implemented by a PXEventSubscriberAttribute on the property) runs before graph-level event handlers (the protected virtual void _(Events.FieldVerifying<T> e) methods you write in a graph or graph extension) for that same field and event. This ordering is consistent and matters when a graph-level handler wants to react to or override what a field's own attribute already decided - by the time your graph-level FieldVerifying handler runs, the attribute's own validation has already had its say:

C#
// The PXPercentRange attribute on UsrDiscountPct's FieldVerifying runs first.
// This graph-level handler runs second, and can add additional
// context-specific checks on top of the attribute's generic range check.
protected virtual void _(Events.FieldVerifying<SOOrderExt.usrDiscountPct> e)
{
    var row = (SOOrder)e.Row;
    if (row.CustomerID == SpecialCustomerId && (decimal)e.NewValue > 5m)
        throw new PXSetPropertyException("This customer is capped at 5% discount.");
}

The rule of thumb I actually use

Anything deterministic and visible in one file (attribute stacking order on a field, attribute-before-graph-handler for the same field) is safe to rely on. Anything that depends on cross-extension or cross-customization-project ordering is not, regardless of how consistently it's behaved so far on one instance - treat consistent-so-far as coincidence, not guarantee, because publish order is an operational detail, not a contract.

Wrapping up

Declaration order on a single field's stacked attributes is deterministic and safe to design around. Attribute-level event logic runs before graph-level handlers for the same field, also reliable. Ordering between independent graph extensions on the same event is publish-order-dependent and not something correctness should ever hinge on - if two extensions need to cooperate, make the dependency explicit through shared, independently-computed state, not assumed sequencing.

John Kihiu
Acumatica ERP Developer · Laravel Engineer

Independent software engineer in Nairobi specialising in Acumatica customisations, Laravel backends, and tax fiscalisation integrations across East and Southern Africa.