Acumatica Cash Management — Extension Patterns is one of those Acumatica customisations that every team eventually needs and almost no team does well the first time. The Acumatica module ships with a complete set of fields, but every real business has a field the framework does not provide — a reference number, a department code, a tenant-specific workflow flag. Adding that field cleanly is what this guide is about.
To add a field, a UDF, or a separate DAC
Before you write any code, decide which of the three patterns fits. The wrong choice costs you at every upgrade; the right choice disappears into the platform.
| Pattern | When to use | Upgrade cost |
|---|---|---|
| User-Defined Field (UDF) | End users should be able to add the field without a developer | Low — UDFs survive upgrades |
| DAC extension (Usr field on existing DAC) | Code needs to read/write the field programmatically | Low — extensions are the standard pattern |
| Separate DAC with a relationship | The field has its own workflow, validations, or sub-records | Medium — a new DAC means a new table and a new graph |
If you find yourself wanting to add more than three fields to the same screen at once, stop. A separate DAC with a relationship is almost always the right move. Fields on a DAC should be the obvious ones the screen needs; complex sub-entities belong in their own table.
The DAC extension pattern
For the 80% case — adding a custom field to a built-in screen — the DAC extension is the right tool. The pattern has three moving parts: the extension class, the graph extension to expose the field, and the screen configuration to show it.
[PXTable(IsOptional = true)]
public class MyEntityExt : PXCacheExtension<MyEntity>
{
#region UsrExternalRef
[PXDBString(40)]
[PXUIField(DisplayName = "External Ref")]
public virtual string UsrExternalRef { get; set; }
public abstract class usrExternalRef :
PX.Data.BQL.BqlString.Field<usrExternalRef> { }
#endregion
#region UsrCustomCategory
[PXDBString(20)]
[PXUIField(DisplayName = "Custom Category")]
[PXStringList(new[] { "STANDARD", "PRIORITY", "INTERNAL" },
new[] { "Standard", "Priority", "Internal" })]
public virtual string UsrCustomCategory { get; set; }
public abstract class usrCustomCategory :
PX.Data.BQL.BqlString.Field<usrCustomCategory> { }
#endregion
}
The pattern above has three Acumatica-specific things that are easy to get wrong:
- The
Usrprefix. Every custom field on a built-in DAC must start withUsr. Acumatica uses this prefix to separate your fields from base fields, and the upgrade tool relies on it to know what to preserve. - The
PXTable(IsOptional = true)attribute. This tells the framework the extension is a "soft" extension — if the database table does not have the column, no error. Without this, an upgrade that drops a table would crash the customisation. - The abstract class for BQL. The dual-field pattern (property + abstract class) is how Acumatica queries work. The property holds the value at runtime; the abstract class is the type token the BQL compiler uses. Skipping the abstract class means you cannot query the field in BQL.
If you add a field and the screen lets you type a value but the value disappears on save, the column probably does not exist in the database. The most common cause is forgetting to publish the database script along with the customization. Always publish both as a pair.
Wiring the field to the screen
For the Modern UI, the screen configuration model picks up your new field automatically once the customization is published. For the classic UI, you need to add the field to the ASPX layout explicitly. Either way, the work is the same — declare the field, declare its container (a tab, a section, a grid), and let the framework render it.
<px:PXFormView ID="form" runat="server" DataSourceID="ds" DataMember="MyEntity">
<Template>
<px:PXLayoutRule runat="server" StartColumn="True" />
<px:PXTextEdit runat="server" ID="edUsrExternalRef" DataField="UsrExternalRef" />
<px:PXDropDown runat="server" ID="edUsrCustomCategory" DataField="UsrCustomCategory" />
</Template>
</px:PXFormView>
For the Modern UI, the equivalent is a screen configuration entry in the Screen Configuration screen. The drag-and-drop editor lets you place the field on any tab; the resulting JSON is committed to the customisation package.
Reading and writing the field
Once the field is on the screen, you need to read it (for filtering, reporting) and write it (for automation). The pattern for both is the same: get the cache, cast to the extension, access the field.
public class PXGraphExt : PXGraphExtension<PXGraph>
{
protected void _(Events.RowPersisting<MyEntity> e)
{
var row = e.Row;
if (row == null) return;
var ext = row.GetExtension<MyEntityExt>();
if (ext?.UsrExternalRef == null)
ext.UsrExternalRef = $"REF-{DateTime.UtcNow:yyyyMMdd}-{Guid.NewGuid().ToString()[..8]}";
}
}
For a deeper treatment of BQL syntax and the dual-field pattern, see the DAC fundamentals article and the cache attach pitfalls guide.
Migrating data into the new field
Adding the field is half the work. The other half is getting existing data into it. Three options, in order of complexity:
- Import Scenario. Define a mapping from a CSV (or another table) to the new field. Run it once. The audit trail tells you what was set.
- Graph event handler. Set the field in
RowPersistingbased on a lookup against another table. The field gets populated as records are touched. - SQL update. A direct
UPDATEstatement. Fast, but no audit trail and no upgrade safety. Use only when you have full control of the database.
UPDATE ar
SET ar.UsrExternalRef = src.ExternalRefNbr
FROM dbo.MyEntity ar
INNER JOIN dbo.SomeSourceTable src ON ar.NoteID = src.NoteID
WHERE ar.UsrExternalRef IS NULL;
SELECT COUNT(*) FROM dbo.MyEntity WHERE UsrExternalRef IS NOT NULL;
For production deployments, prefer the Import Scenario. The audit trail alone is worth the slight extra effort. See the data migration guide for the full pattern.
Upgrade safety
Customisations break on Acumatica upgrades most often because the field they added depended on a base DAC field that was renamed, removed, or had its type changed. The defensive habits:
- Never modify a base DAC. Always extend.
- Use the
Usrprefix religiously. - Wrap your screen in a customisation project so it ships with versioned metadata.
- Maintain a regression suite that touches every record type that has custom fields.
The most common upgrade failure is "the field worked in test but the upgrade failed in production because the data shape was different". Test your customisation on a copy of the production database, not on a sample. The cost of the staging tenant is a fraction of the cost of a production failure.
For the full upgrade playbook, see the upgrade checklist and the customisation upgrade survival guide.
Wrapping up
Adding a custom field to the Acumatica Cash Management is not a 5-minute job, but it is also not a 5-day job if you follow the right pattern. The DAC extension, the screen configuration, the graph event handler, the data migration, and the upgrade test are the five steps. Do them in order; do them with attention; the result is a customisation that survives the next three Acumatica releases without intervention.
If your team is adding custom fields at scale (more than 20 across multiple modules), consider a naming convention document and a shared snippet library. Both pay for themselves within a quarter.