CacheAttached is the graph event that lets you replace the attribute list on a DAC field for the scope of one graph. It is enormously useful — and enormously easy to get wrong, because it silently replaces the field's attributes rather than adding to them. The most common production bug I see traced back to cache attach is a field that suddenly stopped defaulting, stopped validating, or lost its selector, because someone redeclared it and dropped an attribute they did not know was there.
What CacheAttached actually does
When you write a method named <DACField>_CacheAttached in a graph or graph extension and decorate it with attributes, Acumatica uses that method's attributes as the field's attribute set for this graph only. It does not merge with the attributes declared on the DAC — it overrides them. So if the DAC field carries PXDBString, PXDefault, PXUIField, and a PXSelector, and your CacheAttached declares only PXDBString and PXUIField, the selector and the default are gone on this screen. The field still works, which is exactly why the bug ships: it fails quietly.
public class SOOrderEntryExt : PXGraphExtension<SOOrderEntry>
{
// Redeclare the FULL attribute set, then change the one thing you want.
[PXDBString(15, IsUnicode = true)]
[PXDefault]
[PXUIField(DisplayName = "Customer Order Nbr.")]
[PXSelector(typeof(Search<SOOrder.customerOrderNbr>))] // keep what was there
protected virtual void SOOrder_CustomerOrderNbr_CacheAttached(PXCache sender) { }
}
The empty method body is normal — the work is entirely in the attributes. The rule is simple to state and easy to forget: copy the field's existing attributes verbatim, then modify, because you are rebuilding the whole set from scratch.
When you want to keep the base attributes
If your only goal is to tweak one property and you do not want to copy the whole list, use [PXMergeAttributes] with a merge method. It tells the framework to combine your declared attributes with the ones already on the field instead of replacing them. This is the safer default when you are adding behaviour rather than fundamentally redefining the field:
[PXMergeAttributes(Method = MergeMethod.Merge)]
[PXDefault(typeof(Search<Location.cCarrierID,
Where<Location.bAccountID, Equal<Current<SOOrder.customerID>>>>),
PersistingCheck = PXPersistingCheck.Nothing)]
protected virtual void SOOrder_ShipVia_CacheAttached(PXCache sender) { }
MergeMethod.Merge keeps the base attributes and overlays yours; MergeMethod.Replace reproduces the default override behaviour explicitly. Choosing Merge for additive changes is how you avoid the whole "I dropped the selector" class of bug.
The invalidation pitfall across extensions
Here is where it gets genuinely subtle. Multiple graph extensions can each declare a CacheAttached for the same field. Without PXMergeAttributes, the last extension to load wins and the others' attribute changes are discarded — and extension load order is not something you control tightly across a customization project. Two teams each add a CacheAttached to INTran.inventoryID, both work in isolation, and after both projects are published one silently overwrites the other. Using merge on both is what makes them compose instead of clobber.
The symptom is always downstream: a required field stops being required, a selector goes blank, a default no longer fills in, a formatted field loses its mask — but only on one screen. When a field behaves differently on one graph than everywhere else, the first thing to check is whether a CacheAttached for that field redeclared a partial attribute set.
It is a different "cache" than data caching
The name trips people up. CacheAttached has nothing to do with caching data for performance and nothing to do with cache invalidation in the CDN sense. It refers to the PXCache being attached to the graph — the moment the framework builds the in-memory cache object for a DAC in a given graph, it collects the field attributes, and CacheAttached is your hook to influence that collection. If you go in expecting a data-cache invalidation feature, the behaviour will keep surprising you; think of it as "override the field's attribute list for this graph."
When to reach for it
Use CacheAttached when a stock field needs different behaviour on your screen than it has on the DAC everywhere else — a different default, a wider selector, a display name, making an optional field required in one workflow. Prefer PXMergeAttributes with Merge unless you truly need to strip base attributes. And when you do a full replace, keep a comment listing which attributes you copied from the base DAC, so the next developer knows the list is intentional and not accidentally short.
Wrapping up
Cache attach replaces a field's attribute set for one graph, and every pitfall around it comes from forgetting that "replace" is not "add". Copy the full attribute list before you modify it, use PXMergeAttributes for additive changes so extensions compose, and remember the feature has nothing to do with data-cache invalidation despite the name. If you are chasing a field that misbehaves on exactly one screen, this is the first place to look — and if it has you stumped, reach out or keep reading through the rest of the Acumatica blog.
Independent software engineer in Nairobi specialising in Acumatica customisations, Laravel backends, and tax fiscalisation integrations across East and Southern Africa.