Acumatica · Customization

Acumatica Customization — The Definitive Guide

The full picture of an Acumatica customization project: what it contains, how it is structured, how it survives upgrades, and the decisions that determine whether it scales to 50 screens or dies at 5.

John Kihiu12 min read

I've been customizing Acumatica for years now — mostly for distributors and manufacturers in East Africa who need the platform bent around local tax rules, approval chains, and integrations that Acumatica's out-of-the-box modules were never going to cover. This guide is the overview I wish someone had handed me at the start: what the customization surface actually looks like, which layer to reach for when, and the habits that keep a customized instance upgradeable instead of turning it into a liability.

Everything in Acumatica customization sits on one idea: you never modify the base product. You extend it. The framework gives you extension points at every layer — data, business logic, and UI — and a packaging mechanism (the customization project) that layers your changes on top of the standard code at runtime. Respect that model and upgrades are boring. Fight it and every minor release becomes a rescue mission.

The three layers you extend

Acumatica screens are built from three things, and each has a matching extension mechanism:

A useful mental model: the DAC says what the data is, the graph says what you're allowed to do with it, and the screen says how it's presented. Most real customizations touch all three — a new field needs a DAC extension, business logic around it needs a graph extension, and the field needs to be placed on the form.

Adding fields: DAC extensions

Say a client needs a "KRA PIN" (Kenyan tax ID) on the Customer record. You don't touch Customer.cs. You create an extension:

C#
public sealed class CustomerExt : PXCacheExtension<Customer>
{
    public static bool IsActive() => true;

    [PXDBString(11, IsUnicode = true)]
    [PXUIField(DisplayName = "KRA PIN")]
    public string UsrKRAPin { get; set; }
    public abstract class usrKRAPin : PX.Data.BQL.BqlString.Field<usrKRAPin> { }
}

Two conventions matter here. Custom fields are prefixed Usr — that's not just style, it's how Acumatica distinguishes your columns from theirs during upgrades, and the customization project's database script mechanism relies on it. And the IsActive() method lets the framework skip your extension entirely when a feature switch is off, which matters for performance and for multi-tenant instances where not every tenant wants your feature.

Behavior: graph extensions and events

Business logic lives in event handlers. Want to default the credit terms when a customer class changes? That's a FieldUpdated handler in a graph extension on CustomerMaint. Want to block saving an order missing your custom field? That's RowPersisting, where you can throw PXRowPersistingException or set an error on the field with PXUIFieldAttribute.SetError. The event model is the heart of the framework — FieldDefaulting, FieldVerifying, FieldUpdated, RowSelected, RowInserting, RowPersisting, RowPersisted — and knowing which one fires when is most of the job.

The classic beginner mistake

Putting expensive logic (BQL selects, external calls) in RowSelected. It fires constantly — on every refresh, every field change, every grid paint. RowSelected is for UI state: enabling fields, showing warnings. Anything heavier belongs in a more specific event.

Customization projects: the packaging layer

The Customization Projects screen (SM204505) is where everything gets bundled: DAC extensions, graph extensions, screen changes, new tables, site map entries, GIs, reports, even data records. A few things I've learned to do on every project:

Before you write code: GIs, workflows, and the low-code surface

A surprising fraction of "customization" requests need no C# at all. Generic Inquiries handle most reporting and list-screen needs. The workflow engine (the modern, state-machine-based one on the Workflow tab of a customization project) handles document statuses, conditional field states, and action visibility declaratively. Business Events plus import scenarios cover a lot of automation. My rule on client projects: exhaust the no-code tools first, because everything you build there survives upgrades with essentially zero maintenance. Code is for when the platform genuinely has no hook — and with each release, that surface shrinks.

Staying upgrade-safe

The customizations that survive version upgrades share the same traits: they use extensions rather than replacing base objects, they call base.Method() (or the delegate) inside overrides instead of swallowing base behavior, they avoid copying framework source into the project, and they don't depend on undocumented internals. Before every upgrade I republish all projects against the new version in a staging tenant and read the validation warnings — the publish-time validator catches a good share of breakage before users ever see it.

Wrapping up

Acumatica's customization model rewards discipline: DAC extensions for data, graph extensions for behavior, screen/workflow tools for presentation and state, all packaged in customization projects that layer cleanly over the base product. Start with the no-code surface, move to extensions only when you must, keep your C# in a real solution under source control, and treat "will this survive the next upgrade?" as a design constraint from day one — not a question you ask afterwards.

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.