Acumatica · Modernui

Acumatica Modern UI Form Validation — A Complete Guide

Acumatica Modern UI Form Validation — A Complete Guide is one of those Acumatica topics that is both obvious and subtle.

John Kihiu12 min read

A question I get from developers moving from classic ASPX screens to Modern UI: "where does validation actually live now — server-side events like before, or client-side TypeScript?" The honest answer is both, deliberately, and understanding which layer owns which kind of validation is the difference between a Modern UI screen that feels responsive and one that either lags on every keystroke or lets bad data through until save.

The server-side event pipeline hasn't gone anywhere

This is the point that surprises developers most: Modern UI does not replace FieldVerifying and RowPersisting validation in the graph. The exact same event pipeline that drives classic screens drives Modern UI screens, because Modern UI is a new presentation layer over the same PXGraph/DAC/cache foundation — not a parallel business-logic stack. Your server-side validation is still the authoritative, ultimately-enforced layer regardless of what UI renders it:

C#
// Unchanged from classic UI — this still fires and still governs
// whether a Modern UI screen can save the record
protected virtual void _(Events.FieldVerifying<SODeliveryConfirm.signatureData> e)
{
    if (e.Row == null) return;
    if (e.NewValue == null && ((SODeliveryConfirm)e.Row).RequiresSignature == true)
        throw new PXSetPropertyException("A signature is required before this delivery can be confirmed.");
}

Client-side TypeScript validation is for responsiveness, not authority

What Modern UI adds is a client-side validation layer, expressed declaratively on the screen's field definitions or imperatively in the TypeScript screen class, that gives instant feedback without a server round trip:

TYPESCRIPT
@graphInfo({ graphType: "MyNamespace.Graphs.DeliveryConfirmEntry", primaryView: "Document" })
export class DeliveryConfirmScreen extends PXScreen {
  Document = createSingle(DeliveryDocument);
}

class DeliveryDocument extends PXView {
  @linkCommand("changeRecipient")
  RecipientName = createField<PXFieldState<string>>({
    required: true,
    validator: (val: string) => val && val.trim().length > 1
      ? undefined
      : "Recipient name is required",
  });
}

This client validation runs on blur or on-change, before any server request, and gives a user immediate red-underline feedback on an empty required field — a real UX improvement over waiting for a save round trip to discover a mistake. But it is a convenience layer, not a security or integrity boundary: a request crafted directly against the REST/contract API, bypassing the browser entirely, hits the server-side event pipeline with zero awareness that client validation exists. If a rule matters for data integrity, it has to exist server-side regardless of whether you've also implemented a client-side mirror for responsiveness.

Duplicating a rule in TypeScript and C# means maintaining it twice — decide deliberately which rules earn that cost

Not every server-side validation rule needs a client-side mirror. Simple required-field and format checks are worth duplicating for the responsiveness win. Complex business rules — cross-field logic depending on several related records, anything requiring a database lookup to evaluate — are usually not worth reimplementing client-side; let those round-trip to the server on blur or on save, and accept the slightly less snappy feedback in exchange for one source of truth instead of two that can drift out of sync.

A third option: server-validated fields without a full save

Between pure client-side and full-save round trips, Modern UI screens can trigger a server round trip on a specific field's blur or change — invoking the graph's existing FieldVerifying/FieldUpdated logic without committing the whole record — which is the right choice for validation that genuinely needs server data (checking a discount code against a live table, verifying a quantity against current stock) but shouldn't wait for an explicit save button:

TYPESCRIPT
DiscountCode = createField<PXFieldState<string>>({
  commitChanges: true, // triggers a server round trip on this field's commit,
                        // running the graph's FieldVerifying for DiscountCode
                        // without requiring a full-screen save
});

This mirrors the classic UI's CommitChanges="True" flag on an ASPX field editor — same underlying mechanism, same server-side event pipeline, just triggered from a different presentation layer.

Where errors actually display differs from classic UI

One genuine UX difference: classic UI surfaces server-side PXSetPropertyException messages as a red highlight plus an error icon with a tooltip, tightly coupled to the ASPX field control. Modern UI's error surface is more flexible — validation messages from either layer (client TypeScript validator or server exception) render through the same unified error-state UI on the field, so a user genuinely cannot tell, and doesn't need to, whether a given red message came from an instant client check or a server round trip. That consistency is a deliberate design choice in the framework and one of the real usability wins over classic UI's more visually inconsistent error handling.

Wrapping up

Modern UI form validation is layered, not replaced: the server-side PXGraph event pipeline remains the sole authority for data integrity, unchanged from classic UI. Client-side TypeScript validators add instant feedback for simple, cheap-to-duplicate rules. Field-level commitChanges round trips cover validation that needs live server data without a full save. Decide per-field which of these three layers earns the maintenance cost — don't reflexively duplicate every server rule into TypeScript just because the option exists.

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.