"Do I extend the existing graph or write a new one?" comes up on almost every scoping call, and it's a more consequential decision than it first looks — get it wrong and you either end up fighting an unrelated screen's existing behavior, or you duplicate a pile of logic Acumatica already gives you for free. Here's the decision framework I actually use, with the reasoning behind each branch rather than just a rule of thumb.
The question underneath the question
Every version of this decision reduces to one thing: does the requirement modify behavior on a screen/workflow that should continue existing as Acumatica shipped it, or does it need a genuinely new workflow that happens to reuse some existing data? The first is a PXGraphExtension<T>. The second is a new PXGraph, possibly reusing existing DACs and even existing graph's data views by composition, but with its own identity, its own screen, its own lifecycle.
Reach for PXGraphExtension when...
- The base screen's overall purpose is unchanged. Adding approval logic to Sales Order Entry — it's still Sales Order Entry, just with an extra rule.
- You need to react to or block existing events —
RowPersistingvalidation,FieldUpdatedcascades, additional actions alongside the base screen's existing ones. - Upgrade safety matters more than flexibility — extensions ride along with base graph improvements automatically; a duplicated graph does not inherit fixes Acumatica ships to the original.
- The client's users already know the screen and the requirement is additive, not a different workflow — no retraining, no new sitemap entry, no navigation change.
// Right call: adding a rule to an existing, still-relevant screen
public class SOOrderEntry_CreditCheck : PXGraphExtension<SOOrderEntry>
{
public static bool IsActive() => true;
protected virtual void _(Events.RowPersisting<SOOrder> e) { /* ... */ }
}
Reach for a new PXGraph when...
- The workflow is genuinely different, even if the underlying data overlaps heavily — a specialized "Bulk Price Override" screen that touches
SOOrderdata but has a completely different UI shape, action set, and use pattern than Sales Order Entry itself. - You need a distinct screen identity — its own sitemap entry, its own access rights entry, its own mobile/API surface — because the requirement is "give this specific role a purpose-built tool," not "change how everyone uses the existing screen."
- The base graph's existing behavior would actively get in the way — dozens of base event handlers firing for a workflow that doesn't need most of them, adding overhead and risk of unintended interactions for functionality you don't want.
- You're building something Acumatica has no equivalent screen for at all — the straightforward case, and usually the easiest of the four to recognize.
// Right call: a purpose-built bulk tool, not a modification
// of Sales Order Entry's own behavior
public class BulkPriceOverrideEntry : PXGraph<BulkPriceOverrideEntry>
{
public PXSelect<SOOrder,
Where<SOOrder.status, Equal<SOOrderStatus.open>>> CandidateOrders;
// its own action set, its own screen, its own sitemap entry
}
A new graph doesn't mean duplicating logic — reuse via composition or shared methods
Choosing a new graph doesn't mean re-writing validation logic the base graph already has. Acumatica graphs can instantiate and delegate to other graphs (PXGraph.CreateInstance<T>()), and shared business logic is often better factored into a plain static or instance helper class both graphs call, rather than duplicated. The new-graph decision is about screen/workflow identity, not a license to copy-paste validation rules that already exist elsewhere.
The mistake I catch most in code review isn't picking a new graph when an extension would do — it's the opposite: cramming a genuinely different workflow into an extension of an existing graph because it saves the work of setting up a new screen. The result is a screen that behaves inconsistently depending on entry point, with event handlers that only make sense for one of the two use cases silently firing for both. If you're writing if (isSpecialWorkflow) { ... } else { ... } branches inside a graph extension's event handlers, that's usually a sign the special workflow wanted to be its own graph from the start.
A quick test that resolves most ambiguous cases
Ask: "if I described this new workflow to an end user in one sentence, would they say it's the same screen with an extra button, or a different tool"? If it's "the same screen, one more thing it does," extend. If it's "a different tool that happens to touch the same data," write a new graph. This framing catches the cases that feel technically ambiguous but are actually clear once you think about it from the user's mental model rather than the code's.
Wrapping up
PXGraphExtension for modifying behavior on a screen that should keep being that screen; a new PXGraph for a genuinely distinct workflow, even one built heavily on the same underlying data. Reuse logic through composition and shared helpers regardless of which you choose — the decision is about screen and workflow identity, not a reason to duplicate validation rules. And watch for the more common real-world mistake: forcing a distinct workflow into an existing graph's extension because it's the path of least short-term effort.
Independent software engineer in Nairobi specialising in Acumatica customisations, Laravel backends, and tax fiscalisation integrations across East and Southern Africa.